diff --git a/backend/remote-state/swift/backend.go b/backend/remote-state/swift/backend.go index e1e4e95b2..595b05191 100644 --- a/backend/remote-state/swift/backend.go +++ b/backend/remote-state/swift/backend.go @@ -109,7 +109,7 @@ func New() backend.Backend { "insecure": &schema.Schema{ Type: schema.TypeBool, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("OS_INSECURE", ""), + DefaultFunc: schema.EnvDefaultFunc("OS_INSECURE", nil), Description: descriptions["insecure"], }, @@ -256,7 +256,6 @@ func (b *Backend) configure(ctx context.Context) error { DomainName: data.Get("domain_name").(string), EndpointType: data.Get("endpoint_type").(string), IdentityEndpoint: data.Get("auth_url").(string), - Insecure: data.Get("insecure").(bool), Password: data.Get("password").(string), Token: data.Get("token").(string), TenantID: data.Get("tenant_id").(string), @@ -265,6 +264,11 @@ func (b *Backend) configure(ctx context.Context) error { UserID: data.Get("user_id").(string), } + if v, ok := data.GetOkExists("insecure"); ok { + insecure := v.(bool) + config.Insecure = &insecure + } + if err := config.LoadAndValidate(); err != nil { return err } diff --git a/go.mod b/go.mod index dbb131f75..862ddc202 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,8 @@ require ( github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c // indirect github.com/google/go-cmp v0.2.0 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/gorilla/websocket v1.4.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/spf13/afero v1.0.2 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-tls v1.2.0 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6 // indirect diff --git a/go.sum b/go.sum index 6e8e0a9f1..9a0db6352 100644 --- a/go.sum +++ b/go.sum @@ -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/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/gophercloud/gophercloud v0.0.0-20170524130959-3027adb1ce72 h1:I0ssFkBxJw27fhEVIBVjGQVMqKj5HyzfvfIhdr5Tx2E= -github.com/gophercloud/gophercloud v0.0.0-20170524130959-3027adb1ce72/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= +github.com/gophercloud/gophercloud v0.0.0-20190208042652-bc37892e1968 h1:Pu+HW4kcQozw0QyrTTgLE+3RXNqFhQNNzhbnoLFL83c= +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/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 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/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-openstack v0.0.0-20170616075611-4080a521c6ea h1:IfuzHOI3XwwYZS2Xw8SQbxOtGXlIUrKtXtuDCTNxmsQ= -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 h1:adpjqej+F8BAX9dHmuPF47sUIkgifeqBu6p7iCsyj0Y= +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/go.mod h1:/J+B8me5DCMa0rEBH5ic2aKPjhtpWNeScmxFJWxB1EU= github.com/terraform-providers/terraform-provider-tls v1.2.0 h1:wcD0InKzNh8fanUYQwim62WCd4toeD9WJnPw/RjBI4o= diff --git a/vendor/github.com/gophercloud/gophercloud/.gitignore b/vendor/github.com/gophercloud/gophercloud/.gitignore index ead84456e..dd91ed205 100644 --- a/vendor/github.com/gophercloud/gophercloud/.gitignore +++ b/vendor/github.com/gophercloud/gophercloud/.gitignore @@ -1 +1,3 @@ **/*.swp +.idea +.vscode diff --git a/vendor/github.com/gophercloud/gophercloud/.travis.yml b/vendor/github.com/gophercloud/gophercloud/.travis.yml index 015042d2f..ef5629be6 100644 --- a/vendor/github.com/gophercloud/gophercloud/.travis.yml +++ b/vendor/github.com/gophercloud/gophercloud/.travis.yml @@ -7,13 +7,16 @@ install: - go get github.com/mattn/goveralls - go get golang.org/x/tools/cmd/goimports go: -- 1.7 -- tip +- "1.10" +- "tip" env: global: - secure: "xSQsAG5wlL9emjbCdxzz/hYQsSpJ/bABO1kkbwMSISVcJ3Nk0u4ywF+LS4bgeOnwPfmFvNTOqVDu3RwEvMeWXSI76t1piCPcObutb2faKLVD/hLoAS76gYX+Z8yGWGHrSB7Do5vTPj1ERe2UljdrnsSeOXzoDwFxYRaZLX4bBOB4AyoGvRniil5QXPATiA1tsWX1VMicj8a4F8X+xeESzjt1Q5Iy31e7vkptu71bhvXCaoo5QhYwT+pLR9dN0S1b7Ro0KVvkRefmr1lUOSYd2e74h6Lc34tC1h3uYZCS4h47t7v5cOXvMNxinEj2C51RvbjvZI1RLVdkuAEJD1Iz4+Ote46nXbZ//6XRZMZz/YxQ13l7ux1PFjgEB6HAapmF5Xd8PRsgeTU9LRJxpiTJ3P5QJ3leS1va8qnziM5kYipj/Rn+V8g2ad/rgkRox9LSiR9VYZD2Pe45YCb1mTKSl2aIJnV7nkOqsShY5LNB4JZSg7xIffA+9YVDktw8dJlATjZqt7WvJJ49g6A61mIUV4C15q2JPGKTkZzDiG81NtmS7hFa7k0yaE2ELgYocbcuyUcAahhxntYTC0i23nJmEHVNiZmBO3u7EgpWe4KGVfumU+lt12tIn5b3dZRBBUk3QakKKozSK1QPHGpk/AZGrhu7H6l8to6IICKWtDcyMPQ=" +before_script: +- go vet ./... script: - ./script/coverage +- ./script/unittest - ./script/format after_success: - $HOME/gopath/bin/goveralls -service=travis-ci -coverprofile=cover.out diff --git a/vendor/github.com/gophercloud/gophercloud/.zuul.yaml b/vendor/github.com/gophercloud/gophercloud/.zuul.yaml new file mode 100644 index 000000000..8c31ea160 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/.zuul.yaml @@ -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 diff --git a/vendor/github.com/gophercloud/gophercloud/FAQ.md b/vendor/github.com/gophercloud/gophercloud/FAQ.md deleted file mode 100644 index 88a366a28..000000000 --- a/vendor/github.com/gophercloud/gophercloud/FAQ.md +++ /dev/null @@ -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 `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 -} -// ... -``` diff --git a/vendor/github.com/gophercloud/gophercloud/MIGRATING.md b/vendor/github.com/gophercloud/gophercloud/MIGRATING.md deleted file mode 100644 index aa383c9cc..000000000 --- a/vendor/github.com/gophercloud/gophercloud/MIGRATING.md +++ /dev/null @@ -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` diff --git a/vendor/github.com/gophercloud/gophercloud/README.md b/vendor/github.com/gophercloud/gophercloud/README.md index 60ca479de..ad29041d9 100644 --- a/vendor/github.com/gophercloud/gophercloud/README.md +++ b/vendor/github.com/gophercloud/gophercloud/README.md @@ -127,7 +127,7 @@ new resource in the `server` variable (a ## 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 @@ -140,4 +140,20 @@ See the [contributing guide](./.github/CONTRIBUTING.md). ## Help and feedback If you're struggling with something or have spotted a potential bug, feel free -to submit an issue to our [bug tracker](/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 + + + +OpenLab is providing a full CI environment to test each PR and merge for a variety of OpenStack releases. + +### VEXXHOST + + + +VEXXHOST is providing their services to assist with the development and testing of Gophercloud. diff --git a/vendor/github.com/gophercloud/gophercloud/STYLEGUIDE.md b/vendor/github.com/gophercloud/gophercloud/STYLEGUIDE.md deleted file mode 100644 index 5b49ef488..000000000 --- a/vendor/github.com/gophercloud/gophercloud/STYLEGUIDE.md +++ /dev/null @@ -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 ` (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 `OptsBuilder` (eg `CreateOptsBuilder`). - This `interface` should have at the least a method named `ToMap` - (eg `ToPortCreateMap`). - -- Functions in `requests.go` that accept query strings should accept as their - last parameter an `interface` named `OptsBuilder` (eg `ListOptsBuilder`). - This `interface` should have at the least a method named `ToQuery` - (eg `ToServerListQuery`). diff --git a/vendor/github.com/gophercloud/gophercloud/auth_options.go b/vendor/github.com/gophercloud/gophercloud/auth_options.go index eabf18207..5ffa8d1e0 100644 --- a/vendor/github.com/gophercloud/gophercloud/auth_options.go +++ b/vendor/github.com/gophercloud/gophercloud/auth_options.go @@ -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 provider. + +An example of manually providing authentication information: + + opts := gophercloud.AuthOptions{ + IdentityEndpoint: "https://openstack.example.com:5000/v2.0", + Username: "{username}", + Password: "{password}", + TenantID: "{tenant_id}", + } + + provider, err := openstack.AuthenticatedClient(opts) + +An example of using AuthOptionsFromEnv(), where the environment variables can +be read from a file, such as a standard openrc file: + + opts, err := openstack.AuthOptionsFromEnv() + provider, err := openstack.AuthenticatedClient(opts) */ type AuthOptions struct { // IdentityEndpoint specifies the HTTP endpoint that is required to work with // the Identity API of the appropriate version. While it's ultimately needed by // all of the identity services, it will often be populated by a provider-level // function. + // + // The IdentityEndpoint is typically referred to as the "auth_url" or + // "OS_AUTH_URL" in the information provided by the cloud operator. IdentityEndpoint string `json:"-"` // Username is required if using Identity V2 API. Consult with your provider's // control panel to discover your account's username. In Identity V3, either // UserID or a combination of Username and DomainID or DomainName are needed. Username string `json:"username,omitempty"` - UserID string `json:"id,omitempty"` + UserID string `json:"-"` Password string `json:"password,omitempty"` // At most one of DomainID and DomainName must be provided if using Username // with Identity V3. Otherwise, either are optional. - DomainID string `json:"id,omitempty"` + DomainID string `json:"-"` DomainName string `json:"name,omitempty"` // 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. // It is not currently possible to authenticate with Username and a Domain // and scope to a Project in a different Domain by using TenantName. To - // accomplish that, the ProjectID will need to be provided to the TenantID + // accomplish that, the ProjectID will need to be provided as the TenantID // option. TenantID string `json:"tenantId,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 // possible. This setting defaults to false. // - // NOTE: The reauth function will try to re-authenticate endlessly if left unchecked. - // The way to limit the number of attempts is to provide a custom HTTP client to the provider client - // and provide a transport that implements the RoundTripper interface and stores the number of failed retries. - // For an example of this, see here: https://github.com/rackspace/rack/blob/1.0.0/auth/clients.go#L311 + // NOTE: The reauth function will try to re-authenticate endlessly if left + // unchecked. The way to limit the number of attempts is to provide a custom + // HTTP client to the provider client and provide a transport that implements + // the RoundTripper interface and stores the number of failed retries. For an + // example of this, see here: + // https://github.com/rackspace/rack/blob/1.0.0/auth/clients.go#L311 AllowReauth bool `json:"-"` // TokenID allows users to authenticate (possibly as another user) with an // authentication token ID. TokenID string `json:"-"` + + // Scope determines the scoping of the authentication request. + Scope *AuthScope `json:"-"` + + // Authentication through Application Credentials requires supplying name, project and secret + // For project we can use TenantID + ApplicationCredentialID string `json:"-"` + ApplicationCredentialName string `json:"-"` + ApplicationCredentialSecret string `json:"-"` +} + +// AuthScope allows a created token to be limited to a specific domain or project. +type AuthScope struct { + ProjectID string + ProjectName string + DomainID string + DomainName string } // ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder @@ -109,7 +148,7 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s type userReq struct { ID *string `json:"id,omitempty"` Name *string `json:"name,omitempty"` - Password string `json:"password"` + Password string `json:"password,omitempty"` Domain *domainReq `json:"domain,omitempty"` } @@ -121,10 +160,18 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s 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 { - Methods []string `json:"methods"` - Password *passwordReq `json:"password,omitempty"` - Token *tokenReq `json:"token,omitempty"` + Methods []string `json:"methods"` + Password *passwordReq `json:"password,omitempty"` + Token *tokenReq `json:"token,omitempty"` + ApplicationCredential *applicationCredentialReq `json:"application_credential,omitempty"` } type authReq struct { @@ -161,8 +208,68 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s req.Auth.Identity.Token = &tokenReq{ ID: opts.TokenID, } + + } else if opts.ApplicationCredentialID != "" { + // Configure the request for ApplicationCredentialID authentication. + // https://github.com/openstack/keystoneauth/blob/stable/rocky/keystoneauth1/identity/v3/application_credential.py#L48-L67 + // There are three kinds of possible application_credential requests + // 1. application_credential id + secret + // 2. application_credential name + secret + user_id + // 3. application_credential name + secret + username + domain_id / domain_name + if opts.ApplicationCredentialSecret == "" { + return nil, ErrAppCredMissingSecret{} + } + req.Auth.Identity.Methods = []string{"application_credential"} + req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{ + ID: &opts.ApplicationCredentialID, + Secret: &opts.ApplicationCredentialSecret, + } + } else if opts.ApplicationCredentialName != "" { + if opts.ApplicationCredentialSecret == "" { + return nil, ErrAppCredMissingSecret{} + } + + var userRequest *userReq + + if opts.UserID != "" { + // UserID could be used without the domain information + userRequest = &userReq{ + ID: &opts.UserID, + } + } + + if userRequest == nil && opts.Username == "" { + // Make sure that Username or UserID are provided + return nil, ErrUsernameOrUserID{} + } + + if userRequest == nil && opts.DomainID != "" { + userRequest = &userReq{ + Name: &opts.Username, + Domain: &domainReq{ID: &opts.DomainID}, + } + } + + if userRequest == nil && opts.DomainName != "" { + userRequest = &userReq{ + Name: &opts.Username, + Domain: &domainReq{Name: &opts.DomainName}, + } + } + + // Make sure that DomainID or DomainName are provided among Username + if userRequest == nil { + return nil, ErrDomainIDOrDomainName{} + } + + req.Auth.Identity.Methods = []string{"application_credential"} + req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{ + Name: &opts.ApplicationCredentialName, + User: userRequest, + Secret: &opts.ApplicationCredentialSecret, + } } else { - // If no password or token ID are available, authentication can't continue. + // If no password or token ID or ApplicationCredential are available, authentication can't continue. return nil, ErrMissingPassword{} } } else { @@ -241,82 +348,85 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s } func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) { - - var scope struct { - ProjectID string - ProjectName string - DomainID string - DomainName string - } - - if opts.TenantID != "" { - scope.ProjectID = opts.TenantID - } else { - if opts.TenantName != "" { - scope.ProjectName = opts.TenantName - scope.DomainID = opts.DomainID - scope.DomainName = opts.DomainName + // For backwards compatibility. + // If AuthOptions.Scope was not set, try to determine it. + // This works well for common scenarios. + if opts.Scope == nil { + opts.Scope = new(AuthScope) + if opts.TenantID != "" { + opts.Scope.ProjectID = opts.TenantID + } else { + if opts.TenantName != "" { + opts.Scope.ProjectName = opts.TenantName + opts.Scope.DomainID = opts.DomainID + opts.Scope.DomainName = opts.DomainName + } } } - if scope.ProjectName != "" { + if opts.Scope.ProjectName != "" { // ProjectName provided: either DomainID or DomainName must also be supplied. // ProjectID may not be supplied. - if scope.DomainID == "" && scope.DomainName == "" { + if opts.Scope.DomainID == "" && opts.Scope.DomainName == "" { return nil, ErrScopeDomainIDOrDomainName{} } - if scope.ProjectID != "" { + if opts.Scope.ProjectID != "" { return nil, ErrScopeProjectIDOrProjectName{} } - if scope.DomainID != "" { + if opts.Scope.DomainID != "" { // ProjectName + DomainID return map[string]interface{}{ "project": map[string]interface{}{ - "name": &scope.ProjectName, - "domain": map[string]interface{}{"id": &scope.DomainID}, + "name": &opts.Scope.ProjectName, + "domain": map[string]interface{}{"id": &opts.Scope.DomainID}, }, }, nil } - if scope.DomainName != "" { + if opts.Scope.DomainName != "" { // ProjectName + DomainName return map[string]interface{}{ "project": map[string]interface{}{ - "name": &scope.ProjectName, - "domain": map[string]interface{}{"name": &scope.DomainName}, + "name": &opts.Scope.ProjectName, + "domain": map[string]interface{}{"name": &opts.Scope.DomainName}, }, }, nil } - } else if scope.ProjectID != "" { + } else if opts.Scope.ProjectID != "" { // ProjectID provided. ProjectName, DomainID, and DomainName may not be provided. - if scope.DomainID != "" { + if opts.Scope.DomainID != "" { return nil, ErrScopeProjectIDAlone{} } - if scope.DomainName != "" { + if opts.Scope.DomainName != "" { return nil, ErrScopeProjectIDAlone{} } // ProjectID return map[string]interface{}{ "project": map[string]interface{}{ - "id": &scope.ProjectID, + "id": &opts.Scope.ProjectID, }, }, nil - } else if scope.DomainID != "" { + } else if opts.Scope.DomainID != "" { // DomainID provided. ProjectID, ProjectName, and DomainName may not be provided. - if scope.DomainName != "" { + if opts.Scope.DomainName != "" { return nil, ErrScopeDomainIDOrDomainName{} } // DomainID return 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 - } else if scope.DomainName != "" { - return nil, ErrScopeDomainName{} } return nil, nil diff --git a/vendor/github.com/gophercloud/gophercloud/auth_result.go b/vendor/github.com/gophercloud/gophercloud/auth_result.go new file mode 100644 index 000000000..2e4699b97 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/auth_result.go @@ -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) +} diff --git a/vendor/github.com/gophercloud/gophercloud/doc.go b/vendor/github.com/gophercloud/gophercloud/doc.go index b559516f9..131cc8e30 100644 --- a/vendor/github.com/gophercloud/gophercloud/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/doc.go @@ -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 resources. -Provider structs represent the service providers that offer and manage a -collection of services. The IdentityEndpoint is typically refered to as -"auth_url" in information provided by the cloud operator. Additionally, -the cloud may refer to TenantID or TenantName as project_id and project_name. -These are defined like so: +Authenticating with Providers + +Provider structs represent the cloud providers that offer and manage a +collection of services. You will generally want to create one Provider +client per OpenStack cloud. + +Use your OpenStack credentials to create a Provider client. The +IdentityEndpoint is typically refered to as "auth_url" or "OS_AUTH_URL" in +information provided by the cloud operator. Additionally, the cloud may refer to +TenantID or TenantName as project_id and project_name. Credentials are +specified like so: opts := gophercloud.AuthOptions{ IdentityEndpoint: "https://openstack.example.com:5000/v2.0", @@ -18,6 +24,16 @@ These are defined like so: provider, err := openstack.AuthenticatedClient(opts) +You may also use the openstack.AuthOptionsFromEnv() helper function. This +function reads in standard environment variables frequently found in an +OpenStack `openrc` file. Again note that Gophercloud currently uses "tenant" +instead of "project". + + opts, err := openstack.AuthOptionsFromEnv() + provider, err := openstack.AuthenticatedClient(opts) + +Service Clients + Service structs are specific to a provider and handle all of the logic and operations for a particular OpenStack service. Examples of services include: Compute, Object Storage, Block Storage. In order to define one, you need to @@ -25,7 +41,9 @@ pass in the parent provider, like so: 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 to work with and represent the state of API resources: @@ -62,6 +80,12 @@ of results: return true, nil }) +If you want to obtain the entire collection of pages without doing any +intermediary processing on each page, you can use the AllPages method: + + allPages, err := servers.List(client, nil).AllPages() + allServers, err := servers.ExtractServers(allPages) + This top-level package contains utility functions and data types that are used throughout the provider and service packages. Of particular note for end users are the AuthOptions and EndpointOpts structs. diff --git a/vendor/github.com/gophercloud/gophercloud/endpoint_search.go b/vendor/github.com/gophercloud/gophercloud/endpoint_search.go index 9887947f6..2fbc3c97f 100644 --- a/vendor/github.com/gophercloud/gophercloud/endpoint_search.go +++ b/vendor/github.com/gophercloud/gophercloud/endpoint_search.go @@ -27,7 +27,7 @@ const ( // unambiguously identify one, and only one, endpoint within the catalog. // // Usually, these are passed to service client factory functions in a provider -// package, like "rackspace.NewComputeV2()". +// package, like "openstack.NewComputeV2()". type EndpointOpts struct { // Type [required] is the service type for the client (e.g., "compute", // "object-store"). Generally, this will be supplied by the service client diff --git a/vendor/github.com/gophercloud/gophercloud/errors.go b/vendor/github.com/gophercloud/gophercloud/errors.go index e0fe7c1e0..4bf102468 100644 --- a/vendor/github.com/gophercloud/gophercloud/errors.go +++ b/vendor/github.com/gophercloud/gophercloud/errors.go @@ -1,6 +1,9 @@ package gophercloud -import "fmt" +import ( + "fmt" + "strings" +) // BaseError is an error type that all other error types embed. type BaseError struct { @@ -43,6 +46,33 @@ func (e ErrInvalidInput) Error() string { return e.choseErrString() } +// ErrMissingEnvironmentVariable is the error when environment variable is required +// in a particular situation but not provided by the user +type ErrMissingEnvironmentVariable struct { + BaseError + EnvironmentVariable string +} + +func (e ErrMissingEnvironmentVariable) Error() string { + e.DefaultErrString = fmt.Sprintf("Missing environment variable [%s]", e.EnvironmentVariable) + return e.choseErrString() +} + +// ErrMissingAnyoneOfEnvironmentVariables is the error when anyone of the environment variables +// is required in a particular situation but not provided by the user +type ErrMissingAnyoneOfEnvironmentVariables struct { + BaseError + EnvironmentVariables []string +} + +func (e ErrMissingAnyoneOfEnvironmentVariables) Error() string { + e.DefaultErrString = fmt.Sprintf( + "Missing one of the following environment variables [%s]", + strings.Join(e.EnvironmentVariables, ", "), + ) + return e.choseErrString() +} + // ErrUnexpectedResponseCode is returned by the Request method when a response code other than // those listed in OkCodes is encountered. type ErrUnexpectedResponseCode struct { @@ -72,6 +102,11 @@ type ErrDefault401 struct { ErrUnexpectedResponseCode } +// ErrDefault403 is the default error type returned on a 403 HTTP response code. +type ErrDefault403 struct { + ErrUnexpectedResponseCode +} + // ErrDefault404 is the default error type returned on a 404 HTTP response code. type ErrDefault404 struct { ErrUnexpectedResponseCode @@ -103,11 +138,22 @@ type ErrDefault503 struct { } 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 { return "Authentication failed" } +func (e ErrDefault403) Error() string { + e.DefaultErrString = fmt.Sprintf( + "Request forbidden: [%s %s], error message: %s", + e.Method, e.URL, e.Body, + ) + return e.choseErrString() +} func (e ErrDefault404) Error() string { return "Resource not found" } @@ -141,6 +187,12 @@ type Err401er interface { Error401(ErrUnexpectedResponseCode) error } +// Err403er is the interface resource error types implement to override the error message +// from a 403 error. +type Err403er interface { + Error403(ErrUnexpectedResponseCode) error +} + // Err404er is the interface resource error types implement to override the error message // from a 404 error. type Err404er interface { @@ -393,16 +445,16 @@ func (e ErrScopeProjectIDAlone) Error() string { 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. type ErrScopeEmpty struct{ BaseError } func (e ErrScopeEmpty) Error() string { return "You must provide either a Project or Domain in a Scope" } + +// ErrAppCredMissingSecret indicates that no Application Credential Secret was provided with Application Credential ID or Name +type ErrAppCredMissingSecret struct{ BaseError } + +func (e ErrAppCredMissingSecret) Error() string { + return "You must provide an Application Credential Secret" +} diff --git a/vendor/github.com/gophercloud/gophercloud/internal/pkg.go b/vendor/github.com/gophercloud/gophercloud/internal/pkg.go new file mode 100644 index 000000000..5bf0569ce --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/internal/pkg.go @@ -0,0 +1 @@ +package internal diff --git a/vendor/github.com/gophercloud/gophercloud/internal/util.go b/vendor/github.com/gophercloud/gophercloud/internal/util.go new file mode 100644 index 000000000..8efb283e7 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/internal/util.go @@ -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 +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go b/vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go index f6d2eb194..0bb1f4837 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go @@ -8,10 +8,27 @@ import ( var nilOptions = gophercloud.AuthOptions{} -// AuthOptionsFromEnv fills out an identity.AuthOptions structure with the settings found on the various OpenStack -// OS_* environment variables. The following variables provide sources of truth: OS_AUTH_URL, OS_USERNAME, -// OS_PASSWORD, OS_TENANT_ID, and OS_TENANT_NAME. Of these, OS_USERNAME, OS_PASSWORD, and OS_AUTH_URL must -// have settings, or an error will result. OS_TENANT_ID and OS_TENANT_NAME are optional. +/* +AuthOptionsFromEnv fills out an identity.AuthOptions structure with the +settings found on the various OpenStack OS_* environment variables. + +The following variables provide sources of truth: OS_AUTH_URL, OS_USERNAME, +OS_PASSWORD, 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) { authURL := os.Getenv("OS_AUTH_URL") username := os.Getenv("OS_USERNAME") @@ -21,31 +38,76 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) { tenantName := os.Getenv("OS_TENANT_NAME") domainID := os.Getenv("OS_DOMAIN_ID") domainName := os.Getenv("OS_DOMAIN_NAME") + applicationCredentialID := os.Getenv("OS_APPLICATION_CREDENTIAL_ID") + applicationCredentialName := os.Getenv("OS_APPLICATION_CREDENTIAL_NAME") + applicationCredentialSecret := os.Getenv("OS_APPLICATION_CREDENTIAL_SECRET") + + // If OS_PROJECT_ID is set, overwrite tenantID with the value. + if v := os.Getenv("OS_PROJECT_ID"); v != "" { + tenantID = v + } + + // If OS_PROJECT_NAME is set, overwrite tenantName with the value. + if v := os.Getenv("OS_PROJECT_NAME"); v != "" { + tenantName = v + } if authURL == "" { - err := gophercloud.ErrMissingInput{Argument: "authURL"} + err := gophercloud.ErrMissingEnvironmentVariable{ + EnvironmentVariable: "OS_AUTH_URL", + } return nilOptions, err } - if username == "" && userID == "" { - err := gophercloud.ErrMissingInput{Argument: "username"} + if userID == "" && username == "" { + // Empty username and userID could be ignored, when applicationCredentialID and applicationCredentialSecret are set + if applicationCredentialID == "" && applicationCredentialSecret == "" { + err := gophercloud.ErrMissingAnyoneOfEnvironmentVariables{ + EnvironmentVariables: []string{"OS_USERID", "OS_USERNAME"}, + } + return nilOptions, err + } + } + + if password == "" && applicationCredentialID == "" && applicationCredentialName == "" { + err := gophercloud.ErrMissingEnvironmentVariable{ + EnvironmentVariable: "OS_PASSWORD", + } return nilOptions, err } - if password == "" { - err := gophercloud.ErrMissingInput{Argument: "password"} + if (applicationCredentialID != "" || applicationCredentialName != "") && applicationCredentialSecret == "" { + err := gophercloud.ErrMissingEnvironmentVariable{ + EnvironmentVariable: "OS_APPLICATION_CREDENTIAL_SECRET", + } return nilOptions, err } + if applicationCredentialID == "" && applicationCredentialName != "" && applicationCredentialSecret != "" { + if userID == "" && username == "" { + return nilOptions, gophercloud.ErrMissingAnyoneOfEnvironmentVariables{ + EnvironmentVariables: []string{"OS_USERID", "OS_USERNAME"}, + } + } + if username != "" && domainID == "" && domainName == "" { + return nilOptions, gophercloud.ErrMissingAnyoneOfEnvironmentVariables{ + EnvironmentVariables: []string{"OS_DOMAIN_ID", "OS_DOMAIN_NAME"}, + } + } + } + ao := gophercloud.AuthOptions{ - IdentityEndpoint: authURL, - UserID: userID, - Username: username, - Password: password, - TenantID: tenantID, - TenantName: tenantName, - DomainID: domainID, - DomainName: domainName, + IdentityEndpoint: authURL, + UserID: userID, + Username: username, + Password: password, + TenantID: tenantID, + TenantName: tenantName, + DomainID: domainID, + DomainName: domainName, + ApplicationCredentialID: applicationCredentialID, + ApplicationCredentialName: applicationCredentialName, + ApplicationCredentialSecret: applicationCredentialSecret, } return ao, nil diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/doc.go index 0935fdbd5..a78d3d048 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/doc.go @@ -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 -// device, akin to a USB hard drive. It can only be attached to one instance at -// a time. +/* +Package volumeactions 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. + +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 diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/requests.go index e3c7df304..d18bff555 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/requests.go @@ -13,7 +13,7 @@ type AttachOptsBuilder interface { // AttachMode describes the attachment mode for volumes. type AttachMode string -// These constants determine how a volume is attached +// These constants determine how a volume is attached. const ( ReadOnly AttachMode = "ro" ReadWrite AttachMode = "rw" @@ -21,13 +21,16 @@ const ( // AttachOpts contains options for attaching a Volume. type AttachOpts struct { - // The mountpoint of this volume + // The mountpoint of this volume. 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"` - // 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"` - // Mount mode of this volume + + // Mount mode of this volume. Mode AttachMode `json:"mode,omitempty"` } @@ -44,16 +47,16 @@ func Attach(client *gophercloud.ServiceClient, id string, opts AttachOptsBuilder r.Err = err 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}, }) return } -// BeginDetach will mark the volume as detaching +// BeginDetach will mark the volume as detaching. func BeginDetaching(client *gophercloud.ServiceClient, id string) (r BeginDetachingResult) { 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}, }) return @@ -65,7 +68,9 @@ type DetachOptsBuilder interface { ToVolumeDetachMap() (map[string]interface{}, error) } +// DetachOpts contains options for detaching a Volume. type DetachOpts struct { + // AttachmentID is the ID of the attachment between a volume and instance. AttachmentID string `json:"attachment_id,omitempty"` } @@ -75,32 +80,32 @@ func (opts DetachOpts) ToVolumeDetachMap() (map[string]interface{}, error) { 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) { b, err := opts.ToVolumeDetachMap() if err != nil { r.Err = err 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}, }) 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) { 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}, }) 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) { 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}, }) return @@ -113,6 +118,8 @@ type InitializeConnectionOptsBuilder interface { } // InitializeConnectionOpts hosts options for InitializeConnection. +// The fields are specific to the storage driver in use and the destination +// attachment. type InitializeConnectionOpts struct { IP string `json:"ip,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 } -// InitializeConnection initializes iscsi connection. +// InitializeConnection initializes an iSCSI connection by volume ID. func InitializeConnection(client *gophercloud.ServiceClient, id string, opts InitializeConnectionOptsBuilder) (r InitializeConnectionResult) { b, err := opts.ToVolumeInitializeConnectionMap() if err != nil { r.Err = err 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}, }) return @@ -169,14 +176,14 @@ func (opts TerminateConnectionOpts) ToVolumeTerminateConnectionMap() (map[string 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) { b, err := opts.ToVolumeTerminateConnectionMap() if err != nil { r.Err = err 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}, }) return @@ -188,10 +195,10 @@ type ExtendSizeOptsBuilder interface { ToVolumeExtendSizeMap() (map[string]interface{}, error) } -// ExtendSizeOpts contain options for extending the size of an existing Volume. This object is passed -// to the volumes.ExtendSize function. +// ExtendSizeOpts contains options for extending the size of an existing Volume. +// This object is passed to the volumes.ExtendSize function. 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"` } @@ -209,7 +216,7 @@ func ExtendSize(client *gophercloud.ServiceClient, id string, opts ExtendSizeOpt r.Err = err 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}, }) return @@ -225,11 +232,14 @@ type UploadImageOptsBuilder interface { type UploadImageOpts struct { // Container format, may be bare, ofv, ova, etc. ContainerFormat string `json:"container_format,omitempty"` + // Disk format, may be raw, qcow2, vhd, vdi, vmdk, etc. 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"` - // Force image creation, usable if volume attached to instance + + // Force image creation, usable if volume attached to instance. 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") } -// 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) { b, err := opts.ToVolumeUploadImageMap() if err != nil { r.Err = err 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}, }) 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 +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/results.go index 634b04d8d..5cadd360f 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/results.go @@ -1,48 +1,68 @@ 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 { 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 { 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 { 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 { - 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 { 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 { 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 { gophercloud.ErrResult } -type commonResult struct { +// InitializeConnectionResult contains the response body and error from an +// InitializeConnection request. +type InitializeConnectionResult struct { gophercloud.Result } -// Extract will get the Volume object out of the commonResult object. -func (r commonResult) Extract() (map[string]interface{}, error) { +// ExtendSizeResult contains the response body and error from an ExtendSize request. +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 { ConnectionInfo map[string]interface{} `json:"connection_info"` } @@ -50,12 +70,122 @@ func (r commonResult) Extract() (map[string]interface{}, error) { return s.ConnectionInfo, err } -// InitializeConnectionResult contains the response body and error from a Get request. -type InitializeConnectionResult struct { - commonResult +// ImageVolumeType contains volume type information obtained from UploadImage +// action. +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. -type ExtendSizeResult struct { +func (r *ImageVolumeType) UnmarshalJSON(b []byte) error { + 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 } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/urls.go index 5efd2b25c..20486ed71 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/urls.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions/urls.go @@ -2,38 +2,6 @@ package volumeactions 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") } - -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) -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/requests.go index 566def518..1da94238b 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/requests.go @@ -110,8 +110,8 @@ type UpdateOptsBuilder interface { // to the volumes.Update function. For more information about the parameters, see // the Volume object. type UpdateOpts struct { - Name string `json:"display_name,omitempty"` - Description string `json:"display_description,omitempty"` + Name *string `json:"display_name,omitempty"` + Description *string `json:"display_description,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) { count := 0 id := "" - pages, err := List(client, nil).AllPages() + + listOpts := ListOpts{ + Name: name, + } + + pages, err := List(client, listOpts).AllPages() if err != nil { return "", err } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/results.go index 5c954bfee..7f68d1486 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes/results.go @@ -1,6 +1,9 @@ package volumes import ( + "encoding/json" + "time" + "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/pagination" ) @@ -18,7 +21,7 @@ type Volume struct { // Indicates whether this is a bootable volume. Bootable string `json:"bootable"` // The date when this volume was created. - CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"` + CreatedAt time.Time `json:"-"` // Human-readable description for the volume. Description string `json:"display_description"` // The type of volume to create, either SATA or SSD. @@ -35,6 +38,23 @@ type Volume struct { 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. type CreateResult struct { commonResult diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots/doc.go new file mode 100644 index 000000000..198f83077 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots/doc.go @@ -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 diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots/requests.go new file mode 100644 index 000000000..939e50204 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots/requests.go @@ -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"} + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots/results.go new file mode 100644 index 000000000..0b444d08a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots/results.go @@ -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 +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots/urls.go new file mode 100644 index 000000000..778043749 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots/urls.go @@ -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) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots/util.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots/util.go new file mode 100644 index 000000000..40fbb827b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots/util.go @@ -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 + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/requests.go index 18c9cb272..c27ddbf67 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/requests.go @@ -61,9 +61,37 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create 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) (r DeleteResult) { - _, r.Err = client.Delete(deleteURL(client, id), nil) +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 } @@ -83,14 +111,34 @@ type ListOptsBuilder interface { // ListOpts holds options for listing Volumes. It is passed to the volumes.List // function. type ListOpts struct { - // admin-only option. Set it to true to see all tenant volumes. + // AllTenants will retrieve volumes of all tenants/projects. 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"` - // List only volumes that have Name as the display name. + + // Name will filter by the specified volume 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"` + + // 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 [:]. + 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. @@ -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 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 // the Volume object. type UpdateOpts struct { - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,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) { count := 0 id := "" - pages, err := List(client, nil).AllPages() + + listOpts := ListOpts{ + Name: name, + } + + pages, err := List(client, listOpts).AllPages() if err != nil { return "", err } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/results.go index 674ec3468..96572b01b 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/results.go @@ -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. type VolumePage struct { - pagination.SinglePageBase + pagination.LinkedPageBase } // IsEmpty returns true if a ListResult contains no Volumes. @@ -107,6 +107,19 @@ func (r VolumePage) IsEmpty() (bool, error) { 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. func ExtractVolumes(r pagination.Page) ([]Volume, error) { var s []Volume diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots/doc.go new file mode 100644 index 000000000..198f83077 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots/doc.go @@ -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 diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots/requests.go new file mode 100644 index 000000000..22531c9fd --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots/requests.go @@ -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 [:]. + 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"} + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots/results.go new file mode 100644 index 000000000..1ffae8f61 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots/results.go @@ -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 +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots/urls.go new file mode 100644 index 000000000..778043749 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots/urls.go @@ -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) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots/util.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots/util.go new file mode 100644 index 000000000..40fbb827b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots/util.go @@ -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 + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/doc.go new file mode 100644 index 000000000..307b8b12d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/doc.go @@ -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 diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/requests.go new file mode 100644 index 000000000..25f70b27c --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/requests.go @@ -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 [:]. + 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"} + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/results.go new file mode 100644 index 000000000..87f71262c --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/results.go @@ -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 +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/urls.go new file mode 100644 index 000000000..170724905 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/urls.go @@ -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) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/util.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/util.go new file mode 100644 index 000000000..e86c1b4b4 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/util.go @@ -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 + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/client.go b/vendor/github.com/gophercloud/gophercloud/openstack/client.go index 2d30cc60a..8fbd9dc7c 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/client.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/client.go @@ -2,7 +2,6 @@ package openstack import ( "fmt" - "net/url" "reflect" "github.com/gophercloud/gophercloud" @@ -12,43 +11,66 @@ import ( ) const ( - v20 = "v2.0" - v30 = "v3.0" + // v2 represents Keystone v2. + // It should never increase beyond 2.0. + v2 = "v2.0" + + // v3 represents Keystone v3. + // The version can be anything from v3 to v3.x. + v3 = "v3" ) -// NewClient prepares an unauthenticated ProviderClient instance. -// Most users will probably prefer using the AuthenticatedClient function instead. -// This is useful if you wish to explicitly control the version of the identity service that's used for authentication explicitly, -// for example. +/* +NewClient prepares an unauthenticated ProviderClient instance. +Most users will probably prefer using the AuthenticatedClient function +instead. + +This is useful if you wish to explicitly control the version of the identity +service that's used for authentication explicitly, for example. + +A basic example of using this would be: + + ao, err := openstack.AuthOptionsFromEnv() + provider, err := openstack.NewClient(ao.IdentityEndpoint) + client, err := openstack.NewIdentityV3(provider, gophercloud.EndpointOpts{}) +*/ func NewClient(endpoint string) (*gophercloud.ProviderClient, error) { - u, err := url.Parse(endpoint) + base, err := utils.BaseEndpoint(endpoint) if err != nil { return nil, err } - hadPath := u.Path != "" - u.Path, u.RawQuery, u.Fragment = "", "", "" - base := u.String() endpoint = gophercloud.NormalizeURL(endpoint) base = gophercloud.NormalizeURL(base) - if hadPath { - return &gophercloud.ProviderClient{ - IdentityBase: base, - IdentityEndpoint: endpoint, - }, nil - } + p := new(gophercloud.ProviderClient) + p.IdentityBase = base + p.IdentityEndpoint = endpoint + p.UseTokenLock() - return &gophercloud.ProviderClient{ - IdentityBase: base, - IdentityEndpoint: "", - }, nil + return p, 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. -// It first queries the root identity endpoint to determine which versions of the identity service are supported, then chooses -// the most recent identity service available to proceed. +/* +AuthenticatedClient logs in to an OpenStack cloud found at the identity endpoint +specified by the options, acquires a token, and returns a Provider Client +instance that's ready to operate. + +If the full path to a versioned identity endpoint was specified (example: +http://example.com:5000/v3), that path will be used as the endpoint to query. + +If a versionless endpoint was specified (example: http://example.com:5000/), +the endpoint will be queried to determine which versions of the identity service +are available, then chooses the most recent or most supported version. + +Example: + + ao, err := openstack.AuthOptionsFromEnv() + provider, err := openstack.AuthenticatedClient(ao) + client, err := openstack.NewNetworkV2(client, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) +*/ func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) { client, err := NewClient(options.IdentityEndpoint) if err != nil { @@ -62,11 +84,12 @@ func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.Provider 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 { versions := []*utils.Version{ - {ID: v20, Priority: 20, Suffix: "/v2.0/"}, - {ID: v30, Priority: 30, Suffix: "/v3/"}, + {ID: v2, Priority: 20, Suffix: "/v2.0/"}, + {ID: v3, Priority: 30, Suffix: "/v3/"}, } chosen, endpoint, err := utils.ChooseVersion(client, versions) @@ -75,9 +98,9 @@ func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOp } switch chosen.ID { - case v20: + case v2: return v2auth(client, endpoint, options, gophercloud.EndpointOpts{}) - case v30: + case v3: return v3auth(client, endpoint, &options, gophercloud.EndpointOpts{}) default: // The switch statement must be out of date from the versions list. @@ -112,7 +135,7 @@ func v2auth(client *gophercloud.ProviderClient, endpoint string, options gopherc result := tokens2.Create(v2Client, v2Opts) - token, err := result.ExtractToken() + err = client.SetTokenAndAuthResult(result) if err != nil { return err } @@ -123,12 +146,24 @@ func v2auth(client *gophercloud.ProviderClient, endpoint string, options gopherc } 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.TokenID = "" - return v2auth(client, endpoint, options, eo) + err := v2auth(&tac, endpoint, tao, eo) + if err != nil { + return err + } + client.CopyTokenFrom(&tac) + return nil } } - client.TokenID = token.ID client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) { return V2EndpointURL(catalog, opts) } @@ -154,7 +189,7 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au result := tokens3.Create(v3Client, opts) - token, err := result.ExtractToken() + err = client.SetTokenAndAuthResult(result) if err != nil { return err } @@ -164,12 +199,34 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au return err } - client.TokenID = token.ID - 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.TokenID = "" - return v3auth(client, endpoint, opts, eo) + err := v3auth(&tac, endpoint, tao, eo) + if err != nil { + return err + } + client.CopyTokenFrom(&tac) + return nil } } client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) { @@ -179,12 +236,14 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au 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) { endpoint := client.IdentityBase + "v2.0/" + clientType := "identity" var err error if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) { - eo.ApplyDefaults("identity") + eo.ApplyDefaults(clientType) endpoint, err = client.EndpointLocator(eo) if err != nil { return nil, err @@ -194,143 +253,174 @@ func NewIdentityV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOp return &gophercloud.ServiceClient{ ProviderClient: client, Endpoint: endpoint, + Type: clientType, }, 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) { endpoint := client.IdentityBase + "v3/" + clientType := "identity" var err error if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) { - eo.ApplyDefaults("identity") + eo.ApplyDefaults(clientType) endpoint, err = client.EndpointLocator(eo) if err != nil { return nil, err } } + // Ensure endpoint still has a suffix of v3. + // This is because EndpointLocator might have found a versionless + // endpoint or the published endpoint is still /v2.0. In both + // cases, we need to fix the endpoint to point to /v3. + base, err := utils.BaseEndpoint(endpoint) + if err != nil { + return nil, err + } + + base = gophercloud.NormalizeURL(base) + + endpoint = base + "v3/" + return &gophercloud.ServiceClient{ ProviderClient: client, Endpoint: endpoint, + Type: clientType, }, nil } -// 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) { - eo.ApplyDefaults("object-store") - url, err := client.EndpointLocator(eo) - if err != nil { - return nil, err - } - return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil + return initClientOpts(client, eo, "object-store") } -// 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) { - eo.ApplyDefaults("compute") - url, err := client.EndpointLocator(eo) - if err != nil { - return nil, err - } - return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil + return initClientOpts(client, eo, "compute") } -// 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) { - eo.ApplyDefaults("network") - url, err := client.EndpointLocator(eo) - if err != nil { - return nil, err - } - return &gophercloud.ServiceClient{ - ProviderClient: client, - Endpoint: url, - ResourceBase: url + "v2.0/", - }, nil + sc, err := initClientOpts(client, eo, "network") + sc.ResourceBase = sc.Endpoint + "v2.0/" + return sc, err } -// NewBlockStorageV1 creates a ServiceClient that may be used to access the v1 block storage service. +// 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) { - eo.ApplyDefaults("volume") - url, err := client.EndpointLocator(eo) - if err != nil { - return nil, err - } - return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil + return initClientOpts(client, eo, "volume") } -// 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) { - eo.ApplyDefaults("volumev2") - url, err := client.EndpointLocator(eo) - if err != nil { - return nil, err - } - return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil + return initClientOpts(client, eo, "volumev2") +} + +// NewBlockStorageV3 creates a ServiceClient that may be used to access the v3 block storage service. +func NewBlockStorageV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "volumev3") } // NewSharedFileSystemV2 creates a ServiceClient that may be used to access the v2 shared file system service. func NewSharedFileSystemV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - eo.ApplyDefaults("sharev2") - url, err := client.EndpointLocator(eo) - if err != nil { - return nil, err - } - return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil + return initClientOpts(client, eo, "sharev2") } // NewCDNV1 creates a ServiceClient that may be used to access the OpenStack v1 // CDN service. func NewCDNV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - eo.ApplyDefaults("cdn") - url, err := client.EndpointLocator(eo) - if err != nil { - return nil, err - } - return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil + return initClientOpts(client, eo, "cdn") } -// 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) { - eo.ApplyDefaults("orchestration") - url, err := client.EndpointLocator(eo) - if err != nil { - return nil, err - } - return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil + return initClientOpts(client, eo, "orchestration") } // NewDBV1 creates a ServiceClient that may be used to access the v1 DB service. func NewDBV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - eo.ApplyDefaults("database") - url, err := client.EndpointLocator(eo) - if err != nil { - return nil, err - } - return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil + return initClientOpts(client, eo, "database") } -// 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) { - eo.ApplyDefaults("dns") - url, err := client.EndpointLocator(eo) - if err != nil { - return nil, err - } - return &gophercloud.ServiceClient{ - ProviderClient: client, - Endpoint: url, - ResourceBase: url + "v2/"}, nil + sc, err := initClientOpts(client, eo, "dns") + sc.ResourceBase = sc.Endpoint + "v2/" + return sc, err } -// NewImageServiceV2 creates a ServiceClient that may be used to access the v2 image service. +// 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) { - eo.ApplyDefaults("image") - url, err := client.EndpointLocator(eo) - if err != nil { - return nil, err - } - return &gophercloud.ServiceClient{ProviderClient: client, - Endpoint: url, - ResourceBase: url + "v2/"}, nil + sc, err := initClientOpts(client, eo, "image") + sc.ResourceBase = sc.Endpoint + "v2/" + return sc, err +} + +// NewLoadBalancerV2 creates a ServiceClient that may be used to access the v2 +// load balancer service. +func NewLoadBalancerV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(client, eo, "load-balancer") + 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") } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/doc.go new file mode 100644 index 000000000..3653122bf --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/doc.go @@ -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 diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/requests.go new file mode 100644 index 000000000..874f7a61e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/requests.go @@ -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 +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/results.go new file mode 100644 index 000000000..7d15e1ecb --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/results.go @@ -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 +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/urls.go new file mode 100644 index 000000000..50292e8b5 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/urls.go @@ -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) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones/doc.go new file mode 100644 index 000000000..29b554d21 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones/doc.go @@ -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 diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones/requests.go new file mode 100644 index 000000000..f9a2e86e0 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones/requests.go @@ -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)} + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones/results.go index 96a6a50b3..d48a0ea85 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones/results.go @@ -1,12 +1,76 @@ package availabilityzones -// ServerExt is an extension to the base Server object -type ServerExt struct { +import ( + "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 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 -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 } + +// 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 +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones/urls.go new file mode 100644 index 000000000..9d99ec74b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones/urls.go @@ -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") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/doc.go new file mode 100644 index 000000000..d291325e0 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/doc.go @@ -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 diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/requests.go index 9dae14c7a..30c617011 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/requests.go @@ -67,6 +67,14 @@ type BlockDevice struct { // VolumeSize is the size of the volume to create (in gigabytes). This can be // omitted for existing volumes. 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 diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/results.go index 3211fb1f3..ba1eafabc 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/results.go @@ -5,6 +5,8 @@ import ( ) // 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 { os.CreateResult } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/doc.go index 6682fa629..f5dbdbf8b 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/doc.go @@ -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 diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/requests.go index b36aeba59..a922639de 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/requests.go @@ -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 -// CreateOpts struct in this package does. +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. type CreateOptsBuilder interface { ToFloatingIPCreateMap() (map[string]interface{}, error) } -// CreateOpts specifies a Floating IP allocation request +// CreateOpts specifies a Floating IP allocation request. 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"` } @@ -29,7 +29,7 @@ func (opts CreateOpts) ToFloatingIPCreateMap() (map[string]interface{}, error) { 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) { b, err := opts.ToFloatingIPCreateMap() if err != nil { @@ -42,29 +42,30 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create 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) { _, r.Err = client.Get(getURL(client, id), &r.Body, nil) 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) { _, r.Err = client.Delete(deleteURL(client, id), nil) return } -// AssociateOptsBuilder is the interface types must satfisfy to be used as -// Associate options +// AssociateOptsBuilder allows extensions to add additional parameters to the +// Associate request. type AssociateOptsBuilder interface { 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 { - // 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"` - // 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"` } @@ -73,7 +74,7 @@ func (opts AssociateOpts) ToFloatingIPAssociateMap() (map[string]interface{}, er 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) { b, err := opts.ToFloatingIPAssociateMap() if err != nil { @@ -84,23 +85,24 @@ func AssociateInstance(client *gophercloud.ServiceClient, serverID string, opts return } -// DisassociateOptsBuilder is the interface types must satfisfy to be used as -// Disassociate options +// DisassociateOptsBuilder allows extensions to add additional parameters to +// the Disassociate request. type DisassociateOptsBuilder interface { 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 { 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) { 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) { b, err := opts.ToFloatingIPDisassociateMap() if err != nil { diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/results.go index 2f5b33844..da4e9da0e 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/results.go @@ -8,21 +8,21 @@ import ( "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 { // ID is a unique ID of the Floating IP 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"` - // 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"` - // IP is the actual Floating IP + // IP is the actual Floating 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"` } @@ -49,8 +49,7 @@ func (r *FloatingIP) UnmarshalJSON(b []byte) error { return err } -// FloatingIPPage stores a single, only page of FloatingIPs -// results from a List call. +// FloatingIPPage stores a single page of FloatingIPs from a List call. type FloatingIPPage struct { pagination.SinglePageBase } @@ -61,8 +60,7 @@ func (page FloatingIPPage) IsEmpty() (bool, error) { return len(va) == 0, err } -// ExtractFloatingIPs interprets a page of results as a slice of -// FloatingIPs. +// ExtractFloatingIPs interprets a page of results as a slice of FloatingIPs. func ExtractFloatingIPs(r pagination.Page) ([]FloatingIP, error) { var s struct { FloatingIPs []FloatingIP `json:"floating_ips"` @@ -86,32 +84,32 @@ func (r FloatingIPResult) Extract() (*FloatingIP, error) { return s.FloatingIP, err } -// CreateResult is the response from a Create operation. Call its Extract method to interpret it -// as a FloatingIP. +// CreateResult is the response from a Create operation. Call its Extract method +// to interpret it as a FloatingIP. type CreateResult struct { FloatingIPResult } -// GetResult is the response from a Get operation. Call its Extract method to interpret it -// as a FloatingIP. +// GetResult is the response from a Get operation. Call its Extract method to +// interpret it as a FloatingIP. type GetResult struct { FloatingIPResult } -// DeleteResult is the response from a Delete operation. Call its Extract method to determine if -// the call succeeded or failed. +// DeleteResult is the response from a Delete operation. Call its ExtractErr +// method to determine if the call succeeded or failed. type DeleteResult struct { gophercloud.ErrResult } -// AssociateResult is the response from a Delete operation. Call its Extract method to determine if -// the call succeeded or failed. +// AssociateResult is the response from a Delete operation. Call its ExtractErr +// method to determine if the call succeeded or failed. type AssociateResult struct { gophercloud.ErrResult } -// DisassociateResult is the response from a Delete operation. Call its Extract method to determine if -// the call succeeded or failed. +// DisassociateResult is the response from a Delete operation. Call its +// ExtractErr method to determine if the call succeeded or failed. type DisassociateResult struct { gophercloud.ErrResult } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs/doc.go index 856f41bac..24c460772 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs/doc.go @@ -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 diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs/requests.go index adf1e5596..4e5e499e3 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs/requests.go @@ -9,11 +9,12 @@ import ( // CreateOptsExt adds a KeyPair option to the base CreateOpts. type CreateOptsExt struct { servers.CreateOptsBuilder + + // KeyName is the name of the key pair. KeyName string `json:"key_name,omitempty"` } -// ToServerCreateMap adds the key_name and, optionally, key_data options to -// the base server creation options. +// ToServerCreateMap adds the key_name to the base server creation options. func (opts CreateOptsExt) ToServerCreateMap() (map[string]interface{}, error) { base, err := opts.CreateOptsBuilder.ToServerCreateMap() 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 -// CreateOpts struct in this package does. +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. type CreateOptsBuilder interface { ToKeyPairCreateMap() (map[string]interface{}, error) } -// CreateOpts specifies keypair creation or import parameters. +// CreateOpts specifies KeyPair creation or import parameters. type CreateOpts struct { // Name is a friendly name to refer to this KeyPair in other services. 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"` } @@ -57,8 +59,8 @@ func (opts CreateOpts) ToKeyPairCreateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "keypair") } -// Create requests the creation of a new keypair on the server, or to import a pre-existing -// keypair. +// Create requests the creation of a new KeyPair on the server, or to import a +// pre-existing keypair. func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { b, err := opts.ToKeyPairCreateMap() if err != nil { diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs/results.go index 4c785a24c..2d71034b1 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs/results.go @@ -5,29 +5,33 @@ import ( "github.com/gophercloud/gophercloud/pagination" ) -// KeyPair is an SSH key known to the OpenStack Cloud that is available to be injected into -// servers. +// KeyPair is an SSH key known to the OpenStack Cloud that is available to be +// injected into servers. 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"` - // Fingerprint is a short sequence of bytes that can be used to authenticate or validate a longer - // public key. + // Fingerprint is a short sequence of bytes that can be used to authenticate + // or validate a longer public key. 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"` // 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 - // returned from a Create call + // "-----BEGIN RSA PRIVATE KEY-----\nMIICXA..." + // It is only present if this KeyPair was just returned from a Create call. 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"` } -// 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 { pagination.SinglePageBase } @@ -58,7 +62,8 @@ type keyPairResult struct { 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) { var s struct { KeyPair *KeyPair `json:"keypair"` @@ -67,20 +72,20 @@ func (r keyPairResult) Extract() (*KeyPair, error) { return s.KeyPair, err } -// CreateResult is the response from a Create operation. Call its Extract method to interpret it -// as a KeyPair. +// CreateResult is the response from a Create operation. Call its Extract method +// to interpret it as a KeyPair. type CreateResult struct { keyPairResult } -// GetResult is the response from a Get operation. Call its Extract method to interpret it -// as a KeyPair. +// GetResult is the response from a Get operation. Call its Extract method to +// interpret it as a KeyPair. type GetResult struct { keyPairResult } -// DeleteResult is the response from a Delete operation. Call its Extract method to determine if -// the call succeeded or failed. +// DeleteResult is the response from a Delete operation. Call its ExtractErr +// method to determine if the call succeeded or failed. type DeleteResult struct { gophercloud.ErrResult } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints/doc.go index 0bd45661b..2d9d3acde 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints/doc.go @@ -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 diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints/requests.go index a34263efc..3fabeddef 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints/requests.go @@ -10,23 +10,29 @@ import ( ) // SchedulerHints represents a set of scheduling hints that are passed to the -// OpenStack scheduler +// OpenStack scheduler. type SchedulerHints struct { // Group specifies a Server Group to place the instance in. Group string + // DifferentHost will place the instance on a compute node that does not // host the given instances. DifferentHost []string + // SameHost will place the instance on a compute node that hosts the given // instances. SameHost []string + // Query is a conditional statement that results in compute nodes able to // host the instance. Query []interface{} + // TargetCell specifies a cell name where the instance will be placed. TargetCell string `json:"target_cell,omitempty"` + // BuildNearHostIP specifies a subnet of compute nodes to host the instance. BuildNearHostIP string + // AdditionalProperies are arbitrary key/values that are not validated by nova. AdditionalProperties map[string]interface{} } @@ -79,8 +85,9 @@ func (opts SchedulerHints) ToServerSchedulerHintsCreateMap() (map[string]interfa 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: ['and', @@ -130,6 +137,7 @@ func (opts SchedulerHints) ToServerSchedulerHintsCreateMap() (map[string]interfa // CreateOptsExt adds a SchedulerHints option to the base CreateOpts. type CreateOptsExt struct { servers.CreateOptsBuilder + // SchedulerHints provides a set of hints to the scheduler. SchedulerHints CreateOptsBuilder } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/doc.go index 702f32c98..8d3ebf2e5 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/doc.go @@ -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 diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/requests.go index ec8019f18..92a0331e1 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/requests.go @@ -23,25 +23,21 @@ func ListByServer(client *gophercloud.ServiceClient, serverID string) pagination return commonList(client, listByServerURL(client, serverID)) } -// GroupOpts is the underlying struct responsible for creating or updating -// security groups. It therefore represents the mutable attributes of a -// security group. -type GroupOpts struct { +// CreateOpts is the struct responsible for creating a security group. +type CreateOpts struct { // the name of your security group. Name string `json:"name" required:"true"` // 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. -type CreateOpts GroupOpts - -// CreateOptsBuilder builds the create options into a serializable format. +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. type CreateOptsBuilder interface { ToSecGroupCreateMap() (map[string]interface{}, error) } -// ToSecGroupCreateMap builds the create options into a serializable format. +// ToSecGroupCreateMap builds a request body from CreateOpts. func (opts CreateOpts) ToSecGroupCreateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "security_group") } @@ -60,14 +56,20 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create } // 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 { ToSecGroupUpdateMap() (map[string]interface{}, error) } -// ToSecGroupUpdateMap builds the update options into a serializable format. +// ToSecGroupUpdateMap builds a request body from UpdateOpts. func (opts UpdateOpts) ToSecGroupUpdateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "security_group") } @@ -93,7 +95,7 @@ func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { } // Delete will permanently delete a security group from the project. -func Delete(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResult) { +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { _, r.Err = client.Delete(resourceURL(client, id), nil) return } @@ -101,31 +103,41 @@ func Delete(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResu // CreateRuleOpts represents the configuration for adding a new rule to an // existing security group. type CreateRuleOpts struct { - // the ID of the group that this rule will be added to. + // ID is the ID of the group that this rule will be added to. ParentGroupID string `json:"parent_group_id" required:"true"` - // the lower bound of the port range that will be opened. + + // FromPort is the lower bound of the port range that will be opened. + // Use -1 to allow all ICMP traffic. FromPort int `json:"from_port"` - // the upper bound of the port range that will be opened. + + // ToPort is the upper bound of the port range that will be opened. + // Use -1 to allow all ICMP traffic. ToPort int `json:"to_port"` - // the protocol type that will be allowed, e.g. TCP. + + // IPProtocol the protocol type that will be allowed, e.g. TCP. IPProtocol string `json:"ip_protocol" required:"true"` - // ONLY required if FromGroupID is blank. This represents the IP range that - // will be the source of network traffic to your security group. Use - // 0.0.0.0/0 to allow all IP addresses. + + // CIDR is the network CIDR to allow traffic from. + // This is ONLY required if FromGroupID is blank. This represents the IP + // range that will be the source of network traffic to your security group. + // Use 0.0.0.0/0 to allow all IP addresses. CIDR string `json:"cidr,omitempty" or:"FromGroupID"` - // ONLY required if CIDR is blank. This value represents the ID of a group - // that forwards traffic to the parent group. So, instead of accepting + + // FromGroupID represents another security group to allow access. + // This is ONLY required if CIDR is blank. This value represents the ID of a + // group that forwards traffic to the parent group. So, instead of accepting // network traffic from an entire IP range, you can instead refine the // inbound source by an existing security group. FromGroupID string `json:"group_id,omitempty" or:"CIDR"` } -// CreateRuleOptsBuilder builds the create rule options into a serializable format. +// CreateRuleOptsBuilder allows extensions to add additional parameters to the +// CreateRule request. type CreateRuleOptsBuilder interface { ToRuleCreateMap() (map[string]interface{}, error) } -// ToRuleCreateMap builds the create rule options into a serializable format. +// ToRuleCreateMap builds a request body from CreateRuleOpts. func (opts CreateRuleOpts) ToRuleCreateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "security_group_rule") } @@ -146,7 +158,7 @@ func CreateRule(client *gophercloud.ServiceClient, opts CreateRuleOptsBuilder) ( } // DeleteRule will permanently delete a rule from a security group. -func DeleteRule(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResult) { +func DeleteRule(client *gophercloud.ServiceClient, id string) (r DeleteRuleResult) { _, r.Err = client.Delete(resourceRuleURL(client, id), nil) return } @@ -159,13 +171,13 @@ func actionMap(prefix, groupName string) map[string]map[string]string { // AddServer will associate a server and a security group, enforcing the // rules of the group on the server. -func AddServer(client *gophercloud.ServiceClient, serverID, groupName string) (r gophercloud.ErrResult) { - _, r.Err = client.Post(serverActionURL(client, serverID), actionMap("add", groupName), &r.Body, nil) +func AddServer(client *gophercloud.ServiceClient, serverID, groupName string) (r AddServerResult) { + _, r.Err = client.Post(serverActionURL(client, serverID), actionMap("add", groupName), nil, nil) return } // RemoveServer will disassociate a server from a security group. -func RemoveServer(client *gophercloud.ServiceClient, serverID, groupName string) (r gophercloud.ErrResult) { - _, r.Err = client.Post(serverActionURL(client, serverID), actionMap("remove", groupName), &r.Body, nil) +func RemoveServer(client *gophercloud.ServiceClient, serverID, groupName string) (r RemoveServerResult) { + _, r.Err = client.Post(serverActionURL(client, serverID), actionMap("remove", groupName), nil, nil) return } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/results.go index f49338a1d..046889220 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/results.go @@ -59,20 +59,20 @@ type Rule struct { // numeric ID. For the sake of consistency, we always cast it to a string. ID string `json:"-"` - // The lower bound of the port range which this security group should open up + // The lower bound of the port range which this security group should open up. FromPort int `json:"from_port"` - // The upper bound of the port range which this security group should open up + // The upper bound of the port range which this security group should open up. ToPort int `json:"to_port"` - // The IP protocol (e.g. TCP) which the security group accepts + // The IP protocol (e.g. TCP) which the security group accepts. IPProtocol string `json:"ip_protocol"` - // The CIDR IP range whose traffic can be received + // The CIDR IP range whose traffic can be received. IPRange IPRange `json:"ip_range"` - // The security group ID to which this rule belongs - ParentGroupID string `json:"parent_group_id"` + // The security group ID to which this rule belongs. + ParentGroupID string `json:"-"` // Not documented. Group Group @@ -126,13 +126,15 @@ type SecurityGroupPage struct { pagination.SinglePageBase } -// IsEmpty determines whether or not a page of Security Groups contains any results. +// IsEmpty determines whether or not a page of Security Groups contains any +// results. func (page SecurityGroupPage) IsEmpty() (bool, error) { users, err := ExtractSecurityGroups(page) return len(users) == 0, err } -// ExtractSecurityGroups returns a slice of SecurityGroups contained in a single page of results. +// ExtractSecurityGroups returns a slice of SecurityGroups contained in a +// single page of results. func ExtractSecurityGroups(r pagination.Page) ([]SecurityGroup, error) { var s struct { SecurityGroups []SecurityGroup `json:"security_groups"` @@ -145,17 +147,20 @@ type commonResult struct { gophercloud.Result } -// CreateResult represents the result of a create operation. +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret the result as a SecurityGroup. type CreateResult struct { commonResult } -// GetResult represents the result of a get operation. +// GetResult represents the result of a get operation. Call its Extract +// method to interpret the result as a SecurityGroup. type GetResult struct { commonResult } -// UpdateResult represents the result of an update operation. +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret the result as a SecurityGroup. type UpdateResult struct { commonResult } @@ -170,6 +175,7 @@ func (r commonResult) Extract() (*SecurityGroup, error) { } // CreateRuleResult represents the result when adding rules to a security group. +// Call its Extract method to interpret the result as a Rule. type CreateRuleResult struct { gophercloud.Result } @@ -182,3 +188,27 @@ func (r CreateRuleResult) Extract() (*Rule, error) { err := r.ExtractInto(&s) return s.Rule, err } + +// DeleteResult is the response from delete operation. Call its ExtractErr +// method to determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// DeleteRuleResult is the response from a DeleteRule operation. Call its +// ExtractErr method to determine if the request succeeded or failed. +type DeleteRuleResult struct { + gophercloud.ErrResult +} + +// AddServerResult is the response from an AddServer operation. Call its +// ExtractErr method to determine if the request succeeded or failed. +type AddServerResult struct { + gophercloud.ErrResult +} + +// RemoveServerResult is the response from a RemoveServer operation. Call its +// ExtractErr method to determine if the request succeeded or failed. +type RemoveServerResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/doc.go index 1e5ed568d..814bde37f 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/doc.go @@ -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 diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/requests.go index ee9883707..1439a5a34 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/requests.go @@ -5,23 +5,25 @@ import ( "github.com/gophercloud/gophercloud/pagination" ) -// List returns a Pager that allows you to iterate over a collection of ServerGroups. +// List returns a Pager that allows you to iterate over a collection of +// ServerGroups. func List(client *gophercloud.ServiceClient) pagination.Pager { return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page { return ServerGroupPage{pagination.SinglePageBase(r)} }) } -// CreateOptsBuilder describes struct types that can be accepted by the Create call. Notably, the -// CreateOpts struct in this package does. +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. type CreateOptsBuilder interface { ToServerGroupCreateMap() (map[string]interface{}, error) } -// CreateOpts specifies a Server Group allocation request +// CreateOpts specifies Server Group creation parameters. type CreateOpts struct { // Name is the name of the server group Name string `json:"name" required:"true"` + // Policies are the server group policies Policies []string `json:"policies" required:"true"` } @@ -31,7 +33,7 @@ func (opts CreateOpts) ToServerGroupCreateMap() (map[string]interface{}, error) return gophercloud.BuildRequestBody(opts, "server_group") } -// Create requests the creation of a new Server Group +// Create requests the creation of a new Server Group. func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { b, err := opts.ToServerGroupCreateMap() if err != nil { diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/results.go index ab49b35a4..b9aeef981 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/results.go @@ -5,7 +5,7 @@ import ( "github.com/gophercloud/gophercloud/pagination" ) -// A ServerGroup creates a policy for instance placement in the cloud +// A ServerGroup creates a policy for instance placement in the cloud. type ServerGroup struct { // ID is the unique ID of the Server Group. ID string `json:"id"` @@ -14,17 +14,26 @@ type ServerGroup struct { Name string `json:"name"` // Polices are the group policies. + // + // Normally a single policy is applied: + // + // "affinity" will place all servers within the server group on the + // same compute node. + // + // "anti-affinity" will place servers within the server group on different + // compute nodes. Policies []string `json:"policies"` // Members are the members of the server group. Members []string `json:"members"` - // Metadata includes a list of all user-specified key-value pairs attached to the Server Group. + // Metadata includes a list of all user-specified key-value pairs attached + // to the Server Group. Metadata map[string]interface{} } -// ServerGroupPage stores a single, only page of ServerGroups -// results from a List call. +// ServerGroupPage stores a single page of all ServerGroups results from a +// List call. type ServerGroupPage struct { pagination.SinglePageBase } @@ -59,20 +68,20 @@ func (r ServerGroupResult) Extract() (*ServerGroup, error) { return s.ServerGroup, err } -// CreateResult is the response from a Create operation. Call its Extract method to interpret it -// as a ServerGroup. +// CreateResult is the response from a Create operation. Call its Extract method +// to interpret it as a ServerGroup. type CreateResult struct { ServerGroupResult } -// GetResult is the response from a Get operation. Call its Extract method to interpret it -// as a ServerGroup. +// GetResult is the response from a Get operation. Call its Extract method to +// interpret it as a ServerGroup. type GetResult struct { ServerGroupResult } -// DeleteResult is the response from a Delete operation. Call its Extract method to determine if -// the call succeeded or failed. +// DeleteResult is the response from a Delete operation. Call its ExtractErr +// method to determine if the call succeeded or failed. type DeleteResult struct { gophercloud.ErrResult } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop/doc.go index d2729f874..ab97edb77 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop/doc.go @@ -1,5 +1,19 @@ /* Package startstop provides functionality to start and stop servers that have 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 diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop/requests.go index 1d8a593b9..5b4f3f39d 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop/requests.go @@ -7,13 +7,13 @@ func actionURL(client *gophercloud.ServiceClient, id string) string { } // Start is the operation responsible for starting a Compute server. -func Start(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResult) { +func Start(client *gophercloud.ServiceClient, id string) (r StartResult) { _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"os-start": nil}, nil, nil) return } // Stop is the operation responsible for stopping a Compute server. -func Stop(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResult) { +func Stop(client *gophercloud.ServiceClient, id string) (r StopResult) { _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"os-stop": nil}, nil, nil) return } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop/results.go new file mode 100644 index 000000000..834968933 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop/results.go @@ -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 +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/tenantnetworks/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/tenantnetworks/doc.go index 65c46ff50..a32e8ffd5 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/tenantnetworks/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/tenantnetworks/doc.go @@ -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 diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/tenantnetworks/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/tenantnetworks/requests.go index 82836d4b8..00899056f 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/tenantnetworks/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/tenantnetworks/requests.go @@ -5,7 +5,7 @@ import ( "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 { return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page { return NetworkPage{pagination.SinglePageBase(r)} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/tenantnetworks/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/tenantnetworks/results.go index 88cbc80ec..bda77d5f5 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/tenantnetworks/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/tenantnetworks/results.go @@ -5,7 +5,7 @@ import ( "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 { // CIDR is the IPv4 subnet. CIDR string `json:"cidr"` @@ -17,8 +17,7 @@ type Network struct { Name string `json:"label"` } -// NetworkPage stores a single, only page of Networks -// results from a List call. +// NetworkPage stores a single page of all Networks results from a List call. type NetworkPage struct { pagination.SinglePageBase } @@ -29,7 +28,7 @@ func (page NetworkPage) IsEmpty() (bool, error) { 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) { var s struct { Networks []Network `json:"networks"` @@ -42,8 +41,8 @@ type NetworkResult struct { gophercloud.Result } -// Extract is a method that attempts to interpret any Network resource -// response as a Network struct. +// Extract is a method that attempts to interpret any Network resource response +// as a Network struct. func (r NetworkResult) Extract() (*Network, error) { var s struct { Network *Network `json:"network"` @@ -52,8 +51,8 @@ func (r NetworkResult) Extract() (*Network, error) { return s.Network, err } -// GetResult is the response from a Get operation. Call its Extract method to interpret it -// as a Network. +// GetResult is the response from a Get operation. Call its Extract method to +// interpret it as a Network. type GetResult struct { NetworkResult } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/doc.go index 22f68d80e..484eb2000 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/doc.go @@ -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 diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/requests.go index ee4d62ddb..6a262c212 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/requests.go @@ -5,24 +5,26 @@ import ( "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 { return pagination.NewPager(client, listURL(client, serverID), func(r pagination.PageResult) pagination.Page { return VolumeAttachmentPage{pagination.SinglePageBase(r)} }) } -// CreateOptsBuilder describes struct types that can be accepted by the Create call. Notable, the -// CreateOpts struct in this package does. +// CreateOptsBuilder allows extensions to add parameters to the Create request. type CreateOptsBuilder interface { ToVolumeAttachmentCreateMap() (map[string]interface{}, error) } // CreateOpts specifies volume attachment creation or import parameters. type CreateOpts struct { - // Device is the device that the volume will attach to the instance as. Omit for "auto" + // Device is the device that the volume will attach to the instance as. + // Omit for "auto". 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"` } @@ -31,7 +33,7 @@ func (opts CreateOpts) ToVolumeAttachmentCreateMap() (map[string]interface{}, er 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) { b, err := opts.ToVolumeAttachmentCreateMap() if err != nil { @@ -50,7 +52,8 @@ func Get(client *gophercloud.ServiceClient, serverID, attachmentID string) (r Ge 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) { _, r.Err = client.Delete(deleteURL(client, serverID, attachmentID), nil) return diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/results.go index 53faf5d3a..56d503472 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach/results.go @@ -5,35 +5,36 @@ import ( "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 { - // ID is a unique id of the attachment + // ID is a unique id of the attachment. 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"` - // VolumeID is the ID of the attached volume + // VolumeID is the ID of the attached volume. 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"` } -// VolumeAttachmentPage stores a single, only page of VolumeAttachments +// VolumeAttachmentPage stores a single page all of VolumeAttachment // results from a List call. type VolumeAttachmentPage struct { 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) { va, err := ExtractVolumeAttachments(page) return len(va) == 0, err } // ExtractVolumeAttachments interprets a page of results as a slice of -// VolumeAttachments. +// VolumeAttachment. func ExtractVolumeAttachments(r pagination.Page) ([]VolumeAttachment, error) { var s struct { VolumeAttachments []VolumeAttachment `json:"volumeAttachments"` @@ -57,20 +58,20 @@ func (r VolumeAttachmentResult) Extract() (*VolumeAttachment, error) { return s.VolumeAttachment, err } -// CreateResult is the response from a Create operation. Call its Extract method to interpret it -// as a VolumeAttachment. +// CreateResult is the response from a Create operation. Call its Extract method +// to interpret it as a VolumeAttachment. type CreateResult struct { VolumeAttachmentResult } -// GetResult is the response from a Get operation. Call its Extract method to interpret it -// as a VolumeAttachment. +// GetResult is the response from a Get operation. Call its Extract method to +// interpret it as a VolumeAttachment. type GetResult struct { VolumeAttachmentResult } -// DeleteResult is the response from a Delete operation. Call its Extract method to determine if -// the call succeeded or failed. +// 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 } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/doc.go index 5822e1bcf..34d8764fa 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/doc.go @@ -1,7 +1,137 @@ -// Package flavors provides information and interaction with the flavor API -// resource in the OpenStack Compute service. -// -// A flavor is an available hardware configuration for a server. Each flavor -// has a unique combination of disk space, memory capacity and priority for CPU -// time. +/* +Package flavors 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 +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 diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/requests.go index d5d571c3d..539019e90 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/requests.go @@ -11,24 +11,66 @@ type ListOptsBuilder interface { ToFlavorListQuery() (string, error) } -// ListOpts helps control the results returned by the List() function. -// For example, a flavor with a minDisk field of 10 will not be returned if you specify MinDisk set to 20. -// Typically, software will use the last ID of the previous call to List to set the Marker for the current call. -type ListOpts struct { +/* + AccessType maps to OpenStack's Flavor.is_public field. Although the is_public + field is boolean, the request options are ternary, which is why AccessType is + 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"` - // 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"` 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 instructs List where to start listing from. 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"` + + // 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. @@ -38,8 +80,8 @@ func (opts ListOpts) ToFlavorListQuery() (string, error) { } // ListDetail instructs OpenStack to provide a list of flavors. -// You may provide criteria by which List curtails its results for easier processing. -// See ListOpts for more details. +// You may provide criteria by which List curtails its results for easier +// processing. func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { url := listURL(client) if opts != nil { @@ -58,31 +100,42 @@ type CreateOptsBuilder interface { ToFlavorCreateMap() (map[string]interface{}, error) } -// CreateOpts is passed to Create to create a flavor -// Source: -// https://github.com/openstack/nova/blob/stable/newton/nova/api/openstack/compute/schemas/flavor_manage.py#L20 +// CreateOpts specifies parameters used for creating a flavor. type CreateOpts struct { + // Name is the name of the flavor. 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"` - // disk size, in GBs - Disk *int `json:"disk" required:"true"` - ID string `json:"id,omitempty"` - // non-zero, positive - Swap *int `json:"swap,omitempty"` + + // Disk the amount of root disk space, measured in GB. + Disk *int `json:"disk" required:"true"` + + // 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"` - 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"` } -// ToFlavorCreateMap satisfies the CreateOptsBuilder interface -func (opts *CreateOpts) ToFlavorCreateMap() (map[string]interface{}, error) { +// ToFlavorCreateMap constructs a request body from CreateOpts. +func (opts CreateOpts) ToFlavorCreateMap() (map[string]interface{}, error) { 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) { b, err := opts.ToFlavorCreateMap() if err != nil { @@ -95,14 +148,177 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create return } -// Get instructs OpenStack to provide details on a single flavor, identified by its ID. -// Use ExtractFlavor to convert its result into a Flavor. +// Get retrieves details of a single flavor. Use ExtractFlavor to convert its +// result into a Flavor. func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { _, r.Err = client.Get(getURL(client, id), &r.Body, nil) 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) { count := 0 id := "" diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/results.go index 18b843405..92fe1b180 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/results.go @@ -12,16 +12,26 @@ type commonResult struct { gophercloud.Result } +// CreateResult is the response of a Get operations. Call its Extract method to +// interpret it as a Flavor. type CreateResult struct { 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 { 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) { var s struct { Flavor *Flavor `json:"flavor"` @@ -30,22 +40,35 @@ func (r commonResult) Extract() (*Flavor, error) { 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 { - // The Id field contains the flavor's unique identifier. - // For example, this identifier will be useful when specifying which hardware configuration to use for a new server instance. + // ID is the flavor's unique 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"` - RAM int `json:"ram"` - // The Name field provides a human-readable moniker for the flavor. - Name string `json:"name"` + + // RAM is the amount of memory, measured in MB. + 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"` - // Swap indicates how much space is reserved for swap. - // If not provided, this field will be set to 0. - Swap int `json:"swap"` + + // Swap is the amount of swap space, measured in MB. + Swap int `json:"-"` + // VCPUs indicates how many (virtual) CPUs are available for this flavor. 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 { @@ -80,18 +103,19 @@ func (r *Flavor) UnmarshalJSON(b []byte) error { 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 { pagination.LinkedPageBase } -// IsEmpty determines if a page contains any results. +// IsEmpty determines if a FlavorPage contains any results. func (page FlavorPage) IsEmpty() (bool, error) { flavors, err := ExtractFlavors(page) 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) { var s struct { Links []gophercloud.Link `json:"flavors_links"` @@ -103,7 +127,8 @@ func (page FlavorPage) NextPageURL() (string, error) { 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) { var s struct { Flavors []Flavor `json:"flavors"` @@ -111,3 +136,117 @@ func ExtractFlavors(r pagination.Page) ([]Flavor, error) { err := (r.(FlavorPage)).ExtractInto(&s) 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 +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/urls.go index 2fc21796f..8620dd78a 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/urls.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/urls.go @@ -15,3 +15,35 @@ func listURL(client *gophercloud.ServiceClient) string { func createURL(client *gophercloud.ServiceClient) string { 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) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/doc.go index 0edaa3f02..22410a79a 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/doc.go @@ -1,7 +1,32 @@ -// Package images provides information and interaction with the image API -// resource in the OpenStack Compute service. -// -// An image is a collection of files used to create or rebuild a server. -// Operators provide a number of pre-built OS images by default. You may also -// create custom images from cloud servers you have launched. +/* +Package images provides information and interaction with the images through +the OpenStack Compute service. + +This API is deprecated and will be removed from a future version of the Nova +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 diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/requests.go index df9f1da8f..558b481b9 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/requests.go @@ -6,26 +6,33 @@ import ( ) // ListOptsBuilder allows extensions to add additional parameters to the -// List request. +// ListDetail request. type ListOptsBuilder interface { 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 { - // 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"` - // The number of Images to return. + + // Limit limits the number of Images to return. 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"` - // The name of the Image. + + // Name is the name of the Image. 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"` - // The current status of the Image. + + // Status is the current status of the Image. 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"` } @@ -50,8 +57,7 @@ func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) paginat }) } -// Get acquires additional detail about a specific image by ID. -// Use ExtractImage() to interpret the result as an openstack Image. +// Get returns data about a specific image by its ID. func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { _, r.Err = client.Get(getURL(client, id), &r.Body, nil) return @@ -63,7 +69,8 @@ func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { 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) { count := 0 id := "" diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/results.go index f9ebc69e9..70d1018c7 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/results.go @@ -5,12 +5,14 @@ import ( "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 { 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 { gophercloud.ErrResult } @@ -24,44 +26,53 @@ func (r GetResult) Extract() (*Image, error) { return s.Image, err } -// Image is used for JSON (un)marshalling. -// It provides a description of an OS image. +// Image represents an Image returned by the Compute API. type Image struct { - // ID contains the image's unique identifier. + // ID is the unique ID of an image. ID string + // Created is the date when the image was created. 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 - 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 string // The Progress and Status fields indicate image-creation status. - // Any usable image will have 100% progress. 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 + // Metadata provides free-form key/value pairs that further describe the + // image. Metadata map[string]interface{} } -// ImagePage contains a single page of results from a List operation. -// Use ExtractImages to convert it into a slice of usable structs. +// ImagePage contains a single page of all Images returne from a ListDetail +// operation. Use ExtractImages to convert it into a slice of usable structs. type ImagePage struct { pagination.LinkedPageBase } -// IsEmpty returns true if a page contains no Image results. +// IsEmpty returns true if an ImagePage contains no Image results. func (page ImagePage) IsEmpty() (bool, error) { images, err := ExtractImages(page) 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) { var s struct { Links []gophercloud.Link `json:"images_links"` @@ -73,7 +84,8 @@ func (page ImagePage) NextPageURL() (string, error) { 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) { var s struct { Images []Image `json:"images"` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/doc.go index fe4567120..3b0ab7836 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/doc.go @@ -1,6 +1,115 @@ -// Package servers provides information and interaction with the server API -// resource in the OpenStack Compute service. -// -// A server is a virtual machine instance in the compute system. In order for -// one to be provisioned, a valid flavor and image are required. +/* +Package servers provides information and interaction with the server API +resource in the OpenStack Compute service. + +A server is a virtual machine instance in the compute system. In order for +one to be provisioned, a valid flavor and image are required. + +Example to List Servers + + listOpts := servers.ListOpts{ + AllTenants: true, + } + + allPages, err := servers.List(computeClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allServers, err := servers.ExtractServers(allPages) + if err != nil { + panic(err) + } + + for _, server := range allServers { + fmt.Printf("%+v\n", server) + } + +Example to Create a Server + + createOpts := servers.CreateOpts{ + Name: "server_name", + ImageRef: "image-uuid", + FlavorRef: "flavor-uuid", + } + + server, err := servers.Create(computeClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Server + + serverID := "d9072956-1560-487c-97f2-18bdf65ec749" + err := servers.Delete(computeClient, serverID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Force Delete a Server + + serverID := "d9072956-1560-487c-97f2-18bdf65ec749" + err := servers.ForceDelete(computeClient, serverID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Reboot a Server + + rebootOpts := servers.RebootOpts{ + Type: servers.SoftReboot, + } + + serverID := "d9072956-1560-487c-97f2-18bdf65ec749" + + err := servers.Reboot(computeClient, serverID, rebootOpts).ExtractErr() + if err != nil { + panic(err) + } + +Example to Rebuild a Server + + rebuildOpts := servers.RebuildOpts{ + Name: "new_name", + ImageID: "image-uuid", + } + + serverID := "d9072956-1560-487c-97f2-18bdf65ec749" + + server, err := servers.Rebuilt(computeClient, serverID, rebuildOpts).Extract() + if err != nil { + panic(err) + } + +Example to Resize a Server + + resizeOpts := servers.ResizeOpts{ + FlavorRef: "flavor-uuid", + } + + serverID := "d9072956-1560-487c-97f2-18bdf65ec749" + + err := servers.Resize(computeClient, serverID, resizeOpts).ExtractErr() + if err != nil { + panic(err) + } + + err = servers.ConfirmResize(computeClient, serverID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Snapshot a Server + + snapshotOpts := servers.CreateImageOpts{ + Name: "snapshot_name", + } + + serverID := "d9072956-1560-487c-97f2-18bdf65ec749" + + image, err := servers.CreateImage(computeClient, serverID, snapshotOpts).ExtractImageID() + if err != nil { + panic(err) + } +*/ package servers diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/microversions.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/microversions.go new file mode 100644 index 000000000..84ec9f31d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/microversions.go @@ -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 +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go index 961863731..646bd4ccf 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go @@ -21,13 +21,13 @@ type ListOptsBuilder interface { // the server attributes you want to see returned. Marker and Limit are used // for pagination. 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"` - // Name of the image in URL format. + // Image is the name of the image in URL format. 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"` // 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. 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"` - // Name of the host as a string. + // Host is the name of the host as a string. 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"` - // 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"` - // Bool to show all tenants + // AllTenants is a bool to show all tenants. AllTenants bool `q:"all_tenants"` + + // TenantID lists servers for a particular tenant. + // Setting "AllTenants = true" is required. + TenantID string `q:"tenant_id"` } // 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. -// The CreateOpts struct in this package does. +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. type CreateOptsBuilder interface { ToServerCreateMap() (map[string]interface{}, error) } -// Network is used within CreateOpts to control a new server's network attachments. +// Network is used within CreateOpts to control a new server's network +// attachments. 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. UUID string @@ -89,19 +95,21 @@ type Network struct { // Required unless UUID is provided. 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 } // Personality is an array of files that are injected into the server at launch. type Personality []*File -// File is used within CreateOpts and RebuildOpts to inject a file into the server at launch. -// File implements the json.Marshaler interface, so when a Create or Rebuild operation is requested, -// json.Marshal will call File's MarshalJSON method. +// File is used within CreateOpts and RebuildOpts to inject a file into the +// server at launch. +// File implements the json.Marshaler interface, so when a Create or Rebuild +// operation is requested, json.Marshal will call File's MarshalJSON method. type File struct { - // Path of the file + // Path of the file. Path string + // Contents of the file. Maximum content size is 255 bytes. Contents []byte } @@ -123,13 +131,13 @@ type CreateOpts struct { // Name is the name to assign to the newly launched server. Name string `json:"name" required:"true"` - // ImageRef [optional; required if ImageName is not provided] is the ID or full - // URL to the image that contains the server's OS and initial state. + // ImageRef [optional; required if ImageName is not provided] is the ID or + // full URL to the image that contains the server's OS and initial state. // Also optional if using the boot-from-volume extension. ImageRef string `json:"imageRef"` - // ImageName [optional; required if ImageRef is not provided] is the name of the - // image that contains the server's OS and initial state. + // ImageName [optional; required if ImageRef is not provided] is the name of + // the image that contains the server's OS and initial state. // Also optional if using the boot-from-volume extension. ImageName string `json:"-"` @@ -141,7 +149,8 @@ type CreateOpts struct { // the flavor that describes the server's specs. 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:"-"` // UserData contains configuration information or scripts to use upon launch. @@ -152,10 +161,12 @@ type CreateOpts struct { AvailabilityZone string `json:"availability_zone,omitempty"` // Networks dictates how this server will be attached to available networks. - // By default, the server will be attached to all isolated networks for the tenant. + // By default, the server will be attached to all isolated networks for the + // tenant. 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"` // Personality includes files to inject into the server at launch. @@ -166,7 +177,7 @@ type CreateOpts struct { ConfigDrive *bool `json:"config_drive,omitempty"` // AdminPass sets the root user password. If not set, a randomly-generated - // password will be created and returned in the rponse. + // password will be created and returned in the response. AdminPass string `json:"adminPass,omitempty"` // 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 // flavor ID by name. ServiceClient *gophercloud.ServiceClient `json:"-"` + + // Tags allows a server to be tagged with single-word metadata. + // Requires microversion 2.52 or later. + Tags []string `json:"tags,omitempty"` } -// ToServerCreateMap assembles a request body based on the contents of a CreateOpts. +// ToServerCreateMap assembles a request body based on the contents of a +// CreateOpts. func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) { sc := opts.ServiceClient opts.ServiceClient = nil @@ -274,13 +290,14 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create 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) { _, r.Err = client.Delete(deleteURL(client, id), nil) return } -// ForceDelete forces the deletion of a server +// ForceDelete forces the deletion of a server. func ForceDelete(client *gophercloud.ServiceClient, id string) (r ActionResult) { _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"forceDelete": ""}, nil, nil) return @@ -294,12 +311,14 @@ func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { 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 { 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 { // Name changes the displayed name of the server. // The server host name will *not* change. @@ -331,7 +350,8 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder 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) { b := map[string]interface{}{ "changePassword": map[string]string{ @@ -354,33 +374,38 @@ const ( PowerCycle = HardReboot ) -// RebootOptsBuilder is an interface that options must satisfy in order to be -// used when rebooting a server instance +// RebootOptsBuilder allows extensions to add additional parameters to the +// reboot request. type RebootOptsBuilder interface { ToServerRebootMap() (map[string]interface{}, error) } -// RebootOpts satisfies the RebootOptsBuilder interface +// RebootOpts provides options to the reboot request. type RebootOpts struct { + // Type is the type of reboot to perform on the server. Type RebootMethod `json:"type" required:"true"` } -// ToServerRebootMap allows RebootOpts to satisfiy the RebootOptsBuilder -// interface -func (opts *RebootOpts) ToServerRebootMap() (map[string]interface{}, error) { +// ToServerRebootMap builds a body for the reboot request. +func (opts RebootOpts) ToServerRebootMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "reboot") } -// Reboot requests that a given server reboot. -// Two methods exist for rebooting a server: -// -// HardReboot (aka PowerCycle) starts the server instance by physically cutting power to the machine, or if a VM, -// terminating it at the hypervisor level. -// It's done. Caput. Full stop. -// Then, after a brief while, power is rtored or the VM instance rtarted. -// -// SoftReboot (aka OSReboot) simply tells the OS to rtart under its own procedur. -// E.g., in Linux, asking it to enter runlevel 6, or executing "sudo shutdown -r now", or by asking Windows to rtart the machine. +/* + Reboot requests that a given server reboot. + + Two methods exist for rebooting a server: + + HardReboot (aka PowerCycle) starts the server instance by physically cutting + power to the machine, or if a VM, terminating it at the hypervisor level. + It's done. Caput. Full stop. + Then, after a brief while, power is rtored or the VM instance restarted. + + SoftReboot (aka OSReboot) simply tells the OS to restart under its own + procedure. + E.g., in Linux, asking it to enter runlevel 6, or executing + "sudo shutdown -r now", or by asking Windows to rtart the machine. +*/ func Reboot(client *gophercloud.ServiceClient, id string, opts RebootOptsBuilder) (r ActionResult) { b, err := opts.ToServerRebootMap() if err != nil { @@ -391,31 +416,43 @@ func Reboot(client *gophercloud.ServiceClient, id string, opts RebootOptsBuilder return } -// RebuildOptsBuilder is an interface that allows extensions to override the -// default behaviour of rebuild options +// RebuildOptsBuilder allows extensions to provide additional parameters to the +// rebuild request. type RebuildOptsBuilder interface { ToServerRebuildMap() (map[string]interface{}, error) } // RebuildOpts represents the configuration options used in a server rebuild -// operation +// operation. type RebuildOpts struct { - // The server's admin password + // AdminPass is the server's admin password 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:"-"` + // Name to set the server to Name string `json:"name,omitempty"` + // AccessIPv4 [optional] provides a new IPv4 address for the instance. AccessIPv4 string `json:"accessIPv4,omitempty"` + // AccessIPv6 [optional] provides a new IPv6 address for the instance. AccessIPv6 string `json:"accessIPv6,omitempty"` - // Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server. + + // Metadata [optional] contains key-value pairs (up to 255 bytes each) + // to attach to the server. Metadata map[string]string `json:"metadata,omitempty"` + // Personality [optional] includes files to inject into the server at launch. // Rebuild will base64-encode file contents for you. - Personality Personality `json:"personality,omitempty"` + 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:"-"` } @@ -458,31 +495,34 @@ func Rebuild(client *gophercloud.ServiceClient, id string, opts RebuildOptsBuild return } -// ResizeOptsBuilder is an interface that allows extensions to override the default structure of -// a Resize request. +// ResizeOptsBuilder allows extensions to add additional parameters to the +// resize request. type ResizeOptsBuilder interface { ToServerResizeMap() (map[string]interface{}, error) } -// ResizeOpts represents the configuration options used to control a Resize operation. +// ResizeOpts represents the configuration options used to control a Resize +// operation. type ResizeOpts struct { // FlavorRef is the ID of the flavor you wish your server to become. FlavorRef string `json:"flavorRef" required:"true"` } -// ToServerResizeMap formats a ResizeOpts as a map that can be used as a JSON request body for the -// Resize request. +// ToServerResizeMap formats a ResizeOpts as a map that can be used as a JSON +// request body for the Resize request. func (opts ResizeOpts) ToServerResizeMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "resize") } // Resize instructs the provider to change the flavor of the server. +// // Note that this implies rebuilding it. +// // Unfortunately, one cannot pass rebuild parameters to the resize function. -// When the resize completes, the server will be in RESIZE_VERIFY state. -// While in this state, you can explore the use of the new server's configuration. -// If you like it, call ConfirmResize() to commit the resize permanently. -// Otherwise, call RevertResize() to restore the old configuration. +// When the resize completes, the server will be in VERIFY_RESIZE state. +// While in this state, you can explore the use of the new server's +// configuration. If you like it, call ConfirmResize() to commit the resize +// permanently. Otherwise, call RevertResize() to restore the old configuration. func Resize(client *gophercloud.ServiceClient, id string, opts ResizeOptsBuilder) (r ActionResult) { b, err := opts.ToServerResizeMap() if err != nil { @@ -509,41 +549,8 @@ func RevertResize(client *gophercloud.ServiceClient, id string) (r ActionResult) return } -// RescueOptsBuilder is an interface that allows extensions to override the -// default structure of a Rescue 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. +// ResetMetadataOptsBuilder allows extensions to add additional parameters to +// the Reset request. type ResetMetadataOptsBuilder interface { ToMetadataResetMap() (map[string]interface{}, error) } @@ -551,20 +558,23 @@ type ResetMetadataOptsBuilder interface { // MetadataOpts is a map that contains key-value pairs. type MetadataOpts map[string]string -// ToMetadataResetMap assembles a body for a Reset request based on the contents of a MetadataOpts. +// ToMetadataResetMap assembles a body for a Reset request based on the contents +// of a MetadataOpts. func (opts MetadataOpts) ToMetadataResetMap() (map[string]interface{}, error) { return map[string]interface{}{"metadata": opts}, nil } -// ToMetadataUpdateMap assembles a body for an Update request based on the contents of a MetadataOpts. +// ToMetadataUpdateMap assembles a body for an Update request based on the +// contents of a MetadataOpts. func (opts MetadataOpts) ToMetadataUpdateMap() (map[string]interface{}, error) { return map[string]interface{}{"metadata": opts}, nil } -// ResetMetadata will create multiple new key-value pairs for the given server ID. -// Note: Using this operation will erase any already-existing metadata and create -// the new metadata provided. To keep any already-existing metadata, use the -// UpdateMetadatas or UpdateMetadata function. +// ResetMetadata will create multiple new key-value pairs for the given server +// ID. +// Note: Using this operation will erase any already-existing metadata and +// create the new metadata provided. To keep any already-existing metadata, +// use the UpdateMetadatas or UpdateMetadata function. func ResetMetadata(client *gophercloud.ServiceClient, id string, opts ResetMetadataOptsBuilder) (r ResetMetadataResult) { b, err := opts.ToMetadataResetMap() if err != nil { @@ -583,15 +593,15 @@ func Metadata(client *gophercloud.ServiceClient, id string) (r GetMetadataResult return } -// UpdateMetadataOptsBuilder allows extensions to add additional parameters to the -// Create request. +// UpdateMetadataOptsBuilder allows extensions to add additional parameters to +// the Create request. type UpdateMetadataOptsBuilder interface { ToMetadataUpdateMap() (map[string]interface{}, error) } -// UpdateMetadata updates (or creates) all the metadata specified by opts for the given server ID. -// This operation does not affect already-existing metadata that is not specified -// by opts. +// UpdateMetadata updates (or creates) all the metadata specified by opts for +// the given server ID. This operation does not affect already-existing metadata +// that is not specified by opts. func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMetadataOptsBuilder) (r UpdateMetadataResult) { b, err := opts.ToMetadataUpdateMap() if err != nil { @@ -613,7 +623,8 @@ type MetadatumOptsBuilder interface { // MetadatumOpts is a map of length one that contains a key-value pair. type MetadatumOpts map[string]string -// ToMetadatumCreateMap assembles a body for a Create request based on the contents of a MetadataumOpts. +// ToMetadatumCreateMap assembles a body for a Create request based on the +// contents of a MetadataumOpts. func (opts MetadatumOpts) ToMetadatumCreateMap() (map[string]interface{}, string, error) { if len(opts) != 1 { err := gophercloud.ErrInvalidInput{} @@ -629,7 +640,8 @@ func (opts MetadatumOpts) ToMetadatumCreateMap() (map[string]interface{}, string 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) { b, key, err := opts.ToMetadatumCreateMap() if err != nil { @@ -642,53 +654,60 @@ func CreateMetadatum(client *gophercloud.ServiceClient, id string, opts Metadatu 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) { _, r.Err = client.Get(metadatumURL(client, id, key), &r.Body, nil) 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) { _, r.Err = client.Delete(metadatumURL(client, id, key), nil) 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 { return pagination.NewPager(client, listAddressesURL(client, id), func(r pagination.PageResult) pagination.Page { return AddressPage{pagination.SinglePageBase(r)} }) } -// ListAddressesByNetwork makes a request against the API to list the servers IP addresses -// for the given network. +// ListAddressesByNetwork makes a request against the API to list the servers IP +// addresses for the given network. func ListAddressesByNetwork(client *gophercloud.ServiceClient, id, network string) pagination.Pager { return pagination.NewPager(client, listAddressesByNetworkURL(client, id, network), func(r pagination.PageResult) pagination.Page { return NetworkAddressPage{pagination.SinglePageBase(r)} }) } -// CreateImageOptsBuilder is the interface types must satisfy in order to be -// used as CreateImage options +// CreateImageOptsBuilder allows extensions to add additional parameters to the +// CreateImage request. type CreateImageOptsBuilder interface { ToServerCreateImageMap() (map[string]interface{}, error) } -// CreateImageOpts satisfies the CreateImageOptsBuilder +// CreateImageOpts provides options to pass to the CreateImage request. type CreateImageOpts struct { - // Name of the image/snapshot + // Name of the image/snapshot. Name string `json:"name" required:"true"` - // Metadata contains key-value pairs (up to 255 bytes each) to attach to the created image. + + // Metadata contains key-value pairs (up to 255 bytes each) to attach to + // the created image. Metadata map[string]string `json:"metadata,omitempty"` } -// ToServerCreateImageMap formats a CreateImageOpts structure into a request body. +// ToServerCreateImageMap formats a CreateImageOpts structure into a request +// body. func (opts CreateImageOpts) ToServerCreateImageMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "createImage") } -// CreateImage makes a request against the nova API to schedule an image to be created of the server +// CreateImage makes a request against the nova API to schedule an image to be +// created of the server func CreateImage(client *gophercloud.ServiceClient, id string, opts CreateImageOptsBuilder) (r CreateImageResult) { b, err := opts.ToServerCreateImageMap() if err != nil { @@ -703,11 +722,17 @@ func CreateImage(client *gophercloud.ServiceClient, id string, opts CreateImageO 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) { count := 0 id := "" - allPages, err := List(client, nil).AllPages() + + listOpts := ListOpts{ + Name: name, + } + + allPages, err := List(client, listOpts).AllPages() if err != nil { 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) { _, r.Err = client.Get(passwordURL(client, serverId), &r.Body, nil) 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 +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go index 1ae1e91c7..f973d1ea0 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go @@ -32,54 +32,73 @@ func ExtractServersInto(r pagination.Page, v interface{}) error { 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 { 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 { 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 { 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 { 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 { 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 { gophercloud.ErrResult } -// RescueResult represents the result of a server rescue operation -type RescueResult struct { - ActionResult -} - -// CreateImageResult represents the result of an image creation operation +// CreateImageResult is the response from a CreateImage operation. Call its +// ExtractImageID method to retrieve the ID of the newly created image. type CreateImageResult struct { gophercloud.Result } +// ShowConsoleOutputResult represents the result of console output from a server +type ShowConsoleOutputResult struct { + gophercloud.Result +} + +// Extract will return the console output from a ShowConsoleOutput request. +func (r ShowConsoleOutputResult) Extract() (string, error) { + var s struct { + Output string `json:"output"` + } + + err := r.ExtractInto(&s) + return s.Output, err +} + // GetPasswordResult represent the result of a get os-server-password operation. +// Call its ExtractPassword method to retrieve the password. type GetPasswordResult struct { gophercloud.Result } // ExtractPassword gets the encrypted password. // If privateKey != nil the password is decrypted with the private key. -// If privateKey == nil the encrypted password is returned and can be decrypted with: +// If privateKey == nil the encrypted password is returned and can be decrypted +// with: // echo '' | base64 -D | openssl rsautl -decrypt -inkey func (r GetPasswordResult) ExtractPassword(privateKey *rsa.PrivateKey) (string, error) { var s struct { @@ -107,7 +126,7 @@ func decryptPassword(encryptedPassword string, privateKey *rsa.PrivateKey) (stri 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) { if r.Err != nil { return "", r.Err @@ -124,54 +143,84 @@ func (r CreateImageResult) ExtractImageID() (string, error) { return imageID, nil } -// Extract interprets any RescueResult as an AdminPass, if possible. -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. +// Server represents a server/instance in the OpenStack cloud. type Server struct { - // ID uniquely identifies this server amongst all other servers, including those not accessible to the current tenant. + // ID uniquely identifies this server amongst all other servers, + // including those not accessible to the current tenant. ID string `json:"id"` + // TenantID identifies the tenant owning this server resource. TenantID string `json:"tenant_id"` + // UserID uniquely identifies the user account owning the tenant. UserID string `json:"user_id"` + // Name contains the human-readable name for the server. Name string `json:"name"` - // Updated and Created contain ISO-8601 timestamps of when the state of the server last changed, and when it was created. + + // Updated and Created contain ISO-8601 timestamps of when the state of the + // server last changed, and when it was created. Updated time.Time `json:"updated"` Created time.Time `json:"created"` - HostID 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"` + // Progress ranges from 0..100. // A request made against the server completes only once Progress reaches 100. Progress int `json:"progress"` - // AccessIPv4 and AccessIPv6 contain the IP addresses of the server, suitable for remote access for administration. + + // AccessIPv4 and AccessIPv6 contain the IP addresses of the server, + // suitable for remote access for administration. AccessIPv4 string `json:"accessIPv4"` AccessIPv6 string `json:"accessIPv6"` - // Image refers to a JSON object, which itself indicates the OS image used to deploy the server. + + // Image refers to a JSON object, which itself indicates the OS image used to + // deploy the server. Image map[string]interface{} `json:"-"` - // Flavor refers to a JSON object, which itself indicates the hardware configuration of the deployed server. + + // Flavor refers to a JSON object, which itself indicates the hardware + // configuration of the deployed server. Flavor map[string]interface{} `json:"flavor"` - // Addresses includes a list of all IP addresses assigned to the server, keyed by pool. + + // Addresses includes a list of all IP addresses assigned to the server, + // keyed by pool. Addresses map[string]interface{} `json:"addresses"` - // Metadata includes a list of all user-specified key-value pairs attached to the server. + + // Metadata includes a list of all user-specified key-value pairs attached + // to the server. Metadata map[string]string `json:"metadata"` - // Links includes HTTP references to the itself, useful for passing along to other APIs that might want a server reference. + + // Links includes HTTP references to the itself, useful for passing along to + // other APIs that might want a server reference. Links []interface{} `json:"links"` + // KeyName indicates which public key was injected into the server on launch. KeyName string `json:"key_name"` - // AdminPass will generally be empty (""). However, it will contain the administrative password chosen when provisioning a new server without a set AdminPass setting in the first place. + + // AdminPass will generally be empty (""). However, it will contain the + // administrative password chosen when provisioning a new server without a + // set AdminPass setting in the first place. // Note that this is the ONLY time this field will be valid. AdminPass string `json:"adminPass"` - // SecurityGroups includes the security groups that this instance has applied to it + + // SecurityGroups includes the security groups that this instance has applied + // to it. 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 { @@ -200,9 +249,10 @@ func (r *Server) UnmarshalJSON(b []byte) error { return err } -// ServerPage abstracts the raw results of making a List() request against the API. -// As OpenStack extensions may freely alter the response bodies of structures returned to the client, you may only safely access the -// data provided through the ExtractServers call. +// ServerPage abstracts the raw results of making a List() request against +// the API. As OpenStack extensions may freely alter the response bodies of +// structures returned to the client, you may only safely access the data +// provided through the ExtractServers call. type ServerPage struct { pagination.LinkedPageBase } @@ -213,7 +263,8 @@ func (r ServerPage) IsEmpty() (bool, error) { 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) { var s struct { Links []gophercloud.Link `json:"servers_links"` @@ -225,49 +276,59 @@ func (r ServerPage) NextPageURL() (string, error) { 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) { var s []Server err := ExtractServersInto(r, &s) 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { gophercloud.ErrResult } @@ -296,9 +357,10 @@ type Address struct { Address string `json:"addr"` } -// AddressPage abstracts the raw results of making a ListAddresses() request against the API. -// As OpenStack extensions may freely alter the response bodies of structures returned -// to the client, you may only safely access the data provided through the ExtractAddresses call. +// AddressPage abstracts the raw results of making a ListAddresses() request +// against the API. As OpenStack extensions may freely alter the response bodies +// of structures returned to the client, you may only safely access the data +// provided through the ExtractAddresses call. type AddressPage struct { pagination.SinglePageBase } @@ -309,8 +371,8 @@ func (r AddressPage) IsEmpty() (bool, error) { return len(addresses) == 0, err } -// ExtractAddresses interprets the results of a single page from a ListAddresses() call, -// producing a map of addresses. +// ExtractAddresses interprets the results of a single page from a +// ListAddresses() call, producing a map of addresses. func ExtractAddresses(r pagination.Page) (map[string][]Address, error) { var s struct { Addresses map[string][]Address `json:"addresses"` @@ -319,9 +381,11 @@ func ExtractAddresses(r pagination.Page) (map[string][]Address, error) { return s.Addresses, err } -// NetworkAddressPage abstracts the raw results of making a ListAddressesByNetwork() request against the API. -// As OpenStack extensions may freely alter the response bodies of structures returned -// to the client, you may only safely access the data provided through the ExtractAddresses call. +// NetworkAddressPage abstracts the raw results of making a +// ListAddressesByNetwork() request against the API. +// As OpenStack extensions may freely alter the response bodies of structures +// returned to the client, you may only safely access the data provided through +// the ExtractAddresses call. type NetworkAddressPage struct { pagination.SinglePageBase } @@ -332,8 +396,8 @@ func (r NetworkAddressPage) IsEmpty() (bool, error) { return len(addresses) == 0, err } -// ExtractNetworkAddresses interprets the results of a single page from a ListAddressesByNetwork() call, -// producing a slice of addresses. +// ExtractNetworkAddresses interprets the results of a single page from a +// ListAddressesByNetwork() call, producing a slice of addresses. func ExtractNetworkAddresses(r pagination.Page) ([]Address, error) { var s map[string][]Address err := (r.(NetworkAddressPage)).ExtractInto(&s) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/util.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/util.go index 494a0e4dc..cadef0545 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/util.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/util.go @@ -2,8 +2,9 @@ package servers import "github.com/gophercloud/gophercloud" -// WaitForStatus will continually poll a server until it successfully transitions to a specified -// status. It will do this for at most the number of seconds specified. +// WaitForStatus will continually poll a server until it successfully +// transitions to a specified status. It will do this for at most the number +// of seconds specified. func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error { return gophercloud.WaitFor(secs, func() (bool, error) { current, err := Get(c, id).Extract() diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/doc.go new file mode 100644 index 000000000..3fd28d10c --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/doc.go @@ -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 diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/requests.go new file mode 100644 index 000000000..c6aff5ff7 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/requests.go @@ -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 +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/results.go new file mode 100644 index 000000000..b386b9be2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/results.go @@ -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 +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/urls.go new file mode 100644 index 000000000..6d132f74b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters/urls.go @@ -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) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/doc.go new file mode 100644 index 000000000..12289c2ab --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/doc.go @@ -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 diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/requests.go new file mode 100644 index 000000000..c3ceba074 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/requests.go @@ -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 +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/results.go new file mode 100644 index 000000000..8b416f7e7 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/results.go @@ -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 +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/urls.go new file mode 100644 index 000000000..f90fb8510 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates/urls.go @@ -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) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/configurations/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/configurations/doc.go new file mode 100644 index 000000000..45b9cfb4e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/configurations/doc.go @@ -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 diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/configurations/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/configurations/requests.go new file mode 100644 index 000000000..32bfb1dd4 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/configurations/requests.go @@ -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 +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/configurations/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/configurations/results.go new file mode 100644 index 000000000..13bcbffe8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/configurations/results.go @@ -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 +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/configurations/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/configurations/urls.go new file mode 100644 index 000000000..0a69253a7 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/configurations/urls.go @@ -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) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/databases/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/databases/doc.go new file mode 100644 index 000000000..15275fe5d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/databases/doc.go @@ -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 diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/databases/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/databases/requests.go new file mode 100644 index 000000000..ef5394f9c --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/databases/requests.go @@ -0,0 +1,89 @@ +package databases + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder builds create options +type CreateOptsBuilder interface { + ToDBCreateMap() (map[string]interface{}, error) +} + +// CreateOpts is the struct responsible for configuring a database; often in +// the context of an instance. +type CreateOpts struct { + // Specifies the name of the database. Valid names can be composed + // of the following characters: letters (either case); numbers; these + // characters '@', '?', '#', ' ' but NEVER beginning a name string; '_' is + // permitted anywhere. Prohibited characters that are forbidden include: + // single quotes, double quotes, back quotes, semicolons, commas, backslashes, + // and forward slashes. + Name string `json:"name" required:"true"` + // Set of symbols and encodings. The default character set is + // "utf8". See http://dev.mysql.com/doc/refman/5.1/en/charset-mysql.html for + // supported character sets. + CharSet string `json:"character_set,omitempty"` + // Set of rules for comparing characters in a character set. The + // default value for collate is "utf8_general_ci". See + // http://dev.mysql.com/doc/refman/5.1/en/charset-mysql.html for supported + // collations. + Collate string `json:"collate,omitempty"` +} + +// ToMap is a helper function to convert individual DB create opt structures +// into sub-maps. +func (opts CreateOpts) ToMap() (map[string]interface{}, error) { + if len(opts.Name) > 64 { + err := gophercloud.ErrInvalidInput{} + err.Argument = "databases.CreateOpts.Name" + err.Value = opts.Name + err.Info = "Must be less than 64 chars long" + return nil, err + } + return gophercloud.BuildRequestBody(opts, "") +} + +// BatchCreateOpts allows for multiple databases to created and modified. +type BatchCreateOpts []CreateOpts + +// ToDBCreateMap renders a JSON map for creating DBs. +func (opts BatchCreateOpts) ToDBCreateMap() (map[string]interface{}, error) { + dbs := make([]map[string]interface{}, len(opts)) + for i, db := range opts { + dbMap, err := db.ToMap() + if err != nil { + return nil, err + } + dbs[i] = dbMap + } + return map[string]interface{}{"databases": dbs}, nil +} + +// Create will create a new database within the specified instance. If the +// specified instance does not exist, a 404 error will be returned. +func Create(client *gophercloud.ServiceClient, instanceID string, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToDBCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(baseURL(client, instanceID), &b, nil, nil) + return +} + +// List will list all of the databases for a specified instance. Note: this +// operation will only return user-defined databases; it will exclude system +// databases like "mysql", "information_schema", "lost+found" etc. +func List(client *gophercloud.ServiceClient, instanceID string) pagination.Pager { + return pagination.NewPager(client, baseURL(client, instanceID), func(r pagination.PageResult) pagination.Page { + return DBPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Delete will permanently delete the database within a specified instance. +// All contained data inside the database will also be permanently deleted. +func Delete(client *gophercloud.ServiceClient, instanceID, dbName string) (r DeleteResult) { + _, r.Err = client.Delete(dbURL(client, instanceID, dbName), nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/databases/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/databases/results.go new file mode 100644 index 000000000..0479d0e6e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/databases/results.go @@ -0,0 +1,63 @@ +package databases + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// Database represents a Database API resource. +type Database struct { + // Specifies the name of the MySQL database. + Name string + + // Set of symbols and encodings. The default character set is utf8. + CharSet string + + // Set of rules for comparing characters in a character set. The default + // value for collate is utf8_general_ci. + Collate string +} + +// CreateResult represents the result of a Create operation. +type CreateResult struct { + gophercloud.ErrResult +} + +// DeleteResult represents the result of a Delete operation. +type DeleteResult struct { + gophercloud.ErrResult +} + +// DBPage represents a single page of a paginated DB collection. +type DBPage struct { + pagination.LinkedPageBase +} + +// IsEmpty checks to see whether the collection is empty. +func (page DBPage) IsEmpty() (bool, error) { + dbs, err := ExtractDBs(page) + return len(dbs) == 0, err +} + +// NextPageURL will retrieve the next page URL. +func (page DBPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"databases_links"` + } + err := page.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// ExtractDBs will convert a generic pagination struct into a more +// relevant slice of DB structs. +func ExtractDBs(page pagination.Page) ([]Database, error) { + r := page.(DBPage) + var s struct { + Databases []Database `json:"databases"` + } + err := r.ExtractInto(&s) + return s.Databases, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/databases/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/databases/urls.go new file mode 100644 index 000000000..aba42c9c8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/databases/urls.go @@ -0,0 +1,11 @@ +package databases + +import "github.com/gophercloud/gophercloud" + +func baseURL(c *gophercloud.ServiceClient, instanceID string) string { + return c.ServiceURL("instances", instanceID, "databases") +} + +func dbURL(c *gophercloud.ServiceClient, instanceID, dbName string) string { + return c.ServiceURL("instances", instanceID, "databases", dbName) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/datastores/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/datastores/doc.go new file mode 100644 index 000000000..ae14026b5 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/datastores/doc.go @@ -0,0 +1,3 @@ +// Package datastores provides information and interaction with the datastore +// API resource in the Rackspace Database service. +package datastores diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/datastores/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/datastores/requests.go new file mode 100644 index 000000000..134e30907 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/datastores/requests.go @@ -0,0 +1,33 @@ +package datastores + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// List will list all available datastore types that instances can use. +func List(client *gophercloud.ServiceClient) pagination.Pager { + return pagination.NewPager(client, baseURL(client), func(r pagination.PageResult) pagination.Page { + return DatastorePage{pagination.SinglePageBase(r)} + }) +} + +// Get will retrieve the details of a specified datastore type. +func Get(client *gophercloud.ServiceClient, datastoreID string) (r GetResult) { + _, r.Err = client.Get(resourceURL(client, datastoreID), &r.Body, nil) + return +} + +// ListVersions will list all of the available versions for a specified +// datastore type. +func ListVersions(client *gophercloud.ServiceClient, datastoreID string) pagination.Pager { + return pagination.NewPager(client, versionsURL(client, datastoreID), func(r pagination.PageResult) pagination.Page { + return VersionPage{pagination.SinglePageBase(r)} + }) +} + +// GetVersion will retrieve the details of a specified datastore version. +func GetVersion(client *gophercloud.ServiceClient, datastoreID, versionID string) (r GetVersionResult) { + _, r.Err = client.Get(versionURL(client, datastoreID, versionID), &r.Body, nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/datastores/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/datastores/results.go new file mode 100644 index 000000000..a6e27d274 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/datastores/results.go @@ -0,0 +1,100 @@ +package datastores + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// Version represents a version API resource. Multiple versions belong to a Datastore. +type Version struct { + ID string + Links []gophercloud.Link + Name string +} + +// Datastore represents a Datastore API resource. +type Datastore struct { + DefaultVersion string `json:"default_version"` + ID string + Links []gophercloud.Link + Name string + Versions []Version +} + +// DatastorePartial is a meta structure which is used in various API responses. +// It is a lightweight and truncated version of a full Datastore resource, +// offering details of the Version, Type and VersionID only. +type DatastorePartial struct { + Version string + Type string + VersionID string `json:"version_id"` +} + +// GetResult represents the result of a Get operation. +type GetResult struct { + gophercloud.Result +} + +// GetVersionResult represents the result of getting a version. +type GetVersionResult struct { + gophercloud.Result +} + +// DatastorePage represents a page of datastore resources. +type DatastorePage struct { + pagination.SinglePageBase +} + +// IsEmpty indicates whether a Datastore collection is empty. +func (r DatastorePage) IsEmpty() (bool, error) { + is, err := ExtractDatastores(r) + return len(is) == 0, err +} + +// ExtractDatastores retrieves a slice of datastore structs from a paginated +// collection. +func ExtractDatastores(r pagination.Page) ([]Datastore, error) { + var s struct { + Datastores []Datastore `json:"datastores"` + } + err := (r.(DatastorePage)).ExtractInto(&s) + return s.Datastores, err +} + +// Extract retrieves a single Datastore struct from an operation result. +func (r GetResult) Extract() (*Datastore, error) { + var s struct { + Datastore *Datastore `json:"datastore"` + } + err := r.ExtractInto(&s) + return s.Datastore, err +} + +// VersionPage represents a page of version resources. +type VersionPage struct { + pagination.SinglePageBase +} + +// IsEmpty indicates whether a collection of version resources is empty. +func (r VersionPage) IsEmpty() (bool, error) { + is, err := ExtractVersions(r) + return len(is) == 0, err +} + +// ExtractVersions retrieves a slice of versions from a paginated collection. +func ExtractVersions(r pagination.Page) ([]Version, error) { + var s struct { + Versions []Version `json:"versions"` + } + err := (r.(VersionPage)).ExtractInto(&s) + return s.Versions, err +} + +// Extract retrieves a single Version struct from an operation result. +func (r GetVersionResult) Extract() (*Version, error) { + var s struct { + Version *Version `json:"version"` + } + err := r.ExtractInto(&s) + return s.Version, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/datastores/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/datastores/urls.go new file mode 100644 index 000000000..06d1b3dae --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/datastores/urls.go @@ -0,0 +1,19 @@ +package datastores + +import "github.com/gophercloud/gophercloud" + +func baseURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("datastores") +} + +func resourceURL(c *gophercloud.ServiceClient, dsID string) string { + return c.ServiceURL("datastores", dsID) +} + +func versionsURL(c *gophercloud.ServiceClient, dsID string) string { + return c.ServiceURL("datastores", dsID, "versions") +} + +func versionURL(c *gophercloud.ServiceClient, dsID, versionID string) string { + return c.ServiceURL("datastores", dsID, "versions", versionID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/doc.go new file mode 100644 index 000000000..dc5c90f95 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/doc.go @@ -0,0 +1,7 @@ +// Package instances provides information and interaction with the instance API +// resource in the OpenStack Database service. +// +// A database instance is an isolated database environment with compute and +// storage resources in a single tenant environment on a shared physical host +// machine. +package instances diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/requests.go new file mode 100644 index 000000000..bbd909318 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/requests.go @@ -0,0 +1,219 @@ +package instances + +import ( + "github.com/gophercloud/gophercloud" + db "github.com/gophercloud/gophercloud/openstack/db/v1/databases" + "github.com/gophercloud/gophercloud/openstack/db/v1/users" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder is the top-level interface for create options. +type CreateOptsBuilder interface { + ToInstanceCreateMap() (map[string]interface{}, error) +} + +// DatastoreOpts represents the configuration for how an instance stores data. +type DatastoreOpts struct { + Version string `json:"version"` + Type string `json:"type"` +} + +// ToMap converts a DatastoreOpts to a map[string]string (for a request body) +func (opts DatastoreOpts) ToMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// NetworkOpts is used within CreateOpts to control a new server's network attachments. +type NetworkOpts struct { + // UUID of a nova-network to attach to the newly provisioned server. + // Required unless Port is provided. + UUID string `json:"net-id,omitempty"` + + // Port of a neutron network to attach to the newly provisioned server. + // Required unless UUID is provided. + Port string `json:"port-id,omitempty"` + + // V4FixedIP [optional] specifies a fixed IPv4 address to be used on this network. + V4FixedIP string `json:"v4-fixed-ip,omitempty"` + + // V6FixedIP [optional] specifies a fixed IPv6 address to be used on this network. + V6FixedIP string `json:"v6-fixed-ip,omitempty"` +} + +// ToMap converts a NetworkOpts to a map[string]string (for a request body) +func (opts NetworkOpts) ToMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// CreateOpts is the struct responsible for configuring a new database instance. +type CreateOpts struct { + // Either the integer UUID (in string form) of the flavor, or its URI + // reference as specified in the response from the List() call. Required. + FlavorRef string + // Specifies the volume size in gigabytes (GB). The value must be between 1 + // and 300. Required. + Size int + // Name of the instance to create. The length of the name is limited to + // 255 characters and any characters are permitted. Optional. + Name string + // A slice of database information options. + Databases db.CreateOptsBuilder + // A slice of user information options. + Users users.CreateOptsBuilder + // Options to configure the type of datastore the instance will use. This is + // optional, and if excluded will default to MySQL. + Datastore *DatastoreOpts + // Networks dictates how this server will be attached to available networks. + Networks []NetworkOpts +} + +// ToInstanceCreateMap will render a JSON map. +func (opts CreateOpts) ToInstanceCreateMap() (map[string]interface{}, error) { + if opts.Size > 300 || opts.Size < 1 { + err := gophercloud.ErrInvalidInput{} + err.Argument = "instances.CreateOpts.Size" + err.Value = opts.Size + err.Info = "Size (GB) must be between 1-300" + return nil, err + } + + if opts.FlavorRef == "" { + return nil, gophercloud.ErrMissingInput{Argument: "instances.CreateOpts.FlavorRef"} + } + + instance := map[string]interface{}{ + "volume": map[string]int{"size": opts.Size}, + "flavorRef": opts.FlavorRef, + } + + if opts.Name != "" { + instance["name"] = opts.Name + } + if opts.Databases != nil { + dbs, err := opts.Databases.ToDBCreateMap() + if err != nil { + return nil, err + } + instance["databases"] = dbs["databases"] + } + if opts.Users != nil { + users, err := opts.Users.ToUserCreateMap() + if err != nil { + return nil, err + } + instance["users"] = users["users"] + } + if opts.Datastore != nil { + datastore, err := opts.Datastore.ToMap() + if err != nil { + return nil, err + } + instance["datastore"] = datastore + } + + if len(opts.Networks) > 0 { + networks := make([]map[string]interface{}, len(opts.Networks)) + for i, net := range opts.Networks { + var err error + networks[i], err = net.ToMap() + if err != nil { + return nil, err + } + } + instance["nics"] = networks + } + + return map[string]interface{}{"instance": instance}, nil +} + +// Create asynchronously provisions a new database instance. It requires the +// user to specify a flavor and a volume size. The API service then provisions +// the instance with the requested flavor and sets up a volume of the specified +// size, which is the storage for the database instance. +// +// Although this call only allows the creation of 1 instance per request, you +// can create an instance with multiple databases and users. The default +// binding for a MySQL instance is port 3306. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToInstanceCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(baseURL(client), &b, &r.Body, &gophercloud.RequestOpts{OkCodes: []int{200}}) + return +} + +// List retrieves the status and information for all database instances. +func List(client *gophercloud.ServiceClient) pagination.Pager { + return pagination.NewPager(client, baseURL(client), func(r pagination.PageResult) pagination.Page { + return InstancePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves the status and information for a specified database instance. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(resourceURL(client, id), &r.Body, nil) + return +} + +// Delete permanently destroys the database instance. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(resourceURL(client, id), nil) + return +} + +// EnableRootUser enables the login from any host for the root user and +// provides the user with a generated root password. +func EnableRootUser(client *gophercloud.ServiceClient, id string) (r EnableRootUserResult) { + _, r.Err = client.Post(userRootURL(client, id), nil, &r.Body, &gophercloud.RequestOpts{OkCodes: []int{200}}) + return +} + +// IsRootEnabled checks an instance to see if root access is enabled. It returns +// True if root user is enabled for the specified database instance or False +// otherwise. +func IsRootEnabled(client *gophercloud.ServiceClient, id string) (r IsRootEnabledResult) { + _, r.Err = client.Get(userRootURL(client, id), &r.Body, nil) + return +} + +// Restart will restart only the MySQL Instance. Restarting MySQL will +// erase any dynamic configuration settings that you have made within MySQL. +// The MySQL service will be unavailable until the instance restarts. +func Restart(client *gophercloud.ServiceClient, id string) (r ActionResult) { + b := map[string]interface{}{"restart": struct{}{}} + _, r.Err = client.Post(actionURL(client, id), &b, nil, nil) + return +} + +// Resize changes the memory size of the instance, assuming a valid +// flavorRef is provided. It will also restart the MySQL service. +func Resize(client *gophercloud.ServiceClient, id, flavorRef string) (r ActionResult) { + b := map[string]interface{}{"resize": map[string]string{"flavorRef": flavorRef}} + _, r.Err = client.Post(actionURL(client, id), &b, nil, nil) + return +} + +// ResizeVolume will resize the attached volume for an instance. It supports +// only increasing the volume size and does not support decreasing the size. +// The volume size is in gigabytes (GB) and must be an integer. +func ResizeVolume(client *gophercloud.ServiceClient, id string, size int) (r ActionResult) { + b := map[string]interface{}{"resize": map[string]interface{}{"volume": map[string]int{"size": size}}} + _, r.Err = client.Post(actionURL(client, id), &b, nil, nil) + return +} + +// AttachConfigurationGroup will attach configuration group to the instance +func AttachConfigurationGroup(client *gophercloud.ServiceClient, instanceID string, configID string) (r ConfigurationResult) { + b := map[string]interface{}{"instance": map[string]interface{}{"configuration": configID}} + _, r.Err = client.Put(resourceURL(client, instanceID), &b, nil, &gophercloud.RequestOpts{OkCodes: []int{202}}) + return +} + +// DetachConfigurationGroup will dettach configuration group from the instance +func DetachConfigurationGroup(client *gophercloud.ServiceClient, instanceID string) (r ConfigurationResult) { + b := map[string]interface{}{"instance": map[string]interface{}{}} + _, r.Err = client.Put(resourceURL(client, instanceID), &b, nil, &gophercloud.RequestOpts{OkCodes: []int{202}}) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/results.go new file mode 100644 index 000000000..e47a740ce --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/results.go @@ -0,0 +1,219 @@ +package instances + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/db/v1/datastores" + "github.com/gophercloud/gophercloud/openstack/db/v1/users" + "github.com/gophercloud/gophercloud/pagination" +) + +// Volume represents information about an attached volume for a database instance. +type Volume struct { + // The size in GB of the volume + Size int + + Used float64 +} + +// Flavor represents (virtual) hardware configurations for server resources in a region. +type Flavor struct { + // The flavor's unique identifier. + ID string + // Links to access the flavor. + Links []gophercloud.Link +} + +// Fault describes the fault reason in more detail when a database instance has errored +type Fault struct { + // Indicates the time when the fault occured + Created time.Time `json:"-"` + + // A message describing the fault reason + Message string + + // More details about the fault, for example a stack trace. Only filled + // in for admin users. + Details string +} + +func (r *Fault) UnmarshalJSON(b []byte) error { + type tmp Fault + var s struct { + tmp + Created gophercloud.JSONRFC3339NoZ `json:"created"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Fault(s.tmp) + + r.Created = time.Time(s.Created) + + return nil +} + +// Instance represents a remote MySQL instance. +type Instance struct { + // Indicates the datetime that the instance was created + Created time.Time `json:"-"` + + // Indicates the most recent datetime that the instance was updated. + Updated time.Time `json:"-"` + + // Indicates the hardware flavor the instance uses. + Flavor Flavor + + // A DNS-resolvable hostname associated with the database instance (rather + // than an IPv4 address). Since the hostname always resolves to the correct + // IP address of the database instance, this relieves the user from the task + // of maintaining the mapping. Note that although the IP address may likely + // change on resizing, migrating, and so forth, the hostname always resolves + // to the correct database instance. + Hostname string + + // The IP addresses associated with the database instance + // Is empty if the instance has a hostname + IP []string + + // Indicates the unique identifier for the instance resource. + ID string + + // Exposes various links that reference the instance resource. + Links []gophercloud.Link + + // The human-readable name of the instance. + Name string + + // The build status of the instance. + Status string + + // Fault information (only available when the instance has errored) + Fault *Fault + + // Information about the attached volume of the instance. + Volume Volume + + // Indicates how the instance stores data. + Datastore datastores.DatastorePartial +} + +func (r *Instance) UnmarshalJSON(b []byte) error { + type tmp Instance + 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 = Instance(s.tmp) + + r.Created = time.Time(s.Created) + r.Updated = time.Time(s.Updated) + + return nil +} + +type commonResult struct { + gophercloud.Result +} + +// CreateResult represents the result of a Create operation. +type CreateResult struct { + commonResult +} + +// GetResult represents the result of a Get operation. +type GetResult struct { + commonResult +} + +// DeleteResult represents the result of a Delete operation. +type DeleteResult struct { + gophercloud.ErrResult +} + +// ConfigurationResult represents the result of a AttachConfigurationGroup/DetachConfigurationGroup operation. +type ConfigurationResult struct { + gophercloud.ErrResult +} + +// Extract will extract an Instance from various result structs. +func (r commonResult) Extract() (*Instance, error) { + var s struct { + Instance *Instance `json:"instance"` + } + err := r.ExtractInto(&s) + return s.Instance, err +} + +// InstancePage represents a single page of a paginated instance collection. +type InstancePage struct { + pagination.LinkedPageBase +} + +// IsEmpty checks to see whether the collection is empty. +func (page InstancePage) IsEmpty() (bool, error) { + instances, err := ExtractInstances(page) + return len(instances) == 0, err +} + +// NextPageURL will retrieve the next page URL. +func (page InstancePage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"instances_links"` + } + err := page.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// ExtractInstances will convert a generic pagination struct into a more +// relevant slice of Instance structs. +func ExtractInstances(r pagination.Page) ([]Instance, error) { + var s struct { + Instances []Instance `json:"instances"` + } + err := (r.(InstancePage)).ExtractInto(&s) + return s.Instances, err +} + +// EnableRootUserResult represents the result of an operation to enable the root user. +type EnableRootUserResult struct { + gophercloud.Result +} + +// Extract will extract root user information from a UserRootResult. +func (r EnableRootUserResult) Extract() (*users.User, error) { + var s struct { + User *users.User `json:"user"` + } + err := r.ExtractInto(&s) + return s.User, err +} + +// ActionResult represents the result of action requests, such as: restarting +// an instance service, resizing its memory allocation, and resizing its +// attached volume size. +type ActionResult struct { + gophercloud.ErrResult +} + +// IsRootEnabledResult is the result of a call to IsRootEnabled. To see if +// root is enabled, call the type's Extract method. +type IsRootEnabledResult struct { + gophercloud.Result +} + +// Extract is used to extract the data from a IsRootEnabledResult. +func (r IsRootEnabledResult) Extract() (bool, error) { + return r.Body.(map[string]interface{})["rootEnabled"] == true, r.Err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/urls.go new file mode 100644 index 000000000..76d1ca56d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/urls.go @@ -0,0 +1,19 @@ +package instances + +import "github.com/gophercloud/gophercloud" + +func baseURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("instances") +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("instances", id) +} + +func userRootURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("instances", id, "root") +} + +func actionURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("instances", id, "action") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/users/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/users/doc.go new file mode 100644 index 000000000..cf07832f3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/users/doc.go @@ -0,0 +1,3 @@ +// Package users provides information and interaction with the user API +// resource in the OpenStack Database service. +package users diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/users/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/users/requests.go new file mode 100644 index 000000000..d342de344 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/users/requests.go @@ -0,0 +1,91 @@ +package users + +import ( + "github.com/gophercloud/gophercloud" + db "github.com/gophercloud/gophercloud/openstack/db/v1/databases" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder is the top-level interface for creating JSON maps. +type CreateOptsBuilder interface { + ToUserCreateMap() (map[string]interface{}, error) +} + +// CreateOpts is the struct responsible for configuring a new user; often in the +// context of an instance. +type CreateOpts struct { + // Specifies a name for the user. Valid names can be composed + // of the following characters: letters (either case); numbers; these + // characters '@', '?', '#', ' ' but NEVER beginning a name string; '_' is + // permitted anywhere. Prohibited characters that are forbidden include: + // single quotes, double quotes, back quotes, semicolons, commas, backslashes, + // and forward slashes. Spaces at the front or end of a user name are also + // not permitted. + Name string `json:"name" required:"true"` + // Specifies a password for the user. + Password string `json:"password" required:"true"` + // An array of databases that this user will connect to. The + // "name" field is the only requirement for each option. + Databases db.BatchCreateOpts `json:"databases,omitempty"` + // Specifies the host from which a user is allowed to connect to + // the database. Possible values are a string containing an IPv4 address or + // "%" to allow connecting from any host. Optional; the default is "%". + Host string `json:"host,omitempty"` +} + +// ToMap is a convenience function for creating sub-maps for individual users. +func (opts CreateOpts) ToMap() (map[string]interface{}, error) { + if opts.Name == "root" { + err := gophercloud.ErrInvalidInput{} + err.Argument = "users.CreateOpts.Name" + err.Value = "root" + err.Info = "root is a reserved user name and cannot be used" + return nil, err + } + return gophercloud.BuildRequestBody(opts, "") +} + +// BatchCreateOpts allows multiple users to be created at once. +type BatchCreateOpts []CreateOpts + +// ToUserCreateMap will generate a JSON map. +func (opts BatchCreateOpts) ToUserCreateMap() (map[string]interface{}, error) { + users := make([]map[string]interface{}, len(opts)) + for i, opt := range opts { + user, err := opt.ToMap() + if err != nil { + return nil, err + } + users[i] = user + } + return map[string]interface{}{"users": users}, nil +} + +// Create asynchronously provisions a new user for the specified database +// instance based on the configuration defined in CreateOpts. If databases are +// assigned for a particular user, the user will be granted all privileges +// for those specified databases. "root" is a reserved name and cannot be used. +func Create(client *gophercloud.ServiceClient, instanceID string, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToUserCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(baseURL(client, instanceID), &b, nil, nil) + return +} + +// List will list all the users associated with a specified database instance, +// along with their associated databases. This operation will not return any +// system users or administrators for a database. +func List(client *gophercloud.ServiceClient, instanceID string) pagination.Pager { + return pagination.NewPager(client, baseURL(client, instanceID), func(r pagination.PageResult) pagination.Page { + return UserPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Delete will permanently delete a user from a specified database instance. +func Delete(client *gophercloud.ServiceClient, instanceID, userName string) (r DeleteResult) { + _, r.Err = client.Delete(userURL(client, instanceID, userName), nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/users/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/users/results.go new file mode 100644 index 000000000..d12a681bd --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/users/results.go @@ -0,0 +1,62 @@ +package users + +import ( + "github.com/gophercloud/gophercloud" + db "github.com/gophercloud/gophercloud/openstack/db/v1/databases" + "github.com/gophercloud/gophercloud/pagination" +) + +// User represents a database user +type User struct { + // The user name + Name string + + // The user password + Password string + + // The databases associated with this user + Databases []db.Database +} + +// CreateResult represents the result of a create operation. +type CreateResult struct { + gophercloud.ErrResult +} + +// DeleteResult represents the result of a delete operation. +type DeleteResult struct { + gophercloud.ErrResult +} + +// UserPage represents a single page of a paginated user collection. +type UserPage struct { + pagination.LinkedPageBase +} + +// IsEmpty checks to see whether the collection is empty. +func (page UserPage) IsEmpty() (bool, error) { + users, err := ExtractUsers(page) + return len(users) == 0, err +} + +// NextPageURL will retrieve the next page URL. +func (page UserPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"users_links"` + } + err := page.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// ExtractUsers will convert a generic pagination struct into a more +// relevant slice of User structs. +func ExtractUsers(r pagination.Page) ([]User, error) { + var s struct { + Users []User `json:"users"` + } + err := (r.(UserPage)).ExtractInto(&s) + return s.Users, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/users/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/users/urls.go new file mode 100644 index 000000000..8c36a39b3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/users/urls.go @@ -0,0 +1,11 @@ +package users + +import "github.com/gophercloud/gophercloud" + +func baseURL(c *gophercloud.ServiceClient, instanceID string) string { + return c.ServiceURL("instances", instanceID, "users") +} + +func userURL(c *gophercloud.ServiceClient, instanceID, userName string) string { + return c.ServiceURL("instances", instanceID, "users", userName) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/doc.go index 82a0aed5d..617fafa63 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/doc.go @@ -1,6 +1,54 @@ -// Package recordsets provides information and interaction with the zone API -// resource for the OpenStack DNS service. -// -// For more information, see: -// http://developer.openstack.org/api-ref/dns/#recordsets +/* +Package recordsets provides information and interaction with the zone API +resource for the OpenStack DNS service. + +Example to List RecordSets by Zone + + listOpts := recordsets.ListOpts{ + Type: "A", + } + + zoneID := "fff121f5-c506-410a-a69e-2d73ef9cbdbd" + + allPages, err := recordsets.ListByZone(dnsClient, zoneID, listOpts).AllPages() + if err != nil { + panic(err) + } + + allRRs, err := recordsets.ExtractRecordSets(allPages() + if err != nil { + panic(err) + } + + for _, rr := range allRRs { + fmt.Printf("%+v\n", rr) + } + +Example to Create a RecordSet + + createOpts := recordsets.CreateOpts{ + Name: "example.com.", + Type: "A", + TTL: 3600, + Description: "This is a recordset.", + Records: []string{"10.1.0.2"}, + } + + zoneID := "fff121f5-c506-410a-a69e-2d73ef9cbdbd" + + rr, err := recordsets.Create(dnsClient, zoneID, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a RecordSet + + zoneID := "fff121f5-c506-410a-a69e-2d73ef9cbdbd" + recordsetID := "d96ed01a-b439-4eb8-9b90-7a9f71017f7b" + + err := recordsets.Delete(dnsClient, zoneID, recordsetID).ExtractErr() + if err != nil { + panic(err) + } +*/ package recordsets diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/requests.go index eab6bc926..2bc457972 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/requests.go @@ -55,18 +55,20 @@ func ListByZone(client *gophercloud.ServiceClient, zoneID string, opts ListOptsB }) } -// Get implements the recordset get request. +// Get implements the recordset Get request. func Get(client *gophercloud.ServiceClient, zoneID string, rrsetID string) (r GetResult) { _, r.Err = client.Get(rrsetURL(client, zoneID, rrsetID), &r.Body, nil) return } -// CreateOptsBuilder allows extensions to add additional attributes to the Create request. +// CreateOptsBuilder allows extensions to add additional attributes to the +// Create request. type CreateOptsBuilder interface { ToRecordSetCreateMap() (map[string]interface{}, error) } -// CreateOpts specifies the base attributes that may be used to create a RecordSet. +// CreateOpts specifies the base attributes that may be used to create a +// RecordSet. type CreateOpts struct { // Name is the name of the RecordSet. Name string `json:"name" required:"true"` @@ -107,16 +109,23 @@ func Create(client *gophercloud.ServiceClient, zoneID string, opts CreateOptsBui 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 { ToRecordSetUpdateMap() (map[string]interface{}, error) } -// UpdateOpts specifies the base attributes that may be updated on an existing RecordSet. +// UpdateOpts specifies the base attributes that may be updated on an existing +// RecordSet. type UpdateOpts struct { - Description string `json:"description,omitempty"` - TTL int `json:"ttl,omitempty"` - Records []string `json:"records,omitempty"` + // Description is a description of the RecordSet. + Description *string `json:"description,omitempty"` + + // TTL is the time to live of the RecordSet. + TTL int `json:"ttl,omitempty"` + + // Records are the DNS records of the RecordSet. + Records []string `json:"records,omitempty"` } // ToRecordSetUpdateMap formats an UpdateOpts structure into a request body. diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/results.go index f7ce9f59e..0fdc1fe52 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/results.go @@ -12,7 +12,7 @@ type commonResult struct { gophercloud.Result } -// Extract interprets a GetResult, CreateResult or UpdateResult as a concrete RecordSet. +// Extract interprets a GetResult, CreateResult or UpdateResult as a RecordSet. // An error is returned if the original call or the extraction failed. func (r commonResult) Extract() (*RecordSet, error) { var s *RecordSet @@ -20,12 +20,14 @@ func (r commonResult) Extract() (*RecordSet, error) { return s, err } -// CreateResult is the deferred result of a Create call. +// CreateResult is the result of a Create operation. Call its Extract method to +// interpret the result as a RecordSet. type CreateResult struct { commonResult } -// GetResult is the deferred result of a Get call. +// GetResult is the result of a Get operation. Call its Extract method to +// interpret the result as a RecordSet. type GetResult struct { commonResult } @@ -35,12 +37,14 @@ type RecordSetPage struct { pagination.LinkedPageBase } -// UpdateResult is the deferred result of an Update call. +// UpdateResult is result of an Update operation. Call its Extract method to +// interpret the result as a RecordSet. type UpdateResult struct { commonResult } -// DeleteResult is the deferred result of an Delete call. +// DeleteResult is result of a Delete operation. Call its ExtractErr method to +// determine if the operation succeeded or failed. type DeleteResult struct { gophercloud.ErrResult } @@ -51,7 +55,7 @@ func (r RecordSetPage) IsEmpty() (bool, error) { return len(s) == 0, err } -// ExtractRecordSets extracts a slice of RecordSets from a Collection acquired from List. +// ExtractRecordSets extracts a slice of RecordSets from a List result. func ExtractRecordSets(r pagination.Page) ([]RecordSet, error) { var s struct { RecordSets []RecordSet `json:"recordsets"` @@ -60,6 +64,7 @@ func ExtractRecordSets(r pagination.Page) ([]RecordSet, error) { return s.RecordSets, err } +// RecordSet represents a DNS Record Set. type RecordSet struct { // ID is the unique ID of the recordset ID string `json:"id"` @@ -95,7 +100,7 @@ type RecordSet struct { Description string `json:"description"` // Version is the revision of the recordset. - Version int `json:version"` + Version int `json:"version"` // CreatedAt is the date when the recordset was created. CreatedAt time.Time `json:"-"` @@ -104,7 +109,8 @@ type RecordSet struct { UpdatedAt time.Time `json:"-"` // Links includes HTTP references to the itself, - // useful for passing along to other APIs that might want a recordset reference. + // useful for passing along to other APIs that might want a recordset + // reference. Links []gophercloud.Link `json:"-"` } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/doc.go index 1302cb93d..7733155bc 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/doc.go @@ -1,6 +1,48 @@ -// Package tokens provides information and interaction with the zone API -// resource for the OpenStack DNS service. -// -// For more information, see: -// http://developer.openstack.org/api-ref/dns/#zone +/* +Package zones provides information and interaction with the zone API +resource for the OpenStack DNS service. + +Example to List Zones + + listOpts := zones.ListOpts{ + Email: "jdoe@example.com", + } + + allPages, err := zones.List(dnsClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allZones, err := zones.ExtractZones(allPages) + if err != nil { + panic(err) + } + + for _, zone := range allZones { + fmt.Printf("%+v\n", zone) + } + +Example to Create a Zone + + createOpts := zones.CreateOpts{ + Name: "example.com.", + Email: "jdoe@example.com", + Type: "PRIMARY", + TTL: 7200, + Description: "This is a zone.", + } + + zone, err := zones.Create(dnsClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Zone + + zoneID := "99d10f68-5623-4491-91a0-6daafa32b60e" + err := zones.Delete(dnsClient, zoneID).ExtractErr() + if err != nil { + panic(err) + } +*/ package zones diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/requests.go index 1c818a38a..78b08ae4f 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/requests.go @@ -5,6 +5,7 @@ import ( "github.com/gophercloud/gophercloud/pagination" ) +// ListOptsBuilder allows extensions to add parameters to the List request. type ListOptsBuilder interface { ToZoneListQuery() (string, error) } @@ -31,11 +32,13 @@ type ListOpts struct { Type string `q:"type"` } +// ToZoneListQuery formats a ListOpts into a query string. func (opts ListOpts) ToZoneListQuery() (string, error) { q, err := gophercloud.BuildQueryString(opts) return q.String(), err } +// List implements a zone List request. func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { url := baseURL(client) if opts != nil { @@ -50,18 +53,19 @@ func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pa }) } -// Get returns additional information about a zone, given its ID. +// Get returns information about a zone, given its ID. func Get(client *gophercloud.ServiceClient, zoneID string) (r GetResult) { _, r.Err = client.Get(zoneURL(client, zoneID), &r.Body, nil) return } -// CreateOptsBuilder allows extensions to add additional attributes to the Update request. +// CreateOptsBuilder allows extensions to add additional attributes to the +// Create request. type CreateOptsBuilder interface { ToZoneCreateMap() (map[string]interface{}, error) } -// CreateOpts specifies the base attributes used to create a zone. +// CreateOpts specifies the attributes used to create a zone. type CreateOpts struct { // Attributes are settings that supply hints and filters for the zone. Attributes map[string]string `json:"attributes,omitempty"` @@ -73,7 +77,7 @@ type CreateOpts struct { Description string `json:"description,omitempty"` // Name of the zone. - Name string `json:"name,required"` + Name string `json:"name" required:"true"` // Masters specifies zone masters if this is a secondary zone. Masters []string `json:"masters,omitempty"` @@ -99,7 +103,7 @@ func (opts CreateOpts) ToZoneCreateMap() (map[string]interface{}, error) { return b, nil } -// Create a zone +// Create implements a zone create request. func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { b, err := opts.ToZoneCreateMap() if err != nil { @@ -112,17 +116,25 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create 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 { ToZoneUpdateMap() (map[string]interface{}, error) } -// UpdateOpts specifies the base attributes to update a zone. +// UpdateOpts specifies the attributes to update a zone. type UpdateOpts struct { - Email string `json:"email,omitempty"` - TTL int `json:"-"` - Masters []string `json:"masters,omitempty"` - Description string `json:"description,omitempty"` + // Email contact of the zone. + Email string `json:"email,omitempty"` + + // TTL is the time to live of the zone. + TTL int `json:"-"` + + // Masters specifies zone masters if this is a secondary zone. + Masters []string `json:"masters,omitempty"` + + // Description of the zone. + Description *string `json:"description,omitempty"` } // ToZoneUpdateMap formats an UpdateOpts structure into a request body. @@ -139,7 +151,7 @@ func (opts UpdateOpts) ToZoneUpdateMap() (map[string]interface{}, error) { return b, nil } -// Update a zone. +// Update implements a zone update request. func Update(client *gophercloud.ServiceClient, zoneID string, opts UpdateOptsBuilder) (r UpdateResult) { b, err := opts.ToZoneUpdateMap() if err != nil { @@ -152,7 +164,7 @@ func Update(client *gophercloud.ServiceClient, zoneID string, opts UpdateOptsBui return } -// Delete a zone. +// Delete implements a zone delete request. func Delete(client *gophercloud.ServiceClient, zoneID string) (r DeleteResult) { _, r.Err = client.Delete(zoneURL(client, zoneID), &gophercloud.RequestOpts{ OkCodes: []int{202}, diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/results.go index de8069567..a36eca7e2 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/results.go @@ -13,7 +13,7 @@ type commonResult struct { gophercloud.Result } -// Extract interprets a GetResult, CreateResult or UpdateResult as a concrete Zone. +// Extract interprets a GetResult, CreateResult or UpdateResult as a Zone. // An error is returned if the original call or the extraction failed. func (r commonResult) Extract() (*Zone, error) { var s *Zone @@ -21,22 +21,26 @@ func (r commonResult) Extract() (*Zone, error) { return s, err } -// CreateResult is the deferred result of a Create call. +// CreateResult is the result of a Create request. Call its Extract method +// to interpret the result as a Zone. type CreateResult struct { commonResult } -// GetResult is the deferred result of a Get call. +// GetResult is the result of a Get request. Call its Extract method +// to interpret the result as a Zone. type GetResult struct { commonResult } -// UpdateResult is the deferred result of an Update call. +// UpdateResult is the result of an Update request. Call its Extract method +// to interpret the result as a Zone. type UpdateResult struct { commonResult } -// DeleteResult is the deferred result of an Delete call. +// DeleteResult is the result of a Delete request. Call its ExtractErr method +// to determine if the request succeeded or failed. type DeleteResult struct { commonResult } @@ -52,7 +56,7 @@ func (r ZonePage) IsEmpty() (bool, error) { return len(s) == 0, err } -// ExtractZones extracts a slice of Services from a Collection acquired from List. +// ExtractZones extracts a slice of Zones from a List result. func ExtractZones(r pagination.Page) ([]Zone, error) { var s struct { Zones []Zone `json:"zones"` @@ -63,7 +67,8 @@ func ExtractZones(r pagination.Page) ([]Zone, error) { // Zone represents a DNS zone. type Zone struct { - // ID uniquely identifies this zone amongst all other zones, including those not accessible to the current tenant. + // ID uniquely identifies this zone amongst all other zones, including those + // not accessible to the current tenant. ID string `json:"id"` // PoolID is the ID for the pool hosting this zone. @@ -113,10 +118,12 @@ type Zone struct { // UpdatedAt is the date when the last change was made to the zone. UpdatedAt time.Time `json:"-"` - // TransferredAt is the last time an update was retrieved from the master servers. + // TransferredAt is the last time an update was retrieved from the + // master servers. TransferredAt time.Time `json:"-"` - // 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 map[string]interface{} `json:"links"` } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/doc.go new file mode 100644 index 000000000..cedf1f4d3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/doc.go @@ -0,0 +1,14 @@ +/* +Package openstack contains resources for the individual OpenStack projects +supported in Gophercloud. It also includes functions to authenticate to an +OpenStack cloud and for provisioning various service-level clients. + +Example of Creating a Service Client + + ao, err := openstack.AuthOptionsFromEnv() + provider, err := openstack.AuthenticatedClient(ao) + client, err := openstack.NewNetworkV2(client, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) +*/ +package openstack diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go b/vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go index ea37f5b27..12c8aebcf 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go @@ -6,12 +6,16 @@ import ( tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens" ) -// V2EndpointURL discovers the endpoint URL for a specific service from a ServiceCatalog acquired -// during the v2 identity service. The specified EndpointOpts are used to identify a unique, -// unambiguous endpoint to return. It's an error both when multiple endpoints match the provided -// criteria and when none do. The minimum that can be specified is a Type, but you will also often -// need to specify a Name and/or a Region depending on what's available on your OpenStack -// deployment. +/* +V2EndpointURL discovers the endpoint URL for a specific service from a +ServiceCatalog acquired during the v2 identity service. + +The specified EndpointOpts are used to identify a unique, unambiguous endpoint +to return. It's an error both when multiple endpoints match the provided +criteria and when none do. The minimum that can be specified is a Type, but you +will also often need to specify a Name and/or a Region depending on what's +available on your OpenStack deployment. +*/ func V2EndpointURL(catalog *tokens2.ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) { // Extract Endpoints from the catalog entries that match the requested Type, Name if provided, and Region if provided. var endpoints = make([]tokens2.Endpoint, 0, 1) @@ -54,12 +58,16 @@ func V2EndpointURL(catalog *tokens2.ServiceCatalog, opts gophercloud.EndpointOpt return "", err } -// V3EndpointURL discovers the endpoint URL for a specific service from a Catalog acquired -// during the v3 identity service. The specified EndpointOpts are used to identify a unique, -// unambiguous endpoint to return. It's an error both when multiple endpoints match the provided -// criteria and when none do. The minimum that can be specified is a Type, but you will also often -// need to specify a Name and/or a Region depending on what's available on your OpenStack -// deployment. +/* +V3EndpointURL discovers the endpoint URL for a specific service from a Catalog +acquired during the v3 identity service. + +The specified EndpointOpts are used to identify a unique, unambiguous endpoint +to return. It's an error both when multiple endpoints match the provided +criteria and when none do. The minimum that can be specified is a Type, but you +will also often need to specify a Name and/or a Region depending on what's +available on your OpenStack deployment. +*/ func V3EndpointURL(catalog *tokens3.ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) { // Extract Endpoints from the catalog entries that match the requested Type, Interface, // Name if provided, and Region if provided. @@ -76,7 +84,7 @@ func V3EndpointURL(catalog *tokens3.ServiceCatalog, opts gophercloud.EndpointOpt return "", err } if (opts.Availability == gophercloud.Availability(endpoint.Interface)) && - (opts.Region == "" || endpoint.Region == opts.Region) { + (opts.Region == "" || endpoint.Region == opts.Region || endpoint.RegionID == opts.Region) { endpoints = append(endpoints, endpoint) } } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/doc.go index 0c2d49d56..45623369e 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/doc.go @@ -1,7 +1,65 @@ -// Package tenants provides information and interaction with the -// tenants API resource for the OpenStack Identity service. -// -// See http://developer.openstack.org/api-ref-identity-v2.html#identity-auth-v2 -// and http://developer.openstack.org/api-ref-identity-v2.html#admin-tenants -// for more information. +/* +Package tenants provides information and interaction with the +tenants API resource for the OpenStack Identity service. + +See http://developer.openstack.org/api-ref-identity-v2.html#identity-auth-v2 +and http://developer.openstack.org/api-ref-identity-v2.html#admin-tenants +for more information. + +Example to List Tenants + + listOpts := tenants.ListOpts{ + Limit: 2, + } + + allPages, err := tenants.List(identityClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allTenants, err := tenants.ExtractTenants(allPages) + if err != nil { + panic(err) + } + + for _, tenant := range allTenants { + fmt.Printf("%+v\n", tenant) + } + +Example to Create a Tenant + + createOpts := tenants.CreateOpts{ + Name: "tenant_name", + Description: "this is a tenant", + Enabled: gophercloud.Enabled, + } + + tenant, err := tenants.Create(identityClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Tenant + + tenantID := "e6db6ed6277c461a853458589063b295" + + updateOpts := tenants.UpdateOpts{ + Description: "this is a new description", + Enabled: gophercloud.Disabled, + } + + tenant, err := tenants.Update(identityClient, tenantID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Tenant + + tenantID := "e6db6ed6277c461a853458589063b295" + + err := tenants.Delete(identitYClient, tenantID).ExtractErr() + if err != nil { + panic(err) + } +*/ package tenants diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go index b9d7de65f..f21a58f10 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go @@ -9,6 +9,7 @@ import ( type ListOpts struct { // Marker is the ID of the last Tenant on the previous page. Marker string `q:"marker"` + // Limit specifies the page size. Limit int `q:"limit"` } @@ -27,3 +28,89 @@ func List(client *gophercloud.ServiceClient, opts *ListOpts) pagination.Pager { return TenantPage{pagination.LinkedPageBase{PageResult: r}} }) } + +// CreateOpts represents the options needed when creating new tenant. +type CreateOpts struct { + // Name is the name of the tenant. + Name string `json:"name" required:"true"` + + // Description is the description of the tenant. + Description string `json:"description,omitempty"` + + // Enabled sets the tenant status to enabled or disabled. + Enabled *bool `json:"enabled,omitempty"` +} + +// CreateOptsBuilder enables extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToTenantCreateMap() (map[string]interface{}, error) +} + +// ToTenantCreateMap assembles a request body based on the contents of +// a CreateOpts. +func (opts CreateOpts) ToTenantCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "tenant") +} + +// Create is the operation responsible for creating new tenant. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToTenantCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201}, + }) + return +} + +// Get requests details on a single tenant by ID. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToTenantUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts specifies the base attributes that may be updated on an existing +// tenant. +type UpdateOpts struct { + // Name is the name of the tenant. + Name string `json:"name,omitempty"` + + // Description is the description of the tenant. + Description *string `json:"description,omitempty"` + + // Enabled sets the tenant status to enabled or disabled. + Enabled *bool `json:"enabled,omitempty"` +} + +// ToTenantUpdateMap formats an UpdateOpts structure into a request body. +func (opts UpdateOpts) ToTenantUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "tenant") +} + +// Update is the operation responsible for updating exist tenants by their TenantID. +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToTenantUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(updateURL(client, id), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Delete is the operation responsible for permanently deleting a tenant. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/results.go index 3ce1e6773..bb6c2c6b0 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/results.go @@ -43,7 +43,8 @@ func (r TenantPage) NextPageURL() (string, error) { return gophercloud.ExtractNextURL(s.Links) } -// ExtractTenants returns a slice of Tenants contained in a single page of results. +// ExtractTenants returns a slice of Tenants contained in a single page of +// results. func ExtractTenants(r pagination.Page) ([]Tenant, error) { var s struct { Tenants []Tenant `json:"tenants"` @@ -51,3 +52,40 @@ func ExtractTenants(r pagination.Page) ([]Tenant, error) { err := (r.(TenantPage)).ExtractInto(&s) return s.Tenants, err } + +type tenantResult struct { + gophercloud.Result +} + +// Extract interprets any tenantResults as a Tenant. +func (r tenantResult) Extract() (*Tenant, error) { + var s struct { + Tenant *Tenant `json:"tenant"` + } + err := r.ExtractInto(&s) + return s.Tenant, err +} + +// GetResult is the response from a Get request. Call its Extract method to +// interpret it as a Tenant. +type GetResult struct { + tenantResult +} + +// CreateResult is the response from a Create request. Call its Extract method +// to interpret it as a Tenant. +type CreateResult struct { + tenantResult +} + +// DeleteResult is the response from a Get request. Call its ExtractErr method +// to determine if the call succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// UpdateResult is the response from a Update request. Call its Extract method +// to interpret it as a Tenant. +type UpdateResult struct { + tenantResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/urls.go index 101599bc9..0f0266907 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/urls.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/urls.go @@ -5,3 +5,19 @@ import "github.com/gophercloud/gophercloud" func listURL(client *gophercloud.ServiceClient) string { return client.ServiceURL("tenants") } + +func getURL(client *gophercloud.ServiceClient, tenantID string) string { + return client.ServiceURL("tenants", tenantID) +} + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("tenants") +} + +func deleteURL(client *gophercloud.ServiceClient, tenantID string) string { + return client.ServiceURL("tenants", tenantID) +} + +func updateURL(client *gophercloud.ServiceClient, tenantID string) string { + return client.ServiceURL("tenants", tenantID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/doc.go index 31cacc5e1..5375eea87 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/doc.go @@ -1,5 +1,46 @@ -// Package tokens provides information and interaction with the token API -// resource for the OpenStack Identity service. -// For more information, see: -// http://developer.openstack.org/api-ref-identity-v2.html#identity-auth-v2 +/* +Package tokens provides information and interaction with the token API +resource for the OpenStack Identity service. + +For more information, see: +http://developer.openstack.org/api-ref-identity-v2.html#identity-auth-v2 + +Example to Create an Unscoped Token from a Password + + authOpts := gophercloud.AuthOptions{ + Username: "user", + Password: "pass" + } + + token, err := tokens.Create(identityClient, authOpts).ExtractToken() + if err != nil { + panic(err) + } + +Example to Create a Token from a Tenant ID and Password + + authOpts := gophercloud.AuthOptions{ + Username: "user", + Password: "password", + TenantID: "fc394f2ab2df4114bde39905f800dc57" + } + + token, err := tokens.Create(identityClient, authOpts).ExtractToken() + if err != nil { + panic(err) + } + +Example to Create a Token from a Tenant Name and Password + + authOpts := gophercloud.AuthOptions{ + Username: "user", + Password: "password", + TenantName: "tenantname" + } + + token, err := tokens.Create(identityClient, authOpts).ExtractToken() + if err != nil { + panic(err) + } +*/ package tokens diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go index 4983031e7..ab32368cc 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go @@ -2,17 +2,21 @@ package tokens import "github.com/gophercloud/gophercloud" +// PasswordCredentialsV2 represents the required options to authenticate +// with a username and password. type PasswordCredentialsV2 struct { Username string `json:"username" required:"true"` Password string `json:"password" required:"true"` } +// TokenCredentialsV2 represents the required options to authenticate +// with a token. type TokenCredentialsV2 struct { ID string `json:"id,omitempty" required:"true"` } -// AuthOptionsV2 wraps a gophercloud AuthOptions in order to adhere to the AuthOptionsBuilder -// interface. +// AuthOptionsV2 wraps a gophercloud AuthOptions in order to adhere to the +// AuthOptionsBuilder interface. type AuthOptionsV2 struct { PasswordCredentials *PasswordCredentialsV2 `json:"passwordCredentials,omitempty" xor:"TokenCredentials"` @@ -23,15 +27,16 @@ type AuthOptionsV2 struct { TenantID string `json:"tenantId,omitempty"` TenantName string `json:"tenantName,omitempty"` - // TokenCredentials allows users to authenticate (possibly as another user) with an - // authentication token ID. + // TokenCredentials allows users to authenticate (possibly as another user) + // with an authentication token ID. TokenCredentials *TokenCredentialsV2 `json:"token,omitempty" xor:"PasswordCredentials"` } -// AuthOptionsBuilder describes any argument that may be passed to the Create call. +// AuthOptionsBuilder allows extensions to add additional parameters to the +// token create request. type AuthOptionsBuilder interface { - // ToTokenCreateMap assembles the Create request body, returning an error if parameters are - // missing or inconsistent. + // ToTokenCreateMap assembles the Create request body, returning an error + // if parameters are missing or inconsistent. ToTokenV2CreateMap() (map[string]interface{}, error) } @@ -47,8 +52,7 @@ type AuthOptions struct { TokenID string } -// ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder -// interface in the v2 tokens package +// ToTokenV2CreateMap builds a token request body from the given AuthOptions. func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) { v2Opts := AuthOptionsV2{ TenantID: opts.TenantID, @@ -74,9 +78,9 @@ func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) { } // Create authenticates to the identity service and attempts to acquire a Token. -// If successful, the CreateResult -// Generally, rather than interact with this call directly, end users should call openstack.AuthenticatedClient(), -// which abstracts all of the gory details about navigating service catalogs and such. +// Generally, rather than interact with this call directly, end users should +// call openstack.AuthenticatedClient(), which abstracts all of the gory details +// about navigating service catalogs and such. func Create(client *gophercloud.ServiceClient, auth AuthOptionsBuilder) (r CreateResult) { b, err := auth.ToTokenV2CreateMap() if err != nil { diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go index 6b3649370..ee5da37f4 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go @@ -7,20 +7,24 @@ import ( "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants" ) -// Token provides only the most basic information related to an authentication token. +// Token provides only the most basic information related to an authentication +// token. type Token struct { // ID provides the primary means of identifying a user to the OpenStack API. - // OpenStack defines this field as an opaque value, so do not depend on its content. - // It is safe, however, to compare for equality. + // OpenStack defines this field as an opaque value, so do not depend on its + // content. It is safe, however, to compare for equality. ID string - // ExpiresAt provides a timestamp in ISO 8601 format, indicating when the authentication token becomes invalid. - // After this point in time, future API requests made using this authentication token will respond with errors. - // Either the caller will need to reauthenticate manually, or more preferably, the caller should exploit automatic re-authentication. + // ExpiresAt provides a timestamp in ISO 8601 format, indicating when the + // authentication token becomes invalid. After this point in time, future + // API requests made using this authentication token will respond with + // errors. Either the caller will need to reauthenticate manually, or more + // preferably, the caller should exploit automatic re-authentication. // See the AuthOptions structure for more details. ExpiresAt time.Time - // Tenant provides information about the tenant to which this token grants access. + // Tenant provides information about the tenant to which this token grants + // access. Tenant tenants.Tenant } @@ -38,13 +42,17 @@ type User struct { } // Endpoint represents a single API endpoint offered by a service. -// It provides the public and internal URLs, if supported, along with a region specifier, again if provided. +// It provides the public and internal URLs, if supported, along with a region +// specifier, again if provided. +// // The significance of the Region field will depend upon your provider. // -// In addition, the interface offered by the service will have version information associated with it -// through the VersionId, VersionInfo, and VersionList fields, if provided or supported. +// In addition, the interface offered by the service will have version +// information associated with it through the VersionId, VersionInfo, and +// VersionList fields, if provided or supported. // -// In all cases, fields which aren't supported by the provider and service combined will assume a zero-value (""). +// In all cases, fields which aren't supported by the provider and service +// combined will assume a zero-value (""). type Endpoint struct { TenantID string `json:"tenantId"` PublicURL string `json:"publicURL"` @@ -56,38 +64,44 @@ type Endpoint struct { VersionList string `json:"versionList"` } -// CatalogEntry provides a type-safe interface to an Identity API V2 service catalog listing. -// Each class of service, such as cloud DNS or block storage services, will have a single -// CatalogEntry representing it. +// CatalogEntry provides a type-safe interface to an Identity API V2 service +// catalog listing. // -// Note: when looking for the desired service, try, whenever possible, to key off the type field. -// Otherwise, you'll tie the representation of the service to a specific provider. +// Each class of service, such as cloud DNS or block storage services, will have +// a single CatalogEntry representing it. +// +// Note: when looking for the desired service, try, whenever possible, to key +// off the type field. Otherwise, you'll tie the representation of the service +// to a specific provider. type CatalogEntry struct { // Name will contain the provider-specified name for the service. Name string `json:"name"` - // Type will contain a type string if OpenStack defines a type for the service. - // Otherwise, for provider-specific services, the provider may assign their own type strings. + // Type will contain a type string if OpenStack defines a type for the + // service. Otherwise, for provider-specific services, the provider may assign + // their own type strings. Type string `json:"type"` - // Endpoints will let the caller iterate over all the different endpoints that may exist for - // the service. + // Endpoints will let the caller iterate over all the different endpoints that + // may exist for the service. Endpoints []Endpoint `json:"endpoints"` } -// ServiceCatalog provides a view into the service catalog from a previous, successful authentication. +// ServiceCatalog provides a view into the service catalog from a previous, +// successful authentication. type ServiceCatalog struct { Entries []CatalogEntry } -// CreateResult defers the interpretation of a created token. -// Use ExtractToken() to interpret it as a Token, or ExtractServiceCatalog() to interpret it as a service catalog. +// CreateResult is the response from a Create request. Use ExtractToken() to +// interpret it as a Token, or ExtractServiceCatalog() to interpret it as a +// service catalog. type CreateResult struct { gophercloud.Result } -// GetResult is the deferred response from a Get call, which is the same with a Created token. -// Use ExtractUser() to interpret it as a User. +// GetResult is the deferred response from a Get call, which is the same with a +// Created token. Use ExtractUser() to interpret it as a User. type GetResult struct { CreateResult } @@ -121,7 +135,23 @@ func (r CreateResult) ExtractToken() (*Token, error) { }, nil } -// ExtractServiceCatalog returns the ServiceCatalog that was generated along with the user's Token. +// ExtractTokenID implements the gophercloud.AuthResult interface. The returned +// string is the same as the ID field of the Token struct returned from +// ExtractToken(). +func (r CreateResult) ExtractTokenID() (string, error) { + var s struct { + Access struct { + Token struct { + ID string `json:"id"` + } `json:"token"` + } `json:"access"` + } + err := r.ExtractInto(&s) + return s.Access.Token.ID, err +} + +// ExtractServiceCatalog returns the ServiceCatalog that was generated along +// with the user's Token. func (r CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) { var s struct { Access struct { diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/endpoints/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/endpoints/doc.go new file mode 100644 index 000000000..5822017c9 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/endpoints/doc.go @@ -0,0 +1,69 @@ +/* +Package endpoints provides information and interaction with the service +endpoints API resource in the OpenStack Identity service. + +For more information, see: +http://developer.openstack.org/api-ref-identity-v3.html#endpoints-v3 + +Example to List Endpoints + + serviceID := "e629d6e599d9489fb3ae5d9cc12eaea3" + + listOpts := endpoints.ListOpts{ + ServiceID: serviceID, + } + + allPages, err := endpoints.List(identityClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allEndpoints, err := endpoints.ExtractEndpoints(allPages) + if err != nil { + panic(err) + } + + for _, endpoint := range allEndpoints { + fmt.Printf("%+v\n", endpoint) + } + +Example to Create an Endpoint + + serviceID := "e629d6e599d9489fb3ae5d9cc12eaea3" + + createOpts := endpoints.CreateOpts{ + Availability: gophercloud.AvailabilityPublic, + Name: "neutron", + Region: "RegionOne", + URL: "https://localhost:9696", + ServiceID: serviceID, + } + + endpoint, err := endpoints.Create(identityClient, createOpts).Extract() + if err != nil { + panic(err) + } + + +Example to Update an Endpoint + + endpointID := "ad59deeec5154d1fa0dcff518596f499" + + updateOpts := endpoints.UpdateOpts{ + Region: "RegionTwo", + } + + endpoint, err := endpoints.Update(identityClient, endpointID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete an Endpoint + + endpointID := "ad59deeec5154d1fa0dcff518596f499" + err := endpoints.Delete(identityClient, endpointID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package endpoints diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/endpoints/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/endpoints/requests.go new file mode 100644 index 000000000..0764a118e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/endpoints/requests.go @@ -0,0 +1,136 @@ +package endpoints + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type CreateOptsBuilder interface { + ToEndpointCreateMap() (map[string]interface{}, error) +} + +// CreateOpts contains the subset of Endpoint attributes that should be used +// to create an Endpoint. +type CreateOpts struct { + // Availability is the interface type of the Endpoint (admin, internal, + // or public), referenced by the gophercloud.Availability type. + Availability gophercloud.Availability `json:"interface" required:"true"` + + // Name is the name of the Endpoint. + Name string `json:"name" required:"true"` + + // Region is the region the Endpoint is located in. + // This field can be omitted or left as a blank string. + Region string `json:"region,omitempty"` + + // URL is the url of the Endpoint. + URL string `json:"url" required:"true"` + + // ServiceID is the ID of the service the Endpoint refers to. + ServiceID string `json:"service_id" required:"true"` +} + +// ToEndpointCreateMap builds a request body from the Endpoint Create options. +func (opts CreateOpts) ToEndpointCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "endpoint") +} + +// Create inserts a new Endpoint into the service catalog. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToEndpointCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(listURL(client), &b, &r.Body, nil) + return +} + +// ListOptsBuilder allows extensions to add parameters to the List request. +type ListOptsBuilder interface { + ToEndpointListParams() (string, error) +} + +// ListOpts allows finer control over the endpoints returned by a List call. +// All fields are optional. +type ListOpts struct { + // Availability is the interface type of the Endpoint (admin, internal, + // or public), referenced by the gophercloud.Availability type. + Availability gophercloud.Availability `q:"interface"` + + // ServiceID is the ID of the service the Endpoint refers to. + ServiceID string `q:"service_id"` + + // RegionID is the ID of the region the Endpoint refers to. + RegionID int `q:"region_id"` +} + +// ToEndpointListParams builds a list request from the List options. +func (opts ListOpts) ToEndpointListParams() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List enumerates endpoints in a paginated collection, optionally filtered +// by ListOpts criteria. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + u := listURL(client) + if opts != nil { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return pagination.Pager{Err: err} + } + u += q.String() + } + return pagination.NewPager(client, u, func(r pagination.PageResult) pagination.Page { + return EndpointPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// UpdateOptsBuilder allows extensions to add parameters to the Update request. +type UpdateOptsBuilder interface { + ToEndpointUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts contains the subset of Endpoint attributes that should be used to +// update an Endpoint. +type UpdateOpts struct { + // Availability is the interface type of the Endpoint (admin, internal, + // or public), referenced by the gophercloud.Availability type. + Availability gophercloud.Availability `json:"interface,omitempty"` + + // Name is the name of the Endpoint. + Name string `json:"name,omitempty"` + + // Region is the region the Endpoint is located in. + // This field can be omitted or left as a blank string. + Region string `json:"region,omitempty"` + + // URL is the url of the Endpoint. + URL string `json:"url,omitempty"` + + // ServiceID is the ID of the service the Endpoint refers to. + ServiceID string `json:"service_id,omitempty"` +} + +// ToEndpointUpdateMap builds an update request body from the Update options. +func (opts UpdateOpts) ToEndpointUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "endpoint") +} + +// Update changes an existing endpoint with new data. +func Update(client *gophercloud.ServiceClient, endpointID string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToEndpointUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Patch(endpointURL(client, endpointID), &b, &r.Body, nil) + return +} + +// Delete removes an endpoint from the service catalog. +func Delete(client *gophercloud.ServiceClient, endpointID string) (r DeleteResult) { + _, r.Err = client.Delete(endpointURL(client, endpointID), nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/endpoints/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/endpoints/results.go new file mode 100644 index 000000000..6e54f6b41 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/endpoints/results.go @@ -0,0 +1,80 @@ +package endpoints + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type commonResult struct { + gophercloud.Result +} + +// Extract interprets a GetResult, CreateResult or UpdateResult as a concrete +// Endpoint. An error is returned if the original call or the extraction failed. +func (r commonResult) Extract() (*Endpoint, error) { + var s struct { + Endpoint *Endpoint `json:"endpoint"` + } + err := r.ExtractInto(&s) + return s.Endpoint, err +} + +// CreateResult is the response from a Create operation. Call its Extract +// method to interpret it as an Endpoint. +type CreateResult struct { + commonResult +} + +// UpdateResult is the response from an Update operation. Call its Extract +// method to interpret it as an Endpoint. +type UpdateResult struct { + commonResult +} + +// 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 +} + +// Endpoint describes the entry point for another service's API. +type Endpoint struct { + // ID is the unique ID of the endpoint. + ID string `json:"id"` + + // Availability is the interface type of the Endpoint (admin, internal, + // or public), referenced by the gophercloud.Availability type. + Availability gophercloud.Availability `json:"interface"` + + // Name is the name of the Endpoint. + Name string `json:"name"` + + // Region is the region the Endpoint is located in. + Region string `json:"region"` + + // ServiceID is the ID of the service the Endpoint refers to. + ServiceID string `json:"service_id"` + + // URL is the url of the Endpoint. + URL string `json:"url"` +} + +// EndpointPage is a single page of Endpoint results. +type EndpointPage struct { + pagination.LinkedPageBase +} + +// IsEmpty returns true if no Endpoints were returned. +func (r EndpointPage) IsEmpty() (bool, error) { + es, err := ExtractEndpoints(r) + return len(es) == 0, err +} + +// ExtractEndpoints extracts an Endpoint slice from a Page. +func ExtractEndpoints(r pagination.Page) ([]Endpoint, error) { + var s struct { + Endpoints []Endpoint `json:"endpoints"` + } + err := (r.(EndpointPage)).ExtractInto(&s) + return s.Endpoints, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/endpoints/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/endpoints/urls.go new file mode 100644 index 000000000..80cf57eb3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/endpoints/urls.go @@ -0,0 +1,11 @@ +package endpoints + +import "github.com/gophercloud/gophercloud" + +func listURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("endpoints") +} + +func endpointURL(client *gophercloud.ServiceClient, endpointID string) string { + return client.ServiceURL("endpoints", endpointID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/doc.go new file mode 100644 index 000000000..696e2a5d8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/doc.go @@ -0,0 +1,60 @@ +/* +Package groups manages and retrieves Groups in the OpenStack Identity Service. + +Example to List Groups + + listOpts := groups.ListOpts{ + DomainID: "default", + } + + allPages, err := groups.List(identityClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allGroups, err := groups.ExtractGroups(allPages) + if err != nil { + panic(err) + } + + for _, group := range allGroups { + fmt.Printf("%+v\n", group) + } + +Example to Create a Group + + createOpts := groups.CreateOpts{ + Name: "groupname", + DomainID: "default", + Extra: map[string]interface{}{ + "email": "groupname@example.com", + } + } + + group, err := groups.Create(identityClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Group + + groupID := "0fe36e73809d46aeae6705c39077b1b3" + + updateOpts := groups.UpdateOpts{ + Description: "Updated Description for group", + } + + group, err := groups.Update(identityClient, groupID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Group + + groupID := "0fe36e73809d46aeae6705c39077b1b3" + err := groups.Delete(identityClient, groupID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package groups diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/errors.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/errors.go new file mode 100644 index 000000000..98e6fe4b0 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/errors.go @@ -0,0 +1,17 @@ +package groups + +import "fmt" + +// InvalidListFilter is returned by the ToUserListQuery method when validation of +// a filter does not pass +type InvalidListFilter struct { + FilterName string +} + +func (e InvalidListFilter) Error() string { + s := fmt.Sprintf( + "Invalid filter name [%s]: it must be in format of NAME__COMPARATOR", + e.FilterName, + ) + return s +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/requests.go new file mode 100644 index 000000000..032b54480 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/requests.go @@ -0,0 +1,180 @@ +package groups + +import ( + "net/url" + "strings" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to +// the List request +type ListOptsBuilder interface { + ToGroupListQuery() (string, error) +} + +// ListOpts provides options to filter the List results. +type ListOpts struct { + // DomainID filters the response by a domain ID. + DomainID string `q:"domain_id"` + + // Name filters the response by group name. + Name string `q:"name"` + + // Filters filters the response by custom filters such as + // 'name__contains=foo' + Filters map[string]string `q:"-"` +} + +// ToGroupListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToGroupListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return "", err + } + + params := q.Query() + for k, v := range opts.Filters { + i := strings.Index(k, "__") + if i > 0 && i < len(k)-2 { + params.Add(k, v) + } else { + return "", InvalidListFilter{FilterName: k} + } + } + + q = &url.URL{RawQuery: params.Encode()} + return q.String(), err +} + +// List enumerates the Groups to which the current token has access. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToGroupListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return GroupPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves details on a single group, by ID. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + return +} + +// CreateOptsBuilder allows extensions to add additional parameters to +// the Create request. +type CreateOptsBuilder interface { + ToGroupCreateMap() (map[string]interface{}, error) +} + +// CreateOpts provides options used to create a group. +type CreateOpts struct { + // Name is the name of the new group. + Name string `json:"name" required:"true"` + + // Description is a description of the group. + Description string `json:"description,omitempty"` + + // DomainID is the ID of the domain the group belongs to. + DomainID string `json:"domain_id,omitempty"` + + // Extra is free-form extra key/value pairs to describe the group. + Extra map[string]interface{} `json:"-"` +} + +// ToGroupCreateMap formats a CreateOpts into a create request. +func (opts CreateOpts) ToGroupCreateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "group") + if err != nil { + return nil, err + } + + if opts.Extra != nil { + if v, ok := b["group"].(map[string]interface{}); ok { + for key, value := range opts.Extra { + v[key] = value + } + } + } + + return b, nil +} + +// Create creates a new Group. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToGroupCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to +// the Update request. +type UpdateOptsBuilder interface { + ToGroupUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts provides options for updating a group. +type UpdateOpts struct { + // Name is the name of the new group. + Name string `json:"name,omitempty"` + + // Description is a description of the group. + Description *string `json:"description,omitempty"` + + // DomainID is the ID of the domain the group belongs to. + DomainID string `json:"domain_id,omitempty"` + + // Extra is free-form extra key/value pairs to describe the group. + Extra map[string]interface{} `json:"-"` +} + +// ToGroupUpdateMap formats a UpdateOpts into an update request. +func (opts UpdateOpts) ToGroupUpdateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "group") + if err != nil { + return nil, err + } + + if opts.Extra != nil { + if v, ok := b["group"].(map[string]interface{}); ok { + for key, value := range opts.Extra { + v[key] = value + } + } + } + + return b, nil +} + +// Update updates an existing Group. +func Update(client *gophercloud.ServiceClient, groupID string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToGroupUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Patch(updateURL(client, groupID), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Delete deletes a group. +func Delete(client *gophercloud.ServiceClient, groupID string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, groupID), nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/results.go new file mode 100644 index 000000000..ba7d018d1 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/results.go @@ -0,0 +1,132 @@ +package groups + +import ( + "encoding/json" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/internal" + "github.com/gophercloud/gophercloud/pagination" +) + +// Group helps manage related users. +type Group struct { + // Description describes the group purpose. + Description string `json:"description"` + + // DomainID is the domain ID the group belongs to. + DomainID string `json:"domain_id"` + + // ID is the unique ID of the group. + ID string `json:"id"` + + // Extra is a collection of miscellaneous key/values. + Extra map[string]interface{} `json:"-"` + + // Links contains referencing links to the group. + Links map[string]interface{} `json:"links"` + + // Name is the name of the group. + Name string `json:"name"` +} + +func (r *Group) UnmarshalJSON(b []byte) error { + type tmp Group + var s struct { + tmp + Extra map[string]interface{} `json:"extra"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Group(s.tmp) + + // Collect other fields and bundle them into Extra + // but only if a field titled "extra" wasn't sent. + if s.Extra != nil { + r.Extra = s.Extra + } else { + var result interface{} + err := json.Unmarshal(b, &result) + if err != nil { + return err + } + if resultMap, ok := result.(map[string]interface{}); ok { + r.Extra = internal.RemainingKeys(Group{}, resultMap) + } + } + + return err +} + +type groupResult struct { + gophercloud.Result +} + +// GetResult is the response from a Get operation. Call its Extract method +// to interpret it as a Group. +type GetResult struct { + groupResult +} + +// CreateResult is the response from a Create operation. Call its Extract method +// to interpret it as a Group. +type CreateResult struct { + groupResult +} + +// UpdateResult is the response from an Update operation. Call its Extract +// method to interpret it as a Group. +type UpdateResult struct { + groupResult +} + +// DeleteResult is the response from a Delete operation. Call its ExtractErr to +// determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// GroupPage is a single page of Group results. +type GroupPage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines whether or not a page of Groups contains any results. +func (r GroupPage) IsEmpty() (bool, error) { + groups, err := ExtractGroups(r) + return len(groups) == 0, err +} + +// NextPageURL extracts the "next" link from the links section of the result. +func (r GroupPage) NextPageURL() (string, error) { + var s struct { + Links struct { + Next string `json:"next"` + Previous string `json:"previous"` + } `json:"links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return s.Links.Next, err +} + +// ExtractGroups returns a slice of Groups contained in a single page of results. +func ExtractGroups(r pagination.Page) ([]Group, error) { + var s struct { + Groups []Group `json:"groups"` + } + err := (r.(GroupPage)).ExtractInto(&s) + return s.Groups, err +} + +// Extract interprets any group results as a Group. +func (r groupResult) Extract() (*Group, error) { + var s struct { + Group *Group `json:"group"` + } + err := r.ExtractInto(&s) + return s.Group, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/urls.go new file mode 100644 index 000000000..e7d1e53b2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/urls.go @@ -0,0 +1,23 @@ +package groups + +import "github.com/gophercloud/gophercloud" + +func listURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("groups") +} + +func getURL(client *gophercloud.ServiceClient, groupID string) string { + return client.ServiceURL("groups", groupID) +} + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("groups") +} + +func updateURL(client *gophercloud.ServiceClient, groupID string) string { + return client.ServiceURL("groups", groupID) +} + +func deleteURL(client *gophercloud.ServiceClient, groupID string) string { + return client.ServiceURL("groups", groupID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects/doc.go new file mode 100644 index 000000000..4f5b45ab6 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects/doc.go @@ -0,0 +1,58 @@ +/* +Package projects manages and retrieves Projects in the OpenStack Identity +Service. + +Example to List Projects + + listOpts := projects.ListOpts{ + Enabled: gophercloud.Enabled, + } + + allPages, err := projects.List(identityClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allProjects, err := projects.ExtractProjects(allPages) + if err != nil { + panic(err) + } + + for _, project := range allProjects { + fmt.Printf("%+v\n", project) + } + +Example to Create a Project + + createOpts := projects.CreateOpts{ + Name: "project_name", + Description: "Project Description" + } + + project, err := projects.Create(identityClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Project + + projectID := "966b3c7d36a24facaf20b7e458bf2192" + + updateOpts := projects.UpdateOpts{ + Enabled: gophercloud.Disabled, + } + + project, err := projects.Update(identityClient, projectID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Project + + projectID := "966b3c7d36a24facaf20b7e458bf2192" + err := projects.Delete(identityClient, projectID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package projects diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects/errors.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects/errors.go new file mode 100644 index 000000000..7be97d859 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects/errors.go @@ -0,0 +1,17 @@ +package projects + +import "fmt" + +// InvalidListFilter is returned by the ToUserListQuery method when validation of +// a filter does not pass +type InvalidListFilter struct { + FilterName string +} + +func (e InvalidListFilter) Error() string { + s := fmt.Sprintf( + "Invalid filter name [%s]: it must be in format of NAME__COMPARATOR", + e.FilterName, + ) + return s +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects/requests.go new file mode 100644 index 000000000..0e4616954 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects/requests.go @@ -0,0 +1,174 @@ +package projects + +import ( + "net/url" + "strings" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to +// the List request +type ListOptsBuilder interface { + ToProjectListQuery() (string, error) +} + +// ListOpts enables filtering of a list request. +type ListOpts struct { + // DomainID filters the response by a domain ID. + DomainID string `q:"domain_id"` + + // Enabled filters the response by enabled projects. + Enabled *bool `q:"enabled"` + + // IsDomain filters the response by projects that are domains. + // Setting this to true is effectively listing domains. + IsDomain *bool `q:"is_domain"` + + // Name filters the response by project name. + Name string `q:"name"` + + // ParentID filters the response by projects of a given parent project. + ParentID string `q:"parent_id"` + + // Filters filters the response by custom filters such as + // 'name__contains=foo' + Filters map[string]string `q:"-"` +} + +// ToProjectListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToProjectListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return "", err + } + + params := q.Query() + for k, v := range opts.Filters { + i := strings.Index(k, "__") + if i > 0 && i < len(k)-2 { + params.Add(k, v) + } else { + return "", InvalidListFilter{FilterName: k} + } + } + + q = &url.URL{RawQuery: params.Encode()} + return q.String(), err +} + +// List enumerates the Projects to which the current token has access. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToProjectListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return ProjectPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves details on a single project, by ID. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + return +} + +// CreateOptsBuilder allows extensions to add additional parameters to +// the Create request. +type CreateOptsBuilder interface { + ToProjectCreateMap() (map[string]interface{}, error) +} + +// CreateOpts represents parameters used to create a project. +type CreateOpts struct { + // DomainID is the ID this project will belong under. + DomainID string `json:"domain_id,omitempty"` + + // Enabled sets the project status to enabled or disabled. + Enabled *bool `json:"enabled,omitempty"` + + // IsDomain indicates if this project is a domain. + IsDomain *bool `json:"is_domain,omitempty"` + + // Name is the name of the project. + Name string `json:"name" required:"true"` + + // ParentID specifies the parent project of this new project. + ParentID string `json:"parent_id,omitempty"` + + // Description is the description of the project. + Description string `json:"description,omitempty"` +} + +// ToProjectCreateMap formats a CreateOpts into a create request. +func (opts CreateOpts) ToProjectCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "project") +} + +// Create creates a new Project. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToProjectCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), &b, &r.Body, nil) + return +} + +// Delete deletes a project. +func Delete(client *gophercloud.ServiceClient, projectID string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, projectID), nil) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to +// the Update request. +type UpdateOptsBuilder interface { + ToProjectUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts represents parameters to update a project. +type UpdateOpts struct { + // DomainID is the ID this project will belong under. + DomainID string `json:"domain_id,omitempty"` + + // Enabled sets the project status to enabled or disabled. + Enabled *bool `json:"enabled,omitempty"` + + // IsDomain indicates if this project is a domain. + IsDomain *bool `json:"is_domain,omitempty"` + + // Name is the name of the project. + Name string `json:"name,omitempty"` + + // ParentID specifies the parent project of this new project. + ParentID string `json:"parent_id,omitempty"` + + // Description is the description of the project. + Description *string `json:"description,omitempty"` +} + +// ToUpdateCreateMap formats a UpdateOpts into an update request. +func (opts UpdateOpts) ToProjectUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "project") +} + +// Update modifies the attributes of a project. +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToProjectUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Patch(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects/results.go new file mode 100644 index 000000000..a13fa7f2a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects/results.go @@ -0,0 +1,103 @@ +package projects + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type projectResult struct { + gophercloud.Result +} + +// GetResult is the result of a Get request. Call its Extract method to +// interpret it as a Project. +type GetResult struct { + projectResult +} + +// CreateResult is the result of a Create request. Call its Extract method to +// interpret it as a Project. +type CreateResult struct { + projectResult +} + +// DeleteResult is the result of a Delete request. Call its ExtractErr method to +// determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// UpdateResult is the result of an Update request. Call its Extract method to +// interpret it as a Project. +type UpdateResult struct { + projectResult +} + +// Project represents an OpenStack Identity Project. +type Project struct { + // IsDomain indicates whether the project is a domain. + IsDomain bool `json:"is_domain"` + + // Description is the description of the project. + Description string `json:"description"` + + // DomainID is the domain ID the project belongs to. + DomainID string `json:"domain_id"` + + // Enabled is whether or not the project is enabled. + Enabled bool `json:"enabled"` + + // ID is the unique ID of the project. + ID string `json:"id"` + + // Name is the name of the project. + Name string `json:"name"` + + // ParentID is the parent_id of the project. + ParentID string `json:"parent_id"` +} + +// ProjectPage is a single page of Project results. +type ProjectPage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines whether or not a page of Projects contains any results. +func (r ProjectPage) IsEmpty() (bool, error) { + projects, err := ExtractProjects(r) + return len(projects) == 0, err +} + +// NextPageURL extracts the "next" link from the links section of the result. +func (r ProjectPage) NextPageURL() (string, error) { + var s struct { + Links struct { + Next string `json:"next"` + Previous string `json:"previous"` + } `json:"links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return s.Links.Next, err +} + +// ExtractProjects returns a slice of Projects contained in a single page of +// results. +func ExtractProjects(r pagination.Page) ([]Project, error) { + var s struct { + Projects []Project `json:"projects"` + } + err := (r.(ProjectPage)).ExtractInto(&s) + return s.Projects, err +} + +// Extract interprets any projectResults as a Project. +func (r projectResult) Extract() (*Project, error) { + var s struct { + Project *Project `json:"project"` + } + err := r.ExtractInto(&s) + return s.Project, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects/urls.go new file mode 100644 index 000000000..e26cf3684 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects/urls.go @@ -0,0 +1,23 @@ +package projects + +import "github.com/gophercloud/gophercloud" + +func listURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("projects") +} + +func getURL(client *gophercloud.ServiceClient, projectID string) string { + return client.ServiceURL("projects", projectID) +} + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("projects") +} + +func deleteURL(client *gophercloud.ServiceClient, projectID string) string { + return client.ServiceURL("projects", projectID) +} + +func updateURL(client *gophercloud.ServiceClient, projectID string) string { + return client.ServiceURL("projects", projectID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/doc.go new file mode 100644 index 000000000..f0e4d045e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/doc.go @@ -0,0 +1,135 @@ +/* +Package roles provides information and interaction with the roles API +resource for the OpenStack Identity service. + +Example to List Roles + + listOpts := roles.ListOpts{ + DomainID: "default", + } + + allPages, err := roles.List(identityClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allRoles, err := roles.ExtractRoles(allPages) + if err != nil { + panic(err) + } + + for _, role := range allRoles { + fmt.Printf("%+v\n", role) + } + +Example to Create a Role + + createOpts := roles.CreateOpts{ + Name: "read-only-admin", + DomainID: "default", + Extra: map[string]interface{}{ + "description": "this role grants read-only privilege cross tenant", + } + } + + role, err := roles.Create(identityClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Role + + roleID := "0fe36e73809d46aeae6705c39077b1b3" + + updateOpts := roles.UpdateOpts{ + Name: "read only admin", + } + + role, err := roles.Update(identityClient, roleID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Role + + roleID := "0fe36e73809d46aeae6705c39077b1b3" + err := roles.Delete(identityClient, roleID).ExtractErr() + if err != nil { + panic(err) + } + +Example to List Role Assignments + + listOpts := roles.ListAssignmentsOpts{ + UserID: "97061de2ed0647b28a393c36ab584f39", + ScopeProjectID: "9df1a02f5eb2416a9781e8b0c022d3ae", + } + + allPages, err := roles.ListAssignments(identityClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allRoles, err := roles.ExtractRoleAssignments(allPages) + if err != nil { + panic(err) + } + + for _, role := range allRoles { + fmt.Printf("%+v\n", role) + } + +Example to List Role Assignments for a User on a Project + + projectID := "a99e9b4e620e4db09a2dfb6e42a01e66" + userID := "9df1a02f5eb2416a9781e8b0c022d3ae" + listAssignmentsOnResourceOpts := roles.ListAssignmentsOnResourceOpts{ + UserID: userID, + ProjectID: projectID, + } + + allPages, err := roles.ListAssignmentsOnResource(identityClient, listAssignmentsOnResourceOpts).AllPages() + if err != nil { + panic(err) + } + + allRoles, err := roles.ExtractRoles(allPages) + if err != nil { + panic(err) + } + + for _, role := range allRoles { + fmt.Printf("%+v\n", role) + } + +Example to Assign a Role to a User in a Project + + projectID := "a99e9b4e620e4db09a2dfb6e42a01e66" + userID := "9df1a02f5eb2416a9781e8b0c022d3ae" + roleID := "9fe2ff9ee4384b1894a90878d3e92bab" + + err := roles.Assign(identityClient, roleID, roles.AssignOpts{ + UserID: userID, + ProjectID: projectID, + }).ExtractErr() + + if err != nil { + panic(err) + } + +Example to Unassign a Role From a User in a Project + + projectID := "a99e9b4e620e4db09a2dfb6e42a01e66" + userID := "9df1a02f5eb2416a9781e8b0c022d3ae" + roleID := "9fe2ff9ee4384b1894a90878d3e92bab" + + err := roles.Unassign(identityClient, roleID, roles.UnassignOpts{ + UserID: userID, + ProjectID: projectID, + }).ExtractErr() + + if err != nil { + panic(err) + } +*/ +package roles diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/errors.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/errors.go new file mode 100644 index 000000000..b60d7d18b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/errors.go @@ -0,0 +1,17 @@ +package roles + +import "fmt" + +// InvalidListFilter is returned by the ToUserListQuery method when validation of +// a filter does not pass +type InvalidListFilter struct { + FilterName string +} + +func (e InvalidListFilter) Error() string { + s := fmt.Sprintf( + "Invalid filter name [%s]: it must be in format of NAME__COMPARATOR", + e.FilterName, + ) + return s +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/requests.go new file mode 100644 index 000000000..573db9ea2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/requests.go @@ -0,0 +1,392 @@ +package roles + +import ( + "net/url" + "strings" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to +// the List request +type ListOptsBuilder interface { + ToRoleListQuery() (string, error) +} + +// ListOpts provides options to filter the List results. +type ListOpts struct { + // DomainID filters the response by a domain ID. + DomainID string `q:"domain_id"` + + // Name filters the response by role name. + Name string `q:"name"` + + // Filters filters the response by custom filters such as + // 'name__contains=foo' + Filters map[string]string `q:"-"` +} + +// ToRoleListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToRoleListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return "", err + } + + params := q.Query() + for k, v := range opts.Filters { + i := strings.Index(k, "__") + if i > 0 && i < len(k)-2 { + params.Add(k, v) + } else { + return "", InvalidListFilter{FilterName: k} + } + } + + q = &url.URL{RawQuery: params.Encode()} + return q.String(), err +} + +// List enumerates the roles to which the current token has access. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToRoleListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return RolePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves details on a single role, by ID. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + return +} + +// CreateOptsBuilder allows extensions to add additional parameters to +// the Create request. +type CreateOptsBuilder interface { + ToRoleCreateMap() (map[string]interface{}, error) +} + +// CreateOpts provides options used to create a role. +type CreateOpts struct { + // Name is the name of the new role. + Name string `json:"name" required:"true"` + + // DomainID is the ID of the domain the role belongs to. + DomainID string `json:"domain_id,omitempty"` + + // Extra is free-form extra key/value pairs to describe the role. + Extra map[string]interface{} `json:"-"` +} + +// ToRoleCreateMap formats a CreateOpts into a create request. +func (opts CreateOpts) ToRoleCreateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "role") + if err != nil { + return nil, err + } + + if opts.Extra != nil { + if v, ok := b["role"].(map[string]interface{}); ok { + for key, value := range opts.Extra { + v[key] = value + } + } + } + + return b, nil +} + +// Create creates a new Role. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToRoleCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to +// the Update request. +type UpdateOptsBuilder interface { + ToRoleUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts provides options for updating a role. +type UpdateOpts struct { + // Name is the name of the new role. + Name string `json:"name,omitempty"` + + // Extra is free-form extra key/value pairs to describe the role. + Extra map[string]interface{} `json:"-"` +} + +// ToRoleUpdateMap formats a UpdateOpts into an update request. +func (opts UpdateOpts) ToRoleUpdateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "role") + if err != nil { + return nil, err + } + + if opts.Extra != nil { + if v, ok := b["role"].(map[string]interface{}); ok { + for key, value := range opts.Extra { + v[key] = value + } + } + } + + return b, nil +} + +// Update updates an existing Role. +func Update(client *gophercloud.ServiceClient, roleID string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToRoleUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Patch(updateURL(client, roleID), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Delete deletes a role. +func Delete(client *gophercloud.ServiceClient, roleID string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, roleID), nil) + return +} + +// ListAssignmentsOptsBuilder allows extensions to add additional parameters to +// the ListAssignments request. +type ListAssignmentsOptsBuilder interface { + ToRolesListAssignmentsQuery() (string, error) +} + +// ListAssignmentsOpts allows you to query the ListAssignments method. +// Specify one of or a combination of GroupId, RoleId, ScopeDomainId, +// ScopeProjectId, and/or UserId to search for roles assigned to corresponding +// entities. +type ListAssignmentsOpts struct { + // GroupID is the group ID to query. + GroupID string `q:"group.id"` + + // RoleID is the specific role to query assignments to. + RoleID string `q:"role.id"` + + // ScopeDomainID filters the results by the given domain ID. + ScopeDomainID string `q:"scope.domain.id"` + + // ScopeProjectID filters the results by the given Project ID. + ScopeProjectID string `q:"scope.project.id"` + + // UserID filterst he results by the given User ID. + UserID string `q:"user.id"` + + // Effective lists effective assignments at the user, project, and domain + // level, allowing for the effects of group membership. + Effective *bool `q:"effective"` +} + +// ToRolesListAssignmentsQuery formats a ListAssignmentsOpts into a query string. +func (opts ListAssignmentsOpts) ToRolesListAssignmentsQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// ListAssignments enumerates the roles assigned to a specified resource. +func ListAssignments(client *gophercloud.ServiceClient, opts ListAssignmentsOptsBuilder) pagination.Pager { + url := listAssignmentsURL(client) + if opts != nil { + query, err := opts.ToRolesListAssignmentsQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return RoleAssignmentPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// ListAssignmentsOnResourceOpts provides options to list role assignments +// for a user/group on a project/domain +type ListAssignmentsOnResourceOpts struct { + // UserID is the ID of a user to assign a role + // Note: exactly one of UserID or GroupID must be provided + UserID string `xor:"GroupID"` + + // GroupID is the ID of a group to assign a role + // Note: exactly one of UserID or GroupID must be provided + GroupID string `xor:"UserID"` + + // ProjectID is the ID of a project to assign a role on + // Note: exactly one of ProjectID or DomainID must be provided + ProjectID string `xor:"DomainID"` + + // DomainID is the ID of a domain to assign a role on + // Note: exactly one of ProjectID or DomainID must be provided + DomainID string `xor:"ProjectID"` +} + +// AssignOpts provides options to assign a role +type AssignOpts struct { + // UserID is the ID of a user to assign a role + // Note: exactly one of UserID or GroupID must be provided + UserID string `xor:"GroupID"` + + // GroupID is the ID of a group to assign a role + // Note: exactly one of UserID or GroupID must be provided + GroupID string `xor:"UserID"` + + // ProjectID is the ID of a project to assign a role on + // Note: exactly one of ProjectID or DomainID must be provided + ProjectID string `xor:"DomainID"` + + // DomainID is the ID of a domain to assign a role on + // Note: exactly one of ProjectID or DomainID must be provided + DomainID string `xor:"ProjectID"` +} + +// UnassignOpts provides options to unassign a role +type UnassignOpts struct { + // UserID is the ID of a user to unassign a role + // Note: exactly one of UserID or GroupID must be provided + UserID string `xor:"GroupID"` + + // GroupID is the ID of a group to unassign a role + // Note: exactly one of UserID or GroupID must be provided + GroupID string `xor:"UserID"` + + // ProjectID is the ID of a project to unassign a role on + // Note: exactly one of ProjectID or DomainID must be provided + ProjectID string `xor:"DomainID"` + + // DomainID is the ID of a domain to unassign a role on + // Note: exactly one of ProjectID or DomainID must be provided + DomainID string `xor:"ProjectID"` +} + +// ListAssignmentsOnResource is the operation responsible for listing role +// assignments for a user/group on a project/domain. +func ListAssignmentsOnResource(client *gophercloud.ServiceClient, opts ListAssignmentsOnResourceOpts) pagination.Pager { + // Check xor conditions + _, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return pagination.Pager{Err: err} + } + + // Get corresponding URL + var targetID string + var targetType string + if opts.ProjectID != "" { + targetID = opts.ProjectID + targetType = "projects" + } else { + targetID = opts.DomainID + targetType = "domains" + } + + var actorID string + var actorType string + if opts.UserID != "" { + actorID = opts.UserID + actorType = "users" + } else { + actorID = opts.GroupID + actorType = "groups" + } + + url := listAssignmentsOnResourceURL(client, targetType, targetID, actorType, actorID) + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return RolePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Assign is the operation responsible for assigning a role +// to a user/group on a project/domain. +func Assign(client *gophercloud.ServiceClient, roleID string, opts AssignOpts) (r AssignmentResult) { + // Check xor conditions + _, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + r.Err = err + return + } + + // Get corresponding URL + var targetID string + var targetType string + if opts.ProjectID != "" { + targetID = opts.ProjectID + targetType = "projects" + } else { + targetID = opts.DomainID + targetType = "domains" + } + + var actorID string + var actorType string + if opts.UserID != "" { + actorID = opts.UserID + actorType = "users" + } else { + actorID = opts.GroupID + actorType = "groups" + } + + _, r.Err = client.Put(assignURL(client, targetType, targetID, actorType, actorID, roleID), nil, nil, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return +} + +// Unassign is the operation responsible for unassigning a role +// from a user/group on a project/domain. +func Unassign(client *gophercloud.ServiceClient, roleID string, opts UnassignOpts) (r UnassignmentResult) { + // Check xor conditions + _, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + r.Err = err + return + } + + // Get corresponding URL + var targetID string + var targetType string + if opts.ProjectID != "" { + targetID = opts.ProjectID + targetType = "projects" + } else { + targetID = opts.DomainID + targetType = "domains" + } + + var actorID string + var actorType string + if opts.UserID != "" { + actorID = opts.UserID + actorType = "users" + } else { + actorID = opts.GroupID + actorType = "groups" + } + + _, r.Err = client.Delete(assignURL(client, targetType, targetID, actorType, actorID, roleID), &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/results.go new file mode 100644 index 000000000..af8fd9e6a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/results.go @@ -0,0 +1,214 @@ +package roles + +import ( + "encoding/json" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/internal" + "github.com/gophercloud/gophercloud/pagination" +) + +// Role grants permissions to a user. +type Role struct { + // DomainID is the domain ID the role belongs to. + DomainID string `json:"domain_id"` + + // ID is the unique ID of the role. + ID string `json:"id"` + + // Links contains referencing links to the role. + Links map[string]interface{} `json:"links"` + + // Name is the role name + Name string `json:"name"` + + // Extra is a collection of miscellaneous key/values. + Extra map[string]interface{} `json:"-"` +} + +func (r *Role) UnmarshalJSON(b []byte) error { + type tmp Role + var s struct { + tmp + Extra map[string]interface{} `json:"extra"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Role(s.tmp) + + // Collect other fields and bundle them into Extra + // but only if a field titled "extra" wasn't sent. + if s.Extra != nil { + r.Extra = s.Extra + } else { + var result interface{} + err := json.Unmarshal(b, &result) + if err != nil { + return err + } + if resultMap, ok := result.(map[string]interface{}); ok { + r.Extra = internal.RemainingKeys(Role{}, resultMap) + } + } + + return err +} + +type roleResult struct { + gophercloud.Result +} + +// GetResult is the response from a Get operation. Call its Extract method +// to interpret it as a Role. +type GetResult struct { + roleResult +} + +// CreateResult is the response from a Create operation. Call its Extract method +// to interpret it as a Role +type CreateResult struct { + roleResult +} + +// UpdateResult is the response from an Update operation. Call its Extract +// method to interpret it as a Role. +type UpdateResult struct { + roleResult +} + +// DeleteResult is the response from a Delete operation. Call its ExtractErr to +// determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// RolePage is a single page of Role results. +type RolePage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines whether or not a page of Roles contains any results. +func (r RolePage) IsEmpty() (bool, error) { + roles, err := ExtractRoles(r) + return len(roles) == 0, err +} + +// NextPageURL extracts the "next" link from the links section of the result. +func (r RolePage) NextPageURL() (string, error) { + var s struct { + Links struct { + Next string `json:"next"` + Previous string `json:"previous"` + } `json:"links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return s.Links.Next, err +} + +// ExtractProjects returns a slice of Roles contained in a single page of +// results. +func ExtractRoles(r pagination.Page) ([]Role, error) { + var s struct { + Roles []Role `json:"roles"` + } + err := (r.(RolePage)).ExtractInto(&s) + return s.Roles, err +} + +// Extract interprets any roleResults as a Role. +func (r roleResult) Extract() (*Role, error) { + var s struct { + Role *Role `json:"role"` + } + err := r.ExtractInto(&s) + return s.Role, err +} + +// RoleAssignment is the result of a role assignments query. +type RoleAssignment struct { + Role AssignedRole `json:"role,omitempty"` + Scope Scope `json:"scope,omitempty"` + User User `json:"user,omitempty"` + Group Group `json:"group,omitempty"` +} + +// AssignedRole represents a Role in an assignment. +type AssignedRole struct { + ID string `json:"id,omitempty"` +} + +// Scope represents a scope in a Role assignment. +type Scope struct { + Domain Domain `json:"domain,omitempty"` + Project Project `json:"project,omitempty"` +} + +// Domain represents a domain in a role assignment scope. +type Domain struct { + ID string `json:"id,omitempty"` +} + +// Project represents a project in a role assignment scope. +type Project struct { + ID string `json:"id,omitempty"` +} + +// User represents a user in a role assignment scope. +type User struct { + ID string `json:"id,omitempty"` +} + +// Group represents a group in a role assignment scope. +type Group struct { + ID string `json:"id,omitempty"` +} + +// RoleAssignmentPage is a single page of RoleAssignments results. +type RoleAssignmentPage struct { + pagination.LinkedPageBase +} + +// IsEmpty returns true if the RoleAssignmentPage contains no results. +func (r RoleAssignmentPage) IsEmpty() (bool, error) { + roleAssignments, err := ExtractRoleAssignments(r) + return len(roleAssignments) == 0, err +} + +// NextPageURL uses the response's embedded link reference to navigate to +// the next page of results. +func (r RoleAssignmentPage) NextPageURL() (string, error) { + var s struct { + Links struct { + Next string `json:"next"` + } `json:"links"` + } + err := r.ExtractInto(&s) + return s.Links.Next, err +} + +// ExtractRoleAssignments extracts a slice of RoleAssignments from a Collection +// acquired from List. +func ExtractRoleAssignments(r pagination.Page) ([]RoleAssignment, error) { + var s struct { + RoleAssignments []RoleAssignment `json:"role_assignments"` + } + err := (r.(RoleAssignmentPage)).ExtractInto(&s) + return s.RoleAssignments, err +} + +// AssignmentResult represents the result of an assign operation. +// Call ExtractErr method to determine if the request succeeded or failed. +type AssignmentResult struct { + gophercloud.ErrResult +} + +// UnassignmentResult represents the result of an unassign operation. +// Call ExtractErr method to determine if the request succeeded or failed. +type UnassignmentResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/urls.go new file mode 100644 index 000000000..2b8201142 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/urls.go @@ -0,0 +1,39 @@ +package roles + +import "github.com/gophercloud/gophercloud" + +const ( + rolePath = "roles" +) + +func listURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL(rolePath) +} + +func getURL(client *gophercloud.ServiceClient, roleID string) string { + return client.ServiceURL(rolePath, roleID) +} + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL(rolePath) +} + +func updateURL(client *gophercloud.ServiceClient, roleID string) string { + return client.ServiceURL(rolePath, roleID) +} + +func deleteURL(client *gophercloud.ServiceClient, roleID string) string { + return client.ServiceURL(rolePath, roleID) +} + +func listAssignmentsURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("role_assignments") +} + +func listAssignmentsOnResourceURL(client *gophercloud.ServiceClient, targetType, targetID, actorType, actorID string) string { + return client.ServiceURL(targetType, targetID, actorType, actorID, rolePath) +} + +func assignURL(client *gophercloud.ServiceClient, targetType, targetID, actorType, actorID, roleID string) string { + return client.ServiceURL(targetType, targetID, actorType, actorID, rolePath, roleID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/services/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/services/doc.go new file mode 100644 index 000000000..81702359a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/services/doc.go @@ -0,0 +1,66 @@ +/* +Package services provides information and interaction with the services API +resource for the OpenStack Identity service. + +Example to List Services + + listOpts := services.ListOpts{ + ServiceType: "compute", + } + + allPages, err := services.List(identityClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allServices, err := services.ExtractServices(allPages) + if err != nil { + panic(err) + } + + for _, service := range allServices { + fmt.Printf("%+v\n", service) + } + +Example to Create a Service + + createOpts := services.CreateOpts{ + Type: "compute", + Extra: map[string]interface{}{ + "name": "compute-service", + "description": "Compute Service", + }, + } + + service, err := services.Create(identityClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Service + + serviceID := "3c7bbe9a6ecb453ca1789586291380ed" + + var iFalse bool = false + updateOpts := services.UpdateOpts{ + Enabled: &iFalse, + Extra: map[string]interface{}{ + "description": "Disabled Compute Service" + }, + } + + service, err := services.Update(identityClient, serviceID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Service + + serviceID := "3c7bbe9a6ecb453ca1789586291380ed" + err := services.Delete(identityClient, serviceID).ExtractErr() + if err != nil { + panic(err) + } + +*/ +package services diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/services/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/services/requests.go new file mode 100644 index 000000000..1a8b35232 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/services/requests.go @@ -0,0 +1,154 @@ +package services + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder allows extensions to add additional parameters to +// the Create request. +type CreateOptsBuilder interface { + ToServiceCreateMap() (map[string]interface{}, error) +} + +// CreateOpts provides options used to create a service. +type CreateOpts struct { + // Type is the type of the service. + Type string `json:"type"` + + // Enabled is whether or not the service is enabled. + Enabled *bool `json:"enabled,omitempty"` + + // Extra is free-form extra key/value pairs to describe the service. + Extra map[string]interface{} `json:"-"` +} + +// ToServiceCreateMap formats a CreateOpts into a create request. +func (opts CreateOpts) ToServiceCreateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "service") + if err != nil { + return nil, err + } + + if opts.Extra != nil { + if v, ok := b["service"].(map[string]interface{}); ok { + for key, value := range opts.Extra { + v[key] = value + } + } + } + + return b, nil +} + +// Create adds a new service of the requested type to the catalog. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToServiceCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + return +} + +// ListOptsBuilder enables extensions to add additional parameters to the List +// request. +type ListOptsBuilder interface { + ToServiceListMap() (string, error) +} + +// ListOpts provides options for filtering the List results. +type ListOpts struct { + // ServiceType filter the response by a type of service. + ServiceType string `q:"type"` + + // Name filters the response by a service name. + Name string `q:"name"` +} + +// ToServiceListMap builds a list query from the list options. +func (opts ListOpts) ToServiceListMap() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List enumerates the services available to a specific user. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToServiceListMap() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return ServicePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get returns additional information about a service, given its ID. +func Get(client *gophercloud.ServiceClient, serviceID string) (r GetResult) { + _, r.Err = client.Get(serviceURL(client, serviceID), &r.Body, nil) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to +// the Update request. +type UpdateOptsBuilder interface { + ToServiceUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts provides options for updating a service. +type UpdateOpts struct { + // Type is the type of the service. + Type string `json:"type"` + + // Enabled is whether or not the service is enabled. + Enabled *bool `json:"enabled,omitempty"` + + // Extra is free-form extra key/value pairs to describe the service. + Extra map[string]interface{} `json:"-"` +} + +// ToServiceUpdateMap formats a UpdateOpts into an update request. +func (opts UpdateOpts) ToServiceUpdateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "service") + if err != nil { + return nil, err + } + + if opts.Extra != nil { + if v, ok := b["service"].(map[string]interface{}); ok { + for key, value := range opts.Extra { + v[key] = value + } + } + } + + return b, nil +} + +// Update updates an existing Service. +func Update(client *gophercloud.ServiceClient, serviceID string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToServiceUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Patch(updateURL(client, serviceID), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Delete removes an existing service. +// It either deletes all associated endpoints, or fails until all endpoints +// are deleted. +func Delete(client *gophercloud.ServiceClient, serviceID string) (r DeleteResult) { + _, r.Err = client.Delete(serviceURL(client, serviceID), nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/services/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/services/results.go new file mode 100644 index 000000000..f3484d7a9 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/services/results.go @@ -0,0 +1,131 @@ +package services + +import ( + "encoding/json" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/internal" + "github.com/gophercloud/gophercloud/pagination" +) + +type serviceResult struct { + gophercloud.Result +} + +// Extract interprets a GetResult, CreateResult or UpdateResult as a concrete +// Service. An error is returned if the original call or the extraction failed. +func (r serviceResult) Extract() (*Service, error) { + var s struct { + Service *Service `json:"service"` + } + err := r.ExtractInto(&s) + return s.Service, err +} + +// CreateResult is the response from a Create request. Call its Extract method +// to interpret it as a Service. +type CreateResult struct { + serviceResult +} + +// GetResult is the response from a Get request. Call its Extract method +// to interpret it as a Service. +type GetResult struct { + serviceResult +} + +// UpdateResult is the response from an Update request. Call its Extract method +// to interpret it as a Service. +type UpdateResult struct { + serviceResult +} + +// DeleteResult is the response from a Delete request. Call its ExtractErr +// method to interpret it as a Service. +type DeleteResult struct { + gophercloud.ErrResult +} + +// Service represents an OpenStack Service. +type Service struct { + // ID is the unique ID of the service. + ID string `json:"id"` + + // Type is the type of the service. + Type string `json:"type"` + + // Enabled is whether or not the service is enabled. + Enabled bool `json:"enabled"` + + // Links contains referencing links to the service. + Links map[string]interface{} `json:"links"` + + // Extra is a collection of miscellaneous key/values. + Extra map[string]interface{} `json:"-"` +} + +func (r *Service) UnmarshalJSON(b []byte) error { + type tmp Service + var s struct { + tmp + Extra map[string]interface{} `json:"extra"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Service(s.tmp) + + // Collect other fields and bundle them into Extra + // but only if a field titled "extra" wasn't sent. + if s.Extra != nil { + r.Extra = s.Extra + } else { + var result interface{} + err := json.Unmarshal(b, &result) + if err != nil { + return err + } + if resultMap, ok := result.(map[string]interface{}); ok { + r.Extra = internal.RemainingKeys(Service{}, resultMap) + } + } + + return err +} + +// ServicePage is a single page of Service results. +type ServicePage struct { + pagination.LinkedPageBase +} + +// IsEmpty returns true if the ServicePage contains no results. +func (p ServicePage) IsEmpty() (bool, error) { + services, err := ExtractServices(p) + return len(services) == 0, err +} + +// NextPageURL extracts the "next" link from the links section of the result. +func (r ServicePage) NextPageURL() (string, error) { + var s struct { + Links struct { + Next string `json:"next"` + Previous string `json:"previous"` + } `json:"links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return s.Links.Next, err +} + +// ExtractServices extracts a slice of Services from a Collection acquired +// from List. +func ExtractServices(r pagination.Page) ([]Service, error) { + var s struct { + Services []Service `json:"services"` + } + err := (r.(ServicePage)).ExtractInto(&s) + return s.Services, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/services/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/services/urls.go new file mode 100644 index 000000000..caa625a20 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/services/urls.go @@ -0,0 +1,19 @@ +package services + +import "github.com/gophercloud/gophercloud" + +func listURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("services") +} + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("services") +} + +func serviceURL(client *gophercloud.ServiceClient, serviceID string) string { + return client.ServiceURL("services", serviceID) +} + +func updateURL(client *gophercloud.ServiceClient, serviceID string) string { + return client.ServiceURL("services", serviceID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/doc.go index 76ff5f473..966e128f1 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/doc.go @@ -1,6 +1,108 @@ -// Package tokens provides information and interaction with the token API -// resource for the OpenStack Identity service. -// -// For more information, see: -// http://developer.openstack.org/api-ref-identity-v3.html#tokens-v3 +/* +Package tokens provides information and interaction with the token API +resource for the OpenStack Identity service. + +For more information, see: +http://developer.openstack.org/api-ref-identity-v3.html#tokens-v3 + +Example to Create a Token From a Username and Password + + authOptions := tokens.AuthOptions{ + UserID: "username", + Password: "password", + } + + token, err := tokens.Create(identityClient, authOptions).ExtractToken() + if err != nil { + panic(err) + } + +Example to Create a Token From a Username, Password, and Domain + + authOptions := tokens.AuthOptions{ + UserID: "username", + Password: "password", + DomainID: "default", + } + + token, err := tokens.Create(identityClient, authOptions).ExtractToken() + if err != nil { + panic(err) + } + + authOptions = tokens.AuthOptions{ + UserID: "username", + Password: "password", + DomainName: "default", + } + + token, err = tokens.Create(identityClient, authOptions).ExtractToken() + if err != nil { + panic(err) + } + +Example to Create a Token From a Token + + authOptions := tokens.AuthOptions{ + TokenID: "token_id", + } + + token, err := tokens.Create(identityClient, authOptions).ExtractToken() + if err != nil { + panic(err) + } + +Example to Create a Token from a Username and Password with Project ID Scope + + scope := tokens.Scope{ + ProjectID: "0fe36e73809d46aeae6705c39077b1b3", + } + + authOptions := tokens.AuthOptions{ + Scope: &scope, + UserID: "username", + Password: "password", + } + + token, err = tokens.Create(identityClient, authOptions).ExtractToken() + if err != nil { + panic(err) + } + +Example to Create a Token from a Username and Password with Domain ID Scope + + scope := tokens.Scope{ + DomainID: "default", + } + + authOptions := tokens.AuthOptions{ + Scope: &scope, + UserID: "username", + Password: "password", + } + + token, err = tokens.Create(identityClient, authOptions).ExtractToken() + if err != nil { + panic(err) + } + +Example to Create a Token from a Username and Password with Project Name Scope + + scope := tokens.Scope{ + ProjectName: "project_name", + DomainID: "default", + } + + authOptions := tokens.AuthOptions{ + Scope: &scope, + UserID: "username", + Password: "password", + } + + token, err = tokens.Create(identityClient, authOptions).ExtractToken() + if err != nil { + panic(err) + } + +*/ package tokens diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go index 4a6a8a26d..2d20fa6f4 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go @@ -4,26 +4,28 @@ import "github.com/gophercloud/gophercloud" // Scope allows a created token to be limited to a specific domain or project. type Scope struct { - ProjectID string `json:"scope.project.id,omitempty" not:"ProjectName,DomainID,DomainName"` - ProjectName string `json:"scope.project.name,omitempty"` - DomainID string `json:"scope.project.id,omitempty" not:"ProjectName,ProjectID,DomainName"` - DomainName string `json:"scope.project.id,omitempty"` + ProjectID string + ProjectName string + DomainID string + DomainName string } -// AuthOptionsBuilder describes any argument that may be passed to the Create call. +// AuthOptionsBuilder provides the ability for extensions to add additional +// parameters to AuthOptions. Extensions must satisfy all required methods. type AuthOptionsBuilder interface { - // ToTokenV3CreateMap assembles the Create request body, returning an error if parameters are - // missing or inconsistent. + // ToTokenV3CreateMap assembles the Create request body, returning an error + // if parameters are missing or inconsistent. ToTokenV3CreateMap(map[string]interface{}) (map[string]interface{}, error) ToTokenV3ScopeMap() (map[string]interface{}, error) CanReauth() bool } +// AuthOptions represents options for authenticating a user. type AuthOptions struct { // IdentityEndpoint specifies the HTTP endpoint that is required to work with - // the Identity API of the appropriate version. While it's ultimately needed by - // all of the identity services, it will often be populated by a provider-level - // function. + // the Identity API of the appropriate version. While it's ultimately needed + // by all of the identity services, it will often be populated by a + // provider-level function. IdentityEndpoint string `json:"-"` // Username is required if using Identity V2 API. Consult with your provider's @@ -36,99 +38,58 @@ type AuthOptions struct { // At most one of DomainID and DomainName must be provided if using Username // with Identity V3. Otherwise, either are optional. - DomainID string `json:"id,omitempty"` + DomainID string `json:"-"` DomainName string `json:"name,omitempty"` - // AllowReauth should be set to true if you grant permission for Gophercloud to - // cache your credentials in memory, and to allow Gophercloud to attempt to - // re-authenticate automatically if/when your token expires. If you set it to - // false, it will not cache these settings, but re-authentication will not be - // possible. This setting defaults to false. + // AllowReauth should be set to true if you grant permission for Gophercloud + // to cache your credentials in memory, and to allow Gophercloud to attempt + // to re-authenticate automatically if/when your token expires. If you set + // it to false, it will not cache these settings, but re-authentication will + // not be possible. This setting defaults to false. AllowReauth bool `json:"-"` // TokenID allows users to authenticate (possibly as another user) with an // authentication token ID. TokenID string `json:"-"` + // Authentication through Application Credentials requires supplying name, project and secret + // For project we can use TenantID + ApplicationCredentialID string `json:"-"` + ApplicationCredentialName string `json:"-"` + ApplicationCredentialSecret string `json:"-"` + Scope Scope `json:"-"` } +// ToTokenV3CreateMap builds a request body from AuthOptions. func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) { gophercloudAuthOpts := gophercloud.AuthOptions{ - Username: opts.Username, - UserID: opts.UserID, - Password: opts.Password, - DomainID: opts.DomainID, - DomainName: opts.DomainName, - AllowReauth: opts.AllowReauth, - TokenID: opts.TokenID, + Username: opts.Username, + UserID: opts.UserID, + Password: opts.Password, + DomainID: opts.DomainID, + DomainName: opts.DomainName, + AllowReauth: opts.AllowReauth, + TokenID: opts.TokenID, + ApplicationCredentialID: opts.ApplicationCredentialID, + ApplicationCredentialName: opts.ApplicationCredentialName, + ApplicationCredentialSecret: opts.ApplicationCredentialSecret, } return gophercloudAuthOpts.ToTokenV3CreateMap(scope) } +// ToTokenV3CreateMap builds a scope request body from AuthOptions. func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) { - if opts.Scope.ProjectName != "" { - // ProjectName provided: either DomainID or DomainName must also be supplied. - // ProjectID may not be supplied. - if opts.Scope.DomainID == "" && opts.Scope.DomainName == "" { - return nil, gophercloud.ErrScopeDomainIDOrDomainName{} - } - if opts.Scope.ProjectID != "" { - return nil, gophercloud.ErrScopeProjectIDOrProjectName{} - } + scope := gophercloud.AuthScope(opts.Scope) - if opts.Scope.DomainID != "" { - // ProjectName + DomainID - return map[string]interface{}{ - "project": map[string]interface{}{ - "name": &opts.Scope.ProjectName, - "domain": map[string]interface{}{"id": &opts.Scope.DomainID}, - }, - }, nil - } - - if opts.Scope.DomainName != "" { - // ProjectName + DomainName - return map[string]interface{}{ - "project": map[string]interface{}{ - "name": &opts.Scope.ProjectName, - "domain": map[string]interface{}{"name": &opts.Scope.DomainName}, - }, - }, nil - } - } else if opts.Scope.ProjectID != "" { - // ProjectID provided. ProjectName, DomainID, and DomainName may not be provided. - if opts.Scope.DomainID != "" { - return nil, gophercloud.ErrScopeProjectIDAlone{} - } - if opts.Scope.DomainName != "" { - return nil, gophercloud.ErrScopeProjectIDAlone{} - } - - // ProjectID - return map[string]interface{}{ - "project": map[string]interface{}{ - "id": &opts.Scope.ProjectID, - }, - }, nil - } else if opts.Scope.DomainID != "" { - // DomainID provided. ProjectID, ProjectName, and DomainName may not be provided. - if opts.Scope.DomainName != "" { - return nil, gophercloud.ErrScopeDomainIDOrDomainName{} - } - - // DomainID - return map[string]interface{}{ - "domain": map[string]interface{}{ - "id": &opts.Scope.DomainID, - }, - }, nil - } else if opts.Scope.DomainName != "" { - return nil, gophercloud.ErrScopeDomainName{} + gophercloudAuthOpts := gophercloud.AuthOptions{ + Scope: &scope, + DomainID: opts.DomainID, + DomainName: opts.DomainName, } - return nil, nil + return gophercloudAuthOpts.ToTokenV3ScopeMap() } func (opts *AuthOptions) CanReauth() bool { @@ -141,7 +102,8 @@ func subjectTokenHeaders(c *gophercloud.ServiceClient, subjectToken string) map[ } } -// Create authenticates and either generates a new token, or changes the Scope of an existing token. +// Create authenticates and either generates a new token, or changes the Scope +// of an existing token. func Create(c *gophercloud.ServiceClient, opts AuthOptionsBuilder) (r CreateResult) { scope, err := opts.ToTokenV3ScopeMap() if err != nil { @@ -180,9 +142,9 @@ func Get(c *gophercloud.ServiceClient, token string) (r GetResult) { // Validate determines if a specified token is valid or not. func Validate(c *gophercloud.ServiceClient, token string) (bool, error) { - resp, err := c.Request("HEAD", tokenURL(c), &gophercloud.RequestOpts{ + resp, err := c.Head(tokenURL(c), &gophercloud.RequestOpts{ MoreHeaders: subjectTokenHeaders(c, token), - OkCodes: []int{200, 204, 400, 401, 403, 404}, + OkCodes: []int{200, 204, 404}, }) if err != nil { return false, err diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go index 0f1e8c2ba..6f26c96bc 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go @@ -13,35 +13,71 @@ import ( type Endpoint struct { ID string `json:"id"` Region string `json:"region"` + RegionID string `json:"region_id"` Interface string `json:"interface"` URL string `json:"url"` } -// CatalogEntry provides a type-safe interface to an Identity API V3 service catalog listing. -// Each class of service, such as cloud DNS or block storage services, could have multiple -// CatalogEntry representing it (one by interface type, e.g public, admin or internal). +// CatalogEntry provides a type-safe interface to an Identity API V3 service +// catalog listing. Each class of service, such as cloud DNS or block storage +// services, could have multiple CatalogEntry representing it (one by interface +// type, e.g public, admin or internal). // -// Note: when looking for the desired service, try, whenever possible, to key off the type field. -// Otherwise, you'll tie the representation of the service to a specific provider. +// Note: when looking for the desired service, try, whenever possible, to key +// off the type field. Otherwise, you'll tie the representation of the service +// to a specific provider. type CatalogEntry struct { // Service ID ID string `json:"id"` + // Name will contain the provider-specified name for the service. Name string `json:"name"` - // Type will contain a type string if OpenStack defines a type for the service. - // Otherwise, for provider-specific services, the provider may assign their own type strings. + + // Type will contain a type string if OpenStack defines a type for the + // service. Otherwise, for provider-specific services, the provider may + // assign their own type strings. Type string `json:"type"` - // Endpoints will let the caller iterate over all the different endpoints that may exist for - // the service. + + // Endpoints will let the caller iterate over all the different endpoints that + // may exist for the service. Endpoints []Endpoint `json:"endpoints"` } -// ServiceCatalog provides a view into the service catalog from a previous, successful authentication. +// ServiceCatalog provides a view into the service catalog from a previous, +// successful authentication. type ServiceCatalog struct { Entries []CatalogEntry `json:"catalog"` } -// commonResult is the deferred result of a Create or a Get call. +// Domain provides information about the domain to which this token grants +// access. +type Domain struct { + ID string `json:"id"` + Name string `json:"name"` +} + +// User represents a user resource that exists in the Identity Service. +type User struct { + Domain Domain `json:"domain"` + ID string `json:"id"` + Name string `json:"name"` +} + +// Role provides information about roles to which User is authorized. +type Role struct { + ID string `json:"id"` + Name string `json:"name"` +} + +// Project provides information about project to which User is authorized. +type Project struct { + Domain Domain `json:"domain"` + ID string `json:"id"` + Name string `json:"name"` +} + +// commonResult is the response from a request. A commonResult has various +// methods which can be used to extract different details about the result. type commonResult struct { gophercloud.Result } @@ -66,34 +102,73 @@ func (r commonResult) ExtractToken() (*Token, error) { return &s, err } -// ExtractServiceCatalog returns the ServiceCatalog that was generated along with the user's Token. -func (r CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) { +// ExtractTokenID implements the gophercloud.AuthResult interface. The returned +// string is the same as the ID field of the Token struct returned from +// ExtractToken(). +func (r CreateResult) ExtractTokenID() (string, error) { + return r.Header.Get("X-Subject-Token"), r.Err +} + +// ExtractServiceCatalog returns the ServiceCatalog that was generated along +// with the user's Token. +func (r commonResult) ExtractServiceCatalog() (*ServiceCatalog, error) { var s ServiceCatalog err := r.ExtractInto(&s) return &s, err } -// CreateResult defers the interpretation of a created token. -// Use ExtractToken() to interpret it as a Token, or ExtractServiceCatalog() to interpret it as a service catalog. +// ExtractUser returns the User that is the owner of the Token. +func (r commonResult) ExtractUser() (*User, error) { + var s struct { + User *User `json:"user"` + } + err := r.ExtractInto(&s) + return s.User, err +} + +// ExtractRoles returns Roles to which User is authorized. +func (r commonResult) ExtractRoles() ([]Role, error) { + var s struct { + Roles []Role `json:"roles"` + } + err := r.ExtractInto(&s) + return s.Roles, err +} + +// ExtractProject returns Project to which User is authorized. +func (r commonResult) ExtractProject() (*Project, error) { + var s struct { + Project *Project `json:"project"` + } + err := r.ExtractInto(&s) + return s.Project, err +} + +// CreateResult is the response from a Create request. Use ExtractToken() +// to interpret it as a Token, or ExtractServiceCatalog() to interpret it +// as a service catalog. type CreateResult struct { commonResult } -// GetResult is the deferred response from a Get call. +// GetResult is the response from a Get request. Use ExtractToken() +// to interpret it as a Token, or ExtractServiceCatalog() to interpret it +// as a service catalog. type GetResult struct { commonResult } -// RevokeResult is the deferred response from a Revoke call. +// RevokeResult is response from a Revoke request. type RevokeResult struct { commonResult } -// Token is a string that grants a user access to a controlled set of services in an OpenStack provider. -// Each Token is valid for a set length of time. +// Token is a string that grants a user access to a controlled set of services +// in an OpenStack provider. Each Token is valid for a set length of time. type Token struct { // ID is the issued token. ID string `json:"id"` + // ExpiresAt is the timestamp at which this token will no longer be accepted. ExpiresAt time.Time `json:"expires_at"` } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/doc.go new file mode 100644 index 000000000..994ce71bc --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/doc.go @@ -0,0 +1,172 @@ +/* +Package users manages and retrieves Users in the OpenStack Identity Service. + +Example to List Users + + listOpts := users.ListOpts{ + DomainID: "default", + } + + allPages, err := users.List(identityClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allUsers, err := users.ExtractUsers(allPages) + if err != nil { + panic(err) + } + + for _, user := range allUsers { + fmt.Printf("%+v\n", user) + } + +Example to Create a User + + projectID := "a99e9b4e620e4db09a2dfb6e42a01e66" + + createOpts := users.CreateOpts{ + Name: "username", + DomainID: "default", + DefaultProjectID: projectID, + Enabled: gophercloud.Enabled, + Password: "supersecret", + Extra: map[string]interface{}{ + "email": "username@example.com", + } + } + + user, err := users.Create(identityClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a User + + userID := "0fe36e73809d46aeae6705c39077b1b3" + + updateOpts := users.UpdateOpts{ + Enabled: gophercloud.Disabled, + } + + user, err := users.Update(identityClient, userID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Change Password of a User + + userID := "0fe36e73809d46aeae6705c39077b1b3" + originalPassword := "secretsecret" + password := "new_secretsecret" + + changePasswordOpts := users.ChangePasswordOpts{ + OriginalPassword: originalPassword, + Password: password, + } + + err := users.ChangePassword(identityClient, userID, changePasswordOpts).ExtractErr() + if err != nil { + panic(err) + } + +Example to Delete a User + + userID := "0fe36e73809d46aeae6705c39077b1b3" + err := users.Delete(identityClient, userID).ExtractErr() + if err != nil { + panic(err) + } + +Example to List Groups a User Belongs To + + userID := "0fe36e73809d46aeae6705c39077b1b3" + + allPages, err := users.ListGroups(identityClient, userID).AllPages() + if err != nil { + panic(err) + } + + allGroups, err := groups.ExtractGroups(allPages) + if err != nil { + panic(err) + } + + for _, group := range allGroups { + fmt.Printf("%+v\n", group) + } + +Example to Add a User to a Group + + groupID := "bede500ee1124ae9b0006ff859758b3a" + userID := "0fe36e73809d46aeae6705c39077b1b3" + err := users.AddToGroup(identityClient, groupID, userID).ExtractErr() + + if err != nil { + panic(err) + } + +Example to Check Whether a User Belongs to a Group + + groupID := "bede500ee1124ae9b0006ff859758b3a" + userID := "0fe36e73809d46aeae6705c39077b1b3" + ok, err := users.IsMemberOfGroup(identityClient, groupID, userID).Extract() + if err != nil { + panic(err) + } + + if ok { + fmt.Printf("user %s is a member of group %s\n", userID, groupID) + } + +Example to Remove a User from a Group + + groupID := "bede500ee1124ae9b0006ff859758b3a" + userID := "0fe36e73809d46aeae6705c39077b1b3" + err := users.RemoveFromGroup(identityClient, groupID, userID).ExtractErr() + + if err != nil { + panic(err) + } + +Example to List Projects a User Belongs To + + userID := "0fe36e73809d46aeae6705c39077b1b3" + + allPages, err := users.ListProjects(identityClient, userID).AllPages() + if err != nil { + panic(err) + } + + allProjects, err := projects.ExtractProjects(allPages) + if err != nil { + panic(err) + } + + for _, project := range allProjects { + fmt.Printf("%+v\n", project) + } + +Example to List Users in a Group + + groupID := "bede500ee1124ae9b0006ff859758b3a" + listOpts := users.ListOpts{ + DomainID: "default", + } + + allPages, err := users.ListInGroup(identityClient, groupID, listOpts).AllPages() + if err != nil { + panic(err) + } + + allUsers, err := users.ExtractUsers(allPages) + if err != nil { + panic(err) + } + + for _, user := range allUsers { + fmt.Printf("%+v\n", user) + } + +*/ +package users diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/errors.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/errors.go new file mode 100644 index 000000000..0f0b79875 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/errors.go @@ -0,0 +1,17 @@ +package users + +import "fmt" + +// InvalidListFilter is returned by the ToUserListQuery method when validation of +// a filter does not pass +type InvalidListFilter struct { + FilterName string +} + +func (e InvalidListFilter) Error() string { + s := fmt.Sprintf( + "Invalid filter name [%s]: it must be in format of NAME__COMPARATOR", + e.FilterName, + ) + return s +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/requests.go new file mode 100644 index 000000000..7a40ec762 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/requests.go @@ -0,0 +1,338 @@ +package users + +import ( + "net/http" + "net/url" + "strings" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/identity/v3/groups" + "github.com/gophercloud/gophercloud/openstack/identity/v3/projects" + "github.com/gophercloud/gophercloud/pagination" +) + +// Option is a specific option defined at the API to enable features +// on a user account. +type Option string + +const ( + IgnoreChangePasswordUponFirstUse Option = "ignore_change_password_upon_first_use" + IgnorePasswordExpiry Option = "ignore_password_expiry" + IgnoreLockoutFailureAttempts Option = "ignore_lockout_failure_attempts" + MultiFactorAuthRules Option = "multi_factor_auth_rules" + MultiFactorAuthEnabled Option = "multi_factor_auth_enabled" +) + +// ListOptsBuilder allows extensions to add additional parameters to +// the List request +type ListOptsBuilder interface { + ToUserListQuery() (string, error) +} + +// ListOpts provides options to filter the List results. +type ListOpts struct { + // DomainID filters the response by a domain ID. + DomainID string `q:"domain_id"` + + // Enabled filters the response by enabled users. + Enabled *bool `q:"enabled"` + + // IdpID filters the response by an Identity Provider ID. + IdPID string `q:"idp_id"` + + // Name filters the response by username. + Name string `q:"name"` + + // PasswordExpiresAt filters the response based on expiring passwords. + PasswordExpiresAt string `q:"password_expires_at"` + + // ProtocolID filters the response by protocol ID. + ProtocolID string `q:"protocol_id"` + + // UniqueID filters the response by unique ID. + UniqueID string `q:"unique_id"` + + // Filters filters the response by custom filters such as + // 'name__contains=foo' + Filters map[string]string `q:"-"` +} + +// ToUserListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToUserListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return "", err + } + + params := q.Query() + for k, v := range opts.Filters { + i := strings.Index(k, "__") + if i > 0 && i < len(k)-2 { + params.Add(k, v) + } else { + return "", InvalidListFilter{FilterName: k} + } + } + + q = &url.URL{RawQuery: params.Encode()} + return q.String(), err +} + +// List enumerates the Users to which the current token has access. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToUserListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return UserPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves details on a single user, by ID. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + return +} + +// CreateOptsBuilder allows extensions to add additional parameters to +// the Create request. +type CreateOptsBuilder interface { + ToUserCreateMap() (map[string]interface{}, error) +} + +// CreateOpts provides options used to create a user. +type CreateOpts struct { + // Name is the name of the new user. + Name string `json:"name" required:"true"` + + // DefaultProjectID is the ID of the default project of the user. + DefaultProjectID string `json:"default_project_id,omitempty"` + + // Description is a description of the user. + Description string `json:"description,omitempty"` + + // DomainID is the ID of the domain the user belongs to. + DomainID string `json:"domain_id,omitempty"` + + // Enabled sets the user status to enabled or disabled. + Enabled *bool `json:"enabled,omitempty"` + + // Extra is free-form extra key/value pairs to describe the user. + Extra map[string]interface{} `json:"-"` + + // Options are defined options in the API to enable certain features. + Options map[Option]interface{} `json:"options,omitempty"` + + // Password is the password of the new user. + Password string `json:"password,omitempty"` +} + +// ToUserCreateMap formats a CreateOpts into a create request. +func (opts CreateOpts) ToUserCreateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "user") + if err != nil { + return nil, err + } + + if opts.Extra != nil { + if v, ok := b["user"].(map[string]interface{}); ok { + for key, value := range opts.Extra { + v[key] = value + } + } + } + + return b, nil +} + +// Create creates a new User. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToUserCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to +// the Update request. +type UpdateOptsBuilder interface { + ToUserUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts provides options for updating a user account. +type UpdateOpts struct { + // Name is the name of the new user. + Name string `json:"name,omitempty"` + + // DefaultProjectID is the ID of the default project of the user. + DefaultProjectID string `json:"default_project_id,omitempty"` + + // Description is a description of the user. + Description *string `json:"description,omitempty"` + + // DomainID is the ID of the domain the user belongs to. + DomainID string `json:"domain_id,omitempty"` + + // Enabled sets the user status to enabled or disabled. + Enabled *bool `json:"enabled,omitempty"` + + // Extra is free-form extra key/value pairs to describe the user. + Extra map[string]interface{} `json:"-"` + + // Options are defined options in the API to enable certain features. + Options map[Option]interface{} `json:"options,omitempty"` + + // Password is the password of the new user. + Password string `json:"password,omitempty"` +} + +// ToUserUpdateMap formats a UpdateOpts into an update request. +func (opts UpdateOpts) ToUserUpdateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "user") + if err != nil { + return nil, err + } + + if opts.Extra != nil { + if v, ok := b["user"].(map[string]interface{}); ok { + for key, value := range opts.Extra { + v[key] = value + } + } + } + + return b, nil +} + +// Update updates an existing User. +func Update(client *gophercloud.ServiceClient, userID string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToUserUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Patch(updateURL(client, userID), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// ChangePasswordOptsBuilder allows extensions to add additional parameters to +// the ChangePassword request. +type ChangePasswordOptsBuilder interface { + ToUserChangePasswordMap() (map[string]interface{}, error) +} + +// ChangePasswordOpts provides options for changing password for a user. +type ChangePasswordOpts struct { + // OriginalPassword is the original password of the user. + OriginalPassword string `json:"original_password"` + + // Password is the new password of the user. + Password string `json:"password"` +} + +// ToUserChangePasswordMap formats a ChangePasswordOpts into a ChangePassword request. +func (opts ChangePasswordOpts) ToUserChangePasswordMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "user") + if err != nil { + return nil, err + } + + return b, nil +} + +// ChangePassword changes password for a user. +func ChangePassword(client *gophercloud.ServiceClient, userID string, opts ChangePasswordOptsBuilder) (r ChangePasswordResult) { + b, err := opts.ToUserChangePasswordMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Post(changePasswordURL(client, userID), &b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return +} + +// Delete deletes a user. +func Delete(client *gophercloud.ServiceClient, userID string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, userID), nil) + return +} + +// ListGroups enumerates groups user belongs to. +func ListGroups(client *gophercloud.ServiceClient, userID string) pagination.Pager { + url := listGroupsURL(client, userID) + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return groups.GroupPage{LinkedPageBase: pagination.LinkedPageBase{PageResult: r}} + }) +} + +// AddToGroup adds a user to a group. +func AddToGroup(client *gophercloud.ServiceClient, groupID, userID string) (r AddToGroupResult) { + url := addToGroupURL(client, groupID, userID) + _, r.Err = client.Put(url, nil, nil, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return +} + +// IsMemberOfGroup checks whether a user belongs to a group. +func IsMemberOfGroup(client *gophercloud.ServiceClient, groupID, userID string) (r IsMemberOfGroupResult) { + url := isMemberOfGroupURL(client, groupID, userID) + var response *http.Response + response, r.Err = client.Head(url, &gophercloud.RequestOpts{ + OkCodes: []int{204, 404}, + }) + if r.Err == nil && response != nil { + if (*response).StatusCode == 204 { + r.isMember = true + } + } + + return +} + +// RemoveFromGroup removes a user from a group. +func RemoveFromGroup(client *gophercloud.ServiceClient, groupID, userID string) (r RemoveFromGroupResult) { + url := removeFromGroupURL(client, groupID, userID) + _, r.Err = client.Delete(url, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return +} + +// ListProjects enumerates groups user belongs to. +func ListProjects(client *gophercloud.ServiceClient, userID string) pagination.Pager { + url := listProjectsURL(client, userID) + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return projects.ProjectPage{LinkedPageBase: pagination.LinkedPageBase{PageResult: r}} + }) +} + +// ListInGroup enumerates users that belong to a group. +func ListInGroup(client *gophercloud.ServiceClient, groupID string, opts ListOptsBuilder) pagination.Pager { + url := listInGroupURL(client, groupID) + if opts != nil { + query, err := opts.ToUserListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return UserPage{pagination.LinkedPageBase{PageResult: r}} + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/results.go new file mode 100644 index 000000000..e158ac723 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/results.go @@ -0,0 +1,179 @@ +package users + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/internal" + "github.com/gophercloud/gophercloud/pagination" +) + +// User represents a User in the OpenStack Identity Service. +type User struct { + // DefaultProjectID is the ID of the default project of the user. + DefaultProjectID string `json:"default_project_id"` + + // Description is the description of the user. + Description string `json:"description"` + + // DomainID is the domain ID the user belongs to. + DomainID string `json:"domain_id"` + + // Enabled is whether or not the user is enabled. + Enabled bool `json:"enabled"` + + // Extra is a collection of miscellaneous key/values. + Extra map[string]interface{} `json:"-"` + + // ID is the unique ID of the user. + ID string `json:"id"` + + // Links contains referencing links to the user. + Links map[string]interface{} `json:"links"` + + // Name is the name of the user. + Name string `json:"name"` + + // Options are a set of defined options of the user. + Options map[string]interface{} `json:"options"` + + // PasswordExpiresAt is the timestamp when the user's password expires. + PasswordExpiresAt time.Time `json:"-"` +} + +func (r *User) UnmarshalJSON(b []byte) error { + type tmp User + var s struct { + tmp + Extra map[string]interface{} `json:"extra"` + PasswordExpiresAt gophercloud.JSONRFC3339MilliNoZ `json:"password_expires_at"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = User(s.tmp) + + r.PasswordExpiresAt = time.Time(s.PasswordExpiresAt) + + // Collect other fields and bundle them into Extra + // but only if a field titled "extra" wasn't sent. + if s.Extra != nil { + r.Extra = s.Extra + } else { + var result interface{} + err := json.Unmarshal(b, &result) + if err != nil { + return err + } + if resultMap, ok := result.(map[string]interface{}); ok { + delete(resultMap, "password_expires_at") + r.Extra = internal.RemainingKeys(User{}, resultMap) + } + } + + return err +} + +type userResult struct { + gophercloud.Result +} + +// GetResult is the response from a Get operation. Call its Extract method +// to interpret it as a User. +type GetResult struct { + userResult +} + +// CreateResult is the response from a Create operation. Call its Extract method +// to interpret it as a User. +type CreateResult struct { + userResult +} + +// UpdateResult is the response from an Update operation. Call its Extract +// method to interpret it as a User. +type UpdateResult struct { + userResult +} + +// ChangePasswordResult is the response from a ChangePassword operation. Call its +// ExtractErr method to determine if the request succeeded or failed. +type ChangePasswordResult struct { + gophercloud.ErrResult +} + +// DeleteResult is the response from a Delete operation. Call its ExtractErr to +// determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// AddToGroupResult is the response from a AddToGroup operation. Call its +// ExtractErr method to determine if the request succeeded or failed. +type AddToGroupResult struct { + gophercloud.ErrResult +} + +// IsMemberOfGroupResult is the response from a IsMemberOfGroup operation. Call its +// Extract method to determine if the request succeeded or failed. +type IsMemberOfGroupResult struct { + isMember bool + gophercloud.Result +} + +// RemoveFromGroupResult is the response from a RemoveFromGroup operation. Call its +// ExtractErr method to determine if the request succeeded or failed. +type RemoveFromGroupResult struct { + gophercloud.ErrResult +} + +// UserPage is a single page of User results. +type UserPage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines whether or not a UserPage contains any results. +func (r UserPage) IsEmpty() (bool, error) { + users, err := ExtractUsers(r) + return len(users) == 0, err +} + +// NextPageURL extracts the "next" link from the links section of the result. +func (r UserPage) NextPageURL() (string, error) { + var s struct { + Links struct { + Next string `json:"next"` + Previous string `json:"previous"` + } `json:"links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return s.Links.Next, err +} + +// ExtractUsers returns a slice of Users contained in a single page of results. +func ExtractUsers(r pagination.Page) ([]User, error) { + var s struct { + Users []User `json:"users"` + } + err := (r.(UserPage)).ExtractInto(&s) + return s.Users, err +} + +// Extract interprets any user results as a User. +func (r userResult) Extract() (*User, error) { + var s struct { + User *User `json:"user"` + } + err := r.ExtractInto(&s) + return s.User, err +} + +// Extract extracts IsMemberOfGroupResult as bool and error values +func (r IsMemberOfGroupResult) Extract() (bool, error) { + return r.isMember, r.Err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/urls.go new file mode 100644 index 000000000..3caa8bbb6 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/urls.go @@ -0,0 +1,51 @@ +package users + +import "github.com/gophercloud/gophercloud" + +func listURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("users") +} + +func getURL(client *gophercloud.ServiceClient, userID string) string { + return client.ServiceURL("users", userID) +} + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("users") +} + +func updateURL(client *gophercloud.ServiceClient, userID string) string { + return client.ServiceURL("users", userID) +} + +func changePasswordURL(client *gophercloud.ServiceClient, userID string) string { + return client.ServiceURL("users", userID, "password") +} + +func deleteURL(client *gophercloud.ServiceClient, userID string) string { + return client.ServiceURL("users", userID) +} + +func listGroupsURL(client *gophercloud.ServiceClient, userID string) string { + return client.ServiceURL("users", userID, "groups") +} + +func addToGroupURL(client *gophercloud.ServiceClient, groupID, userID string) string { + return client.ServiceURL("groups", groupID, "users", userID) +} + +func isMemberOfGroupURL(client *gophercloud.ServiceClient, groupID, userID string) string { + return client.ServiceURL("groups", groupID, "users", userID) +} + +func removeFromGroupURL(client *gophercloud.ServiceClient, groupID, userID string) string { + return client.ServiceURL("groups", groupID, "users", userID) +} + +func listProjectsURL(client *gophercloud.ServiceClient, userID string) string { + return client.ServiceURL("users", userID, "projects") +} + +func listInGroupURL(client *gophercloud.ServiceClient, groupID string) string { + return client.ServiceURL("groups", groupID, "users") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/doc.go new file mode 100644 index 000000000..0c12bf2e0 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/doc.go @@ -0,0 +1,48 @@ +/* +Package imagedata enables management of image data. + +Example to Upload Image Data + + imageID := "da3b75d9-3f4a-40e7-8a2c-bfab23927dea" + + imageData, err := os.Open("/path/to/image/file") + if err != nil { + panic(err) + } + defer imageData.Close() + + err = imagedata.Upload(imageClient, imageID, imageData).ExtractErr() + if err != nil { + panic(err) + } + +Example to Stage Image Data + + imageID := "da3b75d9-3f4a-40e7-8a2c-bfab23927dea" + + imageData, err := os.Open("/path/to/image/file") + if err != nil { + panic(err) + } + defer imageData.Close() + + err = imagedata.Stage(imageClient, imageID, imageData).ExtractErr() + if err != nil { + panic(err) + } + +Example to Download Image Data + + imageID := "da3b75d9-3f4a-40e7-8a2c-bfab23927dea" + + image, err := imagedata.Download(imageClient, imageID).Extract() + if err != nil { + panic(err) + } + + imageData, err := ioutil.ReadAll(image) + if err != nil { + panic(err) + } +*/ +package imagedata diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/requests.go index 7fd6951d3..545c561f3 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/requests.go @@ -7,7 +7,7 @@ import ( "github.com/gophercloud/gophercloud" ) -// Upload uploads image file +// Upload uploads an image file. func Upload(client *gophercloud.ServiceClient, id string, data io.Reader) (r UploadResult) { _, r.Err = client.Put(uploadURL(client, id), data, nil, &gophercloud.RequestOpts{ MoreHeaders: map[string]string{"Content-Type": "application/octet-stream"}, @@ -16,7 +16,18 @@ func Upload(client *gophercloud.ServiceClient, id string, data io.Reader) (r Upl return } -// Download retrieves file +// Stage performs PUT call on the existing image object in the Imageservice with +// the provided file. +// Existing image object must be in the "queued" status. +func Stage(client *gophercloud.ServiceClient, id string, data io.Reader) (r StageResult) { + _, r.Err = client.Put(stageURL(client, id), data, nil, &gophercloud.RequestOpts{ + MoreHeaders: map[string]string{"Content-Type": "application/octet-stream"}, + OkCodes: []int{204}, + }) + return +} + +// Download retrieves an image. func Download(client *gophercloud.ServiceClient, id string) (r DownloadResult) { var resp *http.Response resp, r.Err = client.Get(downloadURL(client, id), nil, nil) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/results.go index 970b226f2..17a3f0e0f 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/results.go @@ -7,12 +7,20 @@ import ( "github.com/gophercloud/gophercloud" ) -// UploadResult is the result of an upload image operation +// UploadResult is the result of an upload image operation. Call its ExtractErr +// method to determine if the request succeeded or failed. type UploadResult struct { gophercloud.ErrResult } -// DownloadResult is the result of a download image operation +// StageResult is the result of a stage image operation. Call its ExtractErr +// method to determine if the request succeeded or failed. +type StageResult struct { + gophercloud.ErrResult +} + +// DownloadResult is the result of a download image operation. Call its Extract +// method to gain access to the image data. type DownloadResult struct { gophercloud.Result } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/urls.go index ccd6416e5..d9615ba7f 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/urls.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/urls.go @@ -2,10 +2,20 @@ package imagedata import "github.com/gophercloud/gophercloud" +const ( + rootPath = "images" + uploadPath = "file" + stagePath = "stage" +) + // `imageDataURL(c,i)` is the URL for the binary image data for the // image identified by ID `i` in the service `c`. func uploadURL(c *gophercloud.ServiceClient, imageID string) string { - return c.ServiceURL("images", imageID, "file") + return c.ServiceURL(rootPath, imageID, uploadPath) +} + +func stageURL(c *gophercloud.ServiceClient, imageID string) string { + return c.ServiceURL(rootPath, imageID, stagePath) } func downloadURL(c *gophercloud.ServiceClient, imageID string) string { diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/doc.go new file mode 100644 index 000000000..14da9ac90 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/doc.go @@ -0,0 +1,60 @@ +/* +Package images enables management and retrieval of images from the OpenStack +Image Service. + +Example to List Images + + images.ListOpts{ + Owner: "a7509e1ae65945fda83f3e52c6296017", + } + + allPages, err := images.List(imagesClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allImages, err := images.ExtractImages(allPages) + if err != nil { + panic(err) + } + + for _, image := range allImages { + fmt.Printf("%+v\n", image) + } + +Example to Create an Image + + createOpts := images.CreateOpts{ + Name: "image_name", + Visibility: images.ImageVisibilityPrivate, + } + + image, err := images.Create(imageClient, createOpts) + if err != nil { + panic(err) + } + +Example to Update an Image + + imageID := "1bea47ed-f6a9-463b-b423-14b9cca9ad27" + + updateOpts := images.UpdateOpts{ + images.ReplaceImageName{ + NewName: "new_name", + }, + } + + image, err := images.Update(imageClient, imageID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete an Image + + imageID := "1bea47ed-f6a9-463b-b423-14b9cca9ad27" + err := images.Delete(imageClient, imageID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package images diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/requests.go index 044b5cb95..4e487ea9e 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/requests.go @@ -1,6 +1,10 @@ package images import ( + "fmt" + "net/url" + "time" + "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/pagination" ) @@ -15,33 +19,106 @@ type ListOptsBuilder interface { // the API. Filtering is achieved by passing in struct field values that map to // the server attributes you want to see returned. Marker and Limit are used // for pagination. -//http://developer.openstack.org/api-ref-image-v2.html +// +// http://developer.openstack.org/api-ref-image-v2.html type ListOpts struct { + // ID is the ID of the image. + // Multiple IDs can be specified by constructing a string + // such as "in:uuid1,uuid2,uuid3". + ID string `q:"id"` + // Integer value for the limit of values to return. Limit int `q:"limit"` // UUID of the server at which you want to set a marker. Marker string `q:"marker"` - Name string `q:"name"` - Visibility ImageVisibility `q:"visibility"` + // Name filters on the name of the image. + // Multiple names can be specified by constructing a string + // such as "in:name1,name2,name3". + Name string `q:"name"` + + // Visibility filters on the visibility of the image. + Visibility ImageVisibility `q:"visibility"` + + // MemberStatus filters on the member status of the image. MemberStatus ImageMemberStatus `q:"member_status"` - Owner string `q:"owner"` - Status ImageStatus `q:"status"` - SizeMin int64 `q:"size_min"` - SizeMax int64 `q:"size_max"` - SortKey string `q:"sort_key"` - SortDir string `q:"sort_dir"` - Tag string `q:"tag"` + + // Owner filters on the project ID of the image. + Owner string `q:"owner"` + + // Status filters on the status of the image. + // Multiple statuses can be specified by constructing a string + // such as "in:saving,queued". + Status ImageStatus `q:"status"` + + // SizeMin filters on the size_min image property. + SizeMin int64 `q:"size_min"` + + // SizeMax filters on the size_max image property. + SizeMax int64 `q:"size_max"` + + // Sort sorts the results using the new style of sorting. See the OpenStack + // Image API reference for the exact syntax. + // + // Sort cannot be used with the classic sort options (sort_key and sort_dir). + Sort string `q:"sort"` + + // SortKey will sort the results based on a specified image property. + SortKey string `q:"sort_key"` + + // SortDir will sort the list results either ascending or decending. + SortDir string `q:"sort_dir"` + + // Tags filters on specific image tags. + Tags []string `q:"tag"` + + // CreatedAtQuery filters images based on their creation date. + CreatedAtQuery *ImageDateQuery + + // UpdatedAtQuery filters images based on their updated date. + UpdatedAtQuery *ImageDateQuery + + // ContainerFormat filters images based on the container_format. + // Multiple container formats can be specified by constructing a + // string such as "in:bare,ami". + ContainerFormat string `q:"container_format"` + + // DiskFormat filters images based on the disk_format. + // Multiple disk formats can be specified by constructing a string + // such as "in:qcow2,iso". + DiskFormat string `q:"disk_format"` } // ToImageListQuery formats a ListOpts into a query string. func (opts ListOpts) ToImageListQuery() (string, error) { q, err := gophercloud.BuildQueryString(opts) + params := q.Query() + + if opts.CreatedAtQuery != nil { + createdAt := opts.CreatedAtQuery.Date.Format(time.RFC3339) + if v := opts.CreatedAtQuery.Filter; v != "" { + createdAt = fmt.Sprintf("%s:%s", v, createdAt) + } + + params.Add("created_at", createdAt) + } + + if opts.UpdatedAtQuery != nil { + updatedAt := opts.UpdatedAtQuery.Date.Format(time.RFC3339) + if v := opts.UpdatedAtQuery.Filter; v != "" { + updatedAt = fmt.Sprintf("%s:%s", v, updatedAt) + } + + params.Add("updated_at", updatedAt) + } + + q = &url.URL{RawQuery: params.Encode()} + return q.String(), err } -// List implements image list request +// List implements image list request. func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { url := listURL(c) if opts != nil { @@ -52,18 +129,22 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { url += query } return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { - return ImagePage{pagination.LinkedPageBase{PageResult: r}} + imagePage := ImagePage{ + serviceURL: c.ServiceURL(), + LinkedPageBase: pagination.LinkedPageBase{PageResult: r}, + } + + return imagePage }) } -// CreateOptsBuilder describes struct types that can be accepted by the Create call. -// The CreateOpts struct in this package does. +// CreateOptsBuilder allows extensions to add parameters to the Create request. type CreateOptsBuilder interface { // Returns value that can be passed to json.Marshal ToImageCreateMap() (map[string]interface{}, error) } -// CreateOpts implements CreateOptsBuilder +// CreateOpts represents options used to create an image. type CreateOpts struct { // Name is the name of the new image. Name string `json:"name" required:"true"` @@ -118,7 +199,7 @@ func (opts CreateOpts) ToImageCreateMap() (map[string]interface{}, error) { return b, nil } -// Create implements create image request +// Create implements create image request. func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { b, err := opts.ToImageCreateMap() if err != nil { @@ -129,19 +210,19 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create return } -// Delete implements image delete request +// Delete implements image delete request. func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { _, r.Err = client.Delete(deleteURL(client, id), nil) return } -// Get implements image get request +// Get implements image get request. func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { _, r.Err = client.Get(getURL(client, id), &r.Body, nil) return } -// Update implements image updated request +// Update implements image updated request. func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { b, err := opts.ToImageUpdateMap() if err != nil { @@ -155,9 +236,11 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder return } -// UpdateOptsBuilder implements UpdateOptsBuilder +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. type UpdateOptsBuilder interface { - // returns value implementing json.Marshaler which when marshaled matches the patch schema: + // returns value implementing json.Marshaler which when marshaled matches + // the patch schema: // http://specs.openstack.org/openstack/glance-specs/specs/api/v2/http-patch-image-api-v2.html ToImageUpdateMap() ([]interface{}, error) } @@ -165,7 +248,8 @@ type UpdateOptsBuilder interface { // UpdateOpts implements UpdateOpts type UpdateOpts []Patch -// ToImageUpdateMap builder +// ToImageUpdateMap assembles a request body based on the contents of +// UpdateOpts. func (opts UpdateOpts) ToImageUpdateMap() ([]interface{}, error) { m := make([]interface{}, len(opts)) for i, patch := range opts { @@ -175,32 +259,32 @@ func (opts UpdateOpts) ToImageUpdateMap() ([]interface{}, error) { return m, nil } -// Patch represents a single update to an existing image. Multiple updates to an image can be -// submitted at the same time. +// Patch represents a single update to an existing image. Multiple updates +// to an image can be submitted at the same time. type Patch interface { ToImagePatchMap() map[string]interface{} } -// UpdateVisibility updated visibility +// UpdateVisibility represents an updated visibility property request. type UpdateVisibility struct { Visibility ImageVisibility } -// ToImagePatchMap builder -func (u UpdateVisibility) ToImagePatchMap() map[string]interface{} { +// ToImagePatchMap assembles a request body based on UpdateVisibility. +func (r UpdateVisibility) ToImagePatchMap() map[string]interface{} { return map[string]interface{}{ "op": "replace", "path": "/visibility", - "value": u.Visibility, + "value": r.Visibility, } } -// ReplaceImageName implements Patch +// ReplaceImageName represents an updated image_name property request. type ReplaceImageName struct { NewName string } -// ToImagePatchMap builder +// ToImagePatchMap assembles a request body based on ReplaceImageName. func (r ReplaceImageName) ToImagePatchMap() map[string]interface{} { return map[string]interface{}{ "op": "replace", @@ -209,26 +293,26 @@ func (r ReplaceImageName) ToImagePatchMap() map[string]interface{} { } } -// ReplaceImageChecksum implements Patch +// ReplaceImageChecksum represents an updated checksum property request. type ReplaceImageChecksum struct { Checksum string } -// ReplaceImageChecksum builder -func (rc ReplaceImageChecksum) ToImagePatchMap() map[string]interface{} { +// ReplaceImageChecksum assembles a request body based on ReplaceImageChecksum. +func (r ReplaceImageChecksum) ToImagePatchMap() map[string]interface{} { return map[string]interface{}{ "op": "replace", "path": "/checksum", - "value": rc.Checksum, + "value": r.Checksum, } } -// ReplaceImageTags implements Patch +// ReplaceImageTags represents an updated tags property request. type ReplaceImageTags struct { NewTags []string } -// ToImagePatchMap builder +// ToImagePatchMap assembles a request body based on ReplaceImageTags. func (r ReplaceImageTags) ToImagePatchMap() map[string]interface{} { return map[string]interface{}{ "op": "replace", @@ -236,3 +320,47 @@ func (r ReplaceImageTags) ToImagePatchMap() map[string]interface{} { "value": r.NewTags, } } + +// ReplaceImageMinDisk represents an updated min_disk property request. +type ReplaceImageMinDisk struct { + NewMinDisk int +} + +// ToImagePatchMap assembles a request body based on ReplaceImageTags. +func (r ReplaceImageMinDisk) ToImagePatchMap() map[string]interface{} { + return map[string]interface{}{ + "op": "replace", + "path": "/min_disk", + "value": r.NewMinDisk, + } +} + +// UpdateOp represents a valid update operation. +type UpdateOp string + +const ( + AddOp UpdateOp = "add" + ReplaceOp UpdateOp = "replace" + RemoveOp UpdateOp = "remove" +) + +// UpdateImageProperty represents an update property request. +type UpdateImageProperty struct { + Op UpdateOp + Name string + Value string +} + +// ToImagePatchMap assembles a request body based on UpdateImageProperty. +func (r UpdateImageProperty) ToImagePatchMap() map[string]interface{} { + updateMap := map[string]interface{}{ + "op": r.Op, + "path": fmt.Sprintf("/%s", r.Name), + } + + if r.Value != "" { + updateMap["value"] = r.Value + } + + return updateMap +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/results.go index 09996f4af..676181e1f 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/results.go @@ -7,14 +7,13 @@ import ( "time" "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/internal" "github.com/gophercloud/gophercloud/pagination" ) -// Image model -// Does not include the literal image data; just metadata. -// returned by listing images, and by fetching a specific image. +// Image represents an image found in the OpenStack Image service. type Image struct { - // ID is the image UUID + // ID is the image UUID. ID string `json:"id"` // Name is the human-readable display name for the image. @@ -33,16 +32,19 @@ type Image struct { ContainerFormat string `json:"container_format"` // DiskFormat is the format of the disk. - // If set, valid values are ami, ari, aki, vhd, vmdk, raw, qcow2, vdi, and iso. + // If set, valid values are ami, ari, aki, vhd, vmdk, raw, qcow2, vdi, + // and iso. DiskFormat string `json:"disk_format"` - // MinDiskGigabytes is the amount of disk space in GB that is required to boot the image. + // MinDiskGigabytes is the amount of disk space in GB that is required to + // boot the image. MinDiskGigabytes int `json:"min_disk"` - // MinRAMMegabytes [optional] is the amount of RAM in MB that is required to boot the image. + // MinRAMMegabytes [optional] is the amount of RAM in MB that is required to + // boot the image. MinRAMMegabytes int `json:"min_ram"` - // Owner is the tenant the image belongs to. + // Owner is the tenant ID the image belongs to. Owner string `json:"owner"` // Protected is whether the image is deletable or not. @@ -51,32 +53,39 @@ type Image struct { // Visibility defines who can see/use the image. Visibility ImageVisibility `json:"visibility"` - // Checksum is the checksum of the data that's associated with the image + // Checksum is the checksum of the data that's associated with the image. Checksum string `json:"checksum"` // SizeBytes is the size of the data that's associated with the image. - SizeBytes int64 `json:"size"` + SizeBytes int64 `json:"-"` // Metadata is a set of metadata associated with the image. // Image metadata allow for meaningfully define the image properties - // and tags. See http://docs.openstack.org/developer/glance/metadefs-concepts.html. + // and tags. + // See http://docs.openstack.org/developer/glance/metadefs-concepts.html. Metadata map[string]string `json:"metadata"` - // Properties is a set of key-value pairs, if any, that are associated with the image. - Properties map[string]string `json:"properties"` + // Properties is a set of key-value pairs, if any, that are associated with + // the image. + Properties map[string]interface{} // CreatedAt is the date when the image has been created. CreatedAt time.Time `json:"created_at"` - // UpdatedAt is the date when the last change has been made to the image or it's properties. + // UpdatedAt is the date when the last change has been made to the image or + // it's properties. UpdatedAt time.Time `json:"updated_at"` - // File is the trailing path after the glance endpoint that represent the location - // of the image or the path to retrieve it. + // File is the trailing path after the glance endpoint that represent the + // location of the image or the path to retrieve it. File string `json:"file"` - // Schema is the path to the JSON-schema that represent the image or image entity. + // Schema is the path to the JSON-schema that represent the image or image + // entity. Schema string `json:"schema"` + + // VirtualSize is the virtual size of the image + VirtualSize int64 `json:"virtual_size"` } func (r *Image) UnmarshalJSON(b []byte) error { @@ -93,7 +102,7 @@ func (r *Image) UnmarshalJSON(b []byte) error { switch t := s.SizeBytes.(type) { case nil: - return nil + r.SizeBytes = 0 case float32: r.SizeBytes = int64(t) case float64: @@ -102,6 +111,18 @@ func (r *Image) UnmarshalJSON(b []byte) error { return fmt.Errorf("Unknown type for SizeBytes: %v (value: %v)", reflect.TypeOf(t), t) } + // Bundle all other fields into Properties + var result interface{} + err = json.Unmarshal(b, &result) + if err != nil { + return err + } + if resultMap, ok := result.(map[string]interface{}); ok { + delete(resultMap, "self") + delete(resultMap, "size") + r.Properties = internal.RemainingKeys(Image{}, resultMap) + } + return err } @@ -116,38 +137,44 @@ func (r commonResult) Extract() (*Image, error) { return s, err } -// CreateResult represents the result of a Create operation +// CreateResult represents the result of a Create operation. Call its Extract +// method to interpret it as an Image. type CreateResult struct { commonResult } -// UpdateResult represents the result of an Update operation +// UpdateResult represents the result of an Update operation. Call its Extract +// method to interpret it as an Image. type UpdateResult struct { commonResult } -// GetResult represents the result of a Get operation +// GetResult represents the result of a Get operation. Call its Extract +// method to interpret it as an Image. type GetResult struct { commonResult } -//DeleteResult model +// DeleteResult represents the result of a Delete operation. Call its +// ExtractErr method to interpret it as an Image. type DeleteResult struct { gophercloud.ErrResult } -// ImagePage represents page +// ImagePage represents the results of a List request. type ImagePage struct { + serviceURL string pagination.LinkedPageBase } -// IsEmpty returns true if a page contains no Images results. +// IsEmpty returns true if an ImagePage contains no Images results. func (r ImagePage) IsEmpty() (bool, error) { images, err := ExtractImages(r) return len(images) == 0, err } -// NextPageURL uses the response's embedded link reference to navigate to the next page of results. +// NextPageURL uses the response's embedded link reference to navigate to +// the next page of results. func (r ImagePage) NextPageURL() (string, error) { var s struct { Next string `json:"next"` @@ -161,10 +188,11 @@ func (r ImagePage) NextPageURL() (string, error) { return "", nil } - return nextPageURL(r.URL.String(), s.Next), nil + return nextPageURL(r.serviceURL, s.Next) } -// ExtractImages interprets the results of a single page from a List() call, producing a slice of Image entities. +// ExtractImages interprets the results of a single page from a List() call, +// producing a slice of Image entities. func ExtractImages(r pagination.Page) ([]Image, error) { var s struct { Images []Image `json:"images"` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/types.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/types.go index 086e7e5d5..d2f9cbd3b 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/types.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/types.go @@ -1,5 +1,9 @@ package images +import ( + "time" +) + // ImageStatus image statuses // http://docs.openstack.org/developer/glance/statuses.html type ImageStatus string @@ -9,7 +13,8 @@ const ( // been reserved for an image in the image registry. ImageStatusQueued ImageStatus = "queued" - // ImageStatusSaving denotes that an image’s raw data is currently being uploaded to Glance + // ImageStatusSaving denotes that an image’s raw data is currently being + // uploaded to Glance ImageStatusSaving ImageStatus = "saving" // ImageStatusActive denotes an image that is fully available in Glance. @@ -23,16 +28,18 @@ const ( // The image information is retained in the image registry. ImageStatusDeleted ImageStatus = "deleted" - // ImageStatusPendingDelete is similar to Delete, but the image is not yet deleted. + // ImageStatusPendingDelete is similar to Delete, but the image is not yet + // deleted. ImageStatusPendingDelete ImageStatus = "pending_delete" - // ImageStatusDeactivated denotes that access to image data is not allowed to any non-admin user. + // ImageStatusDeactivated denotes that access to image data is not allowed to + // any non-admin user. ImageStatusDeactivated ImageStatus = "deactivated" ) // ImageVisibility denotes an image that is fully available in Glance. -// This occurs when the image data is uploaded, or the image size -// is explicitly set to zero on creation. +// This occurs when the image data is uploaded, or the image size is explicitly +// set to zero on creation. // According to design // https://wiki.openstack.org/wiki/Glance-v2-community-image-visibility-design type ImageVisibility string @@ -52,12 +59,13 @@ const ( // ImageVisibilityCommunity images: // - all users can see and boot it - // - users with tenantId in the member-list of the image with member_status == 'accepted' - // have this image in their default image-list + // - users with tenantId in the member-list of the image with + // member_status == 'accepted' have this image in their default image-list. ImageVisibilityCommunity ImageVisibility = "community" ) -// MemberStatus is a status for adding a new member (tenant) to an image member list. +// MemberStatus is a status for adding a new member (tenant) to an image +// member list. type ImageMemberStatus string const ( @@ -73,3 +81,24 @@ const ( // ImageMemberStatusAll ImageMemberStatusAll ImageMemberStatus = "all" ) + +// ImageDateFilter represents a valid filter to use for filtering +// images by their date during a List. +type ImageDateFilter string + +const ( + FilterGT ImageDateFilter = "gt" + FilterGTE ImageDateFilter = "gte" + FilterLT ImageDateFilter = "lt" + FilterLTE ImageDateFilter = "lte" + FilterNEQ ImageDateFilter = "neq" + FilterEQ ImageDateFilter = "eq" +) + +// ImageDateQuery represents a date field to be used for listing images. +// If no filter is specified, the query will act as though FilterEQ was +// set. +type ImageDateQuery struct { + Date time.Time + Filter ImageDateFilter +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/urls.go index 58cb8f715..1780c3c6c 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/urls.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/urls.go @@ -1,9 +1,11 @@ package images import ( + "net/url" "strings" "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/utils" ) // `listURL` is a pure function. `listURL(c)` is a URL for which a GET @@ -38,7 +40,26 @@ func deleteURL(c *gophercloud.ServiceClient, imageID string) string { } // builds next page full url based on current url -func nextPageURL(currentURL string, next string) string { - base := currentURL[:strings.Index(currentURL, "/images")] - return base + next +func nextPageURL(serviceURL, requestedNext string) (string, error) { + base, err := utils.BaseEndpoint(serviceURL) + if err != nil { + return "", err + } + + requestedNextURL, err := url.Parse(requestedNext) + if err != nil { + return "", err + } + + base = gophercloud.NormalizeURL(base) + nextPath := base + strings.TrimPrefix(requestedNextURL.Path, "/") + + nextURL, err := url.Parse(nextPath) + if err != nil { + return "", err + } + + nextURL.RawQuery = requestedNextURL.RawQuery + + return nextURL.String(), nil } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/doc.go new file mode 100644 index 000000000..3257dd1ba --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/doc.go @@ -0,0 +1,37 @@ +/* +Package attributestags manages Tags on Resources created by the OpenStack Neutron Service. + +This enables tagging via a standard interface for resources types which support it. + +See https://developer.openstack.org/api-ref/network/v2/#standard-attributes-tag-extension for more information on the underlying API. + +Example to ReplaceAll Resource Tags + + network, err := networks.Create(conn, createOpts).Extract() + + tagReplaceAllOpts := attributestags.ReplaceAllOpts{ + Tags: []string{"abc", "123"}, + } + attributestags.ReplaceAll(conn, "networks", network.ID, tagReplaceAllOpts) + +Example to List all Resource Tags + + tags, err = attributestags.List(conn, "networks", network.ID).Extract() + +Example to Delete all Resource Tags + + err = attributestags.DeleteAll(conn, "networks", network.ID).ExtractErr() + +Example to Add a tag to a Resource + + err = attributestags.Add(client, "networks", network.ID, "atag").ExtractErr() + +Example to Delete a tag from a Resource + + err = attributestags.Delete(client, "networks", network.ID, "atag").ExtractErr() + +Example to confirm if a tag exists on a resource + + exists, _ := attributestags.Confirm(client, "networks", network.ID, "atag").Extract() +*/ +package attributestags diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/requests.go new file mode 100644 index 000000000..a08bccbb6 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/requests.go @@ -0,0 +1,81 @@ +package attributestags + +import ( + "github.com/gophercloud/gophercloud" +) + +// ReplaceAllOptsBuilder allows extensions to add additional parameters to +// the ReplaceAll request. +type ReplaceAllOptsBuilder interface { + ToAttributeTagsReplaceAllMap() (map[string]interface{}, error) +} + +// ReplaceAllOpts provides options used to create Tags on a Resource +type ReplaceAllOpts struct { + Tags []string `json:"tags" required:"true"` +} + +// ToAttributeTagsReplaceAllMap formats a ReplaceAllOpts into the body of the +// replace request +func (opts ReplaceAllOpts) ToAttributeTagsReplaceAllMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// ReplaceAll updates all tags on a resource, replacing any existing tags +func ReplaceAll(client *gophercloud.ServiceClient, resourceType string, resourceID string, opts ReplaceAllOptsBuilder) (r ReplaceAllResult) { + b, err := opts.ToAttributeTagsReplaceAllMap() + url := replaceURL(client, resourceType, resourceID) + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(url, &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// List all tags on a resource +func List(client *gophercloud.ServiceClient, resourceType string, resourceID string) (r ListResult) { + url := listURL(client, resourceType, resourceID) + _, r.Err = client.Get(url, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// DeleteAll deletes all tags on a resource +func DeleteAll(client *gophercloud.ServiceClient, resourceType string, resourceID string) (r DeleteResult) { + url := deleteAllURL(client, resourceType, resourceID) + _, r.Err = client.Delete(url, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return +} + +// Add a tag on a resource +func Add(client *gophercloud.ServiceClient, resourceType string, resourceID string, tag string) (r AddResult) { + url := addURL(client, resourceType, resourceID, tag) + _, r.Err = client.Put(url, nil, nil, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + return +} + +// Delete a tag on a resource +func Delete(client *gophercloud.ServiceClient, resourceType string, resourceID string, tag string) (r DeleteResult) { + url := deleteURL(client, resourceType, resourceID, tag) + _, r.Err = client.Delete(url, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return +} + +// Confirm if a tag exists on a resource +func Confirm(client *gophercloud.ServiceClient, resourceType string, resourceID string, tag string) (r ConfirmResult) { + url := confirmURL(client, resourceType, resourceID, tag) + _, r.Err = client.Get(url, nil, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/results.go new file mode 100644 index 000000000..cea8045be --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/results.go @@ -0,0 +1,57 @@ +package attributestags + +import ( + "github.com/gophercloud/gophercloud" +) + +type tagResult struct { + gophercloud.Result +} + +// Extract interprets tagResult to return the list of tags +func (r tagResult) Extract() ([]string, error) { + var s struct { + Tags []string `json:"tags"` + } + err := r.ExtractInto(&s) + return s.Tags, err +} + +// ReplaceAllResult represents the result of a replace operation. +// Call its Extract method to interpret it as a slice of strings. +type ReplaceAllResult struct { + tagResult +} + +type ListResult struct { + tagResult +} + +// DeleteResult is the result from a Delete/DeleteAll operation. +// Call its ExtractErr method to determine if the call succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// AddResult is the result from an Add operation. +// Call its ExtractErr method to determine if the call succeeded or failed. +type AddResult struct { + gophercloud.ErrResult +} + +// ConfirmResult is the result from an Confirm operation. +type ConfirmResult struct { + gophercloud.Result +} + +func (r ConfirmResult) Extract() (bool, error) { + exists := r.Err == nil + + if r.Err != nil { + if _, ok := r.Err.(gophercloud.ErrDefault404); ok { + r.Err = nil + } + } + + return exists, r.Err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/urls.go new file mode 100644 index 000000000..973e00c47 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/urls.go @@ -0,0 +1,31 @@ +package attributestags + +import "github.com/gophercloud/gophercloud" + +const ( + tagsPath = "tags" +) + +func replaceURL(c *gophercloud.ServiceClient, r_type string, id string) string { + return c.ServiceURL(r_type, id, tagsPath) +} + +func listURL(c *gophercloud.ServiceClient, r_type string, id string) string { + return c.ServiceURL(r_type, id, tagsPath) +} + +func deleteAllURL(c *gophercloud.ServiceClient, r_type string, id string) string { + return c.ServiceURL(r_type, id, tagsPath) +} + +func addURL(c *gophercloud.ServiceClient, r_type string, id string, tag string) string { + return c.ServiceURL(r_type, id, tagsPath, tag) +} + +func deleteURL(c *gophercloud.ServiceClient, r_type string, id string, tag string) string { + return c.ServiceURL(r_type, id, tagsPath, tag) +} + +func confirmURL(c *gophercloud.ServiceClient, r_type string, id string, tag string) string { + return c.ServiceURL(r_type, id, tagsPath, tag) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/doc.go new file mode 100644 index 000000000..eda010cb0 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/doc.go @@ -0,0 +1,53 @@ +/* +Package external provides information and interaction with the external +extension for the OpenStack Networking service. + +Example to List Networks with External Information + + iTrue := true + networkListOpts := networks.ListOpts{} + listOpts := external.ListOptsExt{ + ListOptsBuilder: networkListOpts, + External: &iTrue, + } + + type NetworkWithExternalExt struct { + networks.Network + external.NetworkExternalExt + } + + var allNetworks []NetworkWithExternalExt + + allPages, err := networks.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + err = networks.ExtractNetworksInto(allPages, &allNetworks) + if err != nil { + panic(err) + } + + for _, network := range allNetworks { + fmt.Println("%+v\n", network) + } + +Example to Create a Network with External Information + + iTrue := true + networkCreateOpts := networks.CreateOpts{ + Name: "private", + AdminStateUp: &iTrue, + } + + createOpts := external.CreateOptsExt{ + networkCreateOpts, + &iTrue, + } + + network, err := networks.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } +*/ +package external diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/requests.go new file mode 100644 index 000000000..ced5efed8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/requests.go @@ -0,0 +1,84 @@ +package external + +import ( + "net/url" + "strconv" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" +) + +// ListOptsExt adds the external network options to the base ListOpts. +type ListOptsExt struct { + networks.ListOptsBuilder + External *bool `q:"router:external"` +} + +// ToNetworkListQuery adds the router:external option to the base network +// list options. +func (opts ListOptsExt) ToNetworkListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts.ListOptsBuilder) + if err != nil { + return "", err + } + + params := q.Query() + if opts.External != nil { + v := strconv.FormatBool(*opts.External) + params.Add("router:external", v) + } + + q = &url.URL{RawQuery: params.Encode()} + return q.String(), err +} + +// CreateOptsExt is the structure used when creating new external network +// resources. It embeds networks.CreateOpts and so inherits all of its required +// and optional fields, with the addition of the External field. +type CreateOptsExt struct { + networks.CreateOptsBuilder + External *bool `json:"router:external,omitempty"` +} + +// ToNetworkCreateMap adds the router:external options to the base network +// creation options. +func (opts CreateOptsExt) ToNetworkCreateMap() (map[string]interface{}, error) { + base, err := opts.CreateOptsBuilder.ToNetworkCreateMap() + if err != nil { + return nil, err + } + + if opts.External == nil { + return base, nil + } + + networkMap := base["network"].(map[string]interface{}) + networkMap["router:external"] = opts.External + + return base, nil +} + +// UpdateOptsExt is the structure used when updating existing external network +// resources. It embeds networks.UpdateOpts and so inherits all of its required +// and optional fields, with the addition of the External field. +type UpdateOptsExt struct { + networks.UpdateOptsBuilder + External *bool `json:"router:external,omitempty"` +} + +// ToNetworkUpdateMap casts an UpdateOpts struct to a map. +func (opts UpdateOptsExt) ToNetworkUpdateMap() (map[string]interface{}, error) { + base, err := opts.UpdateOptsBuilder.ToNetworkUpdateMap() + if err != nil { + return nil, err + } + + if opts.External == nil { + return base, nil + } + + networkMap := base["network"].(map[string]interface{}) + networkMap["router:external"] = opts.External + + return base, nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/results.go new file mode 100644 index 000000000..7cbbffdcf --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/results.go @@ -0,0 +1,8 @@ +package external + +// NetworkExternalExt represents a decorated form of a Network with based on the +// "external-net" extension. +type NetworkExternalExt struct { + // Specifies whether the network is an external network or not. + External bool `json:"router:external"` +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/extradhcpopts/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/extradhcpopts/doc.go new file mode 100644 index 000000000..ec5d6181d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/extradhcpopts/doc.go @@ -0,0 +1,80 @@ +/* +Package extradhcpopts allow to work with extra DHCP functionality of Neutron ports. + +Example to Get a Port with Extra DHCP Options + + portID := "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2" + var s struct { + ports.Port + extradhcpopts.ExtraDHCPOptsExt + } + + err := ports.Get(networkClient, portID).ExtractInto(&s) + if err != nil { + panic(err) + } + +Example to Create a Port with Extra DHCP Options + + var s struct { + ports.Port + extradhcpopts.ExtraDHCPOptsExt + } + + adminStateUp := true + portCreateOpts := ports.CreateOpts{ + Name: "dhcp-conf-port", + AdminStateUp: &adminStateUp, + NetworkID: "a87cc70a-3e15-4acf-8205-9b711a3531b7", + FixedIPs: []ports.IP{ + {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"}, + }, + } + + createOpts := extradhcpopts.CreateOptsExt{ + CreateOptsBuilder: portCreateOpts, + ExtraDHCPOpts: []extradhcpopts.CreateExtraDHCPOpt{ + { + OptName: "optionA", + OptValue: "valueA", + }, + }, + } + + err := ports.Create(networkClient, createOpts).ExtractInto(&s) + if err != nil { + panic(err) + } + +Example to Update a Port with Extra DHCP Options + + var s struct { + ports.Port + extradhcpopts.ExtraDHCPOptsExt + } + + portUpdateOpts := ports.UpdateOpts{ + Name: "updated-dhcp-conf-port", + FixedIPs: []ports.IP{ + {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"}, + }, + } + + value := "valueB" + updateOpts := extradhcpopts.UpdateOptsExt{ + UpdateOptsBuilder: portUpdateOpts, + ExtraDHCPOpts: []extradhcpopts.UpdateExtraDHCPOpt{ + { + OptName: "optionB", + OptValue: &value, + }, + }, + } + + portID := "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2" + err := ports.Update(networkClient, portID, updateOpts).ExtractInto(&s) + if err != nil { + panic(err) + } +*/ +package extradhcpopts diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/extradhcpopts/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/extradhcpopts/requests.go new file mode 100644 index 000000000..f3eb9bc45 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/extradhcpopts/requests.go @@ -0,0 +1,102 @@ +package extradhcpopts + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" +) + +// CreateOptsExt adds extra DHCP options to the base ports.CreateOpts. +type CreateOptsExt struct { + // CreateOptsBuilder is the interface options structs have to satisfy in order + // to be used in the main Create operation in this package. + ports.CreateOptsBuilder + + // ExtraDHCPOpts field is a set of DHCP options for a single port. + ExtraDHCPOpts []CreateExtraDHCPOpt `json:"extra_dhcp_opts,omitempty"` +} + +// CreateExtraDHCPOpt represents the options required to create an extra DHCP +// option on a port. +type CreateExtraDHCPOpt struct { + // OptName is the name of a DHCP option. + OptName string `json:"opt_name" required:"true"` + + // OptValue is the value of the DHCP option. + OptValue string `json:"opt_value" required:"true"` + + // IPVersion is the IP protocol version of a DHCP option. + IPVersion gophercloud.IPVersion `json:"ip_version,omitempty"` +} + +// ToPortCreateMap casts a CreateOptsExt struct to a map. +func (opts CreateOptsExt) ToPortCreateMap() (map[string]interface{}, error) { + base, err := opts.CreateOptsBuilder.ToPortCreateMap() + if err != nil { + return nil, err + } + + port := base["port"].(map[string]interface{}) + + // Convert opts.ExtraDHCPOpts to a slice of maps. + if opts.ExtraDHCPOpts != nil { + extraDHCPOpts := make([]map[string]interface{}, len(opts.ExtraDHCPOpts)) + for i, opt := range opts.ExtraDHCPOpts { + b, err := gophercloud.BuildRequestBody(opt, "") + if err != nil { + return nil, err + } + extraDHCPOpts[i] = b + } + port["extra_dhcp_opts"] = extraDHCPOpts + } + + return base, nil +} + +// UpdateOptsExt adds extra DHCP options to the base ports.UpdateOpts. +type UpdateOptsExt struct { + // UpdateOptsBuilder is the interface options structs have to satisfy in order + // to be used in the main Update operation in this package. + ports.UpdateOptsBuilder + + // ExtraDHCPOpts field is a set of DHCP options for a single port. + ExtraDHCPOpts []UpdateExtraDHCPOpt `json:"extra_dhcp_opts,omitempty"` +} + +// UpdateExtraDHCPOpt represents the options required to update an extra DHCP +// option on a port. +type UpdateExtraDHCPOpt struct { + // OptName is the name of a DHCP option. + OptName string `json:"opt_name" required:"true"` + + // OptValue is the value of the DHCP option. + OptValue *string `json:"opt_value"` + + // IPVersion is the IP protocol version of a DHCP option. + IPVersion gophercloud.IPVersion `json:"ip_version,omitempty"` +} + +// ToPortUpdateMap casts an UpdateOpts struct to a map. +func (opts UpdateOptsExt) ToPortUpdateMap() (map[string]interface{}, error) { + base, err := opts.UpdateOptsBuilder.ToPortUpdateMap() + if err != nil { + return nil, err + } + + port := base["port"].(map[string]interface{}) + + // Convert opts.ExtraDHCPOpts to a slice of maps. + if opts.ExtraDHCPOpts != nil { + extraDHCPOpts := make([]map[string]interface{}, len(opts.ExtraDHCPOpts)) + for i, opt := range opts.ExtraDHCPOpts { + b, err := gophercloud.BuildRequestBody(opt, "") + if err != nil { + return nil, err + } + extraDHCPOpts[i] = b + } + port["extra_dhcp_opts"] = extraDHCPOpts + } + + return base, nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/extradhcpopts/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/extradhcpopts/results.go new file mode 100644 index 000000000..8e3132ea4 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/extradhcpopts/results.go @@ -0,0 +1,20 @@ +package extradhcpopts + +// ExtraDHCPOptsExt is a struct that contains different DHCP options for a +// single port. +type ExtraDHCPOptsExt struct { + ExtraDHCPOpts []ExtraDHCPOpt `json:"extra_dhcp_opts"` +} + +// ExtraDHCPOpt represents a single set of extra DHCP options for a single port. +type ExtraDHCPOpt struct { + // OptName is the name of a single DHCP option. + OptName string `json:"opt_name"` + + // OptValue is the value of a single DHCP option. + OptValue string `json:"opt_value"` + + // IPVersion is the IP protocol version of a single DHCP option. + // Valid value is 4 or 6. Default is 4. + IPVersion int `json:"ip_version"` +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/doc.go new file mode 100644 index 000000000..c01070edc --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/doc.go @@ -0,0 +1,60 @@ +/* +Package firewalls allows management and retrieval of firewalls from the +OpenStack Networking Service. + +Example to List Firewalls + + listOpts := firewalls.ListOpts{ + TenantID: "tenant-id", + } + + allPages, err := firewalls.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allFirewalls, err := firewalls.ExtractFirewalls(allPages) + if err != nil { + panic(err) + } + + for _, fw := range allFirewalls { + fmt.Printf("%+v\n", fw) + } + +Example to Create a Firewall + + createOpts := firewalls.CreateOpts{ + Name: "firewall_1", + Description: "A firewall", + PolicyID: "19ab8c87-4a32-4e6a-a74e-b77fffb89a0c", + AdminStateUp: gophercloud.Enabled, + } + + firewall, err := firewalls.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Firewall + + firewallID := "a6917946-38ab-4ffd-a55a-26c0980ce5ee" + + updateOpts := firewalls.UpdateOpts{ + AdminStateUp: gophercloud.Disabled, + } + + firewall, err := firewalls.Update(networkClient, firewallID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Firewall + + firewallID := "a6917946-38ab-4ffd-a55a-26c0980ce5ee" + err := firewalls.Delete(networkClient, firewallID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package firewalls diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/requests.go index 21ceb4ea1..c710f94a8 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/requests.go @@ -18,6 +18,7 @@ type ListOptsBuilder interface { // `asc' or `desc'. Marker and Limit are used for pagination. type ListOpts struct { TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` Name string `q:"name"` Description string `q:"description"` AdminStateUp bool `q:"admin_state_up"` @@ -56,10 +57,8 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { }) } -// CreateOptsBuilder is the interface options structs have to satisfy in order -// to be used in the main Create operation in this package. Since many -// extensions decorate or modify the common logic, it is useful for them to -// satisfy a basic interface in order for them to be used. +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. type CreateOptsBuilder interface { ToFirewallCreateMap() (map[string]interface{}, error) } @@ -67,9 +66,11 @@ type CreateOptsBuilder interface { // CreateOpts contains all the values needed to create a new firewall. type CreateOpts struct { PolicyID string `json:"firewall_policy_id" required:"true"` - // Only required if the caller has an admin role and wants to create a firewall - // for another tenant. + // TenantID specifies a tenant to own the firewall. The caller must have + // an admin role in order to set this. Otherwise, this field is left unset + // and the caller will be the owner. TenantID string `json:"tenant_id,omitempty"` + ProjectID string `json:"project_id,omitempty"` Name string `json:"name,omitempty"` Description string `json:"description,omitempty"` AdminStateUp *bool `json:"admin_state_up,omitempty"` @@ -81,7 +82,7 @@ func (opts CreateOpts) ToFirewallCreateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "firewall") } -// Create accepts a CreateOpts struct and uses the values to create a new firewall +// Create accepts a CreateOpts struct and uses the values to create a new firewall. func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { b, err := opts.ToFirewallCreateMap() if err != nil { @@ -98,21 +99,19 @@ func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { return } -// UpdateOptsBuilder is the interface options structs have to satisfy in order -// to be used in the main Update operation in this package. Since many -// extensions decorate or modify the common logic, it is useful for them to -// satisfy a basic interface in order for them to be used. +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. type UpdateOptsBuilder interface { ToFirewallUpdateMap() (map[string]interface{}, error) } // UpdateOpts contains the values used when updating a firewall. type UpdateOpts struct { - PolicyID string `json:"firewall_policy_id" required:"true"` - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - AdminStateUp *bool `json:"admin_state_up,omitempty"` - Shared *bool `json:"shared,omitempty"` + PolicyID string `json:"firewall_policy_id" required:"true"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + AdminStateUp *bool `json:"admin_state_up,omitempty"` + Shared *bool `json:"shared,omitempty"` } // ToFirewallUpdateMap casts a CreateOpts struct to a map. diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/results.go index 1403ced2b..9543f0fae 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls/results.go @@ -14,6 +14,7 @@ type Firewall struct { Status string `json:"status"` PolicyID string `json:"firewall_policy_id"` TenantID string `json:"tenant_id"` + ProjectID string `json:"project_id"` } type commonResult struct { @@ -61,8 +62,8 @@ func (r FirewallPage) IsEmpty() (bool, error) { return len(is) == 0, err } -// ExtractFirewalls accepts a Page struct, specifically a RouterPage struct, -// and extracts the elements into a slice of Router structs. In other words, +// ExtractFirewalls accepts a Page struct, specifically a FirewallPage struct, +// and extracts the elements into a slice of Firewall structs. In other words, // a generic collection is mapped into a relevant slice. func ExtractFirewalls(r pagination.Page) ([]Firewall, error) { var s []Firewall @@ -70,22 +71,26 @@ func ExtractFirewalls(r pagination.Page) ([]Firewall, error) { return s, err } -// GetResult represents the result of a get operation. +// GetResult represents the result of a Get operation. Call its Extract +// method to interpret it as a Firewall. type GetResult struct { commonResult } -// UpdateResult represents the result of an update operation. +// UpdateResult represents the result of an Update operation. Call its Extract +// method to interpret it as a Firewall. type UpdateResult struct { commonResult } -// DeleteResult represents the result of a delete operation. +// DeleteResult represents the result of a delete operation. Call its +// ExtractErr method to determine if the operation succeeded or failed. type DeleteResult struct { gophercloud.ErrResult } -// CreateResult represents the result of a create operation. +// CreateResult represents the result of a Create operation. Call its Extract +// method to interpret it as a Firewall. type CreateResult struct { commonResult } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies/doc.go new file mode 100644 index 000000000..ae824491f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies/doc.go @@ -0,0 +1,84 @@ +/* +Package policies allows management and retrieval of Firewall Policies in the +OpenStack Networking Service. + +Example to List Policies + + listOpts := policies.ListOpts{ + TenantID: "966b3c7d36a24facaf20b7e458bf2192", + } + + allPages, err := policies.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allPolicies, err := policies.ExtractPolicies(allPages) + if err != nil { + panic(err) + } + + for _, policy := range allPolicies { + fmt.Printf("%+v\n", policy) + } + +Example to Create a Policy + + createOpts := policies.CreateOpts{ + Name: "policy_1", + Description: "A policy", + Rules: []string{ + "98a58c87-76be-ae7c-a74e-b77fffb88d95", + "7c4f087a-ed46-4ea8-8040-11ca460a61c0", + } + } + + policy, err := policies.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Policy + + policyID := "38aee955-6283-4279-b091-8b9c828000ec" + + updateOpts := policies.UpdateOpts{ + Description: "New Description", + } + + policy, err := policies.Update(networkClient, policyID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Policy + + policyID := "38aee955-6283-4279-b091-8b9c828000ec" + err := policies.Delete(networkClient, policyID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Add a Rule to a Policy + + policyID := "38aee955-6283-4279-b091-8b9c828000ec" + ruleOpts := policies.InsertRuleOpts{ + ID: "98a58c87-76be-ae7c-a74e-b77fffb88d95", + } + + policy, err := policies.AddRule(networkClient, policyID, ruleOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Rule from a Policy + + policyID := "38aee955-6283-4279-b091-8b9c828000ec" + ruleID := "98a58c87-76be-ae7c-a74e-b77fffb88d95", + + policy, err := policies.RemoveRule(networkClient, policyID, ruleID).Extract() + if err != nil { + panic(err) + } +*/ +package policies diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies/requests.go index 437d1248b..fcecf6a50 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies/requests.go @@ -18,6 +18,7 @@ type ListOptsBuilder interface { // and is either `asc' or `desc'. Marker and Limit are used for pagination. type ListOpts struct { TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` Name string `q:"name"` Description string `q:"description"` Shared *bool `q:"shared"` @@ -39,8 +40,8 @@ func (opts ListOpts) ToPolicyListQuery() (string, error) { // firewall policies. It accepts a ListOpts struct, which allows you to filter // and sort the returned collection for greater efficiency. // -// Default policy settings return only those firewall policies that are owned by the -// tenant who submits the request, unless an admin user submits the request. +// Default policy settings return only those firewall policies that are owned by +// the tenant who submits the request, unless an admin user submits the request. func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { url := rootURL(c) if opts != nil { @@ -55,19 +56,19 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { }) } -// CreateOptsBuilder is the interface options structs have to satisfy in order -// to be used in the main Create operation in this package. Since many -// extensions decorate or modify the common logic, it is useful for them to -// satisfy a basic interface in order for them to be used. +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. type CreateOptsBuilder interface { ToFirewallPolicyCreateMap() (map[string]interface{}, error) } // CreateOpts contains all the values needed to create a new firewall policy. type CreateOpts struct { - // Only required if the caller has an admin role and wants to create a firewall policy - // for another tenant. + // TenantID specifies a tenant to own the firewall. The caller must have + // an admin role in order to set this. Otherwise, this field is left unset + // and the caller will be the owner. TenantID string `json:"tenant_id,omitempty"` + ProjectID string `json:"project_id,omitempty"` Name string `json:"name,omitempty"` Description string `json:"description,omitempty"` Shared *bool `json:"shared,omitempty"` @@ -80,7 +81,8 @@ func (opts CreateOpts) ToFirewallPolicyCreateMap() (map[string]interface{}, erro return gophercloud.BuildRequestBody(opts, "firewall_policy") } -// Create accepts a CreateOpts struct and uses the values to create a new firewall policy +// Create accepts a CreateOpts struct and uses the values to create a new +// firewall policy. func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { b, err := opts.ToFirewallPolicyCreateMap() if err != nil { @@ -97,18 +99,16 @@ func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { return } -// UpdateOptsBuilder is the interface options structs have to satisfy in order -// to be used in the main Update operation in this package. Since many -// extensions decorate or modify the common logic, it is useful for them to -// satisfy a basic interface in order for them to be used. +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. type UpdateOptsBuilder interface { ToFirewallPolicyUpdateMap() (map[string]interface{}, error) } // UpdateOpts contains the values used when updating a firewall policy. type UpdateOpts struct { - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` Shared *bool `json:"shared,omitempty"` Audited *bool `json:"audited,omitempty"` Rules []string `json:"firewall_rules,omitempty"` @@ -132,16 +132,20 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r return } -// Delete will permanently delete a particular firewall policy based on its unique ID. +// Delete will permanently delete a particular firewall policy based on its +// unique ID. func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { _, r.Err = c.Delete(resourceURL(c, id), nil) return } +// InsertRuleOptsBuilder allows extensions to add additional parameters to the +// InsertRule request. type InsertRuleOptsBuilder interface { ToFirewallPolicyInsertRuleMap() (map[string]interface{}, error) } +// InsertRuleOpts contains the values used when updating a policy's rules. type InsertRuleOpts struct { ID string `json:"firewall_rule_id" required:"true"` BeforeRuleID string `json:"insert_before,omitempty"` @@ -152,6 +156,7 @@ func (opts InsertRuleOpts) ToFirewallPolicyInsertRuleMap() (map[string]interface return gophercloud.BuildRequestBody(opts, "") } +// AddRule will add a rule to a policy. func AddRule(c *gophercloud.ServiceClient, id string, opts InsertRuleOptsBuilder) (r InsertRuleResult) { b, err := opts.ToFirewallPolicyInsertRuleMap() if err != nil { @@ -164,6 +169,7 @@ func AddRule(c *gophercloud.ServiceClient, id string, opts InsertRuleOptsBuilder return } +// RemoveRule will add a rule to a policy. func RemoveRule(c *gophercloud.ServiceClient, id, ruleID string) (r RemoveRuleResult) { b := map[string]interface{}{"firewall_rule_id": ruleID} _, r.Err = c.Put(removeURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies/results.go index 9c5b1861e..495cef2c0 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies/results.go @@ -11,6 +11,7 @@ type Policy struct { Name string `json:"name"` Description string `json:"description"` TenantID string `json:"tenant_id"` + ProjectID string `json:"project_id"` Audited bool `json:"audited"` Shared bool `json:"shared"` Rules []string `json:"firewall_rules,omitempty"` @@ -55,8 +56,8 @@ func (r PolicyPage) IsEmpty() (bool, error) { return len(is) == 0, err } -// ExtractPolicies accepts a Page struct, specifically a RouterPage struct, -// and extracts the elements into a slice of Router structs. In other words, +// ExtractPolicies accepts a Page struct, specifically a Policy struct, +// and extracts the elements into a slice of Policy structs. In other words, // a generic collection is mapped into a relevant slice. func ExtractPolicies(r pagination.Page) ([]Policy, error) { var s struct { @@ -66,32 +67,38 @@ func ExtractPolicies(r pagination.Page) ([]Policy, error) { return s.Policies, err } -// GetResult represents the result of a get operation. +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a Policy. type GetResult struct { commonResult } -// UpdateResult represents the result of an update operation. +// UpdateResult represents the result of an update operation. Call its +// Extract method to interpret it as a Policy. type UpdateResult struct { commonResult } -// DeleteResult represents the result of a delete operation. +// DeleteResult represents the result of a delete operation. Call its +// ExtractErr method to determine if the operation succeeded or failed. type DeleteResult struct { gophercloud.ErrResult } -// CreateResult represents the result of a create operation. +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a Policy. type CreateResult struct { commonResult } -// InsertRuleResult represents the result of an InsertRule operation. +// InsertRuleResult represents the result of an InsertRule operation. Call its +// Extract method to interpret it as a Policy. type InsertRuleResult struct { commonResult } -// RemoveRuleResult represents the result of a RemoveRule operation. +// RemoveRuleResult represents the result of a RemoveRule operation. Call its +// Extract method to interpret it as a Policy. type RemoveRuleResult struct { commonResult } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/routerinsertion/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/routerinsertion/doc.go index 9b847e2f5..4f0a779ee 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/routerinsertion/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/routerinsertion/doc.go @@ -1,2 +1,68 @@ -// Package routerinsertion implements the fwaasrouterinsertion FWaaS extension. +/* +Package routerinsertion implements the fwaasrouterinsertion Firewall extension. +It is used to manage the router information of a firewall. + +Example to List Firewalls with Router Information + + type FirewallsWithRouters struct { + firewalls.Firewall + routerinsertion.FirewallExt + } + + var allFirewalls []FirewallsWithRouters + + allPages, err := firewalls.List(networkClient, nil).AllPages() + if err != nil { + panic(err) + } + + err = firewalls.ExtractFirewallsInto(allPages, &allFirewalls) + if err != nil { + panic(err) + } + + for _, fw := range allFirewalls { + fmt.Printf("%+v\n", fw) + } + +Example to Create a Firewall with a Router + + firewallCreateOpts := firewalls.CreateOpts{ + Name: "firewall_1", + PolicyID: "19ab8c87-4a32-4e6a-a74e-b77fffb89a0c", + } + + createOpts := routerinsertion.CreateOptsExt{ + CreateOptsBuilder: firewallCreateOpts, + RouterIDs: []string{ + "8a3a0d6a-34b5-4a92-b65d-6375a4c1e9e8", + }, + } + + firewall, err := firewalls.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Firewall with a Router + + firewallID := "a6917946-38ab-4ffd-a55a-26c0980ce5ee" + + firewallUpdateOpts := firewalls.UpdateOpts{ + Description: "updated firewall", + PolicyID: "19ab8c87-4a32-4e6a-a74e-b77fffb89a0c", + } + + updateOpts := routerinsertion.UpdateOptsExt{ + UpdateOptsBuilder: firewallUpdateOpts, + RouterIDs: []string{ + "8a3a0d6a-34b5-4a92-b65d-6375a4c1e9e8", + }, + } + + firewall, err := firewalls.Update(networkClient, firewallID, updateOpts).Extract() + if err != nil { + panic(err) + } +*/ package routerinsertion diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/routerinsertion/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/routerinsertion/requests.go index fce100f87..b1f6d76e3 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/routerinsertion/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/routerinsertion/requests.go @@ -4,7 +4,7 @@ import ( "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls" ) -// CreateOptsExt adds a RouterIDs option to the base CreateOpts. +// CreateOptsExt adds the RouterIDs option to the base CreateOpts. type CreateOptsExt struct { firewalls.CreateOptsBuilder RouterIDs []string `json:"router_ids"` @@ -23,7 +23,7 @@ func (opts CreateOptsExt) ToFirewallCreateMap() (map[string]interface{}, error) return base, nil } -// UpdateOptsExt updates a RouterIDs option to the base UpdateOpts. +// UpdateOptsExt adds the RouterIDs option to the base UpdateOpts. type UpdateOptsExt struct { firewalls.UpdateOptsBuilder RouterIDs []string `json:"router_ids"` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/rules/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/rules/doc.go new file mode 100644 index 000000000..3351a3e5c --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/rules/doc.go @@ -0,0 +1,64 @@ +/* +Package rules enables management and retrieval of Firewall Rules in the +OpenStack Networking Service. + +Example to List Rules + + listOpts := rules.ListOpts{ + Protocol: rules.ProtocolAny, + } + + allPages, err := rules.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allRules, err := rules.ExtractRules(allPages) + if err != nil { + panic(err) + } + + for _, rule := range allRules { + fmt.Printf("%+v\n", rule) + } + +Example to Create a Rule + + createOpts := rules.CreateOpts{ + Action: "allow", + Protocol: rules.ProtocolTCP, + Description: "ssh", + DestinationPort: 22, + DestinationIPAddress: "192.168.1.0/24", + } + + rule, err := rules.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Rule + + ruleID := "f03bd950-6c56-4f5e-a307-45967078f507" + newPort := 80 + newDescription := "http" + + updateOpts := rules.UpdateOpts{ + Description: &newDescription, + port: &newPort, + } + + rule, err := rules.Update(networkClient, ruleID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Rule + + ruleID := "f03bd950-6c56-4f5e-a307-45967078f507" + err := rules.Delete(networkClient, ruleID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package rules diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/rules/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/rules/requests.go index c1784b732..17979b637 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/rules/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/rules/requests.go @@ -6,21 +6,21 @@ import ( ) type ( - // Protocol represents a valid rule protocol + // Protocol represents a valid rule protocol. Protocol string ) const ( - // ProtocolAny is to allow any protocol + // ProtocolAny is to allow any protocol. ProtocolAny Protocol = "any" - // ProtocolICMP is to allow the ICMP protocol + // ProtocolICMP is to allow the ICMP protocol. ProtocolICMP Protocol = "icmp" - // ProtocolTCP is to allow the TCP protocol + // ProtocolTCP is to allow the TCP protocol. ProtocolTCP Protocol = "tcp" - // ProtocolUDP is to allow the UDP protocol + // ProtocolUDP is to allow the UDP protocol. ProtocolUDP Protocol = "udp" ) @@ -33,10 +33,11 @@ type ListOptsBuilder interface { // ListOpts allows the filtering and sorting of paginated collections through // the API. Filtering is achieved by passing in struct field values that map to // the Firewall rule attributes you want to see returned. SortKey allows you to -// sort by a particular firewall rule attribute. SortDir sets the direction, and is -// either `asc' or `desc'. Marker and Limit are used for pagination. +// sort by a particular firewall rule attribute. SortDir sets the direction, and +// is either `asc' or `desc'. Marker and Limit are used for pagination. type ListOpts struct { TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` Name string `q:"name"` Description string `q:"description"` Protocol string `q:"protocol"` @@ -67,8 +68,8 @@ func (opts ListOpts) ToRuleListQuery() (string, error) { // firewall rules. It accepts a ListOpts struct, which allows you to filter // and sort the returned collection for greater efficiency. // -// Default policy settings return only those firewall rules that are owned by the -// tenant who submits the request, unless an admin user submits the request. +// Default policy settings return only those firewall rules that are owned by +// the tenant who submits the request, unless an admin user submits the request. func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { url := rootURL(c) @@ -85,10 +86,8 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { }) } -// CreateOptsBuilder is the interface options structs have to satisfy in order -// to be used in the main Create operation in this package. Since many -// extensions decorate or modify the common logic, it is useful for them to -// satisfy a basic interface in order for them to be used. +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. type CreateOptsBuilder interface { ToRuleCreateMap() (map[string]interface{}, error) } @@ -98,6 +97,7 @@ type CreateOpts struct { Protocol Protocol `json:"protocol" required:"true"` Action string `json:"action" required:"true"` TenantID string `json:"tenant_id,omitempty"` + ProjectID string `json:"project_id,omitempty"` Name string `json:"name,omitempty"` Description string `json:"description,omitempty"` IPVersion gophercloud.IPVersion `json:"ip_version,omitempty"` @@ -123,7 +123,8 @@ func (opts CreateOpts) ToRuleCreateMap() (map[string]interface{}, error) { return b, nil } -// Create accepts a CreateOpts struct and uses the values to create a new firewall rule +// Create accepts a CreateOpts struct and uses the values to create a new +// firewall rule. func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { b, err := opts.ToRuleCreateMap() if err != nil { @@ -140,15 +141,15 @@ func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { return } -// UpdateOptsBuilder is the interface options structs have to satisfy in order -// to be used in the main Update operation in this package. Since many -// extensions decorate or modify the common logic, it is useful for them to -// satisfy a basic interface in order for them to be used. +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. type UpdateOptsBuilder interface { ToRuleUpdateMap() (map[string]interface{}, error) } // UpdateOpts contains the values used when updating a firewall rule. +// These fields are all pointers so that unset fields will not cause the +// existing Rule attribute to be removed. type UpdateOpts struct { Protocol *string `json:"protocol,omitempty"` Action *string `json:"action,omitempty"` @@ -181,7 +182,8 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r return } -// Delete will permanently delete a particular firewall rule based on its unique ID. +// Delete will permanently delete a particular firewall rule based on its +// unique ID. func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { _, r.Err = c.Delete(resourceURL(c, id), nil) return diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/rules/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/rules/results.go index c44e5a910..82bf4a36a 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/rules/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/rules/results.go @@ -5,7 +5,7 @@ import ( "github.com/gophercloud/gophercloud/pagination" ) -// Rule represents a firewall rule +// Rule represents a firewall rule. type Rule struct { ID string `json:"id"` Name string `json:"name,omitempty"` @@ -22,6 +22,7 @@ type Rule struct { PolicyID string `json:"firewall_policy_id"` Position int `json:"position"` TenantID string `json:"tenant_id"` + ProjectID string `json:"project_id"` } // RulePage is the page returned by a pager when traversing over a @@ -50,8 +51,8 @@ func (r RulePage) IsEmpty() (bool, error) { return len(is) == 0, err } -// ExtractRules accepts a Page struct, specifically a RouterPage struct, -// and extracts the elements into a slice of Router structs. In other words, +// ExtractRules accepts a Page struct, specifically a RulePage struct, +// and extracts the elements into a slice of Rule structs. In other words, // a generic collection is mapped into a relevant slice. func ExtractRules(r pagination.Page) ([]Rule, error) { var s struct { @@ -74,22 +75,26 @@ func (r commonResult) Extract() (*Rule, error) { return s.Rule, err } -// GetResult represents the result of a get operation. +// GetResult represents the result of a get operation. Call its Extract method +// to interpret it as a Rule. type GetResult struct { commonResult } -// UpdateResult represents the result of an update operation. +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a Rule. type UpdateResult struct { commonResult } -// DeleteResult represents the result of a delete operation. +// DeleteResult represents the result of a delete operation. Call its ExtractErr +// method to determine if the request succeeded or failed. type DeleteResult struct { gophercloud.ErrResult } -// CreateResult represents the result of a create operation. +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a Rule. type CreateResult struct { commonResult } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/doc.go new file mode 100644 index 000000000..d68d2b764 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/doc.go @@ -0,0 +1,64 @@ +/* +Package addressscopes provides the ability to retrieve and manage Address scopes through the Neutron API. + +Example of Listing Address scopes + + listOpts := addressscopes.ListOpts{ + IPVersion: 6, + } + + allPages, err := addressscopes.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allAddressScopes, err := addressscopes.ExtractAddressScopes(allPages) + if err != nil { + panic(err) + } + + for _, addressScope := range allAddressScopes { + fmt.Printf("%+v\n", addressScope) + } + +Example to Get an Address scope + + addressScopeID = "9cc35860-522a-4d35-974d-51d4b011801e" + addressScope, err := addressscopes.Get(networkClient, addressScopeID).Extract() + if err != nil { + panic(err) + } + +Example to Create a new Address scope + + addressScopeOpts := addressscopes.CreateOpts{ + Name: "my_address_scope", + IPVersion: 6, + } + addressScope, err := addressscopes.Create(networkClient, addressScopeOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update an Address scope + + addressScopeID = "9cc35860-522a-4d35-974d-51d4b011801e" + newName := "awesome_name" + updateOpts := addressscopes.UpdateOpts{ + Name: &newName, + } + + addressScope, err := addressscopes.Update(networkClient, addressScopeID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete an Address scope + + addressScopeID = "9cc35860-522a-4d35-974d-51d4b011801e" + err := addressscopes.Delete(networkClient, addressScopeID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package addressscopes diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/requests.go new file mode 100644 index 000000000..defa8e1b5 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/requests.go @@ -0,0 +1,147 @@ +package addressscopes + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToAddressScopeListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the Neutron API. Filtering is achieved by passing in struct field values +// that map to the address-scope attributes you want to see returned. +// SortKey allows you to sort by a particular address-scope attribute. +// SortDir sets the direction, and is either `asc' or `desc'. +// Marker and Limit are used for the pagination. +type ListOpts struct { + ID string `q:"id"` + Name string `q:"name"` + TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` + IPVersion int `q:"ip_version"` + Shared *bool `q:"shared"` + Description string `q:"description"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// ToAddressScopeListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToAddressScopeListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of +// address-scopes. It accepts a ListOpts struct, which allows you to filter and +// sort the returned collection for greater efficiency. +// +// Default policy settings return only the address-scopes owned by the project +// of the user submitting the request, unless the user has the administrative +// role. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(c) + if opts != nil { + query, err := opts.ToAddressScopeListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return AddressScopePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves a specific address-scope based on its ID. +func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = c.Get(getURL(c, id), &r.Body, nil) + return +} + +// CreateOptsBuilder allows to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToAddressScopeCreateMap() (map[string]interface{}, error) +} + +// CreateOpts specifies parameters of a new address-scope. +type CreateOpts struct { + // Name is the human-readable name of the address-scope. + Name string `json:"name"` + + // TenantID is the id of the Identity project. + TenantID string `json:"tenant_id,omitempty"` + + // ProjectID is the id of the Identity project. + ProjectID string `json:"project_id,omitempty"` + + // IPVersion is the IP protocol version. + IPVersion int `json:"ip_version"` + + // Shared indicates whether this address-scope is shared across all projects. + Shared bool `json:"shared,omitempty"` +} + +// ToAddressScopeCreateMap constructs a request body from CreateOpts. +func (opts CreateOpts) ToAddressScopeCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "address_scope") +} + +// Create requests the creation of a new address-scope on the server. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToAddressScopeCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToAddressScopeUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts represents options used to update an address-scope. +type UpdateOpts struct { + // Name is the human-readable name of the address-scope. + Name *string `json:"name,omitempty"` + + // Shared indicates whether this address-scope is shared across all projects. + Shared *bool `json:"shared,omitempty"` +} + +// ToAddressScopeUpdateMap builds a request body from UpdateOpts. +func (opts UpdateOpts) ToAddressScopeUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "address_scope") +} + +// Update accepts a UpdateOpts struct and updates an existing address-scope +// using the values provided. +func Update(c *gophercloud.ServiceClient, addressScopeID string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToAddressScopeUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(updateURL(c, addressScopeID), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Delete accepts a unique ID and deletes the address-scope associated with it. +func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = c.Delete(deleteURL(c, id), nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/results.go new file mode 100644 index 000000000..5f78dfeef --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/results.go @@ -0,0 +1,99 @@ +package addressscopes + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts an address-scope resource. +func (r commonResult) Extract() (*AddressScope, error) { + var s struct { + AddressScope *AddressScope `json:"address_scope"` + } + err := r.ExtractInto(&s) + return s.AddressScope, err +} + +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a SubnetPool. +type GetResult struct { + commonResult +} + +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a SubnetPool. +type CreateResult struct { + commonResult +} + +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as an AddressScope. +type UpdateResult struct { + commonResult +} + +// DeleteResult represents the result of a delete operation. Call its +// ExtractErr method to determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// AddressScope represents a Neutron address-scope. +type AddressScope struct { + // ID is the id of the address-scope. + ID string `json:"id"` + + // Name is the human-readable name of the address-scope. + Name string `json:"name"` + + // TenantID is the id of the Identity project. + TenantID string `json:"tenant_id"` + + // ProjectID is the id of the Identity project. + ProjectID string `json:"project_id"` + + // IPVersion is the IP protocol version. + IPVersion int `json:"ip_version"` + + // Shared indicates whether this address-scope is shared across all projects. + Shared bool `json:"shared"` +} + +// AddressScopePage stores a single page of AddressScopes from a List() API call. +type AddressScopePage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of address-scope 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 AddressScopePage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"address_scopes_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty determines whether or not a AddressScopePage is empty. +func (r AddressScopePage) IsEmpty() (bool, error) { + addressScopes, err := ExtractAddressScopes(r) + return len(addressScopes) == 0, err +} + +// ExtractAddressScopes interprets the results of a single page from a List() +// API call, producing a slice of AddressScopes structs. +func ExtractAddressScopes(r pagination.Page) ([]AddressScope, error) { + var s struct { + AddressScopes []AddressScope `json:"address_scopes"` + } + err := (r.(AddressScopePage)).ExtractInto(&s) + return s.AddressScopes, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/urls.go new file mode 100644 index 000000000..9fe7e01a0 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes/urls.go @@ -0,0 +1,33 @@ +package addressscopes + +import "github.com/gophercloud/gophercloud" + +const resourcePath = "address-scopes" + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(resourcePath, id) +} + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(resourcePath) +} + +func listURL(c *gophercloud.ServiceClient) string { + return rootURL(c) +} + +func getURL(c *gophercloud.ServiceClient, id string) string { + return resourceURL(c, id) +} + +func createURL(c *gophercloud.ServiceClient) string { + return rootURL(c) +} + +func updateURL(c *gophercloud.ServiceClient, id string) string { + return resourceURL(c, id) +} + +func deleteURL(c *gophercloud.ServiceClient, id string) string { + return resourceURL(c, id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/doc.go new file mode 100644 index 000000000..a71a3ec88 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/doc.go @@ -0,0 +1,71 @@ +/* +package floatingips enables management and retrieval of Floating IPs from the +OpenStack Networking service. + +Example to List Floating IPs + + listOpts := floatingips.ListOpts{ + FloatingNetworkID: "a6917946-38ab-4ffd-a55a-26c0980ce5ee", + } + + allPages, err := floatingips.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allFIPs, err := floatingips.ExtractFloatingIPs(allPages) + if err != nil { + panic(err) + } + + for _, fip := range allFIPs { + fmt.Printf("%+v\n", fip) + } + +Example to Create a Floating IP + + createOpts := floatingips.CreateOpts{ + FloatingNetworkID: "a6917946-38ab-4ffd-a55a-26c0980ce5ee", + } + + fip, err := floatingips.Create(networkingClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Floating IP + + fipID := "2f245a7b-796b-4f26-9cf9-9e82d248fda7" + portID := "76d0a61b-b8e5-490c-9892-4cf674f2bec8" + + updateOpts := floatingips.UpdateOpts{ + PortID: &portID, + } + + fip, err := floatingips.Update(networkingClient, fipID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Disassociate a Floating IP with a Port + + fipID := "2f245a7b-796b-4f26-9cf9-9e82d248fda7" + + updateOpts := floatingips.UpdateOpts{ + PortID: new(string), + } + + fip, err := floatingips.Update(networkingClient, fipID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Floating IP + + fipID := "2f245a7b-796b-4f26-9cf9-9e82d248fda7" + err := floatingips.Delete(networkClient, fipID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package floatingips diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/requests.go index 83930874c..b7e6e9189 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/requests.go @@ -12,16 +12,23 @@ import ( // either `asc' or `desc'. Marker and Limit are used for pagination. type ListOpts struct { ID string `q:"id"` + Description string `q:"description"` FloatingNetworkID string `q:"floating_network_id"` PortID string `q:"port_id"` FixedIP string `q:"fixed_ip_address"` FloatingIP string `q:"floating_ip_address"` TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` Limit int `q:"limit"` Marker string `q:"marker"` SortKey string `q:"sort_key"` SortDir string `q:"sort_dir"` RouterID string `q:"router_id"` + Status string `q:"status"` + Tags string `q:"tags"` + TagsAny string `q:"tags-any"` + NotTags string `q:"not-tags"` + NotTagsAny string `q:"not-tags-any"` } // List returns a Pager which allows you to iterate over a collection of @@ -38,8 +45,8 @@ func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager { }) } -// CreateOptsBuilder is the interface type must satisfy to be used as Create -// options. +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. type CreateOptsBuilder interface { ToFloatingIPCreateMap() (map[string]interface{}, error) } @@ -48,11 +55,14 @@ type CreateOptsBuilder interface { // resource. The only required fields are FloatingNetworkID and PortID which // refer to the external network and internal port respectively. type CreateOpts struct { + Description string `json:"description,omitempty"` FloatingNetworkID string `json:"floating_network_id" required:"true"` FloatingIP string `json:"floating_ip_address,omitempty"` PortID string `json:"port_id,omitempty"` FixedIP string `json:"fixed_ip_address,omitempty"` + SubnetID string `json:"subnet_id,omitempty"` TenantID string `json:"tenant_id,omitempty"` + ProjectID string `json:"project_id,omitempty"` } // ToFloatingIPCreateMap allows CreateOpts to satisfy the CreateOptsBuilder @@ -78,10 +88,10 @@ func (opts CreateOpts) ToFloatingIPCreateMap() (map[string]interface{}, error) { // return 404 error code. // // You must also configure an IP address for the port associated with the PortID -// you have provided - this is what the FixedIP refers to: an IP fixed to a port. -// Because a port might be associated with multiple IP addresses, you can use -// the FixedIP field to associate a particular IP address rather than have the -// API assume for you. If you specify an IP address that is not valid, the +// you have provided - this is what the FixedIP refers to: an IP fixed to a +// port. Because a port might be associated with multiple IP addresses, you can +// use the FixedIP field to associate a particular IP address rather than have +// the API assume for you. If you specify an IP address that is not valid, the // operation will fail and return a 400 error code. If the PortID and FixedIP // are already associated with another resource, the operation will fail and // returns a 409 error code. @@ -101,8 +111,8 @@ func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { return } -// UpdateOptsBuilder is the interface type must satisfy to be used as Update -// options. +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. type UpdateOptsBuilder interface { ToFloatingIPUpdateMap() (map[string]interface{}, error) } @@ -112,13 +122,24 @@ type UpdateOptsBuilder interface { // linked to. To associate the floating IP with a new internal port, provide its // ID. To disassociate the floating IP from all ports, provide an empty string. type UpdateOpts struct { - PortID *string `json:"port_id"` + Description *string `json:"description,omitempty"` + PortID *string `json:"port_id,omitempty"` + FixedIP string `json:"fixed_ip_address,omitempty"` } // ToFloatingIPUpdateMap allows UpdateOpts to satisfy the UpdateOptsBuilder // interface func (opts UpdateOpts) ToFloatingIPUpdateMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "floatingip") + b, err := gophercloud.BuildRequestBody(opts, "floatingip") + if err != nil { + return nil, err + } + + if m := b["floatingip"].(map[string]interface{}); m["port_id"] == "" { + m["port_id"] = nil + } + + return b, nil } // Update allows floating IP resources to be updated. Currently, the only way to diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/results.go index 29d5b5662..b0b25a9d6 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/results.go @@ -12,38 +12,49 @@ import ( // floating IPs can only be defined on networks where the `router:external' // attribute (provided by the external network extension) is set to True. type FloatingIP struct { - // Unique identifier for the floating IP instance. + // ID is the unique identifier for the floating IP instance. ID string `json:"id"` - // UUID of the external network where the floating IP is to be created. + // Description for the floating IP instance. + Description string `json:"description"` + + // FloatingNetworkID is the UUID of the external network where the floating + // IP is to be created. FloatingNetworkID string `json:"floating_network_id"` - // Address of the floating IP on the external network. + // FloatingIP is the address of the floating IP on the external network. FloatingIP string `json:"floating_ip_address"` - // UUID of the port on an internal network that is associated with the floating IP. + // PortID is the UUID of the port on an internal network that is associated + // with the floating IP. PortID string `json:"port_id"` - // The specific IP address of the internal port which should be associated - // with the floating IP. + // FixedIP is the specific IP address of the internal port which should be + // associated with the floating IP. FixedIP string `json:"fixed_ip_address"` - // Owner of the floating IP. Only admin users can specify a tenant identifier - // other than its own. + // TenantID is the project owner of the floating IP. Only admin users can + // specify a project identifier other than its own. TenantID string `json:"tenant_id"` - // The condition of the API resource. + // ProjectID is the project owner of the floating IP. + ProjectID string `json:"project_id"` + + // Status is the condition of the API resource. Status string `json:"status"` - //The ID of the router used for this Floating-IP + // RouterID is the ID of the router used for this floating IP. RouterID string `json:"router_id"` + + // Tags optionally set via extensions/attributestags + Tags []string `json:"tags"` } type commonResult struct { gophercloud.Result } -// Extract a result and extracts a FloatingIP resource. +// Extract will extract a FloatingIP resource from a result. func (r commonResult) Extract() (*FloatingIP, error) { var s struct { FloatingIP *FloatingIP `json:"floatingip"` @@ -52,22 +63,26 @@ func (r commonResult) Extract() (*FloatingIP, error) { return s.FloatingIP, err } -// CreateResult represents the result of a create operation. +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a FloatingIP. type CreateResult struct { commonResult } -// GetResult represents the result of a get operation. +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a FloatingIP. type GetResult struct { commonResult } -// UpdateResult represents the result of an update operation. +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a FloatingIP. type UpdateResult struct { commonResult } -// DeleteResult represents the result of an update operation. +// DeleteResult represents the result of an update operation. Call its +// ExtractErr method to determine if the request succeeded or failed. type DeleteResult struct { gophercloud.ErrResult } @@ -78,9 +93,9 @@ type FloatingIPPage struct { pagination.LinkedPageBase } -// NextPageURL is invoked when a paginated collection of floating IPs 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. +// NextPageURL is invoked when a paginated collection of floating IPs 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 FloatingIPPage) NextPageURL() (string, error) { var s struct { Links []gophercloud.Link `json:"floatingips_links"` @@ -92,15 +107,15 @@ func (r FloatingIPPage) NextPageURL() (string, error) { return gophercloud.ExtractNextURL(s.Links) } -// IsEmpty checks whether a NetworkPage struct is empty. +// IsEmpty checks whether a FloatingIPPage struct is empty. func (r FloatingIPPage) IsEmpty() (bool, error) { is, err := ExtractFloatingIPs(r) return len(is) == 0, err } -// ExtractFloatingIPs accepts a Page struct, specifically a FloatingIPPage struct, -// and extracts the elements into a slice of FloatingIP structs. In other words, -// a generic collection is mapped into a relevant slice. +// ExtractFloatingIPs accepts a Page struct, specifically a FloatingIPPage +// struct, and extracts the elements into a slice of FloatingIP structs. In +// other words, a generic collection is mapped into a relevant slice. func ExtractFloatingIPs(r pagination.Page) ([]FloatingIP, error) { var s struct { FloatingIPs []FloatingIP `json:"floatingips"` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/doc.go new file mode 100644 index 000000000..6ede7f5e1 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/doc.go @@ -0,0 +1,108 @@ +/* +Package routers enables management and retrieval of Routers from the OpenStack +Networking service. + +Example to List Routers + + listOpts := routers.ListOpts{} + allPages, err := routers.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allRouters, err := routers.ExtractRouters(allPages) + if err != nil { + panic(err) + } + + for _, router := range allRoutes { + fmt.Printf("%+v\n", router) + } + +Example to Create a Router + + iTrue := true + gwi := routers.GatewayInfo{ + NetworkID: "8ca37218-28ff-41cb-9b10-039601ea7e6b", + } + + createOpts := routers.CreateOpts{ + Name: "router_1", + AdminStateUp: &iTrue, + GatewayInfo: &gwi, + } + + router, err := routers.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Router + + routerID := "4e8e5957-649f-477b-9e5b-f1f75b21c03c" + + routes := []routers.Route{{ + DestinationCIDR: "40.0.1.0/24", + NextHop: "10.1.0.10", + }} + + updateOpts := routers.UpdateOpts{ + Name: "new_name", + Routes: routes, + } + + router, err := routers.Update(networkClient, routerID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Remove all Routes from a Router + + routerID := "4e8e5957-649f-477b-9e5b-f1f75b21c03c" + + routes := []routers.Route{} + + updateOpts := routers.UpdateOpts{ + Routes: routes, + } + + router, err := routers.Update(networkClient, routerID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Router + + routerID := "4e8e5957-649f-477b-9e5b-f1f75b21c03c" + err := routers.Delete(networkClient, routerID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Add an Interface to a Router + + routerID := "4e8e5957-649f-477b-9e5b-f1f75b21c03c" + + intOpts := routers.AddInterfaceOpts{ + SubnetID: "a2f1f29d-571b-4533-907f-5803ab96ead1", + } + + interface, err := routers.AddInterface(networkClient, routerID, intOpts).Extract() + if err != nil { + panic(err) + } + +Example to Remove an Interface from a Router + + routerID := "4e8e5957-649f-477b-9e5b-f1f75b21c03c" + + intOpts := routers.RemoveInterfaceOpts{ + SubnetID: "a2f1f29d-571b-4533-907f-5803ab96ead1", + } + + interface, err := routers.RemoveInterface(networkClient, routerID, intOpts).Extract() + if err != nil { + panic(err) + } +*/ +package routers diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/requests.go index 71b2f627d..cf499f987 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/requests.go @@ -13,14 +13,20 @@ import ( type ListOpts struct { ID string `q:"id"` Name string `q:"name"` + Description string `q:"description"` AdminStateUp *bool `q:"admin_state_up"` Distributed *bool `q:"distributed"` Status string `q:"status"` TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` Limit int `q:"limit"` Marker string `q:"marker"` SortKey string `q:"sort_key"` SortDir string `q:"sort_dir"` + Tags string `q:"tags"` + TagsAny string `q:"tags-any"` + NotTags string `q:"not-tags"` + NotTagsAny string `q:"not-tags-any"` } // List returns a Pager which allows you to iterate over a collection of @@ -40,10 +46,8 @@ func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager { }) } -// CreateOptsBuilder is the interface options structs have to satisfy in order -// to be used in the main Create operation in this package. Since many -// extensions decorate or modify the common logic, it is useful for them to -// satisfy a basic interface in order for them to be used. +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. type CreateOptsBuilder interface { ToRouterCreateMap() (map[string]interface{}, error) } @@ -51,13 +55,17 @@ type CreateOptsBuilder interface { // CreateOpts contains all the values needed to create a new router. There are // no required values. type CreateOpts struct { - Name string `json:"name,omitempty"` - AdminStateUp *bool `json:"admin_state_up,omitempty"` - Distributed *bool `json:"distributed,omitempty"` - TenantID string `json:"tenant_id,omitempty"` - GatewayInfo *GatewayInfo `json:"external_gateway_info,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + AdminStateUp *bool `json:"admin_state_up,omitempty"` + Distributed *bool `json:"distributed,omitempty"` + TenantID string `json:"tenant_id,omitempty"` + ProjectID string `json:"project_id,omitempty"` + GatewayInfo *GatewayInfo `json:"external_gateway_info,omitempty"` + AvailabilityZoneHints []string `json:"availability_zone_hints,omitempty"` } +// ToRouterCreateMap builds a create request body from CreateOpts. func (opts CreateOpts) ToRouterCreateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "router") } @@ -86,6 +94,8 @@ func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { return } +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. type UpdateOptsBuilder interface { ToRouterUpdateMap() (map[string]interface{}, error) } @@ -93,12 +103,14 @@ type UpdateOptsBuilder interface { // UpdateOpts contains the values used when updating a router. type UpdateOpts struct { Name string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` AdminStateUp *bool `json:"admin_state_up,omitempty"` Distributed *bool `json:"distributed,omitempty"` GatewayInfo *GatewayInfo `json:"external_gateway_info,omitempty"` Routes []Route `json:"routes"` } +// ToRouterUpdateMap builds an update body based on UpdateOpts. func (opts UpdateOpts) ToRouterUpdateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "router") } @@ -126,21 +138,19 @@ func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { return } -// AddInterfaceOptsBuilder is what types must satisfy to be used as AddInterface -// options. +// AddInterfaceOptsBuilder allows extensions to add additional parameters to +// the AddInterface request. type AddInterfaceOptsBuilder interface { ToRouterAddInterfaceMap() (map[string]interface{}, error) } -// AddInterfaceOpts allow you to work with operations that either add -// an internal interface from a router. +// AddInterfaceOpts represents the options for adding an interface to a router. type AddInterfaceOpts struct { SubnetID string `json:"subnet_id,omitempty" xor:"PortID"` PortID string `json:"port_id,omitempty" xor:"SubnetID"` } -// ToRouterAddInterfaceMap allows InterfaceOpts to satisfy the InterfaceOptsBuilder -// interface +// ToRouterAddInterfaceMap builds a request body from AddInterfaceOpts. func (opts AddInterfaceOpts) ToRouterAddInterfaceMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "") } @@ -178,21 +188,21 @@ func AddInterface(c *gophercloud.ServiceClient, id string, opts AddInterfaceOpts return } -// RemoveInterfaceOptsBuilder is what types must satisfy to be used as RemoveInterface -// options. +// RemoveInterfaceOptsBuilder allows extensions to add additional parameters to +// the RemoveInterface request. type RemoveInterfaceOptsBuilder interface { ToRouterRemoveInterfaceMap() (map[string]interface{}, error) } -// RemoveInterfaceOpts allow you to work with operations that either add or remote -// an internal interface from a router. +// RemoveInterfaceOpts represents options for removing an interface from +// a router. type RemoveInterfaceOpts struct { SubnetID string `json:"subnet_id,omitempty" or:"PortID"` PortID string `json:"port_id,omitempty" or:"SubnetID"` } -// ToRouterRemoveInterfaceMap allows RemoveInterfaceOpts to satisfy the RemoveInterfaceOptsBuilder -// interface +// ToRouterRemoveInterfaceMap builds a request body based on +// RemoveInterfaceOpts. func (opts RemoveInterfaceOpts) ToRouterRemoveInterfaceMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "") } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/results.go index d849d457a..a6fa4a35e 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/results.go @@ -8,7 +8,16 @@ import ( // GatewayInfo represents the information of an external gateway for any // particular network router. type GatewayInfo struct { - NetworkID string `json:"network_id"` + NetworkID string `json:"network_id"` + EnableSNAT *bool `json:"enable_snat,omitempty"` + ExternalFixedIPs []ExternalFixedIP `json:"external_fixed_ips,omitempty"` +} + +// ExternalFixedIP is the IP address and subnet ID of the external gateway of a +// router. +type ExternalFixedIP struct { + IPAddress string `json:"ip_address,omitempty"` + SubnetID string `json:"subnet_id"` } // Route is a possible route in a router. @@ -26,29 +35,44 @@ type Route struct { // whenever a router is associated with a subnet, a port for that router // interface is added to the subnet's network. type Router struct { - // Indicates whether or not a router is currently operational. + // Status indicates whether or not a router is currently operational. Status string `json:"status"` - // Information on external gateway for the router. + // GateayInfo provides information on external gateway for the router. GatewayInfo GatewayInfo `json:"external_gateway_info"` - // Administrative state of the router. + // AdminStateUp is the administrative state of the router. AdminStateUp bool `json:"admin_state_up"` - // Whether router is disitrubted or not.. + // Distributed is whether router is disitrubted or not. Distributed bool `json:"distributed"` - // Human readable name for the router. Does not have to be unique. + // Name is the human readable name for the router. It does not have to be + // unique. Name string `json:"name"` - // Unique identifier for the router. + // Description for the router. + Description string `json:"description"` + + // ID is the unique identifier for the router. ID string `json:"id"` - // Owner of the router. Only admin users can specify a tenant identifier - // other than its own. + // TenantID is the project owner of the router. Only admin users can + // specify a project identifier other than its own. TenantID string `json:"tenant_id"` + // ProjectID is the project owner of the router. + ProjectID string `json:"project_id"` + + // Routes are a collection of static routes that the router will host. Routes []Route `json:"routes"` + + // Availability zone hints groups network nodes that run services like DHCP, L3, FW, and others. + // Used to make network resources highly available. + AvailabilityZoneHints []string `json:"availability_zone_hints"` + + // Tags optionally set via extensions/attributestags + Tags []string `json:"tags"` } // RouterPage is the page returned by a pager when traversing over a @@ -101,22 +125,26 @@ func (r commonResult) Extract() (*Router, error) { return s.Router, err } -// CreateResult represents the result of a create operation. +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a Router. type CreateResult struct { commonResult } -// GetResult represents the result of a get operation. +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a Router. type GetResult struct { commonResult } -// UpdateResult represents the result of an update operation. +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a Router. type UpdateResult struct { commonResult } -// DeleteResult represents the result of a delete operation. +// DeleteResult represents the result of a delete operation. Call its ExtractErr +// method to determine if the request succeeded or failed. type DeleteResult struct { gophercloud.ErrResult } @@ -125,21 +153,22 @@ type DeleteResult struct { // mentioned above, in order for a router to forward to a subnet, it needs an // interface. type InterfaceInfo struct { - // The ID of the subnet which this interface is associated with. + // SubnetID is the ID of the subnet which this interface is associated with. SubnetID string `json:"subnet_id"` - // The ID of the port that is a part of the subnet. + // PortID is the ID of the port that is a part of the subnet. PortID string `json:"port_id"` - // The UUID of the interface. + // ID is the UUID of the interface. ID string `json:"id"` - // Owner of the interface. + // TenantID is the owner of the interface. TenantID string `json:"tenant_id"` } // InterfaceResult represents the result of interface operations, such as -// AddInterface() and RemoveInterface(). +// AddInterface() and RemoveInterface(). Call its Extract method to interpret +// the result as a InterfaceInfo. type InterfaceResult struct { gophercloud.Result } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/members/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/members/doc.go new file mode 100644 index 000000000..bad3324b5 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/members/doc.go @@ -0,0 +1,59 @@ +/* +Package members provides information and interaction with Members of the +Load Balancer as a Service extension for the OpenStack Networking service. + +Example to List Members + + listOpts := members.ListOpts{ + ProtocolPort: 80, + } + + allPages, err := members.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allMembers, err := members.ExtractMembers(allPages) + if err != nil { + panic(err) + } + + for _, member := range allMembers { + fmt.Printf("%+v\n", member) + } + +Example to Create a Member + + createOpts := members.CreateOpts{ + Address: "192.168.2.14", + ProtocolPort: 80, + PoolID: "0b266a12-0fdf-4434-bd11-649d84e54bd5" + } + + member, err := members.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Member + + memberID := "46592c54-03f7-40ef-9cdf-b1fcf2775ddf" + + updateOpts := members.UpdateOpts{ + AdminStateUp: gophercloud.Disabled, + } + + member, err := members.Update(networkClient, memberID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Member + + memberID := "46592c54-03f7-40ef-9cdf-b1fcf2775ddf" + err := members.Delete(networkClient, memberID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package members diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/members/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/members/requests.go index 7e7b76885..1a3128844 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/members/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/members/requests.go @@ -26,10 +26,10 @@ type ListOpts struct { } // List returns a Pager which allows you to iterate over a collection of -// pools. It accepts a ListOpts struct, which allows you to filter and sort +// members. It accepts a ListOpts struct, which allows you to filter and sort // the returned collection for greater efficiency. // -// Default policy settings return only those pools that are owned by the +// Default policy settings return only those members that are owned by the // tenant who submits the request, unless an admin user submits the request. func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager { q, err := gophercloud.BuildQueryString(&opts) @@ -42,23 +42,29 @@ func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager { }) } +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. type CreateOptsBuilder interface { ToLBMemberCreateMap() (map[string]interface{}, error) } // CreateOpts contains all the values needed to create a new pool member. type CreateOpts struct { - // The IP address of the member. + // Address is the IP address of the member. Address string `json:"address" required:"true"` - // The port on which the application is hosted. + + // ProtocolPort is the port on which the application is hosted. ProtocolPort int `json:"protocol_port" required:"true"` - // The pool to which this member will belong. + + // PoolID is the pool to which this member will belong. PoolID string `json:"pool_id" required:"true"` - // Only required if the caller has an admin role and wants to create a pool - // for another tenant. + + // TenantID is only required if the caller has an admin role and wants + // to create a pool for another tenant. TenantID string `json:"tenant_id,omitempty"` } +// ToLBMemberCreateMap builds a request body from CreateOpts. func (opts CreateOpts) ToLBMemberCreateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "member") } @@ -81,6 +87,8 @@ func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { return } +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. type UpdateOptsBuilder interface { ToLBMemberUpdateMap() (map[string]interface{}, error) } @@ -91,6 +99,7 @@ type UpdateOpts struct { AdminStateUp *bool `json:"admin_state_up,omitempty"` } +// ToLBMemberUpdateMap builds a request body from UpdateOpts. func (opts UpdateOpts) ToLBMemberUpdateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "member") } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/members/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/members/results.go index 933e1aedb..804dbe844 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/members/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/members/results.go @@ -7,29 +7,30 @@ import ( // Member represents the application running on a backend server. type Member struct { - // The status of the member. Indicates whether the member is operational. + // Status is the status of the member. Indicates whether the member + // is operational. Status string - // Weight of member. + // Weight is the weight of member. Weight int - // The administrative state of the member, which is up (true) or down (false). + // AdminStateUp is the administrative state of the member, which is up + // (true) or down (false). AdminStateUp bool `json:"admin_state_up"` - // Owner of the member. Only an administrative user can specify a tenant ID - // other than its own. + // TenantID is the owner of the member. TenantID string `json:"tenant_id"` - // The pool to which the member belongs. + // PoolID is the pool to which the member belongs. PoolID string `json:"pool_id"` - // The IP address of the member. + // Address is the IP address of the member. Address string - // The port on which the application is hosted. + // ProtocolPort is the port on which the application is hosted. ProtocolPort int `json:"protocol_port"` - // The unique ID for the member. + // ID is the unique ID for the member. ID string } @@ -74,7 +75,7 @@ type commonResult struct { gophercloud.Result } -// Extract is a function that accepts a result and extracts a router. +// Extract is a function that accepts a result and extracts a member. func (r commonResult) Extract() (*Member, error) { var s struct { Member *Member `json:"member"` @@ -83,22 +84,26 @@ func (r commonResult) Extract() (*Member, error) { return s.Member, err } -// CreateResult represents the result of a create operation. +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a Member. type CreateResult struct { commonResult } -// GetResult represents the result of a get operation. +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a Member. type GetResult struct { commonResult } -// UpdateResult represents the result of an update operation. +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a Member. type UpdateResult struct { commonResult } -// DeleteResult represents the result of a delete operation. +// DeleteResult represents the result of a delete operation. Call its +// ExtractErr method to determine if the result succeeded or failed. type DeleteResult struct { gophercloud.ErrResult } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/monitors/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/monitors/doc.go new file mode 100644 index 000000000..b5c0f29f0 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/monitors/doc.go @@ -0,0 +1,63 @@ +/* +Package monitors provides information and interaction with the Monitors +of the Load Balancer as a Service extension for the OpenStack Networking +Service. + +Example to List Monitors + + listOpts: monitors.ListOpts{ + Type: "HTTP", + } + + allPages, err := monitors.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allMonitors, err := monitors.ExtractMonitors(allPages) + if err != nil { + panic(err) + } + + for _, monitor := range allMonitors { + fmt.Printf("%+v\n", monitor) + } + +Example to Create a Monitor + + createOpts := monitors.CreateOpts{ + Type: "HTTP", + Delay: 20, + Timeout: 20, + MaxRetries: 5, + URLPath: "/check", + ExpectedCodes: "200-299", + } + + monitor, err := monitors.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Monitor + + monitorID := "681aed03-aadb-43ae-aead-b9016375650a" + + updateOpts := monitors.UpdateOpts{ + Timeout: 30, + } + + monitor, err := monitors.Update(networkClient, monitorID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Member + + monitorID := "681aed03-aadb-43ae-aead-b9016375650a" + err := monitors.Delete(networkClient, monitorID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package monitors diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/monitors/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/monitors/requests.go index f1b964b65..9ed0c769c 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/monitors/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/monitors/requests.go @@ -31,10 +31,10 @@ type ListOpts struct { } // List returns a Pager which allows you to iterate over a collection of -// routers. It accepts a ListOpts struct, which allows you to filter and sort +// monitors. It accepts a ListOpts struct, which allows you to filter and sort // the returned collection for greater efficiency. // -// Default policy settings return only those routers that are owned by the +// Default policy settings return only those monitors that are owned by the // tenant who submits the request, unless an admin user submits the request. func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager { q, err := gophercloud.BuildQueryString(&opts) @@ -47,7 +47,7 @@ func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager { }) } -// MonitorType is the type for all the types of LB monitors +// MonitorType is the type for all the types of LB monitors. type MonitorType string // Constants that represent approved monitoring types. @@ -58,42 +58,52 @@ const ( TypeHTTPS MonitorType = "HTTPS" ) -// CreateOptsBuilder is what types must satisfy to be used as Create -// options. +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. type CreateOptsBuilder interface { ToLBMonitorCreateMap() (map[string]interface{}, error) } // CreateOpts contains all the values needed to create a new health monitor. type CreateOpts struct { - // Required. The type of probe, which is PING, TCP, HTTP, or HTTPS, that is - // sent by the load balancer to verify the member state. + // MonitorType is the type of probe, which is PING, TCP, HTTP, or HTTPS, + // that is sent by the load balancer to verify the member state. Type MonitorType `json:"type" required:"true"` - // Required. The time, in seconds, between sending probes to members. + + // Delay is the time, in seconds, between sending probes to members. Delay int `json:"delay" required:"true"` - // Required. Maximum number of seconds for a monitor to wait for a ping reply - // before it times out. The value must be less than the delay value. + + // Timeout is the maximum number of seconds for a monitor to wait for a ping + // reply before it times out. The value must be less than the delay value. Timeout int `json:"timeout" required:"true"` - // Required. Number of permissible ping failures before changing the member's - // status to INACTIVE. Must be a number between 1 and 10. + + // MaxRetries is the number of permissible ping failures before changing the + // member's status to INACTIVE. Must be a number between 1 and 10. MaxRetries int `json:"max_retries" required:"true"` - // Required for HTTP(S) types. URI path that will be accessed if monitor type - // is HTTP or HTTPS. + + // URLPath is the URI path that will be accessed if monitor type + // is HTTP or HTTPS. Required for HTTP(S) types. URLPath string `json:"url_path,omitempty"` - // Required for HTTP(S) types. The HTTP method used for requests by the - // monitor. If this attribute is not specified, it defaults to "GET". + + // HTTPMethod is the HTTP method used for requests by the monitor. If this + // attribute is not specified, it defaults to "GET". Required for HTTP(S) + // types. HTTPMethod string `json:"http_method,omitempty"` - // Required for HTTP(S) types. Expected HTTP codes for a passing HTTP(S) - // monitor. You can either specify a single status like "200", or a range - // like "200-202". + + // ExpectedCodes is the expected HTTP codes for a passing HTTP(S) monitor + // You can either specify a single status like "200", or a range like + // "200-202". Required for HTTP(S) types. ExpectedCodes string `json:"expected_codes,omitempty"` - // Required for admins. Indicates the owner of the VIP. - TenantID string `json:"tenant_id,omitempty"` - AdminStateUp *bool `json:"admin_state_up,omitempty"` + + // TenantID is only required if the caller has an admin role and wants + // to create a pool for another tenant. + TenantID string `json:"tenant_id,omitempty"` + + // AdminStateUp denotes whether the monitor is administratively up or down. + AdminStateUp *bool `json:"admin_state_up,omitempty"` } -// ToLBMonitorCreateMap allows CreateOpts to satisfy the CreateOptsBuilder -// interface +// ToLBMonitorCreateMap builds a request body from CreateOpts. func (opts CreateOpts) ToLBMonitorCreateMap() (map[string]interface{}, error) { if opts.Type == TypeHTTP || opts.Type == TypeHTTPS { if opts.URLPath == "" { @@ -146,39 +156,45 @@ func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { return } -// UpdateOptsBuilder is what types must satisfy to be used as Update -// options. +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. type UpdateOptsBuilder interface { ToLBMonitorUpdateMap() (map[string]interface{}, error) } -// UpdateOpts contains all the values needed to update an existing virtual IP. +// UpdateOpts contains all the values needed to update an existing monitor. // Attributes not listed here but appear in CreateOpts are immutable and cannot // be updated. type UpdateOpts struct { - // The time, in seconds, between sending probes to members. + // Delay is the time, in seconds, between sending probes to members. Delay int `json:"delay,omitempty"` - // Maximum number of seconds for a monitor to wait for a ping reply - // before it times out. The value must be less than the delay value. + + // Timeout is the maximum number of seconds for a monitor to wait for a ping + // reply before it times out. The value must be less than the delay value. Timeout int `json:"timeout,omitempty"` - // Number of permissible ping failures before changing the member's - // status to INACTIVE. Must be a number between 1 and 10. + + // MaxRetries is the number of permissible ping failures before changing the + // member's status to INACTIVE. Must be a number between 1 and 10. MaxRetries int `json:"max_retries,omitempty"` - // URI path that will be accessed if monitor type + + // URLPath is the URI path that will be accessed if monitor type // is HTTP or HTTPS. URLPath string `json:"url_path,omitempty"` - // The HTTP method used for requests by the - // monitor. If this attribute is not specified, it defaults to "GET". + + // HTTPMethod is the HTTP method used for requests by the monitor. If this + // attribute is not specified, it defaults to "GET". HTTPMethod string `json:"http_method,omitempty"` - // Expected HTTP codes for a passing HTTP(S) - // monitor. You can either specify a single status like "200", or a range - // like "200-202". + + // ExpectedCodes is the expected HTTP codes for a passing HTTP(S) monitor + // You can either specify a single status like "200", or a range like + // "200-202". ExpectedCodes string `json:"expected_codes,omitempty"` - AdminStateUp *bool `json:"admin_state_up,omitempty"` + + // AdminStateUp denotes whether the monitor is administratively up or down. + AdminStateUp *bool `json:"admin_state_up,omitempty"` } -// ToLBMonitorUpdateMap allows UpdateOpts to satisfy the UpdateOptsBuilder -// interface +// ToLBMonitorUpdateMap builds a request body from UpdateOpts. func (opts UpdateOpts) ToLBMonitorUpdateMap() (map[string]interface{}, error) { if opts.Delay > 0 && opts.Timeout > 0 && opts.Delay < opts.Timeout { err := gophercloud.ErrInvalidInput{} @@ -190,7 +206,8 @@ func (opts UpdateOpts) ToLBMonitorUpdateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "health_monitor") } -// Update is an operation which modifies the attributes of the specified monitor. +// Update is an operation which modifies the attributes of the specified +// monitor. func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { b, err := opts.ToLBMonitorUpdateMap() if err != nil { diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/monitors/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/monitors/results.go index 0385942c8..cc99f7cce 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/monitors/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/monitors/results.go @@ -21,46 +21,47 @@ import ( // won't participate in its pool's load balancing. In other words, ALL monitors // must declare the member to be healthy for it to stay ACTIVE. type Monitor struct { - // The unique ID for the VIP. + // ID is the unique ID for the Monitor. ID string - // Monitor name. Does not have to be unique. + // Name is the monitor name. Does not have to be unique. Name string - // Owner of the VIP. Only an administrative user can specify a tenant ID - // other than its own. + // TenantID is the owner of the Monitor. TenantID string `json:"tenant_id"` - // The type of probe sent by the load balancer to verify the member state, - // which is PING, TCP, HTTP, or HTTPS. + // Type is the type of probe sent by the load balancer to verify the member + // state, which is PING, TCP, HTTP, or HTTPS. Type string - // The time, in seconds, between sending probes to members. + // Delay is the time, in seconds, between sending probes to members. Delay int - // The maximum number of seconds for a monitor to wait for a connection to be - // established before it times out. This value must be less than the delay value. + // Timeout is the maximum number of seconds for a monitor to wait for a + // connection to be established before it times out. This value must be less + // than the delay value. Timeout int - // Number of allowed connection failures before changing the status of the - // member to INACTIVE. A valid value is from 1 to 10. + // MaxRetries is the number of allowed connection failures before changing the + // status of the member to INACTIVE. A valid value is from 1 to 10. MaxRetries int `json:"max_retries"` - // The HTTP method that the monitor uses for requests. + // HTTPMethod is the HTTP method that the monitor uses for requests. HTTPMethod string `json:"http_method"` - // The HTTP path of the request sent by the monitor to test the health of a - // member. Must be a string beginning with a forward slash (/). + // URLPath is the HTTP path of the request sent by the monitor to test the + // health of a member. Must be a string beginning with a forward slash (/). URLPath string `json:"url_path"` - // Expected HTTP codes for a passing HTTP(S) monitor. + // ExpectedCodes is the expected HTTP codes for a passing HTTP(S) monitor. ExpectedCodes string `json:"expected_codes"` - // The administrative state of the health monitor, which is up (true) or down (false). + // AdminStateUp is the administrative state of the health monitor, which is up + // (true) or down (false). AdminStateUp bool `json:"admin_state_up"` - // The status of the health monitor. Indicates whether the health monitor is - // operational. + // Status is the status of the health monitor. Indicates whether the health + // monitor is operational. Status string } @@ -115,22 +116,26 @@ func (r commonResult) Extract() (*Monitor, error) { return s.Monitor, err } -// CreateResult represents the result of a create operation. +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a Monitor. type CreateResult struct { commonResult } -// GetResult represents the result of a get operation. +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a Monitor. type GetResult struct { commonResult } -// UpdateResult represents the result of an update operation. +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a Monitor. type UpdateResult struct { commonResult } -// DeleteResult represents the result of a delete operation. +// DeleteResult represents the result of a delete operation. Call its Extract +// method to determine if the request succeeded or failed. type DeleteResult struct { gophercloud.ErrResult } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools/doc.go new file mode 100644 index 000000000..25c4204dc --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools/doc.go @@ -0,0 +1,81 @@ +/* +Package pools provides information and interaction with the Pools of the +Load Balancing as a Service extension for the OpenStack Networking service. + +Example to List Pools + + listOpts := pools.ListOpts{ + SubnetID: "d9bd223b-f1a9-4f98-953b-df977b0f902d", + } + + allPages, err := pools.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allPools, err := pools.ExtractPools(allPages) + if err != nil { + panic(err) + } + + for _, pool := range allPools { + fmt.Printf("%+v\n", pool) + } + +Example to Create a Pool + + createOpts := pools.CreateOpts{ + LBMethod: pools.LBMethodRoundRobin, + Protocol: "HTTP", + Name: "Example pool", + SubnetID: "1981f108-3c48-48d2-b908-30f7d28532c9", + Provider: "haproxy", + } + + pool, err := pools.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Pool + + poolID := "166db5e6-c72a-4d77-8776-3573e27ae271" + + updateOpts := pools.UpdateOpts{ + LBMethod: pools.LBMethodLeastConnections, + } + + pool, err := pools.Update(networkClient, poolID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Pool + + poolID := "166db5e6-c72a-4d77-8776-3573e27ae271" + err := pools.Delete(networkClient, poolID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Associate a Monitor to a Pool + + poolID := "166db5e6-c72a-4d77-8776-3573e27ae271" + monitorID := "8bbfbe1c-6faa-4d97-abdb-0df6c90df70b" + + pool, err := pools.AssociateMonitor(networkClient, poolID, monitorID).Extract() + if err != nil { + panic(err) + } + +Example to Disassociate a Monitor from a Pool + + poolID := "166db5e6-c72a-4d77-8776-3573e27ae271" + monitorID := "8bbfbe1c-6faa-4d97-abdb-0df6c90df70b" + + pool, err := pools.DisassociateMonitor(networkClient, poolID, monitorID).Extract() + if err != nil { + panic(err) + } +*/ +package pools diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools/requests.go index 2a75737a8..f5f4e9a0d 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools/requests.go @@ -43,10 +43,10 @@ func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager { }) } -// LBMethod is a type used for possible load balancing methods +// LBMethod is a type used for possible load balancing methods. type LBMethod string -// LBProtocol is a type used for possible load balancing protocols +// LBProtocol is a type used for possible load balancing protocols. type LBProtocol string // Supported attributes for create/update operations. @@ -59,8 +59,8 @@ const ( ProtocolHTTPS LBProtocol = "HTTPS" ) -// CreateOptsBuilder is the interface types must satisfy to be used as options -// for the Create function +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. type CreateOptsBuilder interface { ToLBPoolCreateMap() (map[string]interface{}, error) } @@ -69,25 +69,29 @@ type CreateOptsBuilder interface { type CreateOpts struct { // Name of the pool. Name string `json:"name" required:"true"` - // The protocol used by the pool members, you can use either + + // Protocol used by the pool members, you can use either // ProtocolTCP, ProtocolHTTP, or ProtocolHTTPS. Protocol LBProtocol `json:"protocol" required:"true"` - // Only required if the caller has an admin role and wants to create a pool - // for another tenant. + + // TenantID is only required if the caller has an admin role and wants + // to create a pool for another tenant. TenantID string `json:"tenant_id,omitempty"` - // The network on which the members of the pool will be located. Only members - // that are on this network can be added to the pool. + + // SubnetID is the network on which the members of the pool will be located. + // Only members that are on this network can be added to the pool. SubnetID string `json:"subnet_id,omitempty"` - // The algorithm used to distribute load between the members of the pool. The - // current specification supports LBMethodRoundRobin and + + // LBMethod is the algorithm used to distribute load between the members of + // the pool. The current specification supports LBMethodRoundRobin and // LBMethodLeastConnections as valid values for this attribute. LBMethod LBMethod `json:"lb_method" required:"true"` - // The provider of the pool + // Provider of the pool. Provider string `json:"provider,omitempty"` } -// ToLBPoolCreateMap allows CreateOpts to satisfy the CreateOptsBuilder interface +// ToLBPoolCreateMap builds a request body based on CreateOpts. func (opts CreateOpts) ToLBPoolCreateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "pool") } @@ -110,8 +114,8 @@ func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { return } -// UpdateOptsBuilder is the interface types must satisfy to be used as options -// for the Update function +// UpdateOptsBuilder allows extensions to add additional parameters ot the +// Update request. type UpdateOptsBuilder interface { ToLBPoolUpdateMap() (map[string]interface{}, error) } @@ -119,14 +123,15 @@ type UpdateOptsBuilder interface { // UpdateOpts contains the values used when updating a pool. type UpdateOpts struct { // Name of the pool. - Name string `json:"name,omitempty"` - // The algorithm used to distribute load between the members of the pool. The - // current specification supports LBMethodRoundRobin and + Name *string `json:"name,omitempty"` + + // LBMethod is the algorithm used to distribute load between the members of + // the pool. The current specification supports LBMethodRoundRobin and // LBMethodLeastConnections as valid values for this attribute. LBMethod LBMethod `json:"lb_method,omitempty"` } -// ToLBPoolUpdateMap allows UpdateOpts to satisfy the UpdateOptsBuilder interface +// ToLBPoolUpdateMap builds a request body based on UpdateOpts. func (opts UpdateOpts) ToLBPoolUpdateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "pool") } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools/results.go index 2ca1963f2..c2bae82d5 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools/results.go @@ -11,47 +11,48 @@ import ( // method to handle the new requests or connections received on the VIP address. // There is only one pool per virtual IP. type Pool struct { - // The status of the pool. Indicates whether the pool is operational. + // Status of the pool. Indicates whether the pool is operational. Status string - // The load-balancer algorithm, which is round-robin, least-connections, and - // so on. This value, which must be supported, is dependent on the provider. - // Round-robin must be supported. + // LBMethod is the load-balancer algorithm, which is round-robin, + // least-connections, and so on. This value, which must be supported, is + // dependent on the provider. LBMethod string `json:"lb_method"` - // The protocol of the pool, which is TCP, HTTP, or HTTPS. + // Protocol of the pool, which is TCP, HTTP, or HTTPS. Protocol string // Description for the pool. Description string - // The IDs of associated monitors which check the health of the pool members. + // MonitorIDs are the IDs of associated monitors which check the health of + // the pool members. MonitorIDs []string `json:"health_monitors"` - // The network on which the members of the pool will be located. Only members - // that are on this network can be added to the pool. + // SubnetID is the network on which the members of the pool will be located. + // Only members that are on this network can be added to the pool. SubnetID string `json:"subnet_id"` - // Owner of the pool. Only an administrative user can specify a tenant ID - // other than its own. + // TenantID is the owner of the pool. TenantID string `json:"tenant_id"` - // The administrative state of the pool, which is up (true) or down (false). + // AdminStateUp is the administrative state of the pool, which is up + // (true) or down (false). AdminStateUp bool `json:"admin_state_up"` - // Pool name. Does not have to be unique. + // Name of the pool. Name string - // List of member IDs that belong to the pool. + // MemberIDs is the list of member IDs that belong to the pool. MemberIDs []string `json:"members"` - // The unique ID for the pool. + // ID is the unique ID for the pool. ID string - // The ID of the virtual IP associated with this pool + // VIPID is the ID of the virtual IP associated with this pool. VIPID string `json:"vip_id"` - // The provider + // The provider. Provider string } @@ -81,7 +82,7 @@ func (r PoolPage) IsEmpty() (bool, error) { return len(is) == 0, err } -// ExtractPools accepts a Page struct, specifically a RouterPage struct, +// ExtractPools accepts a Page struct, specifically a PoolPage struct, // and extracts the elements into a slice of Router structs. In other words, // a generic collection is mapped into a relevant slice. func ExtractPools(r pagination.Page) ([]Pool, error) { @@ -105,27 +106,32 @@ func (r commonResult) Extract() (*Pool, error) { return s.Pool, err } -// CreateResult represents the result of a create operation. +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a Pool. type CreateResult struct { commonResult } -// GetResult represents the result of a get operation. +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a Pool. type GetResult struct { commonResult } -// UpdateResult represents the result of an update operation. +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a Pool. type UpdateResult struct { commonResult } -// DeleteResult represents the result of a delete operation. +// DeleteResult represents the result of a delete operation. Call its +// ExtractErr method to interpret it as a Pool. type DeleteResult struct { gophercloud.ErrResult } -// AssociateResult represents the result of an association operation. +// AssociateResult represents the result of an association operation. Call its Extract +// method to interpret it as a Pool. type AssociateResult struct { commonResult } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/vips/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/vips/doc.go new file mode 100644 index 000000000..7fd861044 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/vips/doc.go @@ -0,0 +1,65 @@ +/* +Package vips provides information and interaction with the Virtual IPs of the +Load Balancing as a Service extension for the OpenStack Networking service. + +Example to List Virtual IPs + + listOpts := vips.ListOpts{ + SubnetID: "d9bd223b-f1a9-4f98-953b-df977b0f902d", + } + + allPages, err := vips.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allVIPs, err := vips.ExtractVIPs(allPages) + if err != nil { + panic(err) + } + + for _, vip := range allVIPs { + fmt.Printf("%+v\n", vip) + } + +Example to Create a Virtual IP + + createOpts := vips.CreateOpts{ + Protocol: "HTTP", + Name: "NewVip", + AdminStateUp: gophercloud.Enabled, + SubnetID: "8032909d-47a1-4715-90af-5153ffe39861", + PoolID: "61b1f87a-7a21-4ad3-9dda-7f81d249944f", + ProtocolPort: 80, + Persistence: &vips.SessionPersistence{Type: "SOURCE_IP"}, + } + + vip, err := vips.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Virtual IP + + vipID := "93f1bad4-0423-40a8-afac-3fc541839912" + + i1000 := 1000 + updateOpts := vips.UpdateOpts{ + ConnLimit: &i1000, + Persistence: &vips.SessionPersistence{Type: "SOURCE_IP"}, + } + + vip, err := vips.Update(networkClient, vipID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Virtual IP + + vipID := "93f1bad4-0423-40a8-afac-3fc541839912" + err := vips.Delete(networkClient, vipID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package vips diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/vips/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/vips/requests.go index f89d769ad..53b81bfdb 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/vips/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/vips/requests.go @@ -29,10 +29,10 @@ type ListOpts struct { } // List returns a Pager which allows you to iterate over a collection of -// routers. It accepts a ListOpts struct, which allows you to filter and sort -// the returned collection for greater efficiency. +// Virtual IPs. It accepts a ListOpts struct, which allows you to filter and +// sort the returned collection for greater efficiency. // -// Default policy settings return only those routers that are owned by the +// Default policy settings return only those virtual IPs that are owned by the // tenant who submits the request, unless an admin user submits the request. func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager { q, err := gophercloud.BuildQueryString(&opts) @@ -45,43 +45,54 @@ func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager { }) } -// CreateOptsBuilder is what types must satisfy to be used as Create -// options. +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create Request. type CreateOptsBuilder interface { ToVIPCreateMap() (map[string]interface{}, error) } // CreateOpts contains all the values needed to create a new virtual IP. type CreateOpts struct { - // Human-readable name for the VIP. Does not have to be unique. + // Name is the human-readable name for the VIP. Does not have to be unique. Name string `json:"name" required:"true"` - // The network on which to allocate the VIP's address. A tenant can - // only create VIPs on networks authorized by policy (e.g. networks that + + // SubnetID is the network on which to allocate the VIP's address. A tenant + // can only create VIPs on networks authorized by policy (e.g. networks that // belong to them or networks that are shared). SubnetID string `json:"subnet_id" required:"true"` - // The protocol - can either be TCP, HTTP or HTTPS. + + // Protocol - can either be TCP, HTTP or HTTPS. Protocol string `json:"protocol" required:"true"` - // The port on which to listen for client traffic. + + // ProtocolPort is the port on which to listen for client traffic. ProtocolPort int `json:"protocol_port" required:"true"` - // The ID of the pool with which the VIP is associated. + + // PoolID is the ID of the pool with which the VIP is associated. PoolID string `json:"pool_id" required:"true"` - // Required for admins. Indicates the owner of the VIP. + + // TenantID is only required if the caller has an admin role and wants + // to create a pool for another tenant. TenantID string `json:"tenant_id,omitempty"` - // The IP address of the VIP. + + // Address is the IP address of the VIP. Address string `json:"address,omitempty"` - // Human-readable description for the VIP. + + // Description is the human-readable description for the VIP. Description string `json:"description,omitempty"` + + // Persistence is the the of session persistence to use. // Omit this field to prevent session persistence. Persistence *SessionPersistence `json:"session_persistence,omitempty"` - // The maximum number of connections allowed for the VIP. + + // ConnLimit is the maximum number of connections allowed for the VIP. ConnLimit *int `json:"connection_limit,omitempty"` - // The administrative state of the VIP. A valid value is true (UP) - // or false (DOWN). + + // AdminStateUp is the administrative state of the VIP. A valid value is + // true (UP) or false (DOWN). AdminStateUp *bool `json:"admin_state_up,omitempty"` } -// ToVIPCreateMap allows CreateOpts to satisfy the CreateOptsBuilder -// interface +// ToVIPCreateMap builds a request body from CreateOpts. func (opts CreateOpts) ToVIPCreateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "vip") } @@ -113,8 +124,8 @@ func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { return } -// UpdateOptsBuilder is what types must satisfy to be used as Update -// options. +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. type UpdateOptsBuilder interface { ToVIPUpdateMap() (map[string]interface{}, error) } @@ -123,22 +134,28 @@ type UpdateOptsBuilder interface { // Attributes not listed here but appear in CreateOpts are immutable and cannot // be updated. type UpdateOpts struct { - // Human-readable name for the VIP. Does not have to be unique. + // Name is the human-readable name for the VIP. Does not have to be unique. Name *string `json:"name,omitempty"` - // The ID of the pool with which the VIP is associated. + + // PoolID is the ID of the pool with which the VIP is associated. PoolID *string `json:"pool_id,omitempty"` - // Human-readable description for the VIP. + + // Description is the human-readable description for the VIP. Description *string `json:"description,omitempty"` + + // Persistence is the the of session persistence to use. // Omit this field to prevent session persistence. Persistence *SessionPersistence `json:"session_persistence,omitempty"` - // The maximum number of connections allowed for the VIP. + + // ConnLimit is the maximum number of connections allowed for the VIP. ConnLimit *int `json:"connection_limit,omitempty"` - // The administrative state of the VIP. A valid value is true (UP) - // or false (DOWN). + + // AdminStateUp is the administrative state of the VIP. A valid value is + // true (UP) or false (DOWN). AdminStateUp *bool `json:"admin_state_up,omitempty"` } -// ToVIPUpdateMap allows UpdateOpts to satisfy the UpdateOptsBuilder interface +// ToVIPUpdateMap builds a request body based on UpdateOpts. func (opts UpdateOpts) ToVIPUpdateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "vip") } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/vips/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/vips/results.go index 7ac7e79be..cb0994a7b 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/vips/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/vips/results.go @@ -21,10 +21,10 @@ import ( // requests carrying the same cookie value will be handled by the // same member of the pool. type SessionPersistence struct { - // The type of persistence mode + // Type is the type of persistence mode. Type string `json:"type"` - // Name of cookie if persistence mode is set appropriately + // CookieName is the name of cookie if persistence mode is set appropriately. CookieName string `json:"cookie_name,omitempty"` } @@ -34,54 +34,55 @@ type SessionPersistence struct { // This entity is sometimes known in LB products under the name of a "virtual // server", a "vserver" or a "listener". type VirtualIP struct { - // The unique ID for the VIP. + // ID is the unique ID for the VIP. ID string `json:"id"` - // Owner of the VIP. Only an admin user can specify a tenant ID other than its own. + // TenantID is the owner of the VIP. TenantID string `json:"tenant_id"` - // Human-readable name for the VIP. Does not have to be unique. + // Name is the human-readable name for the VIP. Does not have to be unique. Name string `json:"name"` - // Human-readable description for the VIP. + // Description is the human-readable description for the VIP. Description string `json:"description"` - // The ID of the subnet on which to allocate the VIP address. + // SubnetID is the ID of the subnet on which to allocate the VIP address. SubnetID string `json:"subnet_id"` - // The IP address of the VIP. + // Address is the IP address of the VIP. Address string `json:"address"` - // The protocol of the VIP address. A valid value is TCP, HTTP, or HTTPS. + // Protocol of the VIP address. A valid value is TCP, HTTP, or HTTPS. Protocol string `json:"protocol"` - // The port on which to listen to client traffic that is associated with the - // VIP address. A valid value is from 0 to 65535. + // ProtocolPort is the port on which to listen to client traffic that is + // associated with the VIP address. A valid value is from 0 to 65535. ProtocolPort int `json:"protocol_port"` - // The ID of the pool with which the VIP is associated. + // PoolID is the ID of the pool with which the VIP is associated. PoolID string `json:"pool_id"` - // The ID of the port which belongs to the load balancer + // PortID is the ID of the port which belongs to the load balancer. PortID string `json:"port_id"` - // Indicates whether connections in the same session will be processed by the - // same pool member or not. + // Persistence indicates whether connections in the same session will be + // processed by the same pool member or not. Persistence SessionPersistence `json:"session_persistence"` - // The maximum number of connections allowed for the VIP. Default is -1, - // meaning no limit. + // ConnLimit is the maximum number of connections allowed for the VIP. + // Default is -1, meaning no limit. ConnLimit int `json:"connection_limit"` - // The administrative state of the VIP. A valid value is true (UP) or false (DOWN). + // AdminStateUp is the administrative state of the VIP. A valid value is + // true (UP) or false (DOWN). AdminStateUp bool `json:"admin_state_up"` - // The status of the VIP. Indicates whether the VIP is operational. + // Status is the status of the VIP. Indicates whether the VIP is operational. Status string `json:"status"` } // VIPPage is the page returned by a pager when traversing over a -// collection of routers. +// collection of virtual IPs. type VIPPage struct { pagination.LinkedPageBase } @@ -121,7 +122,7 @@ type commonResult struct { gophercloud.Result } -// Extract is a function that accepts a result and extracts a router. +// Extract is a function that accepts a result and extracts a VirtualIP. func (r commonResult) Extract() (*VirtualIP, error) { var s struct { VirtualIP *VirtualIP `json:"vip" json:"vip"` @@ -130,22 +131,26 @@ func (r commonResult) Extract() (*VirtualIP, error) { return s.VirtualIP, err } -// CreateResult represents the result of a create operation. +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a VirtualIP type CreateResult struct { commonResult } -// GetResult represents the result of a get operation. +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a VirtualIP type GetResult struct { commonResult } -// UpdateResult represents the result of an update operation. +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a VirtualIP type UpdateResult struct { commonResult } -// DeleteResult represents the result of a delete operation. +// DeleteResult represents the result of a delete operation. Call its +// ExtractErr method to determine if the request succeeded or failed. type DeleteResult struct { gophercloud.ErrResult } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/doc.go new file mode 100644 index 000000000..813579905 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/doc.go @@ -0,0 +1,123 @@ +/* +Package l7policies provides information and interaction with L7Policies and +Rules of the LBaaS v2 extension for the OpenStack Networking service. + +Example to Create a L7Policy + + createOpts := l7policies.CreateOpts{ + Name: "redirect-example.com", + ListenerID: "023f2e34-7806-443b-bfae-16c324569a3d", + Action: l7policies.ActionRedirectToURL, + RedirectURL: "http://www.example.com", + } + l7policy, err := l7policies.Create(lbClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to List L7Policies + + listOpts := l7policies.ListOpts{ + ListenerID: "c79a4468-d788-410c-bf79-9a8ef6354852", + } + allPages, err := l7policies.List(lbClient, listOpts).AllPages() + if err != nil { + panic(err) + } + allL7Policies, err := l7policies.ExtractL7Policies(allPages) + if err != nil { + panic(err) + } + for _, l7policy := range allL7Policies { + fmt.Printf("%+v\n", l7policy) + } + +Example to Get a L7Policy + + l7policy, err := l7policies.Get(lbClient, "023f2e34-7806-443b-bfae-16c324569a3d").Extract() + if err != nil { + panic(err) + } + +Example to Delete a L7Policy + + l7policyID := "d67d56a6-4a86-4688-a282-f46444705c64" + err := l7policies.Delete(lbClient, l7policyID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Update a L7Policy + + l7policyID := "d67d56a6-4a86-4688-a282-f46444705c64" + name := "new-name" + updateOpts := l7policies.UpdateOpts{ + Name: &name, + } + l7policy, err := l7policies.Update(lbClient, l7policyID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Create a Rule + + l7policyID := "d67d56a6-4a86-4688-a282-f46444705c64" + createOpts := l7policies.CreateRuleOpts{ + RuleType: l7policies.TypePath, + CompareType: l7policies.CompareTypeRegex, + Value: "/images*", + } + rule, err := l7policies.CreateRule(lbClient, l7policyID, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to List L7 Rules + + l7policyID := "d67d56a6-4a86-4688-a282-f46444705c64" + listOpts := l7policies.ListRulesOpts{ + RuleType: l7policies.TypePath, + } + allPages, err := l7policies.ListRules(lbClient, l7policyID, listOpts).AllPages() + if err != nil { + panic(err) + } + allRules, err := l7policies.ExtractRules(allPages) + if err != nil { + panic(err) + } + for _, rule := allRules { + fmt.Printf("%+v\n", rule) + } + +Example to Get a l7 rule + + l7rule, err := l7policies.GetRule(lbClient, "023f2e34-7806-443b-bfae-16c324569a3d", "53ad8ab8-40fa-11e8-a508-00224d6b7bc1").Extract() + if err != nil { + panic(err) + } + +Example to Delete a l7 rule + + l7policyID := "d67d56a6-4a86-4688-a282-f46444705c64" + ruleID := "64dba99f-8af8-4200-8882-e32a0660f23e" + err := l7policies.DeleteRule(lbClient, l7policyID, ruleID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Update a Rule + + l7policyID := "d67d56a6-4a86-4688-a282-f46444705c64" + ruleID := "64dba99f-8af8-4200-8882-e32a0660f23e" + updateOpts := l7policies.UpdateRuleOpts{ + RuleType: l7policies.TypePath, + CompareType: l7policies.CompareTypeRegex, + Value: "/images/special*", + } + rule, err := l7policies.UpdateRule(lbClient, l7policyID, ruleID, updateOpts).Extract() + if err != nil { + panic(err) + } +*/ +package l7policies diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/requests.go new file mode 100644 index 000000000..9d2b3a0d3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/requests.go @@ -0,0 +1,376 @@ +package l7policies + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToL7PolicyCreateMap() (map[string]interface{}, error) +} + +type Action string +type RuleType string +type CompareType string + +const ( + ActionRedirectToPool Action = "REDIRECT_TO_POOL" + ActionRedirectToURL Action = "REDIRECT_TO_URL" + ActionReject Action = "REJECT" + + TypeCookie RuleType = "COOKIE" + TypeFileType RuleType = "FILE_TYPE" + TypeHeader RuleType = "HEADER" + TypeHostName RuleType = "HOST_NAME" + TypePath RuleType = "PATH" + + CompareTypeContains CompareType = "CONTAINS" + CompareTypeEndWith CompareType = "ENDS_WITH" + CompareTypeEqual CompareType = "EQUAL_TO" + CompareTypeRegex CompareType = "REGEX" + CompareTypeStartWith CompareType = "STARTS_WITH" +) + +// CreateOpts is the common options struct used in this package's Create +// operation. +type CreateOpts struct { + // Name of the L7 policy. + Name string `json:"name,omitempty"` + + // The ID of the listener. + ListenerID string `json:"listener_id" required:"true"` + + // The L7 policy action. One of REDIRECT_TO_POOL, REDIRECT_TO_URL, or REJECT. + Action Action `json:"action" required:"true"` + + // The position of this policy on the listener. + Position int32 `json:"position,omitempty"` + + // A human-readable description for the resource. + Description string `json:"description,omitempty"` + + // TenantID is the UUID of the tenant who owns the L7 policy in octavia. + // Only administrative users can specify a project UUID other than their own. + TenantID string `json:"tenant_id,omitempty"` + + // Requests matching this policy will be redirected to the pool with this ID. + // Only valid if action is REDIRECT_TO_POOL. + RedirectPoolID string `json:"redirect_pool_id,omitempty"` + + // Requests matching this policy will be redirected to this URL. + // Only valid if action is REDIRECT_TO_URL. + RedirectURL string `json:"redirect_url,omitempty"` + + // The administrative state of the Loadbalancer. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool `json:"admin_state_up,omitempty"` +} + +// ToL7PolicyCreateMap builds a request body from CreateOpts. +func (opts CreateOpts) ToL7PolicyCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "l7policy") +} + +// Create accepts a CreateOpts struct and uses the values to create a new l7policy. +func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToL7PolicyCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Post(rootURL(c), b, &r.Body, nil) + return +} + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToL7PolicyListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. +type ListOpts struct { + Name string `q:"name"` + Description string `q:"description"` + ListenerID string `q:"listener_id"` + Action string `q:"action"` + TenantID string `q:"tenant_id"` + RedirectPoolID string `q:"redirect_pool_id"` + RedirectURL string `q:"redirect_url"` + Position int32 `q:"position"` + AdminStateUp bool `q:"admin_state_up"` + ID string `q:"id"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// ToL7PolicyListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToL7PolicyListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of +// l7policies. It accepts a ListOpts struct, which allows you to filter and sort +// the returned collection for greater efficiency. +// +// Default policy settings return only those l7policies that are owned by the +// project who submits the request, unless an admin user submits the request. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + if opts != nil { + query, err := opts.ToL7PolicyListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return L7PolicyPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves a particular l7policy based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) + return +} + +// Delete will permanently delete a particular l7policy based on its unique ID. +func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = c.Delete(resourceURL(c, id), nil) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToL7PolicyUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts is the common options struct used in this package's Update +// operation. +type UpdateOpts struct { + // Name of the L7 policy, empty string is allowed. + Name *string `json:"name,omitempty"` + + // The L7 policy action. One of REDIRECT_TO_POOL, REDIRECT_TO_URL, or REJECT. + Action Action `json:"action,omitempty"` + + // The position of this policy on the listener. + Position int32 `json:"position,omitempty"` + + // A human-readable description for the resource, empty string is allowed. + Description *string `json:"description,omitempty"` + + // Requests matching this policy will be redirected to the pool with this ID. + // Only valid if action is REDIRECT_TO_POOL. + RedirectPoolID *string `json:"redirect_pool_id,omitempty"` + + // Requests matching this policy will be redirected to this URL. + // Only valid if action is REDIRECT_TO_URL. + RedirectURL *string `json:"redirect_url,omitempty"` + + // The administrative state of the Loadbalancer. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool `json:"admin_state_up,omitempty"` +} + +// ToL7PolicyUpdateMap builds a request body from UpdateOpts. +func (opts UpdateOpts) ToL7PolicyUpdateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "l7policy") + if err != nil { + return nil, err + } + + m := b["l7policy"].(map[string]interface{}) + + if m["redirect_pool_id"] == "" { + m["redirect_pool_id"] = nil + } + + if m["redirect_url"] == "" { + m["redirect_url"] = nil + } + + return b, nil +} + +// Update allows l7policy to be updated. +func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToL7PolicyUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// CreateRuleOpts is the common options struct used in this package's CreateRule +// operation. +type CreateRuleOpts struct { + // The L7 rule type. One of COOKIE, FILE_TYPE, HEADER, HOST_NAME, or PATH. + RuleType RuleType `json:"type" required:"true"` + + // The comparison type for the L7 rule. One of CONTAINS, ENDS_WITH, EQUAL_TO, REGEX, or STARTS_WITH. + CompareType CompareType `json:"compare_type" required:"true"` + + // The value to use for the comparison. For example, the file type to compare. + Value string `json:"value" required:"true"` + + // TenantID is the UUID of the tenant who owns the rule in octavia. + // Only administrative users can specify a project UUID other than their own. + TenantID string `json:"tenant_id,omitempty"` + + // The key to use for the comparison. For example, the name of the cookie to evaluate. + Key string `json:"key,omitempty"` + + // When true the logic of the rule is inverted. For example, with invert true, + // equal to would become not equal to. Default is false. + Invert bool `json:"invert,omitempty"` + + // The administrative state of the Loadbalancer. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool `json:"admin_state_up,omitempty"` +} + +// ToRuleCreateMap builds a request body from CreateRuleOpts. +func (opts CreateRuleOpts) ToRuleCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "rule") +} + +// CreateRule will create and associate a Rule with a particular L7Policy. +func CreateRule(c *gophercloud.ServiceClient, policyID string, opts CreateRuleOpts) (r CreateRuleResult) { + b, err := opts.ToRuleCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Post(ruleRootURL(c, policyID), b, &r.Body, nil) + return +} + +// ListRulesOptsBuilder allows extensions to add additional parameters to the +// ListRules request. +type ListRulesOptsBuilder interface { + ToRulesListQuery() (string, error) +} + +// ListRulesOpts allows the filtering and sorting of paginated collections +// through the API. +type ListRulesOpts struct { + RuleType RuleType `q:"type"` + TenantID string `q:"tenant_id"` + CompareType CompareType `q:"compare_type"` + Value string `q:"value"` + Key string `q:"key"` + Invert bool `q:"invert"` + AdminStateUp bool `q:"admin_state_up"` + ID string `q:"id"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// ToRulesListQuery formats a ListOpts into a query string. +func (opts ListRulesOpts) ToRulesListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// ListRules returns a Pager which allows you to iterate over a collection of +// rules. It accepts a ListRulesOptsBuilder, which allows you to filter and +// sort the returned collection for greater efficiency. +// +// Default policy settings return only those rules that are owned by the +// project who submits the request, unless an admin user submits the request. +func ListRules(c *gophercloud.ServiceClient, policyID string, opts ListRulesOptsBuilder) pagination.Pager { + url := ruleRootURL(c, policyID) + if opts != nil { + query, err := opts.ToRulesListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return RulePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// GetRule retrieves a particular L7Policy Rule based on its unique ID. +func GetRule(c *gophercloud.ServiceClient, policyID string, ruleID string) (r GetRuleResult) { + _, r.Err = c.Get(ruleResourceURL(c, policyID, ruleID), &r.Body, nil) + return +} + +// DeleteRule will remove a Rule from a particular L7Policy. +func DeleteRule(c *gophercloud.ServiceClient, policyID string, ruleID string) (r DeleteRuleResult) { + _, r.Err = c.Delete(ruleResourceURL(c, policyID, ruleID), nil) + return +} + +// UpdateRuleOptsBuilder allows to add additional parameters to the PUT request. +type UpdateRuleOptsBuilder interface { + ToRuleUpdateMap() (map[string]interface{}, error) +} + +// UpdateRuleOpts is the common options struct used in this package's Update +// operation. +type UpdateRuleOpts struct { + // The L7 rule type. One of COOKIE, FILE_TYPE, HEADER, HOST_NAME, or PATH. + RuleType RuleType `json:"type,omitempty"` + + // The comparison type for the L7 rule. One of CONTAINS, ENDS_WITH, EQUAL_TO, REGEX, or STARTS_WITH. + CompareType CompareType `json:"compare_type,omitempty"` + + // The value to use for the comparison. For example, the file type to compare. + Value string `json:"value,omitempty"` + + // The key to use for the comparison. For example, the name of the cookie to evaluate. + Key *string `json:"key,omitempty"` + + // When true the logic of the rule is inverted. For example, with invert true, + // equal to would become not equal to. Default is false. + Invert *bool `json:"invert,omitempty"` + + // The administrative state of the Loadbalancer. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool `json:"admin_state_up,omitempty"` +} + +// ToRuleUpdateMap builds a request body from UpdateRuleOpts. +func (opts UpdateRuleOpts) ToRuleUpdateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "rule") + if err != nil { + return nil, err + } + + if m := b["rule"].(map[string]interface{}); m["key"] == "" { + m["key"] = nil + } + + return b, nil +} + +// UpdateRule allows Rule to be updated. +func UpdateRule(c *gophercloud.ServiceClient, policyID string, ruleID string, opts UpdateRuleOptsBuilder) (r UpdateRuleResult) { + b, err := opts.ToRuleUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(ruleResourceURL(c, policyID, ruleID), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201, 202}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/results.go new file mode 100644 index 000000000..5153b1b90 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/results.go @@ -0,0 +1,245 @@ +package l7policies + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// L7Policy is a collection of L7 rules associated with a Listener, and which +// may also have an association to a back-end pool. +type L7Policy struct { + // The unique ID for the L7 policy. + ID string `json:"id"` + + // Name of the L7 policy. + Name string `json:"name"` + + // The ID of the listener. + ListenerID string `json:"listener_id"` + + // The L7 policy action. One of REDIRECT_TO_POOL, REDIRECT_TO_URL, or REJECT. + Action string `json:"action"` + + // The position of this policy on the listener. + Position int32 `json:"position"` + + // A human-readable description for the resource. + Description string `json:"description"` + + // TenantID is the UUID of the tenant who owns the L7 policy in octavia. + // Only administrative users can specify a project UUID other than their own. + TenantID string `json:"tenant_id"` + + // Requests matching this policy will be redirected to the pool with this ID. + // Only valid if action is REDIRECT_TO_POOL. + RedirectPoolID string `json:"redirect_pool_id"` + + // Requests matching this policy will be redirected to this URL. + // Only valid if action is REDIRECT_TO_URL. + RedirectURL string `json:"redirect_url"` + + // The administrative state of the L7 policy, which is up (true) or down (false). + AdminStateUp bool `json:"admin_state_up"` + + // The provisioning status of the L7 policy. + // This value is ACTIVE, PENDING_* or ERROR. + // This field seems to only be returned during a call to a load balancer's /status + // see: https://github.com/gophercloud/gophercloud/issues/1362 + ProvisioningStatus string `json:"provisioning_status"` + + // The operating status of the L7 policy. + // This field seems to only be returned during a call to a load balancer's /status + // see: https://github.com/gophercloud/gophercloud/issues/1362 + OperatingStatus string `json:"operating_status"` + + // Rules are List of associated L7 rule IDs. + Rules []Rule `json:"rules"` +} + +// Rule represents layer 7 load balancing rule. +type Rule struct { + // The unique ID for the L7 rule. + ID string `json:"id"` + + // The L7 rule type. One of COOKIE, FILE_TYPE, HEADER, HOST_NAME, or PATH. + RuleType string `json:"type"` + + // The comparison type for the L7 rule. One of CONTAINS, ENDS_WITH, EQUAL_TO, REGEX, or STARTS_WITH. + CompareType string `json:"compare_type"` + + // The value to use for the comparison. For example, the file type to compare. + Value string `json:"value"` + + // TenantID is the UUID of the tenant who owns the rule in octavia. + // Only administrative users can specify a project UUID other than their own. + TenantID string `json:"tenant_id"` + + // The key to use for the comparison. For example, the name of the cookie to evaluate. + Key string `json:"key"` + + // When true the logic of the rule is inverted. For example, with invert true, + // equal to would become not equal to. Default is false. + Invert bool `json:"invert"` + + // The administrative state of the L7 rule, which is up (true) or down (false). + AdminStateUp bool `json:"admin_state_up"` + + // The provisioning status of the L7 rule. + // This value is ACTIVE, PENDING_* or ERROR. + // This field seems to only be returned during a call to a load balancer's /status + // see: https://github.com/gophercloud/gophercloud/issues/1362 + ProvisioningStatus string `json:"provisioning_status"` + + // The operating status of the L7 policy. + // This field seems to only be returned during a call to a load balancer's /status + // see: https://github.com/gophercloud/gophercloud/issues/1362 + OperatingStatus string `json:"operating_status"` +} + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a l7policy. +func (r commonResult) Extract() (*L7Policy, error) { + var s struct { + L7Policy *L7Policy `json:"l7policy"` + } + err := r.ExtractInto(&s) + return s.L7Policy, err +} + +// CreateResult represents the result of a Create operation. Call its Extract +// method to interpret the result as a L7Policy. +type CreateResult struct { + commonResult +} + +// L7PolicyPage is the page returned by a pager when traversing over a +// collection of l7policies. +type L7PolicyPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of l7policies 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 L7PolicyPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"l7policies_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a L7PolicyPage struct is empty. +func (r L7PolicyPage) IsEmpty() (bool, error) { + is, err := ExtractL7Policies(r) + return len(is) == 0, err +} + +// ExtractL7Policies accepts a Page struct, specifically a L7PolicyPage struct, +// and extracts the elements into a slice of L7Policy structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractL7Policies(r pagination.Page) ([]L7Policy, error) { + var s struct { + L7Policies []L7Policy `json:"l7policies"` + } + err := (r.(L7PolicyPage)).ExtractInto(&s) + return s.L7Policies, err +} + +// GetResult represents the result of a Get operation. Call its Extract +// method to interpret the result as a L7Policy. +type GetResult struct { + commonResult +} + +// DeleteResult represents the result of a Delete operation. Call its +// ExtractErr method to determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// UpdateResult represents the result of an Update operation. Call its Extract +// method to interpret the result as a L7Policy. +type UpdateResult struct { + commonResult +} + +type commonRuleResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a rule. +func (r commonRuleResult) Extract() (*Rule, error) { + var s struct { + Rule *Rule `json:"rule"` + } + err := r.ExtractInto(&s) + return s.Rule, err +} + +// CreateRuleResult represents the result of a CreateRule operation. +// Call its Extract method to interpret it as a Rule. +type CreateRuleResult struct { + commonRuleResult +} + +// RulePage is the page returned by a pager when traversing over a +// collection of Rules in a L7Policy. +type RulePage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of rules 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 RulePage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"rules_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a RulePage struct is empty. +func (r RulePage) IsEmpty() (bool, error) { + is, err := ExtractRules(r) + return len(is) == 0, err +} + +// ExtractRules accepts a Page struct, specifically a RulePage struct, +// and extracts the elements into a slice of Rules structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractRules(r pagination.Page) ([]Rule, error) { + var s struct { + Rules []Rule `json:"rules"` + } + err := (r.(RulePage)).ExtractInto(&s) + return s.Rules, err +} + +// GetRuleResult represents the result of a GetRule operation. +// Call its Extract method to interpret it as a Rule. +type GetRuleResult struct { + commonRuleResult +} + +// DeleteRuleResult represents the result of a DeleteRule operation. +// Call its ExtractErr method to determine if the request succeeded or failed. +type DeleteRuleResult struct { + gophercloud.ErrResult +} + +// UpdateRuleResult represents the result of an UpdateRule operation. +// Call its Extract method to interpret it as a Rule. +type UpdateRuleResult struct { + commonRuleResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/urls.go new file mode 100644 index 000000000..ecb607a8e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies/urls.go @@ -0,0 +1,25 @@ +package l7policies + +import "github.com/gophercloud/gophercloud" + +const ( + rootPath = "lbaas" + resourcePath = "l7policies" + rulePath = "rules" +) + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath, resourcePath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id) +} + +func ruleRootURL(c *gophercloud.ServiceClient, policyID string) string { + return c.ServiceURL(rootPath, resourcePath, policyID, rulePath) +} + +func ruleResourceURL(c *gophercloud.ServiceClient, policyID string, ruleID string) string { + return c.ServiceURL(rootPath, resourcePath, policyID, rulePath, ruleID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/doc.go new file mode 100644 index 000000000..108cdb03d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/doc.go @@ -0,0 +1,63 @@ +/* +Package listeners provides information and interaction with Listeners of the +LBaaS v2 extension for the OpenStack Networking service. + +Example to List Listeners + + listOpts := listeners.ListOpts{ + LoadbalancerID : "ca430f80-1737-4712-8dc6-3f640d55594b", + } + + allPages, err := listeners.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allListeners, err := listeners.ExtractListeners(allPages) + if err != nil { + panic(err) + } + + for _, listener := range allListeners { + fmt.Printf("%+v\n", listener) + } + +Example to Create a Listener + + createOpts := listeners.CreateOpts{ + Protocol: "TCP", + Name: "db", + LoadbalancerID: "79e05663-7f03-45d2-a092-8b94062f22ab", + AdminStateUp: gophercloud.Enabled, + DefaultPoolID: "41efe233-7591-43c5-9cf7-923964759f9e", + ProtocolPort: 3306, + } + + listener, err := listeners.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Listener + + listenerID := "d67d56a6-4a86-4688-a282-f46444705c64" + + i1001 := 1001 + updateOpts := listeners.UpdateOpts{ + ConnLimit: &i1001, + } + + listener, err := listeners.Update(networkClient, listenerID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Listener + + listenerID := "d67d56a6-4a86-4688-a282-f46444705c64" + err := listeners.Delete(networkClient, listenerID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package listeners diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/requests.go index 4a7844790..f2966b6c4 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/requests.go @@ -5,13 +5,15 @@ import ( "github.com/gophercloud/gophercloud/pagination" ) +// Type Protocol represents a listener protocol. type Protocol string // Supported attributes for create/update operations. const ( - ProtocolTCP Protocol = "TCP" - ProtocolHTTP Protocol = "HTTP" - ProtocolHTTPS Protocol = "HTTPS" + ProtocolTCP Protocol = "TCP" + ProtocolHTTP Protocol = "HTTP" + ProtocolHTTPS Protocol = "HTTPS" + ProtocolTerminatedHTTPS Protocol = "TERMINATED_HTTPS" ) // ListOptsBuilder allows extensions to add additional parameters to the @@ -30,6 +32,7 @@ type ListOpts struct { Name string `q:"name"` AdminStateUp *bool `q:"admin_state_up"` TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` LoadbalancerID string `q:"loadbalancer_id"` DefaultPoolID string `q:"default_pool_id"` Protocol string `q:"protocol"` @@ -48,10 +51,10 @@ func (opts ListOpts) ToListenerListQuery() (string, error) { } // List returns a Pager which allows you to iterate over a collection of -// routers. It accepts a ListOpts struct, which allows you to filter and sort +// listeners. It accepts a ListOpts struct, which allows you to filter and sort // the returned collection for greater efficiency. // -// Default policy settings return only those routers that are owned by the +// Default policy settings return only those listeners that are owned by the // tenant who submits the request, unless an admin user submits the request. func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { url := rootURL(c) @@ -67,43 +70,55 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { }) } -// CreateOptsBuilder is the interface options structs have to satisfy in order -// to be used in the main Create operation in this package. Since many -// extensions decorate or modify the common logic, it is useful for them to -// satisfy a basic interface in order for them to be used. +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. type CreateOptsBuilder interface { ToListenerCreateMap() (map[string]interface{}, error) } -// CreateOpts is the common options struct used in this package's Create -// operation. +// CreateOpts represents options for creating a listener. type CreateOpts struct { // The load balancer on which to provision this listener. LoadbalancerID string `json:"loadbalancer_id" required:"true"` + // The protocol - can either be TCP, HTTP or HTTPS. Protocol Protocol `json:"protocol" required:"true"` + // The port on which to listen for client traffic. ProtocolPort int `json:"protocol_port" required:"true"` - // Indicates the owner of the Listener. Required for admins. + + // TenantID is only required if the caller has an admin role and wants + // to create a pool for another project. TenantID string `json:"tenant_id,omitempty"` + + // ProjectID is only required if the caller has an admin role and wants + // to create a pool for another project. + ProjectID string `json:"project_id,omitempty"` + // Human-readable name for the Listener. Does not have to be unique. Name string `json:"name,omitempty"` + // The ID of the default pool with which the Listener is associated. DefaultPoolID string `json:"default_pool_id,omitempty"` + // Human-readable description for the Listener. Description string `json:"description,omitempty"` + // The maximum number of connections allowed for the Listener. ConnLimit *int `json:"connection_limit,omitempty"` - // A reference to a container of TLS secrets. + + // A reference to a Barbican container of TLS secrets. DefaultTlsContainerRef string `json:"default_tls_container_ref,omitempty"` + // A list of references to TLS secrets. SniContainerRefs []string `json:"sni_container_refs,omitempty"` + // The administrative state of the Listener. A valid value is true (UP) // or false (DOWN). AdminStateUp *bool `json:"admin_state_up,omitempty"` } -// ToListenerCreateMap casts a CreateOpts struct to a map. +// ToListenerCreateMap builds a request body from CreateOpts. func (opts CreateOpts) ToListenerCreateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "listener") } @@ -131,38 +146,53 @@ func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { return } -// UpdateOptsBuilder is the interface options structs have to satisfy in order -// to be used in the main Update operation in this package. Since many -// extensions decorate or modify the common logic, it is useful for them to -// satisfy a basic interface in order for them to be used. +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. type UpdateOptsBuilder interface { ToListenerUpdateMap() (map[string]interface{}, error) } -// UpdateOpts is the common options struct used in this package's Update -// operation. +// UpdateOpts represents options for updating a Listener. type UpdateOpts struct { // Human-readable name for the Listener. Does not have to be unique. - Name string `json:"name,omitempty"` + Name *string `json:"name,omitempty"` + + // The ID of the default pool with which the Listener is associated. + DefaultPoolID *string `json:"default_pool_id,omitempty"` + // Human-readable description for the Listener. - Description string `json:"description,omitempty"` + Description *string `json:"description,omitempty"` + // The maximum number of connections allowed for the Listener. ConnLimit *int `json:"connection_limit,omitempty"` - // A reference to a container of TLS secrets. + + // A reference to a Barbican container of TLS secrets. DefaultTlsContainerRef string `json:"default_tls_container_ref,omitempty"` - // A list of references to TLS secrets. + + // A list of references to TLS secrets. SniContainerRefs []string `json:"sni_container_refs,omitempty"` + // The administrative state of the Listener. A valid value is true (UP) // or false (DOWN). AdminStateUp *bool `json:"admin_state_up,omitempty"` } -// ToListenerUpdateMap casts a UpdateOpts struct to a map. +// ToListenerUpdateMap builds a request body from UpdateOpts. func (opts UpdateOpts) ToListenerUpdateMap() (map[string]interface{}, error) { - return gophercloud.BuildRequestBody(opts, "listener") + b, err := gophercloud.BuildRequestBody(opts, "listener") + if err != nil { + return nil, err + } + + if m := b["listener"].(map[string]interface{}); m["default_pool_id"] == "" { + m["default_pool_id"] = nil + } + + return b, nil } -// Update is an operation which modifies the attributes of the specified Listener. +// Update is an operation which modifies the attributes of the specified +// Listener. func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) (r UpdateResult) { b, err := opts.ToListenerUpdateMap() if err != nil { diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/results.go index aa8ed1bde..ae1057932 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/results.go @@ -2,6 +2,7 @@ package listeners import ( "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools" "github.com/gophercloud/gophercloud/pagination" ) @@ -16,40 +17,62 @@ type LoadBalancerID struct { type Listener struct { // The unique ID for the Listener. ID string `json:"id"` - // Owner of the Listener. Only an admin user can specify a tenant ID other than its own. + + // Owner of the Listener. TenantID string `json:"tenant_id"` + // Human-readable name for the Listener. Does not have to be unique. Name string `json:"name"` + // Human-readable description for the Listener. Description string `json:"description"` + // The protocol to loadbalance. A valid value is TCP, HTTP, or HTTPS. Protocol string `json:"protocol"` + // The port on which to listen to client traffic that is associated with the // Loadbalancer. A valid value is from 0 to 65535. ProtocolPort int `json:"protocol_port"` + // The UUID of default pool. Must have compatible protocol with listener. DefaultPoolID string `json:"default_pool_id"` + // A list of load balancer IDs. Loadbalancers []LoadBalancerID `json:"loadbalancers"` - // The maximum number of connections allowed for the Loadbalancer. Default is -1, - // meaning no limit. + + // The maximum number of connections allowed for the Loadbalancer. + // Default is -1, meaning no limit. ConnLimit int `json:"connection_limit"` + // The list of references to TLS secrets. SniContainerRefs []string `json:"sni_container_refs"` - // Optional. A reference to a container of TLS secrets. + + // A reference to a Barbican container of TLS secrets. DefaultTlsContainerRef string `json:"default_tls_container_ref"` + // The administrative state of the Listener. A valid value is true (UP) or false (DOWN). - AdminStateUp bool `json:"admin_state_up"` - Pools []pools.Pool `json:"pools"` + AdminStateUp bool `json:"admin_state_up"` + + // Pools are the pools which are part of this listener. + Pools []pools.Pool `json:"pools"` + + // L7policies are the L7 policies which are part of this listener. + // This field seems to only be returned during a call to a load balancer's /status + // see: https://github.com/gophercloud/gophercloud/issues/1352 + L7Policies []l7policies.L7Policy `json:"l7policies"` + + // The provisioning status of the listener. + // This value is ACTIVE, PENDING_* or ERROR. + ProvisioningStatus string `json:"provisioning_status"` } // ListenerPage is the page returned by a pager when traversing over a -// collection of routers. +// collection of listeners. type ListenerPage struct { pagination.LinkedPageBase } -// NextPageURL is invoked when a paginated collection of routers has reached +// NextPageURL is invoked when a paginated collection of listeners 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 ListenerPage) NextPageURL() (string, error) { @@ -63,7 +86,7 @@ func (r ListenerPage) NextPageURL() (string, error) { return gophercloud.ExtractNextURL(s.Links) } -// IsEmpty checks whether a RouterPage struct is empty. +// IsEmpty checks whether a ListenerPage struct is empty. func (r ListenerPage) IsEmpty() (bool, error) { is, err := ExtractListeners(r) return len(is) == 0, err @@ -84,7 +107,7 @@ type commonResult struct { gophercloud.Result } -// Extract is a function that accepts a result and extracts a router. +// Extract is a function that accepts a result and extracts a listener. func (r commonResult) Extract() (*Listener, error) { var s struct { Listener *Listener `json:"listener"` @@ -93,22 +116,26 @@ func (r commonResult) Extract() (*Listener, error) { return s.Listener, err } -// CreateResult represents the result of a create operation. +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a Listener. type CreateResult struct { commonResult } -// GetResult represents the result of a get operation. +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a Listener. type GetResult struct { commonResult } -// UpdateResult represents the result of an update operation. +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a Listener. type UpdateResult struct { commonResult } -// DeleteResult represents the result of a delete operation. +// DeleteResult represents the result of a delete operation. Call its +// ExtractErr method to determine if the request succeeded or failed. type DeleteResult struct { gophercloud.ErrResult } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/doc.go new file mode 100644 index 000000000..c6d53a7b0 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/doc.go @@ -0,0 +1,79 @@ +/* +Package loadbalancers provides information and interaction with Load Balancers +of the LBaaS v2 extension for the OpenStack Networking service. + +Example to List Load Balancers + + listOpts := loadbalancers.ListOpts{ + Provider: "haproxy", + } + + allPages, err := loadbalancers.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allLoadbalancers, err := loadbalancers.ExtractLoadBalancers(allPages) + if err != nil { + panic(err) + } + + for _, lb := range allLoadbalancers { + fmt.Printf("%+v\n", lb) + } + +Example to Create a Load Balancer + + createOpts := loadbalancers.CreateOpts{ + Name: "db_lb", + AdminStateUp: gophercloud.Enabled, + VipSubnetID: "9cedb85d-0759-4898-8a4b-fa5a5ea10086", + VipAddress: "10.30.176.48", + Flavor: "medium", + Provider: "haproxy", + } + + lb, err := loadbalancers.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Load Balancer + + lbID := "d67d56a6-4a86-4688-a282-f46444705c64" + + i1001 := 1001 + updateOpts := loadbalancers.UpdateOpts{ + Name: "new-name", + } + + lb, err := loadbalancers.Update(networkClient, lbID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Load Balancers + + lbID := "d67d56a6-4a86-4688-a282-f46444705c64" + err := loadbalancers.Delete(networkClient, lbID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Get the Status of a Load Balancer + + lbID := "d67d56a6-4a86-4688-a282-f46444705c64" + status, err := loadbalancers.GetStatuses(networkClient, LBID).Extract() + if err != nil { + panic(err) + } + +Example to Get the Statistics of a Load Balancer + + lbID := "d67d56a6-4a86-4688-a282-f46444705c64" + stats, err := loadbalancers.GetStats(networkClient, LBID).Extract() + if err != nil { + panic(err) + } +*/ +package loadbalancers diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go index bc4a3c69a..f5b141348 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go @@ -1,6 +1,8 @@ package loadbalancers import ( + "fmt" + "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/pagination" ) @@ -20,6 +22,7 @@ type ListOpts struct { Description string `q:"description"` AdminStateUp *bool `q:"admin_state_up"` TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` ProvisioningStatus string `q:"provisioning_status"` VipAddress string `q:"vip_address"` VipPortID string `q:"vip_port_id"` @@ -35,18 +38,18 @@ type ListOpts struct { SortDir string `q:"sort_dir"` } -// ToLoadbalancerListQuery formats a ListOpts into a query string. +// ToLoadBalancerListQuery formats a ListOpts into a query string. func (opts ListOpts) ToLoadBalancerListQuery() (string, error) { q, err := gophercloud.BuildQueryString(opts) return q.String(), err } // List returns a Pager which allows you to iterate over a collection of -// routers. It accepts a ListOpts struct, which allows you to filter and sort -// the returned collection for greater efficiency. +// load balancers. It accepts a ListOpts struct, which allows you to filter +// and sort the returned collection for greater efficiency. // -// Default policy settings return only those routers that are owned by the -// tenant who submits the request, unless an admin user submits the request. +// Default policy settings return only those load balancers that are owned by +// the tenant who submits the request, unless an admin user submits the request. func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { url := rootURL(c) if opts != nil { @@ -61,10 +64,8 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { }) } -// CreateOptsBuilder is the interface options structs have to satisfy in order -// to be used in the main Create operation in this package. Since many -// extensions decorate or modify the common logic, it is useful for them to -// satisfy a basic interface in order for them to be used. +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. type CreateOptsBuilder interface { ToLoadBalancerCreateMap() (map[string]interface{}, error) } @@ -72,29 +73,40 @@ type CreateOptsBuilder interface { // CreateOpts is the common options struct used in this package's Create // operation. type CreateOpts struct { - // Optional. Human-readable name for the Loadbalancer. Does not have to be unique. + // Human-readable name for the Loadbalancer. Does not have to be unique. Name string `json:"name,omitempty"` - // Optional. Human-readable description for the Loadbalancer. + + // Human-readable description for the Loadbalancer. Description string `json:"description,omitempty"` - // Required. The network on which to allocate the Loadbalancer's address. A tenant can - // only create Loadbalancers on networks authorized by policy (e.g. networks that - // belong to them or networks that are shared). + + // The network on which to allocate the Loadbalancer's address. A tenant can + // only create Loadbalancers on networks authorized by policy (e.g. networks + // that belong to them or networks that are shared). VipSubnetID string `json:"vip_subnet_id" required:"true"` - // Required for admins. The UUID of the tenant who owns the Loadbalancer. - // Only administrative users can specify a tenant UUID other than their own. + + // TenantID is the UUID of the project who owns the Loadbalancer. + // Only administrative users can specify a project UUID other than their own. TenantID string `json:"tenant_id,omitempty"` - // Optional. The IP address of the Loadbalancer. + + // ProjectID is the UUID of the project who owns the Loadbalancer. + // Only administrative users can specify a project UUID other than their own. + ProjectID string `json:"project_id,omitempty"` + + // The IP address of the Loadbalancer. VipAddress string `json:"vip_address,omitempty"` - // Optional. The administrative state of the Loadbalancer. A valid value is true (UP) + + // The administrative state of the Loadbalancer. A valid value is true (UP) // or false (DOWN). AdminStateUp *bool `json:"admin_state_up,omitempty"` - // Optional. The UUID of a flavor. + + // The UUID of a flavor. Flavor string `json:"flavor,omitempty"` - // Optional. The name of the provider. + + // The name of the provider. Provider string `json:"provider,omitempty"` } -// ToLoadBalancerCreateMap casts a CreateOpts struct to a map. +// ToLoadBalancerCreateMap builds a request body from CreateOpts. func (opts CreateOpts) ToLoadBalancerCreateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "loadbalancer") } @@ -103,9 +115,6 @@ func (opts CreateOpts) ToLoadBalancerCreateMap() (map[string]interface{}, error) // configuration defined in the CreateOpts struct. Once the request is // validated and progress has started on the provisioning process, a // CreateResult will be returned. -// -// Users with an admin role can create loadbalancers on behalf of other tenants by -// specifying a TenantID attribute different than their own. func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { b, err := opts.ToLoadBalancerCreateMap() if err != nil { @@ -122,10 +131,8 @@ func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { return } -// UpdateOptsBuilder is the interface options structs have to satisfy in order -// to be used in the main Update operation in this package. Since many -// extensions decorate or modify the common logic, it is useful for them to -// satisfy a basic interface in order for them to be used. +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. type UpdateOptsBuilder interface { ToLoadBalancerUpdateMap() (map[string]interface{}, error) } @@ -133,21 +140,24 @@ type UpdateOptsBuilder interface { // UpdateOpts is the common options struct used in this package's Update // operation. type UpdateOpts struct { - // Optional. Human-readable name for the Loadbalancer. Does not have to be unique. - Name string `json:"name,omitempty"` - // Optional. Human-readable description for the Loadbalancer. - Description string `json:"description,omitempty"` - // Optional. The administrative state of the Loadbalancer. A valid value is true (UP) + // Human-readable name for the Loadbalancer. Does not have to be unique. + Name *string `json:"name,omitempty"` + + // Human-readable description for the Loadbalancer. + Description *string `json:"description,omitempty"` + + // The administrative state of the Loadbalancer. A valid value is true (UP) // or false (DOWN). AdminStateUp *bool `json:"admin_state_up,omitempty"` } -// ToLoadBalancerUpdateMap casts a UpdateOpts struct to a map. +// ToLoadBalancerUpdateMap builds a request body from UpdateOpts. func (opts UpdateOpts) ToLoadBalancerUpdateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "loadbalancer") } -// Update is an operation which modifies the attributes of the specified LoadBalancer. +// Update is an operation which modifies the attributes of the specified +// LoadBalancer. func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) (r UpdateResult) { b, err := opts.ToLoadBalancerUpdateMap() if err != nil { @@ -160,13 +170,35 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) (r UpdateR return } -// Delete will permanently delete a particular LoadBalancer based on its unique ID. +// Delete will permanently delete a particular LoadBalancer based on its +// unique ID. func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { _, r.Err = c.Delete(resourceURL(c, id), nil) return } +// CascadingDelete is like `Delete`, but will also delete any of the load balancer's +// children (listener, monitor, etc). +// NOTE: This function will only work with Octavia load balancers; Neutron does not +// support this. +func CascadingDelete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { + if c.Type != "load-balancer" { + r.Err = fmt.Errorf("error prior to running cascade delete: only Octavia LBs supported") + return + } + u := fmt.Sprintf("%s?cascade=true", resourceURL(c, id)) + _, r.Err = c.Delete(u, nil) + return +} + +// GetStatuses will return the status of a particular LoadBalancer. func GetStatuses(c *gophercloud.ServiceClient, id string) (r GetStatusesResult) { _, r.Err = c.Get(statusRootURL(c, id), &r.Body, nil) return } + +// GetStats will return the shows the current statistics of a particular LoadBalancer. +func GetStats(c *gophercloud.ServiceClient, id string) (r StatsResult) { + _, r.Err = c.Get(statisticsRootURL(c, id), &r.Body, nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go index 4423c2460..7f423c933 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go @@ -3,53 +3,91 @@ package loadbalancers import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools" "github.com/gophercloud/gophercloud/pagination" ) -// LoadBalancer is the primary load balancing configuration object that specifies -// the virtual IP address on which client traffic is received, as well +// LoadBalancer is the primary load balancing configuration object that +// specifies the virtual IP address on which client traffic is received, as well // as other details such as the load balancing method to be use, protocol, etc. type LoadBalancer struct { // Human-readable description for the Loadbalancer. Description string `json:"description"` - // The administrative state of the Loadbalancer. A valid value is true (UP) or false (DOWN). + + // The administrative state of the Loadbalancer. + // A valid value is true (UP) or false (DOWN). AdminStateUp bool `json:"admin_state_up"` - // Owner of the LoadBalancer. Only an admin user can specify a tenant ID other than its own. + + // Owner of the LoadBalancer. TenantID string `json:"tenant_id"` - // The provisioning status of the LoadBalancer. This value is ACTIVE, PENDING_CREATE or ERROR. + + // The provisioning status of the LoadBalancer. + // This value is ACTIVE, PENDING_CREATE or ERROR. ProvisioningStatus string `json:"provisioning_status"` + // The IP address of the Loadbalancer. VipAddress string `json:"vip_address"` + // The UUID of the port associated with the IP address. VipPortID string `json:"vip_port_id"` - // The UUID of the subnet on which to allocate the virtual IP for the Loadbalancer address. + + // The UUID of the subnet on which to allocate the virtual IP for the + // Loadbalancer address. VipSubnetID string `json:"vip_subnet_id"` + // The unique ID for the LoadBalancer. ID string `json:"id"` + // The operating status of the LoadBalancer. This value is ONLINE or OFFLINE. OperatingStatus string `json:"operating_status"` + // Human-readable name for the LoadBalancer. Does not have to be unique. Name string `json:"name"` + // The UUID of a flavor if set. Flavor string `json:"flavor"` + // The name of the provider. - Provider string `json:"provider"` + Provider string `json:"provider"` + + // Listeners are the listeners related to this Loadbalancer. Listeners []listeners.Listener `json:"listeners"` + + // Pools are the pools related to this Loadbalancer. + Pools []pools.Pool `json:"pools"` } +// StatusTree represents the status of a loadbalancer. type StatusTree struct { Loadbalancer *LoadBalancer `json:"loadbalancer"` } +type Stats struct { + // The currently active connections. + ActiveConnections int `json:"active_connections"` + + // The total bytes received. + BytesIn int `json:"bytes_in"` + + // The total bytes sent. + BytesOut int `json:"bytes_out"` + + // The total requests that were unable to be fulfilled. + RequestErrors int `json:"request_errors"` + + // The total connections handled. + TotalConnections int `json:"total_connections"` +} + // LoadBalancerPage is the page returned by a pager when traversing over a -// collection of routers. +// collection of load balancers. type LoadBalancerPage struct { pagination.LinkedPageBase } -// NextPageURL is invoked when a paginated collection of routers 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. +// NextPageURL is invoked when a paginated collection of load balancers 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 LoadBalancerPage) NextPageURL() (string, error) { var s struct { Links []gophercloud.Link `json:"loadbalancers_links"` @@ -62,14 +100,14 @@ func (r LoadBalancerPage) NextPageURL() (string, error) { } // IsEmpty checks whether a LoadBalancerPage struct is empty. -func (p LoadBalancerPage) IsEmpty() (bool, error) { - is, err := ExtractLoadBalancers(p) +func (r LoadBalancerPage) IsEmpty() (bool, error) { + is, err := ExtractLoadBalancers(r) return len(is) == 0, err } -// ExtractLoadBalancers accepts a Page struct, specifically a LoadbalancerPage struct, -// and extracts the elements into a slice of LoadBalancer structs. In other words, -// a generic collection is mapped into a relevant slice. +// ExtractLoadBalancers accepts a Page struct, specifically a LoadbalancerPage +// struct, and extracts the elements into a slice of LoadBalancer structs. In +// other words, a generic collection is mapped into a relevant slice. func ExtractLoadBalancers(r pagination.Page) ([]LoadBalancer, error) { var s struct { LoadBalancers []LoadBalancer `json:"loadbalancers"` @@ -82,7 +120,7 @@ type commonResult struct { gophercloud.Result } -// Extract is a function that accepts a result and extracts a router. +// Extract is a function that accepts a result and extracts a loadbalancer. func (r commonResult) Extract() (*LoadBalancer, error) { var s struct { LoadBalancer *LoadBalancer `json:"loadbalancer"` @@ -91,11 +129,14 @@ func (r commonResult) Extract() (*LoadBalancer, error) { return s.LoadBalancer, err } +// GetStatusesResult represents the result of a GetStatuses operation. +// Call its Extract method to interpret it as a StatusTree. type GetStatusesResult struct { gophercloud.Result } -// Extract is a function that accepts a result and extracts a Loadbalancer. +// Extract is a function that accepts a result and extracts the status of +// a Loadbalancer. func (r GetStatusesResult) Extract() (*StatusTree, error) { var s struct { Statuses *StatusTree `json:"statuses"` @@ -104,22 +145,42 @@ func (r GetStatusesResult) Extract() (*StatusTree, error) { return s.Statuses, err } -// CreateResult represents the result of a create operation. +// StatsResult represents the result of a GetStats operation. +// Call its Extract method to interpret it as a Stats. +type StatsResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts the status of +// a Loadbalancer. +func (r StatsResult) Extract() (*Stats, error) { + var s struct { + Stats *Stats `json:"stats"` + } + err := r.ExtractInto(&s) + return s.Stats, err +} + +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a LoadBalancer. type CreateResult struct { commonResult } -// GetResult represents the result of a get operation. +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a LoadBalancer. type GetResult struct { commonResult } -// UpdateResult represents the result of an update operation. +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a LoadBalancer. type UpdateResult struct { commonResult } -// DeleteResult represents the result of a delete operation. +// DeleteResult represents the result of a delete operation. Call its +// ExtractErr method to determine if the request succeeded or failed. type DeleteResult struct { gophercloud.ErrResult } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/urls.go index 73cf5dc12..2d2a99b77 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/urls.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/urls.go @@ -3,9 +3,10 @@ package loadbalancers import "github.com/gophercloud/gophercloud" const ( - rootPath = "lbaas" - resourcePath = "loadbalancers" - statusPath = "statuses" + rootPath = "lbaas" + resourcePath = "loadbalancers" + statusPath = "statuses" + statisticsPath = "stats" ) func rootURL(c *gophercloud.ServiceClient) string { @@ -19,3 +20,7 @@ func resourceURL(c *gophercloud.ServiceClient, id string) string { func statusRootURL(c *gophercloud.ServiceClient, id string) string { return c.ServiceURL(rootPath, resourcePath, id, statusPath) } + +func statisticsRootURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id, statisticsPath) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/doc.go new file mode 100644 index 000000000..6ed8c8fb5 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/doc.go @@ -0,0 +1,69 @@ +/* +Package monitors provides information and interaction with Monitors +of the LBaaS v2 extension for the OpenStack Networking service. + +Example to List Monitors + + listOpts := monitors.ListOpts{ + PoolID: "c79a4468-d788-410c-bf79-9a8ef6354852", + } + + allPages, err := monitors.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allMonitors, err := monitors.ExtractMonitors(allPages) + if err != nil { + panic(err) + } + + for _, monitor := range allMonitors { + fmt.Printf("%+v\n", monitor) + } + +Example to Create a Monitor + + createOpts := monitors.CreateOpts{ + Type: "HTTP", + Name: "db", + PoolID: "84f1b61f-58c4-45bf-a8a9-2dafb9e5214d", + Delay: 20, + Timeout: 10, + MaxRetries: 5, + URLPath: "/check", + ExpectedCodes: "200-299", + } + + monitor, err := monitors.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Monitor + + monitorID := "d67d56a6-4a86-4688-a282-f46444705c64" + + updateOpts := monitors.UpdateOpts{ + Name: "NewHealthmonitorName", + Delay: 3, + Timeout: 20, + MaxRetries: 10, + URLPath: "/another_check", + ExpectedCodes: "301", + } + + monitor, err := monitors.Update(networkClient, monitorID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Monitor + + monitorID := "d67d56a6-4a86-4688-a282-f46444705c64" + err := monitors.Delete(networkClient, monitorID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package monitors diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/requests.go index 1e776bfee..f728f5a82 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/requests.go @@ -22,6 +22,7 @@ type ListOpts struct { ID string `q:"id"` Name string `q:"name"` TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` PoolID string `q:"pool_id"` Type string `q:"type"` Delay int `q:"delay"` @@ -79,10 +80,8 @@ var ( errDelayMustGETimeout = fmt.Errorf("Delay must be greater than or equal to timeout") ) -// CreateOptsBuilder is the interface options structs have to satisfy in order -// to be used in the main Create operation in this package. Since many -// extensions decorate or modify the common logic, it is useful for them to -// satisfy a basic interface in order for them to be used. +// CreateOptsBuilder allows extensions to add additional parameters to the +// List request. type CreateOptsBuilder interface { ToMonitorCreateMap() (map[string]interface{}, error) } @@ -90,37 +89,54 @@ type CreateOptsBuilder interface { // CreateOpts is the common options struct used in this package's Create // operation. type CreateOpts struct { - // Required. The Pool to Monitor. + // The Pool to Monitor. PoolID string `json:"pool_id" required:"true"` - // Required. The type of probe, which is PING, TCP, HTTP, or HTTPS, that is + + // The type of probe, which is PING, TCP, HTTP, or HTTPS, that is // sent by the load balancer to verify the member state. Type string `json:"type" required:"true"` - // Required. The time, in seconds, between sending probes to members. + + // The time, in seconds, between sending probes to members. Delay int `json:"delay" required:"true"` - // Required. Maximum number of seconds for a Monitor to wait for a ping reply + + // Maximum number of seconds for a Monitor to wait for a ping reply // before it times out. The value must be less than the delay value. Timeout int `json:"timeout" required:"true"` - // Required. Number of permissible ping failures before changing the member's + + // Number of permissible ping failures before changing the member's // status to INACTIVE. Must be a number between 1 and 10. MaxRetries int `json:"max_retries" required:"true"` - // Required for HTTP(S) types. URI path that will be accessed if Monitor type - // is HTTP or HTTPS. + + // URI path that will be accessed if Monitor type is HTTP or HTTPS. + // Required for HTTP(S) types. URLPath string `json:"url_path,omitempty"` - // Required for HTTP(S) types. The HTTP method used for requests by the - // Monitor. If this attribute is not specified, it defaults to "GET". + + // The HTTP method used for requests by the Monitor. If this attribute + // is not specified, it defaults to "GET". Required for HTTP(S) types. HTTPMethod string `json:"http_method,omitempty"` - // Required for HTTP(S) types. Expected HTTP codes for a passing HTTP(S) - // Monitor. You can either specify a single status like "200", or a range - // like "200-202". + + // Expected HTTP codes for a passing HTTP(S) Monitor. You can either specify + // a single status like "200", or a range like "200-202". Required for HTTP(S) + // types. ExpectedCodes string `json:"expected_codes,omitempty"` - // Indicates the owner of the Loadbalancer. Required for admins. + + // TenantID is the UUID of the project who owns the Monitor. + // Only administrative users can specify a project UUID other than their own. TenantID string `json:"tenant_id,omitempty"` - // Optional. The Name of the Monitor. - Name string `json:"name,omitempty"` - AdminStateUp *bool `json:"admin_state_up,omitempty"` + + // ProjectID is the UUID of the project who owns the Monitor. + // Only administrative users can specify a project UUID other than their own. + ProjectID string `json:"project_id,omitempty"` + + // The Name of the Monitor. + Name string `json:"name,omitempty"` + + // The administrative state of the Monitor. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool `json:"admin_state_up,omitempty"` } -// ToMonitorCreateMap casts a CreateOpts struct to a map. +// ToMonitorCreateMap builds a request body from CreateOpts. func (opts CreateOpts) ToMonitorCreateMap() (map[string]interface{}, error) { b, err := gophercloud.BuildRequestBody(opts, "healthmonitor") if err != nil { @@ -173,10 +189,8 @@ func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { return } -// UpdateOptsBuilder is the interface options structs have to satisfy in order -// to be used in the main Update operation in this package. Since many -// extensions decorate or modify the common logic, it is useful for them to -// satisfy a basic interface in order for them to be used. +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. type UpdateOptsBuilder interface { ToMonitorUpdateMap() (map[string]interface{}, error) } @@ -184,35 +198,45 @@ type UpdateOptsBuilder interface { // UpdateOpts is the common options struct used in this package's Update // operation. type UpdateOpts struct { - // Required. The time, in seconds, between sending probes to members. + // The time, in seconds, between sending probes to members. Delay int `json:"delay,omitempty"` - // Required. Maximum number of seconds for a Monitor to wait for a ping reply + + // Maximum number of seconds for a Monitor to wait for a ping reply // before it times out. The value must be less than the delay value. Timeout int `json:"timeout,omitempty"` - // Required. Number of permissible ping failures before changing the member's + + // Number of permissible ping failures before changing the member's // status to INACTIVE. Must be a number between 1 and 10. MaxRetries int `json:"max_retries,omitempty"` - // Required for HTTP(S) types. URI path that will be accessed if Monitor type - // is HTTP or HTTPS. + + // URI path that will be accessed if Monitor type is HTTP or HTTPS. + // Required for HTTP(S) types. URLPath string `json:"url_path,omitempty"` - // Required for HTTP(S) types. The HTTP method used for requests by the - // Monitor. If this attribute is not specified, it defaults to "GET". + + // The HTTP method used for requests by the Monitor. If this attribute + // is not specified, it defaults to "GET". Required for HTTP(S) types. HTTPMethod string `json:"http_method,omitempty"` - // Required for HTTP(S) types. Expected HTTP codes for a passing HTTP(S) - // Monitor. You can either specify a single status like "200", or a range - // like "200-202". + + // Expected HTTP codes for a passing HTTP(S) Monitor. You can either specify + // a single status like "200", or a range like "200-202". Required for HTTP(S) + // types. ExpectedCodes string `json:"expected_codes,omitempty"` - // Optional. The Name of the Monitor. - Name string `json:"name,omitempty"` - AdminStateUp *bool `json:"admin_state_up,omitempty"` + + // The Name of the Monitor. + Name *string `json:"name,omitempty"` + + // The administrative state of the Monitor. A valid value is true (UP) + // or false (DOWN). + AdminStateUp *bool `json:"admin_state_up,omitempty"` } -// ToMonitorUpdateMap casts a UpdateOpts struct to a map. +// ToMonitorUpdateMap builds a request body from UpdateOpts. func (opts UpdateOpts) ToMonitorUpdateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "healthmonitor") } -// Update is an operation which modifies the attributes of the specified Monitor. +// Update is an operation which modifies the attributes of the specified +// Monitor. func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { b, err := opts.ToMonitorUpdateMap() if err != nil { diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/results.go index 05dcf477b..a78f7aeb0 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/results.go @@ -31,8 +31,7 @@ type Monitor struct { // The Name of the Monitor. Name string `json:"name"` - // Only an administrative user can specify a tenant ID - // other than its own. + // TenantID is the owner of the Monitor. TenantID string `json:"tenant_id"` // The type of probe sent by the load balancer to verify the member state, @@ -43,7 +42,8 @@ type Monitor struct { Delay int `json:"delay"` // The maximum number of seconds for a monitor to wait for a connection to be - // established before it times out. This value must be less than the delay value. + // established before it times out. This value must be less than the delay + // value. Timeout int `json:"timeout"` // Number of allowed connection failures before changing the status of the @@ -60,7 +60,8 @@ type Monitor struct { // Expected HTTP codes for a passing HTTP(S) monitor. ExpectedCodes string `json:"expected_codes"` - // The administrative state of the health monitor, which is up (true) or down (false). + // The administrative state of the health monitor, which is up (true) or + // down (false). AdminStateUp bool `json:"admin_state_up"` // The status of the health monitor. Indicates whether the health monitor is @@ -69,6 +70,10 @@ type Monitor struct { // List of pools that are associated with the health monitor. Pools []PoolID `json:"pools"` + + // The provisioning status of the monitor. + // This value is ACTIVE, PENDING_* or ERROR. + ProvisioningStatus string `json:"provisioning_status"` } // MonitorPage is the page returned by a pager when traversing over a @@ -123,22 +128,26 @@ func (r commonResult) Extract() (*Monitor, error) { return s.Monitor, err } -// CreateResult represents the result of a create operation. +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a Monitor. type CreateResult struct { commonResult } -// GetResult represents the result of a get operation. +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a Monitor. type GetResult struct { commonResult } -// UpdateResult represents the result of an update operation. +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a Monitor. type UpdateResult struct { commonResult } -// DeleteResult represents the result of a delete operation. +// DeleteResult represents the result of a delete operation. Call its +// ExtractErr method to determine if the result succeeded or failed. type DeleteResult struct { gophercloud.ErrResult } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/doc.go new file mode 100644 index 000000000..069714868 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/doc.go @@ -0,0 +1,126 @@ +/* +Package pools provides information and interaction with Pools and +Members of the LBaaS v2 extension for the OpenStack Networking service. + +Example to List Pools + + listOpts := pools.ListOpts{ + LoadbalancerID: "c79a4468-d788-410c-bf79-9a8ef6354852", + } + + allPages, err := pools.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allPools, err := pools.ExtractPools(allPages) + if err != nil { + panic(err) + } + + for _, pools := range allPools { + fmt.Printf("%+v\n", pool) + } + +Example to Create a Pool + + createOpts := pools.CreateOpts{ + LBMethod: pools.LBMethodRoundRobin, + Protocol: "HTTP", + Name: "Example pool", + LoadbalancerID: "79e05663-7f03-45d2-a092-8b94062f22ab", + } + + pool, err := pools.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Pool + + poolID := "d67d56a6-4a86-4688-a282-f46444705c64" + + updateOpts := pools.UpdateOpts{ + Name: "new-name", + } + + pool, err := pools.Update(networkClient, poolID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Pool + + poolID := "d67d56a6-4a86-4688-a282-f46444705c64" + err := pools.Delete(networkClient, poolID).ExtractErr() + if err != nil { + panic(err) + } + +Example to List Pool Members + + poolID := "d67d56a6-4a86-4688-a282-f46444705c64" + + listOpts := pools.ListMemberOpts{ + ProtocolPort: 80, + } + + allPages, err := pools.ListMembers(networkClient, poolID, listOpts).AllPages() + if err != nil { + panic(err) + } + + allMembers, err := pools.ExtractMembers(allPages) + if err != nil { + panic(err) + } + + for _, member := allMembers { + fmt.Printf("%+v\n", member) + } + +Example to Create a Member + + poolID := "d67d56a6-4a86-4688-a282-f46444705c64" + + weight := 10 + createOpts := pools.CreateMemberOpts{ + Name: "db", + SubnetID: "1981f108-3c48-48d2-b908-30f7d28532c9", + Address: "10.0.2.11", + ProtocolPort: 80, + Weight: &weight, + } + + member, err := pools.CreateMember(networkClient, poolID, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Member + + poolID := "d67d56a6-4a86-4688-a282-f46444705c64" + memberID := "64dba99f-8af8-4200-8882-e32a0660f23e" + + weight := 4 + updateOpts := pools.UpdateMemberOpts{ + Name: "new-name", + Weight: &weight, + } + + member, err := pools.UpdateMember(networkClient, poolID, memberID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Member + + poolID := "d67d56a6-4a86-4688-a282-f46444705c64" + memberID := "64dba99f-8af8-4200-8882-e32a0660f23e" + + err := pools.DeleteMember(networkClient, poolID, memberID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package pools diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/requests.go index 093df6ad0..f427ae7bf 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/requests.go @@ -20,6 +20,7 @@ type ListOpts struct { LBMethod string `q:"lb_algorithm"` Protocol string `q:"protocol"` TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` AdminStateUp *bool `q:"admin_state_up"` Name string `q:"name"` ID string `q:"id"` @@ -71,10 +72,8 @@ const ( ProtocolHTTPS Protocol = "HTTPS" ) -// CreateOptsBuilder is the interface options structs have to satisfy in order -// to be used in the main Create operation in this package. Since many -// extensions decorate or modify the common logic, it is useful for them to -// satisfy a basic interface in order for them to be used. +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. type CreateOptsBuilder interface { ToPoolCreateMap() (map[string]interface{}, error) } @@ -86,30 +85,43 @@ type CreateOpts struct { // current specification supports LBMethodRoundRobin, LBMethodLeastConnections // and LBMethodSourceIp as valid values for this attribute. LBMethod LBMethod `json:"lb_algorithm" required:"true"` + // The protocol used by the pool members, you can use either // ProtocolTCP, ProtocolHTTP, or ProtocolHTTPS. Protocol Protocol `json:"protocol" required:"true"` + // The Loadbalancer on which the members of the pool will be associated with. - // Note: one of LoadbalancerID or ListenerID must be provided. + // Note: one of LoadbalancerID or ListenerID must be provided. LoadbalancerID string `json:"loadbalancer_id,omitempty" xor:"ListenerID"` + // The Listener on which the members of the pool will be associated with. - // Note: one of LoadbalancerID or ListenerID must be provided. + // Note: one of LoadbalancerID or ListenerID must be provided. ListenerID string `json:"listener_id,omitempty" xor:"LoadbalancerID"` - // Only required if the caller has an admin role and wants to create a pool - // for another tenant. + + // TenantID is the UUID of the project who owns the Pool. + // Only administrative users can specify a project UUID other than their own. TenantID string `json:"tenant_id,omitempty"` + + // ProjectID is the UUID of the project who owns the Pool. + // Only administrative users can specify a project UUID other than their own. + ProjectID string `json:"project_id,omitempty"` + // Name of the pool. Name string `json:"name,omitempty"` + // Human-readable description for the pool. Description string `json:"description,omitempty"` + + // Persistence is the session persistence of the pool. // Omit this field to prevent session persistence. Persistence *SessionPersistence `json:"session_persistence,omitempty"` + // The administrative state of the Pool. A valid value is true (UP) // or false (DOWN). AdminStateUp *bool `json:"admin_state_up,omitempty"` } -// ToPoolCreateMap casts a CreateOpts struct to a map. +// ToPoolCreateMap builds a request body from CreateOpts. func (opts CreateOpts) ToPoolCreateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "pool") } @@ -132,10 +144,8 @@ func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { return } -// UpdateOptsBuilder is the interface options structs have to satisfy in order -// to be used in the main Update operation in this package. Since many -// extensions decorate or modify the common logic, it is useful for them to -// satisfy a basic interface in order for them to be used. +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. type UpdateOptsBuilder interface { ToPoolUpdateMap() (map[string]interface{}, error) } @@ -144,19 +154,22 @@ type UpdateOptsBuilder interface { // operation. type UpdateOpts struct { // Name of the pool. - Name string `json:"name,omitempty"` + Name *string `json:"name,omitempty"` + // Human-readable description for the pool. - Description string `json:"description,omitempty"` + Description *string `json:"description,omitempty"` + // The algorithm used to distribute load between the members of the pool. The // current specification supports LBMethodRoundRobin, LBMethodLeastConnections // and LBMethodSourceIp as valid values for this attribute. LBMethod LBMethod `json:"lb_algorithm,omitempty"` + // The administrative state of the Pool. A valid value is true (UP) // or false (DOWN). AdminStateUp *bool `json:"admin_state_up,omitempty"` } -// ToPoolUpdateMap casts a UpdateOpts struct to a map. +// ToPoolUpdateMap builds a request body from UpdateOpts. func (opts UpdateOpts) ToPoolUpdateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "pool") } @@ -186,11 +199,11 @@ type ListMembersOptsBuilder interface { ToMembersListQuery() (string, error) } -// ListMembersOpts allows the filtering and sorting of paginated collections through -// the API. Filtering is achieved by passing in struct field values that map to -// the Member attributes you want to see returned. SortKey allows you to -// sort by a particular Member attribute. SortDir sets the direction, and is -// either `asc' or `desc'. Marker and Limit are used for pagination. +// ListMembersOpts allows the filtering and sorting of paginated collections +// through the API. Filtering is achieved by passing in struct field values +// that map to the Member attributes you want to see returned. SortKey allows +// you to sort by a particular Member attribute. SortDir sets the direction, +// and is either `asc' or `desc'. Marker and Limit are used for pagination. type ListMembersOpts struct { Name string `q:"name"` Weight int `q:"weight"` @@ -212,8 +225,8 @@ func (opts ListMembersOpts) ToMembersListQuery() (string, error) { } // ListMembers returns a Pager which allows you to iterate over a collection of -// members. It accepts a ListMembersOptsBuilder, which allows you to filter and sort -// the returned collection for greater efficiency. +// members. It accepts a ListMembersOptsBuilder, which allows you to filter and +// sort the returned collection for greater efficiency. // // Default policy settings return only those members that are owned by the // tenant who submits the request, unless an admin user submits the request. @@ -231,10 +244,8 @@ func ListMembers(c *gophercloud.ServiceClient, poolID string, opts ListMembersOp }) } -// CreateMemberOptsBuilder is the interface options structs have to satisfy in order -// to be used in the CreateMember operation in this package. Since many -// extensions decorate or modify the common logic, it is useful for them to -// satisfy a basic interface in order for them to be used. +// CreateMemberOptsBuilder allows extensions to add additional parameters to the +// CreateMember request. type CreateMemberOptsBuilder interface { ToMemberCreateMap() (map[string]interface{}, error) } @@ -242,29 +253,39 @@ type CreateMemberOptsBuilder interface { // CreateMemberOpts is the common options struct used in this package's CreateMember // operation. type CreateMemberOpts struct { - // Required. The IP address of the member to receive traffic from the load balancer. + // The IP address of the member to receive traffic from the load balancer. Address string `json:"address" required:"true"` - // Required. The port on which to listen for client traffic. + + // The port on which to listen for client traffic. ProtocolPort int `json:"protocol_port" required:"true"` - // Optional. Name of the Member. + + // Name of the Member. Name string `json:"name,omitempty"` - // Only required if the caller has an admin role and wants to create a Member - // for another tenant. + + // TenantID is the UUID of the project who owns the Member. + // Only administrative users can specify a project UUID other than their own. TenantID string `json:"tenant_id,omitempty"` - // Optional. A positive integer value that indicates the relative portion of - // traffic that this member should receive from the pool. For example, a - // member with a weight of 10 receives five times as much traffic as a member - // with a weight of 2. - Weight int `json:"weight,omitempty"` - // Optional. If you omit this parameter, LBaaS uses the vip_subnet_id - // parameter value for the subnet UUID. + + // ProjectID is the UUID of the project who owns the Member. + // Only administrative users can specify a project UUID other than their own. + ProjectID string `json:"project_id,omitempty"` + + // A positive integer value that indicates the relative portion of traffic + // that this member should receive from the pool. For example, a member with + // a weight of 10 receives five times as much traffic as a member with a + // weight of 2. + Weight *int `json:"weight,omitempty"` + + // If you omit this parameter, LBaaS uses the vip_subnet_id parameter value + // for the subnet UUID. SubnetID string `json:"subnet_id,omitempty"` - // Optional. The administrative state of the Pool. A valid value is true (UP) + + // The administrative state of the Pool. A valid value is true (UP) // or false (DOWN). AdminStateUp *bool `json:"admin_state_up,omitempty"` } -// ToMemberCreateMap casts a CreateOpts struct to a map. +// ToMemberCreateMap builds a request body from CreateMemberOpts. func (opts CreateMemberOpts) ToMemberCreateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "member") } @@ -286,10 +307,8 @@ func GetMember(c *gophercloud.ServiceClient, poolID string, memberID string) (r return } -// MemberUpdateOptsBuilder is the interface options structs have to satisfy in order -// to be used in the main Update operation in this package. Since many -// extensions decorate or modify the common logic, it is useful for them to -// satisfy a basic interface in order for them to be used. +// UpdateMemberOptsBuilder allows extensions to add additional parameters to the +// List request. type UpdateMemberOptsBuilder interface { ToMemberUpdateMap() (map[string]interface{}, error) } @@ -297,19 +316,21 @@ type UpdateMemberOptsBuilder interface { // UpdateMemberOpts is the common options struct used in this package's Update // operation. type UpdateMemberOpts struct { - // Optional. Name of the Member. - Name string `json:"name,omitempty"` - // Optional. A positive integer value that indicates the relative portion of - // traffic that this member should receive from the pool. For example, a - // member with a weight of 10 receives five times as much traffic as a member - // with a weight of 2. - Weight int `json:"weight,omitempty"` - // Optional. The administrative state of the Pool. A valid value is true (UP) + // Name of the Member. + Name *string `json:"name,omitempty"` + + // A positive integer value that indicates the relative portion of traffic + // that this member should receive from the pool. For example, a member with + // a weight of 10 receives five times as much traffic as a member with a + // weight of 2. + Weight *int `json:"weight,omitempty"` + + // The administrative state of the Pool. A valid value is true (UP) // or false (DOWN). AdminStateUp *bool `json:"admin_state_up,omitempty"` } -// ToMemberUpdateMap casts a UpdateOpts struct to a map. +// ToMemberUpdateMap builds a request body from UpdateMemberOpts. func (opts UpdateMemberOpts) ToMemberUpdateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "member") } @@ -327,7 +348,8 @@ func UpdateMember(c *gophercloud.ServiceClient, poolID string, memberID string, return } -// DisassociateMember will remove and disassociate a Member from a particular Pool. +// DisassociateMember will remove and disassociate a Member from a particular +// Pool. func DeleteMember(c *gophercloud.ServiceClient, poolID string, memberID string) (r DeleteMemberResult) { _, r.Err = c.Delete(memberResourceURL(c, poolID, memberID), nil) return diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/results.go index 0e0bf366b..fba0d3a87 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/results.go @@ -22,17 +22,19 @@ import ( // requests carrying the same cookie value will be handled by the // same Member of the Pool. type SessionPersistence struct { - // The type of persistence mode + // The type of persistence mode. Type string `json:"type"` - // Name of cookie if persistence mode is set appropriately + // Name of cookie if persistence mode is set appropriately. CookieName string `json:"cookie_name,omitempty"` } +// LoadBalancerID represents a load balancer. type LoadBalancerID struct { ID string `json:"id"` } +// ListenerID represents a listener. type ListenerID struct { ID string `json:"id"` } @@ -46,36 +48,59 @@ type Pool struct { // so on. This value, which must be supported, is dependent on the provider. // Round-robin must be supported. LBMethod string `json:"lb_algorithm"` + // The protocol of the Pool, which is TCP, HTTP, or HTTPS. Protocol string `json:"protocol"` + // Description for the Pool. Description string `json:"description"` + // A list of listeners objects IDs. Listeners []ListenerID `json:"listeners"` //[]map[string]interface{} + // A list of member objects IDs. Members []Member `json:"members"` + // The ID of associated health monitor. MonitorID string `json:"healthmonitor_id"` + // The network on which the members of the Pool will be located. Only members // that are on this network can be added to the Pool. SubnetID string `json:"subnet_id"` - // Owner of the Pool. Only an administrative user can specify a tenant ID - // other than its own. + + // Owner of the Pool. TenantID string `json:"tenant_id"` + // The administrative state of the Pool, which is up (true) or down (false). AdminStateUp bool `json:"admin_state_up"` + // Pool name. Does not have to be unique. Name string `json:"name"` + // The unique ID for the Pool. ID string `json:"id"` + // A list of load balancer objects IDs. Loadbalancers []LoadBalancerID `json:"loadbalancers"` + // Indicates whether connections in the same session will be processed by the // same Pool member or not. Persistence SessionPersistence `json:"session_persistence"` - // The provider - Provider string `json:"provider"` - Monitor monitors.Monitor `json:"healthmonitor"` + + // The load balancer provider. + Provider string `json:"provider"` + + // The Monitor associated with this Pool. + Monitor monitors.Monitor `json:"healthmonitor"` + + // The provisioning status of the pool. + // This value is ACTIVE, PENDING_* or ERROR. + ProvisioningStatus string `json:"provisioning_status"` + + // The operating status of the pool. + // This field seems to only be returned during a call to a load balancer's /status + // see: https://github.com/gophercloud/gophercloud/issues/1362 + OperatingStatus string `json:"operating_status"` } // PoolPage is the page returned by a pager when traversing over a @@ -105,7 +130,7 @@ func (r PoolPage) IsEmpty() (bool, error) { } // ExtractPools accepts a Page struct, specifically a PoolPage struct, -// and extracts the elements into a slice of Router structs. In other words, +// and extracts the elements into a slice of Pool structs. In other words, // a generic collection is mapped into a relevant slice. func ExtractPools(r pagination.Page) ([]Pool, error) { var s struct { @@ -119,7 +144,7 @@ type commonResult struct { gophercloud.Result } -// Extract is a function that accepts a result and extracts a router. +// Extract is a function that accepts a result and extracts a pool. func (r commonResult) Extract() (*Pool, error) { var s struct { Pool *Pool `json:"pool"` @@ -128,22 +153,26 @@ func (r commonResult) Extract() (*Pool, error) { return s.Pool, err } -// 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 Pool. type CreateResult struct { commonResult } -// GetResult represents the result of a Get operation. +// GetResult represents the result of a Get operation. Call its Extract +// method to interpret the result as a Pool. type GetResult struct { commonResult } -// UpdateResult represents the result of an Update operation. +// UpdateResult represents the result of an Update operation. Call its Extract +// method to interpret the result as a Pool. type UpdateResult struct { commonResult } -// DeleteResult represents the result of a Delete operation. +// DeleteResult represents the result of a Delete operation. Call its +// ExtractErr method to determine if the request succeeded or failed. type DeleteResult struct { gophercloud.ErrResult } @@ -152,23 +181,39 @@ type DeleteResult struct { type Member struct { // Name of the Member. Name string `json:"name"` + // Weight of Member. Weight int `json:"weight"` + // The administrative state of the member, which is up (true) or down (false). AdminStateUp bool `json:"admin_state_up"` - // Owner of the Member. Only an administrative user can specify a tenant ID - // other than its own. + + // Owner of the Member. TenantID string `json:"tenant_id"` - // parameter value for the subnet UUID. + + // Parameter value for the subnet UUID. SubnetID string `json:"subnet_id"` + // The Pool to which the Member belongs. PoolID string `json:"pool_id"` + // The IP address of the Member. Address string `json:"address"` + // The port on which the application is hosted. ProtocolPort int `json:"protocol_port"` + // The unique ID for the Member. ID string `json:"id"` + + // The provisioning status of the member. + // This value is ACTIVE, PENDING_* or ERROR. + ProvisioningStatus string `json:"provisioning_status"` + + // The operating status of the member. + // This field seems to only be returned during a call to a load balancer's /status + // see: https://github.com/gophercloud/gophercloud/issues/1362 + OperatingStatus string `json:"operating_status"` } // MemberPage is the page returned by a pager when traversing over a @@ -197,8 +242,8 @@ func (r MemberPage) IsEmpty() (bool, error) { return len(is) == 0, err } -// ExtractMembers accepts a Page struct, specifically a RouterPage struct, -// and extracts the elements into a slice of Router structs. In other words, +// ExtractMembers accepts a Page struct, specifically a MemberPage struct, +// and extracts the elements into a slice of Members structs. In other words, // a generic collection is mapped into a relevant slice. func ExtractMembers(r pagination.Page) ([]Member, error) { var s struct { @@ -212,7 +257,7 @@ type commonMemberResult struct { gophercloud.Result } -// ExtractMember is a function that accepts a result and extracts a router. +// ExtractMember is a function that accepts a result and extracts a member. func (r commonMemberResult) Extract() (*Member, error) { var s struct { Member *Member `json:"member"` @@ -222,21 +267,25 @@ func (r commonMemberResult) Extract() (*Member, error) { } // CreateMemberResult represents the result of a CreateMember operation. +// Call its Extract method to interpret it as a Member. type CreateMemberResult struct { commonMemberResult } // GetMemberResult represents the result of a GetMember operation. +// Call its Extract method to interpret it as a Member. type GetMemberResult struct { commonMemberResult } // UpdateMemberResult represents the result of an UpdateMember operation. +// Call its Extract method to interpret it as a Member. type UpdateMemberResult struct { commonMemberResult } // DeleteMemberResult represents the result of a DeleteMember operation. +// Call its ExtractErr method to determine if the request succeeded or failed. type DeleteMemberResult struct { gophercloud.ErrResult } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/provider/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/provider/doc.go index 373da44f8..ddc44175a 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/provider/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/provider/doc.go @@ -1,21 +1,73 @@ -// Package provider gives access to the provider Neutron plugin, allowing -// network extended attributes. The provider extended attributes for networks -// enable administrative users to specify how network objects map to the -// underlying networking infrastructure. These extended attributes also appear -// when administrative users query networks. -// -// For more information about extended attributes, see the NetworkExtAttrs -// struct. The actual semantics of these attributes depend on the technology -// back end of the particular plug-in. See the plug-in documentation and the -// OpenStack Cloud Administrator Guide to understand which values should be -// specific for each of these attributes when OpenStack Networking is deployed -// with a particular plug-in. The examples shown in this chapter refer to the -// Open vSwitch plug-in. -// -// The default policy settings enable only users with administrative rights to -// specify these parameters in requests and to see their values in responses. By -// default, the provider network extension attributes are completely hidden from -// regular tenants. As a rule of thumb, if these attributes are not visible in a -// GET /networks/ operation, this implies the user submitting the -// request is not authorized to view or manipulate provider network attributes. +/* +Package provider gives access to the provider Neutron plugin, allowing +network extended attributes. The provider extended attributes for networks +enable administrative users to specify how network objects map to the +underlying networking infrastructure. These extended attributes also appear +when administrative users query networks. + +For more information about extended attributes, see the NetworkExtAttrs +struct. The actual semantics of these attributes depend on the technology +back end of the particular plug-in. See the plug-in documentation and the +OpenStack Cloud Administrator Guide to understand which values should be +specific for each of these attributes when OpenStack Networking is deployed +with a particular plug-in. The examples shown in this chapter refer to the +Open vSwitch plug-in. + +The default policy settings enable only users with administrative rights to +specify these parameters in requests and to see their values in responses. By +default, the provider network extension attributes are completely hidden from +regular tenants. As a rule of thumb, if these attributes are not visible in a +GET /networks/ operation, this implies the user submitting the +request is not authorized to view or manipulate provider network attributes. + +Example to List Networks with Provider Information + + type NetworkWithProvider { + networks.Network + provider.NetworkProviderExt + } + + var allNetworks []NetworkWithProvider + + allPages, err := networks.List(networkClient, nil).AllPages() + if err != nil { + panic(err) + } + + err = networks.ExtractNetworksInto(allPages, &allNetworks) + if err != nil { + panic(err) + } + + for _, network := range allNetworks { + fmt.Printf("%+v\n", network) + } + +Example to Create a Provider Network + + segments := []provider.Segment{ + provider.Segment{ + NetworkType: "vxlan", + PhysicalNetwork: "br-ex", + SegmentationID: 615, + }, + } + + iTrue := true + networkCreateOpts := networks.CreateOpts{ + Name: "provider-network", + AdminStateUp: &iTrue, + Shared: &iTrue, + } + + createOpts : provider.CreateOptsExt{ + CreateOptsBuilder: networkCreateOpts, + Segments: segments, + } + + network, err := networks.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } +*/ package provider diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/provider/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/provider/results.go index 55770d8b8..9babd2ab6 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/provider/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/provider/results.go @@ -3,54 +3,31 @@ package provider import ( "encoding/json" "strconv" - - "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" - "github.com/gophercloud/gophercloud/pagination" ) -// NetworkExtAttrs represents an extended form of a Network with additional fields. -type NetworkExtAttrs struct { - // UUID for the network - ID string `json:"id"` - - // Human-readable name for the network. Might not be unique. - Name string `json:"name"` - - // The administrative state of network. If false (down), the network does not forward packets. - AdminStateUp bool `json:"admin_state_up"` - - // Indicates whether network is currently operational. Possible values include - // `ACTIVE', `DOWN', `BUILD', or `ERROR'. Plug-ins might define additional values. - Status string `json:"status"` - - // Subnets associated with this network. - Subnets []string `json:"subnets"` - - // Owner of network. Only admin users can specify a tenant_id other than its own. - TenantID string `json:"tenant_id"` - - // Specifies whether the network resource can be accessed by any tenant or not. - Shared bool `json:"shared"` - +// NetworkProviderExt represents an extended form of a Network with additional +// fields. +type NetworkProviderExt struct { // Specifies the nature of the physical network mapped to this network // resource. Examples are flat, vlan, or gre. NetworkType string `json:"provider:network_type"` // Identifies the physical network on top of which this network object is - // being implemented. The OpenStack Networking API does not expose any facility - // for retrieving the list of available physical networks. As an example, in - // the Open vSwitch plug-in this is a symbolic name which is then mapped to - // specific bridges on each compute host through the Open vSwitch plug-in - // configuration file. + // being implemented. The OpenStack Networking API does not expose any + // facility for retrieving the list of available physical networks. As an + // example, in the Open vSwitch plug-in this is a symbolic name which is + // then mapped to specific bridges on each compute host through the Open + // vSwitch plug-in configuration file. PhysicalNetwork string `json:"provider:physical_network"` // Identifies an isolated segment on the physical network; the nature of the // segment depends on the segmentation model defined by network_type. For // instance, if network_type is vlan, then this is a vlan identifier; // otherwise, if network_type is gre, then this will be a gre key. - SegmentationID string `json:"provider:segmentation_id"` + SegmentationID string `json:"-"` - // Segments is an array of Segment which defines multiple physical bindings to logical networks. + // Segments is an array of Segment which defines multiple physical bindings + // to logical networks. Segments []Segment `json:"segments"` } @@ -61,66 +38,25 @@ type Segment struct { SegmentationID int `json:"provider:segmentation_id"` } -func (n *NetworkExtAttrs) UnmarshalJSON(b []byte) error { - type tmp NetworkExtAttrs - var networkExtAttrs *struct { +func (r *NetworkProviderExt) UnmarshalJSON(b []byte) error { + type tmp NetworkProviderExt + var networkProviderExt struct { tmp SegmentationID interface{} `json:"provider:segmentation_id"` } - if err := json.Unmarshal(b, &networkExtAttrs); err != nil { + if err := json.Unmarshal(b, &networkProviderExt); err != nil { return err } - *n = NetworkExtAttrs(networkExtAttrs.tmp) + *r = NetworkProviderExt(networkProviderExt.tmp) - switch t := networkExtAttrs.SegmentationID.(type) { + switch t := networkProviderExt.SegmentationID.(type) { case float64: - n.SegmentationID = strconv.FormatFloat(t, 'f', -1, 64) + r.SegmentationID = strconv.FormatFloat(t, 'f', -1, 64) case string: - n.SegmentationID = string(t) + r.SegmentationID = string(t) } return nil } - -// ExtractGet decorates a GetResult struct returned from a networks.Get() -// function with extended attributes. -func ExtractGet(r networks.GetResult) (*NetworkExtAttrs, error) { - var s struct { - Network *NetworkExtAttrs `json:"network"` - } - err := r.ExtractInto(&s) - return s.Network, err -} - -// ExtractCreate decorates a CreateResult struct returned from a networks.Create() -// function with extended attributes. -func ExtractCreate(r networks.CreateResult) (*NetworkExtAttrs, error) { - var s struct { - Network *NetworkExtAttrs `json:"network"` - } - err := r.ExtractInto(&s) - return s.Network, err -} - -// ExtractUpdate decorates a UpdateResult struct returned from a -// networks.Update() function with extended attributes. -func ExtractUpdate(r networks.UpdateResult) (*NetworkExtAttrs, error) { - var s struct { - Network *NetworkExtAttrs `json:"network"` - } - err := r.ExtractInto(&s) - return s.Network, err -} - -// ExtractList accepts a Page struct, specifically a NetworkPage struct, and -// extracts the elements into a slice of NetworkExtAttrs structs. In other -// words, a generic collection is mapped into a relevant slice. -func ExtractList(r pagination.Page) ([]NetworkExtAttrs, error) { - var s struct { - Networks []NetworkExtAttrs `json:"networks" json:"networks"` - } - err := (r.(networks.NetworkPage)).ExtractInto(&s) - return s.Networks, err -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/doc.go new file mode 100644 index 000000000..7d8bbcaac --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/doc.go @@ -0,0 +1,58 @@ +/* +Package groups provides information and interaction with Security Groups +for the OpenStack Networking service. + +Example to List Security Groups + + listOpts := groups.ListOpts{ + TenantID: "966b3c7d36a24facaf20b7e458bf2192", + } + + allPages, err := groups.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allGroups, err := groups.ExtractGroups(allPages) + if err != nil { + panic(err) + } + + for _, group := range allGroups { + fmt.Printf("%+v\n", group) + } + +Example to Create a Security Group + + createOpts := groups.CreateOpts{ + Name: "group_name", + Description: "A Security Group", + } + + group, err := groups.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Security Group + + groupID := "37d94f8a-d136-465c-ae46-144f0d8ef141" + + updateOpts := groups.UpdateOpts{ + Name: "new_name", + } + + group, err := groups.Update(networkClient, groupID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Security Group + + groupID := "37d94f8a-d136-465c-ae46-144f0d8ef141" + err := groups.Delete(networkClient, groupID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package groups diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/requests.go index 7e1f60812..a22cd306e 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/requests.go @@ -7,17 +7,23 @@ import ( // ListOpts allows the filtering and sorting of paginated collections through // the API. Filtering is achieved by passing in struct field values that map to -// the floating IP attributes you want to see returned. SortKey allows you to +// the group attributes you want to see returned. SortKey allows you to // sort by a particular network attribute. SortDir sets the direction, and is // either `asc' or `desc'. Marker and Limit are used for pagination. type ListOpts struct { - ID string `q:"id"` - Name string `q:"name"` - TenantID string `q:"tenant_id"` - Limit int `q:"limit"` - Marker string `q:"marker"` - SortKey string `q:"sort_key"` - SortDir string `q:"sort_dir"` + ID string `q:"id"` + Name string `q:"name"` + Description string `q:"description"` + TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` + Tags string `q:"tags"` + TagsAny string `q:"tags-any"` + NotTags string `q:"not-tags"` + NotTagsAny string `q:"not-tags-any"` } // List returns a Pager which allows you to iterate over a collection of @@ -34,20 +40,30 @@ func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager { }) } +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. type CreateOptsBuilder interface { ToSecGroupCreateMap() (map[string]interface{}, error) } // CreateOpts contains all the values needed to create a new security group. type CreateOpts struct { - // Required. Human-readable name for the Security Group. Does not have to be unique. + // Human-readable name for the Security Group. Does not have to be unique. Name string `json:"name" required:"true"` - // Required for admins. Indicates the owner of the Security Group. + + // TenantID is the UUID of the project who owns the Group. + // Only administrative users can specify a tenant UUID other than their own. TenantID string `json:"tenant_id,omitempty"` - // Optional. Describes the security group. + + // ProjectID is the UUID of the project who owns the Group. + // Only administrative users can specify a tenant UUID other than their own. + ProjectID string `json:"project_id,omitempty"` + + // Describes the security group. Description string `json:"description,omitempty"` } +// ToSecGroupCreateMap builds a request body from CreateOpts. func (opts CreateOpts) ToSecGroupCreateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "security_group") } @@ -64,18 +80,23 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul return } +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. type UpdateOptsBuilder interface { ToSecGroupUpdateMap() (map[string]interface{}, error) } -// UpdateOpts contains all the values needed to update an existing security group. +// UpdateOpts contains all the values needed to update an existing security +// group. type UpdateOpts struct { // Human-readable name for the Security Group. Does not have to be unique. Name string `json:"name,omitempty"` - // Optional. Describes the security group. - Description string `json:"description,omitempty"` + + // Describes the security group. + Description *string `json:"description,omitempty"` } +// ToSecGroupUpdateMap builds a request body from UpdateOpts. func (opts UpdateOpts) ToSecGroupUpdateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "security_group") } @@ -100,17 +121,24 @@ func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { return } -// Delete will permanently delete a particular security group based on its unique ID. +// Delete will permanently delete a particular security group based on its +// unique ID. func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { _, r.Err = c.Delete(resourceURL(c, id), nil) return } -// IDFromName is a convenience function that returns a security group's ID given its name. +// IDFromName is a convenience function that returns a security group's ID, +// given its name. func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { count := 0 id := "" - pages, err := List(client, ListOpts{}).AllPages() + + listOpts := ListOpts{ + Name: name, + } + + pages, err := List(client, listOpts).AllPages() if err != nil { return "", err } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/results.go index d737abb06..468952b3e 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/results.go @@ -11,8 +11,8 @@ type SecGroup struct { // The UUID for the security group. ID string - // Human-readable name for the security group. Might not be unique. Cannot be - // named "default" as that is automatically created for a tenant. + // Human-readable name for the security group. Might not be unique. + // Cannot be named "default" as that is automatically created for a tenant. Name string // The security group description. @@ -22,9 +22,14 @@ type SecGroup struct { // traffic entering and leaving the group. Rules []rules.SecGroupRule `json:"security_group_rules"` - // Owner of the security group. Only admin users can specify a TenantID - // other than their own. + // TenantID is the project owner of the security group. TenantID string `json:"tenant_id"` + + // ProjectID is the project owner of the security group. + ProjectID string `json:"project_id"` + + // Tags optionally set via extensions/attributestags + Tags []string `json:"tags"` } // SecGroupPage is the page returned by a pager when traversing over a @@ -78,22 +83,26 @@ func (r commonResult) Extract() (*SecGroup, error) { return s.SecGroup, err } -// CreateResult represents the result of a create operation. +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a SecGroup. type CreateResult struct { commonResult } -// UpdateResult represents the result of an update operation. +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a SecGroup. type UpdateResult struct { commonResult } -// GetResult represents the result of a get operation. +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a SecGroup. type GetResult struct { commonResult } -// DeleteResult represents the result of a delete operation. +// DeleteResult represents the result of a delete operation. Call its +// ExtractErr method to determine if the request succeeded or failed. type DeleteResult struct { gophercloud.ErrResult } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/doc.go new file mode 100644 index 000000000..bf66dc8b4 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/doc.go @@ -0,0 +1,50 @@ +/* +Package rules provides information and interaction with Security Group Rules +for the OpenStack Networking service. + +Example to List Security Groups Rules + + listOpts := rules.ListOpts{ + Protocol: "tcp", + } + + allPages, err := rules.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allRules, err := rules.ExtractRules(allPages) + if err != nil { + panic(err) + } + + for _, rule := range allRules { + fmt.Printf("%+v\n", rule) + } + +Example to Create a Security Group Rule + + createOpts := rules.CreateOpts{ + Direction: "ingress", + PortRangeMin: 80, + EtherType: rules.EtherType4, + PortRangeMax: 80, + Protocol: "tcp", + RemoteGroupID: "85cc3048-abc3-43cc-89b3-377341426ac5", + SecGroupID: "a7734e61-b545-452d-a3cd-0189cbd9747a", + } + + rule, err := rules.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Security Group Rule + + ruleID := "37d94f8a-d136-465c-ae46-144f0d8ef141" + err := rules.Delete(networkClient, ruleID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package rules diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/requests.go index 59ba721d6..c7741ffcd 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/requests.go @@ -7,13 +7,14 @@ import ( // ListOpts allows the filtering and sorting of paginated collections through // the API. Filtering is achieved by passing in struct field values that map to -// the security group attributes you want to see returned. SortKey allows you to -// sort by a particular network attribute. SortDir sets the direction, and is -// either `asc' or `desc'. Marker and Limit are used for pagination. +// the security group rule attributes you want to see returned. SortKey allows +// you to sort by a particular network attribute. SortDir sets the direction, +// and is either `asc' or `desc'. Marker and Limit are used for pagination. type ListOpts struct { Direction string `q:"direction"` EtherType string `q:"ethertype"` ID string `q:"id"` + Description string `q:"description"` PortRangeMax int `q:"port_range_max"` PortRangeMin int `q:"port_range_min"` Protocol string `q:"protocol"` @@ -21,6 +22,7 @@ type ListOpts struct { RemoteIPPrefix string `q:"remote_ip_prefix"` SecGroupID string `q:"security_group_id"` TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` Limit int `q:"limit"` Marker string `q:"marker"` SortKey string `q:"sort_key"` @@ -74,48 +76,59 @@ const ( ProtocolVRRP RuleProtocol = "vrrp" ) -// CreateOptsBuilder is what types must satisfy to be used as Create -// options. +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. type CreateOptsBuilder interface { ToSecGroupRuleCreateMap() (map[string]interface{}, error) } -// CreateOpts contains all the values needed to create a new security group rule. +// CreateOpts contains all the values needed to create a new security group +// rule. type CreateOpts struct { - // Required. Must be either "ingress" or "egress": the direction in which the - // security group rule is applied. + // Must be either "ingress" or "egress": the direction in which the security + // group rule is applied. Direction RuleDirection `json:"direction" required:"true"` - // Required. Must be "IPv4" or "IPv6", and addresses represented in CIDR must - // match the ingress or egress rules. + + // String description of each rule, optional + Description string `json:"description,omitempty"` + + // Must be "IPv4" or "IPv6", and addresses represented in CIDR must match the + // ingress or egress rules. EtherType RuleEtherType `json:"ethertype" required:"true"` - // Required. The security group ID to associate with this security group rule. + + // The security group ID to associate with this security group rule. SecGroupID string `json:"security_group_id" required:"true"` - // Optional. The maximum port number in the range that is matched by the - // security group rule. The PortRangeMin attribute constrains the PortRangeMax - // attribute. If the protocol is ICMP, this value must be an ICMP type. + + // The maximum port number in the range that is matched by the security group + // rule. The PortRangeMin attribute constrains the PortRangeMax attribute. If + // the protocol is ICMP, this value must be an ICMP type. PortRangeMax int `json:"port_range_max,omitempty"` - // Optional. The minimum port number in the range that is matched by the - // security group rule. If the protocol is TCP or UDP, this value must be - // less than or equal to the value of the PortRangeMax attribute. If the - // protocol is ICMP, this value must be an ICMP type. + + // The minimum port number in the range that is matched by the security group + // rule. If the protocol is TCP or UDP, this value must be less than or equal + // to the value of the PortRangeMax attribute. If the protocol is ICMP, this + // value must be an ICMP type. PortRangeMin int `json:"port_range_min,omitempty"` - // Optional. The protocol that is matched by the security group rule. Valid - // values are "tcp", "udp", "icmp" or an empty string. + + // The protocol that is matched by the security group rule. Valid values are + // "tcp", "udp", "icmp" or an empty string. Protocol RuleProtocol `json:"protocol,omitempty"` - // Optional. The remote group ID to be associated with this security group - // rule. You can specify either RemoteGroupID or RemoteIPPrefix. + + // The remote group ID to be associated with this security group rule. You can + // specify either RemoteGroupID or RemoteIPPrefix. RemoteGroupID string `json:"remote_group_id,omitempty"` - // Optional. The remote IP prefix to be associated with this security group - // rule. You can specify either RemoteGroupID or RemoteIPPrefix. This - // attribute matches the specified IP prefix as the source IP address of the - // IP packet. + + // The remote IP prefix to be associated with this security group rule. You can + // specify either RemoteGroupID or RemoteIPPrefix. This attribute matches the + // specified IP prefix as the source IP address of the IP packet. RemoteIPPrefix string `json:"remote_ip_prefix,omitempty"` - // Required for admins. Indicates the owner of the VIP. - TenantID string `json:"tenant_id,omitempty"` + + // TenantID is the UUID of the project who owns the Rule. + // Only administrative users can specify a project UUID other than their own. + ProjectID string `json:"project_id,omitempty"` } -// ToSecGroupRuleCreateMap allows CreateOpts to satisfy the CreateOptsBuilder -// interface +// ToSecGroupRuleCreateMap builds a request body from CreateOpts. func (opts CreateOpts) ToSecGroupRuleCreateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "security_group_rule") } @@ -138,7 +151,8 @@ func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { return } -// Delete will permanently delete a particular security group rule based on its unique ID. +// Delete will permanently delete a particular security group rule based on its +// unique ID. func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { _, r.Err = c.Delete(resourceURL(c, id), nil) return diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/results.go index 18476a602..3bf5501d9 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/results.go @@ -17,6 +17,9 @@ type SecGroupRule struct { // instance. An egress rule is applied to traffic leaving the instance. Direction string + // Descripton of the rule + Description string `json:"description"` + // Must be IPv4 or IPv6, and addresses represented in CIDR must match the // ingress or egress rules. EtherType string `json:"ethertype"` @@ -48,8 +51,11 @@ type SecGroupRule struct { // matches the specified IP prefix as the source IP address of the IP packet. RemoteIPPrefix string `json:"remote_ip_prefix"` - // The owner of this security group rule. + // TenantID is the project owner of this security group rule. TenantID string `json:"tenant_id"` + + // ProjectID is the project owner of this security group rule. + ProjectID string `json:"project_id"` } // SecGroupRulePage is the page returned by a pager when traversing over a @@ -102,17 +108,20 @@ func (r commonResult) Extract() (*SecGroupRule, error) { return s.SecGroupRule, err } -// CreateResult represents the result of a create operation. +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a SecGroupRule. type CreateResult struct { commonResult } -// GetResult represents the result of a get operation. +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a SecGroupRule. type GetResult struct { commonResult } -// DeleteResult represents the result of a delete operation. +// DeleteResult represents the result of a delete operation. Call its +// ExtractErr method to determine if the request succeeded or failed. type DeleteResult struct { gophercloud.ErrResult } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/subnetpools/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/subnetpools/doc.go new file mode 100644 index 000000000..2a9fe63dd --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/subnetpools/doc.go @@ -0,0 +1,72 @@ +/* +Package subnetpools provides the ability to retrieve and manage subnetpools through the Neutron API. + +Example of Listing Subnetpools + + listOpts := subnets.ListOpts{ + IPVersion: 6, + } + + allPages, err := subnetpools.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allSubnetpools, err := subnetpools.ExtractSubnetPools(allPages) + if err != nil { + panic(err) + } + + for _, subnetpools := range allSubnetpools { + fmt.Printf("%+v\n", subnetpools) + } + +Example to Get a Subnetpool + + subnetPoolID = "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55" + subnetPool, err := subnetpools.Get(networkClient, subnetPoolID).Extract() + if err != nil { + panic(err) + } + +Example to Create a new Subnetpool + + subnetPoolName := "private_pool" + subnetPoolPrefixes := []string{ + "10.0.0.0/8", + "172.16.0.0/12", + "192.168.0.0/16", + } + subnetPoolOpts := subnetpools.CreateOpts{ + Name: subnetPoolName, + Prefixes: subnetPoolPrefixes, + } + subnetPool, err := subnetpools.Create(networkClient, subnetPoolOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Subnetpool + + subnetPoolID := "099546ca-788d-41e5-a76d-17d8cd282d3e" + updateOpts := networks.UpdateOpts{ + Prefixes: []string{ + "fdf7:b13d:dead:beef::/64", + }, + MaxPrefixLen: 72, + } + + subnetPool, err := subnetpools.Update(networkClient, subnetPoolID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Subnetpool + + subnetPoolID := "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55" + err := subnetpools.Delete(networkClient, subnetPoolID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package subnetpools diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/subnetpools/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/subnetpools/requests.go new file mode 100644 index 000000000..c54813b85 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/subnetpools/requests.go @@ -0,0 +1,225 @@ +package subnetpools + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToSubnetPoolListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the Neutron API. Filtering is achieved by passing in struct field values +// that map to the subnetpool attributes you want to see returned. +// SortKey allows you to sort by a particular subnetpool attribute. +// SortDir sets the direction, and is either `asc' or `desc'. +// Marker and Limit are used for the pagination. +type ListOpts struct { + ID string `q:"id"` + Name string `q:"name"` + DefaultQuota int `q:"default_quota"` + TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` + DefaultPrefixLen int `q:"default_prefixlen"` + MinPrefixLen int `q:"min_prefixlen"` + MaxPrefixLen int `q:"max_prefixlen"` + AddressScopeID string `q:"address_scope_id"` + IPVersion int `q:"ip_version"` + Shared *bool `q:"shared"` + Description string `q:"description"` + IsDefault *bool `q:"is_default"` + RevisionNumber int `q:"revision_number"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` + Tags string `q:"tags"` + TagsAny string `q:"tags-any"` + NotTags string `q:"not-tags"` + NotTagsAny string `q:"not-tags-any"` +} + +// ToSubnetPoolListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToSubnetPoolListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of +// subnetpools. It accepts a ListOpts struct, which allows you to filter and sort +// the returned collection for greater efficiency. +// +// Default policy settings return only the subnetpools owned by the project +// of the user submitting the request, unless the user has the administrative role. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(c) + if opts != nil { + query, err := opts.ToSubnetPoolListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return SubnetPoolPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves a specific subnetpool based on its ID. +func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = c.Get(getURL(c, id), &r.Body, nil) + return +} + +// CreateOptsBuilder allows to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToSubnetPoolCreateMap() (map[string]interface{}, error) +} + +// CreateOpts specifies parameters of a new subnetpool. +type CreateOpts struct { + // Name is the human-readable name of the subnetpool. + Name string `json:"name"` + + // DefaultQuota is the per-project quota on the prefix space + // that can be allocated from the subnetpool for project subnets. + DefaultQuota int `json:"default_quota,omitempty"` + + // TenantID is the id of the Identity project. + TenantID string `json:"tenant_id,omitempty"` + + // ProjectID is the id of the Identity project. + ProjectID string `json:"project_id,omitempty"` + + // Prefixes is the list of subnet prefixes to assign to the subnetpool. + // Neutron API merges adjacent prefixes and treats them as a single prefix. + // Each subnet prefix must be unique among all subnet prefixes in all subnetpools + // that are associated with the address scope. + Prefixes []string `json:"prefixes"` + + // DefaultPrefixLen is the size of the prefix to allocate when the cidr + // or prefixlen attributes are omitted when you create the subnet. + // Defaults to the MinPrefixLen. + DefaultPrefixLen int `json:"default_prefixlen,omitempty"` + + // MinPrefixLen is the smallest prefix that can be allocated from a subnetpool. + // For IPv4 subnetpools, default is 8. + // For IPv6 subnetpools, default is 64. + MinPrefixLen int `json:"min_prefixlen,omitempty"` + + // MaxPrefixLen is the maximum prefix size that can be allocated from the subnetpool. + // For IPv4 subnetpools, default is 32. + // For IPv6 subnetpools, default is 128. + MaxPrefixLen int `json:"max_prefixlen,omitempty"` + + // AddressScopeID is the Neutron address scope to assign to the subnetpool. + AddressScopeID string `json:"address_scope_id,omitempty"` + + // Shared indicates whether this network is shared across all projects. + Shared bool `json:"shared,omitempty"` + + // Description is the human-readable description for the resource. + Description string `json:"description,omitempty"` + + // IsDefault indicates if the subnetpool is default pool or not. + IsDefault bool `json:"is_default,omitempty"` +} + +// ToSubnetPoolCreateMap constructs a request body from CreateOpts. +func (opts CreateOpts) ToSubnetPoolCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "subnetpool") +} + +// Create requests the creation of a new subnetpool on the server. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToSubnetPoolCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToSubnetPoolUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts represents options used to update a network. +type UpdateOpts struct { + // Name is the human-readable name of the subnetpool. + Name string `json:"name,omitempty"` + + // DefaultQuota is the per-project quota on the prefix space + // that can be allocated from the subnetpool for project subnets. + DefaultQuota *int `json:"default_quota,omitempty"` + + // TenantID is the id of the Identity project. + TenantID string `json:"tenant_id,omitempty"` + + // ProjectID is the id of the Identity project. + ProjectID string `json:"project_id,omitempty"` + + // Prefixes is the list of subnet prefixes to assign to the subnetpool. + // Neutron API merges adjacent prefixes and treats them as a single prefix. + // Each subnet prefix must be unique among all subnet prefixes in all subnetpools + // that are associated with the address scope. + Prefixes []string `json:"prefixes,omitempty"` + + // DefaultPrefixLen is yhe size of the prefix to allocate when the cidr + // or prefixlen attributes are omitted when you create the subnet. + // Defaults to the MinPrefixLen. + DefaultPrefixLen int `json:"default_prefixlen,omitempty"` + + // MinPrefixLen is the smallest prefix that can be allocated from a subnetpool. + // For IPv4 subnetpools, default is 8. + // For IPv6 subnetpools, default is 64. + MinPrefixLen int `json:"min_prefixlen,omitempty"` + + // MaxPrefixLen is the maximum prefix size that can be allocated from the subnetpool. + // For IPv4 subnetpools, default is 32. + // For IPv6 subnetpools, default is 128. + MaxPrefixLen int `json:"max_prefixlen,omitempty"` + + // AddressScopeID is the Neutron address scope to assign to the subnetpool. + AddressScopeID *string `json:"address_scope_id,omitempty"` + + // Description is thehuman-readable description for the resource. + Description *string `json:"description,omitempty"` + + // IsDefault indicates if the subnetpool is default pool or not. + IsDefault *bool `json:"is_default,omitempty"` +} + +// ToSubnetPoolUpdateMap builds a request body from UpdateOpts. +func (opts UpdateOpts) ToSubnetPoolUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "subnetpool") +} + +// Update accepts a UpdateOpts struct and updates an existing subnetpool using the +// values provided. +func Update(c *gophercloud.ServiceClient, subnetPoolID string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToSubnetPoolUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(updateURL(c, subnetPoolID), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Delete accepts a unique ID and deletes the subnetpool associated with it. +func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = c.Delete(deleteURL(c, id), nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/subnetpools/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/subnetpools/results.go new file mode 100644 index 000000000..e97e1e60a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/subnetpools/results.go @@ -0,0 +1,203 @@ +package subnetpools + +import ( + "encoding/json" + "fmt" + "strconv" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a subnetpool resource. +func (r commonResult) Extract() (*SubnetPool, error) { + var s struct { + SubnetPool *SubnetPool `json:"subnetpool"` + } + err := r.ExtractInto(&s) + return s.SubnetPool, err +} + +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a SubnetPool. +type GetResult struct { + commonResult +} + +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a SubnetPool. +type CreateResult struct { + commonResult +} + +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a SubnetPool. +type UpdateResult struct { + commonResult +} + +// DeleteResult represents the result of a delete operation. Call its +// ExtractErr method to determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// SubnetPool represents a Neutron subnetpool. +// A subnetpool is a pool of addresses from which subnets can be allocated. +type SubnetPool struct { + // ID is the id of the subnetpool. + ID string `json:"id"` + + // Name is the human-readable name of the subnetpool. + Name string `json:"name"` + + // DefaultQuota is the per-project quota on the prefix space + // that can be allocated from the subnetpool for project subnets. + DefaultQuota int `json:"default_quota"` + + // TenantID is the id of the Identity project. + TenantID string `json:"tenant_id"` + + // ProjectID is the id of the Identity project. + ProjectID string `json:"project_id"` + + // CreatedAt is the time at which subnetpool has been created. + CreatedAt time.Time `json:"created_at"` + + // UpdatedAt is the time at which subnetpool has been created. + UpdatedAt time.Time `json:"updated_at"` + + // Prefixes is the list of subnet prefixes to assign to the subnetpool. + // Neutron API merges adjacent prefixes and treats them as a single prefix. + // Each subnet prefix must be unique among all subnet prefixes in all subnetpools + // that are associated with the address scope. + Prefixes []string `json:"prefixes"` + + // DefaultPrefixLen is yhe size of the prefix to allocate when the cidr + // or prefixlen attributes are omitted when you create the subnet. + // Defaults to the MinPrefixLen. + DefaultPrefixLen int `json:"-"` + + // MinPrefixLen is the smallest prefix that can be allocated from a subnetpool. + // For IPv4 subnetpools, default is 8. + // For IPv6 subnetpools, default is 64. + MinPrefixLen int `json:"-"` + + // MaxPrefixLen is the maximum prefix size that can be allocated from the subnetpool. + // For IPv4 subnetpools, default is 32. + // For IPv6 subnetpools, default is 128. + MaxPrefixLen int `json:"-"` + + // AddressScopeID is the Neutron address scope to assign to the subnetpool. + AddressScopeID string `json:"address_scope_id"` + + // IPversion is the IP protocol version. + // Valid value is 4 or 6. Default is 4. + IPversion int `json:"ip_version"` + + // Shared indicates whether this network is shared across all projects. + Shared bool `json:"shared"` + + // Description is thehuman-readable description for the resource. + Description string `json:"description"` + + // IsDefault indicates if the subnetpool is default pool or not. + IsDefault bool `json:"is_default"` + + // RevisionNumber is the revision number of the subnetpool. + RevisionNumber int `json:"revision_number"` + + // Tags optionally set via extensions/attributestags + Tags []string `json:"tags"` +} + +func (r *SubnetPool) UnmarshalJSON(b []byte) error { + type tmp SubnetPool + var s struct { + tmp + DefaultPrefixLen interface{} `json:"default_prefixlen"` + MinPrefixLen interface{} `json:"min_prefixlen"` + MaxPrefixLen interface{} `json:"max_prefixlen"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = SubnetPool(s.tmp) + + switch t := s.DefaultPrefixLen.(type) { + case string: + if r.DefaultPrefixLen, err = strconv.Atoi(t); err != nil { + return err + } + case float64: + r.DefaultPrefixLen = int(t) + default: + return fmt.Errorf("DefaultPrefixLen has unexpected type: %T", t) + } + + switch t := s.MinPrefixLen.(type) { + case string: + if r.MinPrefixLen, err = strconv.Atoi(t); err != nil { + return err + } + case float64: + r.MinPrefixLen = int(t) + default: + return fmt.Errorf("MinPrefixLen has unexpected type: %T", t) + } + + switch t := s.MaxPrefixLen.(type) { + case string: + if r.MaxPrefixLen, err = strconv.Atoi(t); err != nil { + return err + } + case float64: + r.MaxPrefixLen = int(t) + default: + return fmt.Errorf("MaxPrefixLen has unexpected type: %T", t) + } + + return nil +} + +// SubnetPoolPage stores a single page of SubnetPools from a List() API call. +type SubnetPoolPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of subnetpools 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 SubnetPoolPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"subnetpools_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty determines whether or not a SubnetPoolPage is empty. +func (r SubnetPoolPage) IsEmpty() (bool, error) { + subnetpools, err := ExtractSubnetPools(r) + return len(subnetpools) == 0, err +} + +// ExtractSubnetPools interprets the results of a single page from a List() API call, +// producing a slice of SubnetPools structs. +func ExtractSubnetPools(r pagination.Page) ([]SubnetPool, error) { + var s struct { + SubnetPools []SubnetPool `json:"subnetpools"` + } + err := (r.(SubnetPoolPage)).ExtractInto(&s) + return s.SubnetPools, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/subnetpools/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/subnetpools/urls.go new file mode 100644 index 000000000..a05062c96 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/subnetpools/urls.go @@ -0,0 +1,33 @@ +package subnetpools + +import "github.com/gophercloud/gophercloud" + +const resourcePath = "subnetpools" + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(resourcePath, id) +} + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(resourcePath) +} + +func listURL(c *gophercloud.ServiceClient) string { + return rootURL(c) +} + +func getURL(c *gophercloud.ServiceClient, id string) string { + return resourceURL(c, id) +} + +func createURL(c *gophercloud.ServiceClient) string { + return rootURL(c) +} + +func updateURL(c *gophercloud.ServiceClient, id string) string { + return resourceURL(c, id) +} + +func deleteURL(c *gophercloud.ServiceClient, id string) string { + return resourceURL(c, id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/doc.go new file mode 100644 index 000000000..82496ffd0 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/doc.go @@ -0,0 +1,143 @@ +/* +Package trunks provides the ability to retrieve and manage trunks through the Neutron API. +Trunks allow you to multiplex multiple ports traffic on a single port. For example, you could +have a compute instance port be the parent port of a trunk and inside the VM run workloads +using other ports, without the need of plugging those ports. + +Example of a new empty Trunk creation + + iTrue := true + createOpts := trunks.CreateOpts{ + Name: "gophertrunk", + Description: "Trunk created by gophercloud", + AdminStateUp: &iTrue, + PortID: "a6f0560c-b7a8-401f-bf6e-d0a5c851ae10", + } + + trunk, err := trunks.Create(networkClient, trunkOpts).Extract() + if err != nil { + panic(err) + } + fmt.Printf("%+v\n", trunk) + +Example of a new Trunk creation with 2 subports + + iTrue := true + createOpts := trunks.CreateOpts{ + Name: "gophertrunk", + Description: "Trunk created by gophercloud", + AdminStateUp: &iTrue, + PortID: "a6f0560c-b7a8-401f-bf6e-d0a5c851ae10", + Subports: []trunks.Subport{ + { + SegmentationID: 1, + SegmentationType: "vlan", + PortID: "bf4efcc0-b1c7-4674-81f0-31f58a33420a", + }, + { + SegmentationID: 10, + SegmentationType: "vlan", + PortID: "2cf671b9-02b3-4121-9e85-e0af3548d112", + }, + }, + } + + trunk, err := trunks.Create(client, trunkOpts).Extract() + if err != nil { + panic(err) + } + fmt.Printf("%+v\n", trunk) + +Example of deleting a Trunk + + trunkID := "c36e7f2e-0c53-4742-8696-aee77c9df159" + err := trunks.Delete(networkClient, trunkID).ExtractErr() + if err != nil { + panic(err) + } + +Example of listing Trunks + + listOpts := trunks.ListOpts{} + allPages, err := trunks.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + allTrunks, err := trunks.ExtractTrunks(allPages) + if err != nil { + panic(err) + } + for _, trunk := range allTrunks { + fmt.Printf("%+v\n", trunk) + } + +Example of getting a Trunk + + trunkID = "52d8d124-3dc9-4563-9fef-bad3187ecf2d" + trunk, err := trunks.Get(networkClient, trunkID).Extract() + if err != nil { + panic(err) + } + fmt.Printf("%+v\n", trunk) + +Example of updating a Trunk + + trunkID := "c36e7f2e-0c53-4742-8696-aee77c9df159" + subports, err := trunks.GetSubports(client, trunkID).Extract() + iFalse := false + updateOpts := trunks.UpdateOpts{ + AdminStateUp: &iFalse, + Name: "updated_gophertrunk", + Description: "trunk updated by gophercloud", + } + trunk, err = trunks.Update(networkClient, trunkID, updateOpts).Extract() + if err != nil { + log.Fatal(err) + } + fmt.Printf("%+v\n", trunk) + +Example of showing subports of a Trunk + + trunkID := "c36e7f2e-0c53-4742-8696-aee77c9df159" + subports, err := trunks.GetSubports(client, trunkID).Extract() + fmt.Printf("%+v\n", subports) + +Example of adding two subports to a Trunk + + trunkID := "c36e7f2e-0c53-4742-8696-aee77c9df159" + addSubportsOpts := trunks.AddSubportsOpts{ + Subports: []trunks.Subport{ + { + SegmentationID: 1, + SegmentationType: "vlan", + PortID: "bf4efcc0-b1c7-4674-81f0-31f58a33420a", + }, + { + SegmentationID: 10, + SegmentationType: "vlan", + PortID: "2cf671b9-02b3-4121-9e85-e0af3548d112", + }, + }, + } + trunk, err := trunks.AddSubports(client, trunkID, addSubportsOpts).Extract() + if err != nil { + panic(err) + } + fmt.Printf("%+v\n", trunk) + +Example of deleting two subports from a Trunk + + trunkID := "c36e7f2e-0c53-4742-8696-aee77c9df159" + removeSubportsOpts := trunks.RemoveSubportsOpts{ + Subports: []trunks.RemoveSubport{ + {PortID: "bf4efcc0-b1c7-4674-81f0-31f58a33420a"}, + {PortID: "2cf671b9-02b3-4121-9e85-e0af3548d112"}, + }, + } + trunk, err := trunks.RemoveSubports(networkClient, trunkID, removeSubportsOpts).Extract() + if err != nil { + panic(err) + } + fmt.Printf("%+v\n", trunk) +*/ +package trunks diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/requests.go new file mode 100644 index 000000000..447a0d411 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/requests.go @@ -0,0 +1,195 @@ +package trunks + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToTrunkCreateMap() (map[string]interface{}, error) +} + +// CreateOpts represents the attributes used when creating a new trunk. +type CreateOpts struct { + TenantID string `json:"tenant_id,omitempty"` + ProjectID string `json:"project_id,omitempty"` + PortID string `json:"port_id" required:"true"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + AdminStateUp *bool `json:"admin_state_up,omitempty"` + Subports []Subport `json:"sub_ports"` +} + +// ToTrunkCreateMap builds a request body from CreateOpts. +func (opts CreateOpts) ToTrunkCreateMap() (map[string]interface{}, error) { + if opts.Subports == nil { + opts.Subports = []Subport{} + } + return gophercloud.BuildRequestBody(opts, "trunk") +} + +func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + body, err := opts.ToTrunkCreateMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = c.Post(createURL(c), body, &r.Body, nil) + return +} + +// Delete accepts a unique ID and deletes the trunk associated with it. +func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = c.Delete(deleteURL(c, id), nil) + return +} + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToTrunkListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the trunk attributes you want to see returned. SortKey allows you to sort +// by a particular trunk attribute. SortDir sets the direction, and is either +// `asc' or `desc'. Marker and Limit are used for pagination. +type ListOpts struct { + AdminStateUp *bool `q:"admin_state_up"` + Description string `q:"description"` + ID string `q:"id"` + Name string `q:"name"` + PortID string `q:"port_id"` + RevisionNumber string `q:"revision_number"` + Status string `q:"status"` + TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` + SortDir string `q:"sort_dir"` + SortKey string `q:"sort_key"` + Tags string `q:"tags"` + TagsAny string `q:"tags-any"` + NotTags string `q:"not-tags"` + NotTagsAny string `q:"not-tags-any"` +} + +// ToTrunkListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToTrunkListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of +// trunks. It accepts a ListOpts struct, which allows you to filter and sort +// the returned collection for greater efficiency. +// +// Default policy settings return only those trunks that are owned by the tenant +// who submits the request, unless the request is submitted by a user with +// administrative rights. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(c) + if opts != nil { + query, err := opts.ToTrunkListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return TrunkPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves a specific trunk based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = c.Get(getURL(c, id), &r.Body, nil) + return +} + +type UpdateOptsBuilder interface { + ToTrunkUpdateMap() (map[string]interface{}, error) +} + +type UpdateOpts struct { + AdminStateUp *bool `json:"admin_state_up,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` +} + +func (opts UpdateOpts) ToTrunkUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "trunk") +} + +func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + body, err := opts.ToTrunkUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(updateURL(c, id), body, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +func GetSubports(c *gophercloud.ServiceClient, id string) (r GetSubportsResult) { + _, r.Err = c.Get(getSubportsURL(c, id), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +type AddSubportsOpts struct { + Subports []Subport `json:"sub_ports" required:"true"` +} + +type AddSubportsOptsBuilder interface { + ToTrunkAddSubportsMap() (map[string]interface{}, error) +} + +func (opts AddSubportsOpts) ToTrunkAddSubportsMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +func AddSubports(c *gophercloud.ServiceClient, id string, opts AddSubportsOptsBuilder) (r UpdateSubportsResult) { + body, err := opts.ToTrunkAddSubportsMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(addSubportsURL(c, id), body, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +type RemoveSubport struct { + PortID string `json:"port_id" required:"true"` +} + +type RemoveSubportsOpts struct { + Subports []RemoveSubport `json:"sub_ports"` +} + +type RemoveSubportsOptsBuilder interface { + ToTrunkRemoveSubportsMap() (map[string]interface{}, error) +} + +func (opts RemoveSubportsOpts) ToTrunkRemoveSubportsMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +func RemoveSubports(c *gophercloud.ServiceClient, id string, opts RemoveSubportsOptsBuilder) (r UpdateSubportsResult) { + body, err := opts.ToTrunkRemoveSubportsMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(removeSubportsURL(c, id), body, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/results.go new file mode 100644 index 000000000..6d979ef7a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/results.go @@ -0,0 +1,137 @@ +package trunks + +import ( + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type Subport struct { + SegmentationID int `json:"segmentation_id" required:"true"` + SegmentationType string `json:"segmentation_type" required:"true"` + PortID string `json:"port_id" required:"true"` +} + +type commonResult struct { + gophercloud.Result +} + +// CreateResult is the response from a Create operation. Call its Extract method +// to interpret it as a Trunk. +type CreateResult struct { + commonResult +} + +// DeleteResult is the response from a Delete operation. Call its ExtractErr to +// determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// GetResult is the response from a Get operation. Call its Extract method +// to interpret it as a Trunk. +type GetResult struct { + commonResult +} + +// UpdateResult is the result of an Update request. Call its Extract method to +// interpret it as a Trunk. +type UpdateResult struct { + commonResult +} + +// GetSubportsResult is the result of a Get request on the trunks subports +// resource. Call its Extract method to interpret it as a slice of Subport. +type GetSubportsResult struct { + commonResult +} + +// UpdateSubportsResult is the result of either an AddSubports or a RemoveSubports +// request. Call its Extract method to interpret it as a Trunk. +type UpdateSubportsResult struct { + commonResult +} + +type Trunk struct { + // Indicates whether the trunk is currently operational. Possible values include + // `ACTIVE', `DOWN', `BUILD', 'DEGRADED' or `ERROR'. + Status string `json:"status"` + + // A list of ports associated with the trunk + Subports []Subport `json:"sub_ports"` + + // Human-readable name for the trunk. Might not be unique. + Name string `json:"name,omitempty"` + + // The administrative state of the trunk. If false (down), the trunk does not + // forward packets. + AdminStateUp bool `json:"admin_state_up,omitempty"` + + // ProjectID is the project owner of the trunk. + ProjectID string `json:"project_id"` + + // TenantID is the project owner of the trunk. + TenantID string `json:"tenant_id"` + + // The date and time when the resource was created. + CreatedAt time.Time `json:"created_at"` + + // The date and time when the resource was updated, + // if the resource has not been updated, this field will show as null. + UpdatedAt time.Time `json:"updated_at"` + + RevisionNumber int `json:"revision_number"` + + // UUID of the trunk's parent port + PortID string `json:"port_id"` + + // UUID for the trunk resource + ID string `json:"id"` + + // Display description. + Description string `json:"description"` + + // A list of tags associated with the trunk + Tags []string `json:"tags,omitempty"` +} + +func (r commonResult) Extract() (*Trunk, error) { + var s struct { + Trunk *Trunk `json:"trunk"` + } + err := r.ExtractInto(&s) + return s.Trunk, err +} + +// TrunkPage is the page returned by a pager when traversing a collection of +// trunk resources. +type TrunkPage struct { + pagination.LinkedPageBase +} + +func (page TrunkPage) IsEmpty() (bool, error) { + trunks, err := ExtractTrunks(page) + return len(trunks) == 0, err +} + +func ExtractTrunks(page pagination.Page) ([]Trunk, error) { + var a struct { + Trunks []Trunk `json:"trunks"` + } + err := (page.(TrunkPage)).ExtractInto(&a) + return a.Trunks, err +} + +func (r GetSubportsResult) Extract() ([]Subport, error) { + var s struct { + Subports []Subport `json:"sub_ports"` + } + err := r.ExtractInto(&s) + return s.Subports, err +} + +func (r UpdateSubportsResult) Extract() (t *Trunk, err error) { + err = r.ExtractInto(&t) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/urls.go new file mode 100644 index 000000000..ac7dff096 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/urls.go @@ -0,0 +1,45 @@ +package trunks + +import "github.com/gophercloud/gophercloud" + +const resourcePath = "trunks" + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(resourcePath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(resourcePath, id) +} + +func createURL(c *gophercloud.ServiceClient) string { + return rootURL(c) +} + +func deleteURL(c *gophercloud.ServiceClient, id string) string { + return resourceURL(c, id) +} + +func listURL(c *gophercloud.ServiceClient) string { + return rootURL(c) +} + +func getURL(c *gophercloud.ServiceClient, id string) string { + return resourceURL(c, id) +} + +func updateURL(c *gophercloud.ServiceClient, id string) string { + return resourceURL(c, id) +} + +func getSubportsURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(resourcePath, id, "get_subports") +} + +func addSubportsURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(resourcePath, id, "add_subports") +} + +func removeSubportsURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(resourcePath, id, "remove_subports") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/doc.go new file mode 100644 index 000000000..a309a9667 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/doc.go @@ -0,0 +1,97 @@ +/* +Package vlantransparent provides the ability to retrieve and manage networks +with the vlan-transparent extension through the Neutron API. + +Example of Listing Networks with the vlan-transparent extension + + iTrue := true + networkListOpts := networks.ListOpts{} + listOpts := vlantransparent.ListOptsExt{ + ListOptsBuilder: networkListOpts, + VLANTransparent: &iTrue, + } + + type NetworkWithVLANTransparentExt struct { + networks.Network + vlantransparent.NetworkVLANTransparentExt + } + + var allNetworks []NetworkWithVLANTransparentExt + + allPages, err := networks.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + err = networks.ExtractNetworksInto(allPages, &allNetworks) + if err != nil { + panic(err) + } + + for _, network := range allNetworks { + fmt.Println("%+v\n", network) + } + +Example of Getting a Network with the vlan-transparent extension + + var network struct { + networks.Network + vlantransparent.TransparentExt + } + + err := networks.Get(networkClient, "db193ab3-96e3-4cb3-8fc5-05f4296d0324").ExtractInto(&network) + if err != nil { + panic(err) + } + + fmt.Println("%+v\n", network) + +Example of Creating Network with the vlan-transparent extension + + iTrue := true + networkCreateOpts := networks.CreateOpts{ + Name: "private", + } + + createOpts := vlantransparent.CreateOptsExt{ + CreateOptsBuilder: &networkCreateOpts, + VLANTransparent: &iTrue, + } + + var network struct { + networks.Network + vlantransparent.TransparentExt + } + + err := networks.Create(networkClient, createOpts).ExtractInto(&network) + if err != nil { + panic(err) + } + + fmt.Println("%+v\n", network) + +Example of Updating Network with the vlan-transparent extension + + iFalse := false + networkUpdateOpts := networks.UpdateOpts{ + Name: "new_network_name", + } + + updateOpts := vlantransparent.UpdateOptsExt{ + UpdateOptsBuilder: &networkUpdateOpts, + VLANTransparent: &iFalse, + } + + var network struct { + networks.Network + vlantransparent.TransparentExt + } + + err := networks.Update(networkClient, updateOpts).ExtractInto(&network) + if err != nil { + panic(err) + } + + fmt.Println("%+v\n", network) +*/ +package vlantransparent diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/requests.go new file mode 100644 index 000000000..65504cf3e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/requests.go @@ -0,0 +1,84 @@ +package vlantransparent + +import ( + "net/url" + "strconv" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" +) + +// ListOptsExt adds the vlan-transparent network options to the base ListOpts. +type ListOptsExt struct { + networks.ListOptsBuilder + VLANTransparent *bool `q:"vlan_transparent"` +} + +// ToNetworkListQuery adds the vlan_transparent option to the base network +// list options. +func (opts ListOptsExt) ToNetworkListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts.ListOptsBuilder) + if err != nil { + return "", err + } + + params := q.Query() + if opts.VLANTransparent != nil { + v := strconv.FormatBool(*opts.VLANTransparent) + params.Add("vlan_transparent", v) + } + + q = &url.URL{RawQuery: params.Encode()} + return q.String(), err +} + +// CreateOptsExt is the structure used when creating new vlan-transparent +// network resources. It embeds networks.CreateOpts and so inherits all of its +// required and optional fields, with the addition of the VLANTransparent field. +type CreateOptsExt struct { + networks.CreateOptsBuilder + VLANTransparent *bool `json:"vlan_transparent,omitempty"` +} + +// ToNetworkCreateMap adds the vlan_transparent option to the base network +// creation options. +func (opts CreateOptsExt) ToNetworkCreateMap() (map[string]interface{}, error) { + base, err := opts.CreateOptsBuilder.ToNetworkCreateMap() + if err != nil { + return nil, err + } + + if opts.VLANTransparent == nil { + return base, nil + } + + networkMap := base["network"].(map[string]interface{}) + networkMap["vlan_transparent"] = opts.VLANTransparent + + return base, nil +} + +// UpdateOptsExt is the structure used when updating existing vlan-transparent +// network resources. It embeds networks.UpdateOpts and so inherits all of its +// required and optional fields, with the addition of the VLANTransparent field. +type UpdateOptsExt struct { + networks.UpdateOptsBuilder + VLANTransparent *bool `json:"vlan_transparent,omitempty"` +} + +// ToNetworkUpdateMap casts an UpdateOpts struct to a map. +func (opts UpdateOptsExt) ToNetworkUpdateMap() (map[string]interface{}, error) { + base, err := opts.UpdateOptsBuilder.ToNetworkUpdateMap() + if err != nil { + return nil, err + } + + if opts.VLANTransparent == nil { + return base, nil + } + + networkMap := base["network"].(map[string]interface{}) + networkMap["vlan_transparent"] = opts.VLANTransparent + + return base, nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/results.go new file mode 100644 index 000000000..62eae2091 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent/results.go @@ -0,0 +1,8 @@ +package vlantransparent + +// TransparentExt represents a decorated form of a network with +// "vlan-transparent" extension attributes. +type TransparentExt struct { + // VLANTransparent whether the network is a VLAN transparent network or not. + VLANTransparent bool `json:"vlan_transparent"` +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/endpointgroups/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/endpointgroups/doc.go new file mode 100644 index 000000000..5f49bd1da --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/endpointgroups/doc.go @@ -0,0 +1,58 @@ +/* +Package endpointgroups allows management of endpoint groups in the Openstack Network Service + +Example to create an Endpoint Group + + createOpts := endpointgroups.CreateOpts{ + Name: groupName, + Type: endpointgroups.TypeCIDR, + Endpoints: []string{ + "10.2.0.0/24", + "10.3.0.0/24", + }, + } + group, err := endpointgroups.Create(client, createOpts).Extract() + if err != nil { + return group, err + } + +Example to retrieve an Endpoint Group + + group, err := endpointgroups.Get(client, "6ecd9cf3-ca64-46c7-863f-f2eb1b9e838a").Extract() + if err != nil { + panic(err) + } + +Example to Delete an Endpoint Group + + err := endpointgroups.Delete(client, "5291b189-fd84-46e5-84bd-78f40c05d69c").ExtractErr() + if err != nil { + panic(err) + } + +Example to List Endpoint groups + + allPages, err := endpointgroups.List(client, nil).AllPages() + if err != nil { + panic(err) + } + + allGroups, err := endpointgroups.ExtractEndpointGroups(allPages) + if err != nil { + panic(err) + } + +Example to Update an endpoint group + + name := "updatedname" + description := "updated description" + updateOpts := endpointgroups.UpdateOpts{ + Name: &name, + Description: &description, + } + updatedPolicy, err := endpointgroups.Update(client, "5c561d9d-eaea-45f6-ae3e-08d1a7080828", updateOpts).Extract() + if err != nil { + panic(err) + } +*/ +package endpointgroups diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/endpointgroups/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/endpointgroups/requests.go new file mode 100644 index 000000000..c12d0a800 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/endpointgroups/requests.go @@ -0,0 +1,144 @@ +package endpointgroups + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type EndpointType string + +const ( + TypeSubnet EndpointType = "subnet" + TypeCIDR EndpointType = "cidr" + TypeVLAN EndpointType = "vlan" + TypeNetwork EndpointType = "network" + TypeRouter EndpointType = "router" +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToEndpointGroupCreateMap() (map[string]interface{}, error) +} + +// CreateOpts contains all the values needed to create a new endpoint group +type CreateOpts struct { + // TenantID specifies a tenant to own the endpoint group. The caller must have + // an admin role in order to set this. Otherwise, this field is left unset + // and the caller will be the owner. + TenantID string `json:"tenant_id,omitempty"` + + // Description is the human readable description of the endpoint group. + Description string `json:"description,omitempty"` + + // Name is the human readable name of the endpoint group. + Name string `json:"name,omitempty"` + + // The type of the endpoints in the group. + // A valid value is subnet, cidr, network, router, or vlan. + Type EndpointType `json:"type,omitempty"` + + // List of endpoints of the same type, for the endpoint group. + // The values will depend on the type. + Endpoints []string `json:"endpoints"` +} + +// ToEndpointGroupCreateMap casts a CreateOpts struct to a map. +func (opts CreateOpts) ToEndpointGroupCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "endpoint_group") +} + +// Create accepts a CreateOpts struct and uses the values to create a new +// endpoint group. +func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToEndpointGroupCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Post(rootURL(c), b, &r.Body, nil) + return +} + +// Get retrieves a particular endpoint group based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) + return +} + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToEndpointGroupListQuery() (string, error) +} + +// ListOpts allows the filtering of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the Endpoint group attributes you want to see returned. +type ListOpts struct { + TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` + Description string `q:"description"` + Name string `q:"name"` + Type string `q:"type"` +} + +// ToEndpointGroupListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToEndpointGroupListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of +// Endpoint groups. It accepts a ListOpts struct, which allows you to filter +// the returned collection for greater efficiency. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + if opts != nil { + query, err := opts.ToEndpointGroupListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return EndpointGroupPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Delete will permanently delete a particular endpoint group based on its +// unique ID. +func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = c.Delete(resourceURL(c, id), nil) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToEndpointGroupUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts contains the values used when updating an endpoint group. +type UpdateOpts struct { + Description *string `json:"description,omitempty"` + Name *string `json:"name,omitempty"` +} + +// ToEndpointGroupUpdateMap casts an UpdateOpts struct to a map. +func (opts UpdateOpts) ToEndpointGroupUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "endpoint_group") +} + +// Update allows endpoint groups to be updated. +func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToEndpointGroupUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/endpointgroups/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/endpointgroups/results.go new file mode 100644 index 000000000..822b70002 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/endpointgroups/results.go @@ -0,0 +1,104 @@ +package endpointgroups + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// EndpointGroup is an endpoint group. +type EndpointGroup struct { + // TenantID specifies a tenant to own the endpoint group. + TenantID string `json:"tenant_id"` + + // TenantID specifies a tenant to own the endpoint group. + ProjectID string `json:"project_id"` + + // Description is the human readable description of the endpoint group. + Description string `json:"description"` + + // Name is the human readable name of the endpoint group. + Name string `json:"name"` + + // Type is the type of the endpoints in the group. + Type string `json:"type"` + + // Endpoints is a list of endpoints. + Endpoints []string `json:"endpoints"` + + // ID is the id of the endpoint group + ID string `json:"id"` +} + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts an endpoint group. +func (r commonResult) Extract() (*EndpointGroup, error) { + var s struct { + Service *EndpointGroup `json:"endpoint_group"` + } + err := r.ExtractInto(&s) + return s.Service, err +} + +// EndpointGroupPage is the page returned by a pager when traversing over a +// collection of Policies. +type EndpointGroupPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of Endpoint groups 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 EndpointGroupPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"endpoint_groups_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether an EndpointGroupPage struct is empty. +func (r EndpointGroupPage) IsEmpty() (bool, error) { + is, err := ExtractEndpointGroups(r) + return len(is) == 0, err +} + +// ExtractEndpointGroups accepts a Page struct, specifically an EndpointGroupPage struct, +// and extracts the elements into a slice of Endpoint group structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractEndpointGroups(r pagination.Page) ([]EndpointGroup, error) { + var s struct { + EndpointGroups []EndpointGroup `json:"endpoint_groups"` + } + err := (r.(EndpointGroupPage)).ExtractInto(&s) + return s.EndpointGroups, err +} + +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as an endpoint group. +type CreateResult struct { + commonResult +} + +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as an EndpointGroup. +type GetResult struct { + commonResult +} + +// DeleteResult represents the results of a Delete operation. Call its ExtractErr method +// to determine whether the operation succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// UpdateResult represents the result of an update operation. Call its Extract method +// to interpret it as an EndpointGroup. +type UpdateResult struct { + commonResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/endpointgroups/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/endpointgroups/urls.go new file mode 100644 index 000000000..9e83563ce --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/endpointgroups/urls.go @@ -0,0 +1,16 @@ +package endpointgroups + +import "github.com/gophercloud/gophercloud" + +const ( + rootPath = "vpn" + resourcePath = "endpoint-groups" +) + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath, resourcePath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ikepolicies/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ikepolicies/doc.go new file mode 100644 index 000000000..ee44279af --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ikepolicies/doc.go @@ -0,0 +1,64 @@ +/* +Package ikepolicies allows management and retrieval of IKE policies in the +OpenStack Networking Service. + + +Example to Create an IKE policy + + createOpts := ikepolicies.CreateOpts{ + Name: "ikepolicy1", + Description: "Description of ikepolicy1", + EncryptionAlgorithm: ikepolicies.EncryptionAlgorithm3DES, + PFS: ikepolicies.PFSGroup5, + } + + policy, err := ikepolicies.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Show the details of a specific IKE policy by ID + + policy, err := ikepolicies.Get(client, "f2b08c1e-aa81-4668-8ae1-1401bcb0576c").Extract() + if err != nil { + panic(err) + } + + +Example to Delete a Policy + + err := ikepolicies.Delete(client, "5291b189-fd84-46e5-84bd-78f40c05d69c").ExtractErr() + if err != nil { + panic(err) + +Example to Update an IKE policy + + name := "updatedname" + description := "updated policy" + updateOpts := ikepolicies.UpdateOpts{ + Name: &name, + Description: &description, + Lifetime: &ikepolicies.LifetimeUpdateOpts{ + Value: 7000, + }, + } + updatedPolicy, err := ikepolicies.Update(client, "5c561d9d-eaea-45f6-ae3e-08d1a7080828", updateOpts).Extract() + if err != nil { + panic(err) + } + + +Example to List IKE policies + + allPages, err := ikepolicies.List(client, nil).AllPages() + if err != nil { + panic(err) + } + + allPolicies, err := ikepolicies.ExtractPolicies(allPages) + if err != nil { + panic(err) + } + +*/ +package ikepolicies diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ikepolicies/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ikepolicies/requests.go new file mode 100644 index 000000000..6b084ff62 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ikepolicies/requests.go @@ -0,0 +1,209 @@ +package ikepolicies + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type AuthAlgorithm string +type EncryptionAlgorithm string +type PFS string +type Unit string +type IKEVersion string +type Phase1NegotiationMode string + +const ( + AuthAlgorithmSHA1 AuthAlgorithm = "sha1" + AuthAlgorithmSHA256 AuthAlgorithm = "sha256" + AuthAlgorithmSHA384 AuthAlgorithm = "sha384" + AuthAlgorithmSHA512 AuthAlgorithm = "sha512" + EncryptionAlgorithm3DES EncryptionAlgorithm = "3des" + EncryptionAlgorithmAES128 EncryptionAlgorithm = "aes-128" + EncryptionAlgorithmAES256 EncryptionAlgorithm = "aes-256" + EncryptionAlgorithmAES192 EncryptionAlgorithm = "aes-192" + UnitSeconds Unit = "seconds" + UnitKilobytes Unit = "kilobytes" + PFSGroup2 PFS = "group2" + PFSGroup5 PFS = "group5" + PFSGroup14 PFS = "group14" + IKEVersionv1 IKEVersion = "v1" + IKEVersionv2 IKEVersion = "v2" + Phase1NegotiationModeMain Phase1NegotiationMode = "main" +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToPolicyCreateMap() (map[string]interface{}, error) +} + +// CreateOpts contains all the values needed to create a new IKE policy +type CreateOpts struct { + // TenantID specifies a tenant to own the IKE policy. The caller must have + // an admin role in order to set this. Otherwise, this field is left unset + // and the caller will be the owner. + TenantID string `json:"tenant_id,omitempty"` + + // Description is the human readable description of the policy. + Description string `json:"description,omitempty"` + + // Name is the human readable name of the policy. + // Does not have to be unique. + Name string `json:"name,omitempty"` + + // AuthAlgorithm is the authentication hash algorithm. + // Valid values are sha1, sha256, sha384, sha512. + // The default is sha1. + AuthAlgorithm AuthAlgorithm `json:"auth_algorithm,omitempty"` + + // EncryptionAlgorithm is the encryption algorithm. + // A valid value is 3des, aes-128, aes-192, aes-256, and so on. + // Default is aes-128. + EncryptionAlgorithm EncryptionAlgorithm `json:"encryption_algorithm,omitempty"` + + // PFS is the Perfect forward secrecy mode. + // A valid value is Group2, Group5, Group14, and so on. + // Default is Group5. + PFS PFS `json:"pfs,omitempty"` + + // The IKE mode. + // A valid value is main, which is the default. + Phase1NegotiationMode Phase1NegotiationMode `json:"phase1_negotiation_mode,omitempty"` + + // The IKE version. + // A valid value is v1 or v2. + // Default is v1. + IKEVersion IKEVersion `json:"ike_version,omitempty"` + + //Lifetime is the lifetime of the security association + Lifetime *LifetimeCreateOpts `json:"lifetime,omitempty"` +} + +// The lifetime consists of a unit and integer value +// You can omit either the unit or value portion of the lifetime +type LifetimeCreateOpts struct { + // Units is the units for the lifetime of the security association + // Default unit is seconds + Units Unit `json:"units,omitempty"` + + // The lifetime value. + // Must be a positive integer. + // Default value is 3600. + Value int `json:"value,omitempty"` +} + +// ToPolicyCreateMap casts a CreateOpts struct to a map. +func (opts CreateOpts) ToPolicyCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "ikepolicy") +} + +// Create accepts a CreateOpts struct and uses the values to create a new +// IKE policy +func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToPolicyCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Post(rootURL(c), b, &r.Body, nil) + return +} + +// Get retrieves a particular IKE policy based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) + return +} + +// Delete will permanently delete a particular IKE policy based on its +// unique ID. +func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = c.Delete(resourceURL(c, id), nil) + return +} + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToPolicyListQuery() (string, error) +} + +// ListOpts allows the filtering of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the IKE policy attributes you want to see returned. +type ListOpts struct { + TenantID string `q:"tenant_id"` + Name string `q:"name"` + Description string `q:"description"` + ProjectID string `q:"project_id"` + AuthAlgorithm string `q:"auth_algorithm"` + EncapsulationMode string `q:"encapsulation_mode"` + EncryptionAlgorithm string `q:"encryption_algorithm"` + PFS string `q:"pfs"` + Phase1NegotiationMode string `q:"phase_1_negotiation_mode"` + IKEVersion string `q:"ike_version"` +} + +// ToPolicyListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToPolicyListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of +// IKE policies. It accepts a ListOpts struct, which allows you to filter +// the returned collection for greater efficiency. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + if opts != nil { + query, err := opts.ToPolicyListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return PolicyPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToPolicyUpdateMap() (map[string]interface{}, error) +} + +type LifetimeUpdateOpts struct { + Units Unit `json:"units,omitempty"` + Value int `json:"value,omitempty"` +} + +// UpdateOpts contains the values used when updating an IKE policy +type UpdateOpts struct { + Description *string `json:"description,omitempty"` + Name *string `json:"name,omitempty"` + AuthAlgorithm AuthAlgorithm `json:"auth_algorithm,omitempty"` + EncryptionAlgorithm EncryptionAlgorithm `json:"encryption_algorithm,omitempty"` + PFS PFS `json:"pfs,omitempty"` + Lifetime *LifetimeUpdateOpts `json:"lifetime,omitempty"` + Phase1NegotiationMode Phase1NegotiationMode `json:"phase_1_negotiation_mode,omitempty"` + IKEVersion IKEVersion `json:"ike_version,omitempty"` +} + +// ToPolicyUpdateMap casts an UpdateOpts struct to a map. +func (opts UpdateOpts) ToPolicyUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "ikepolicy") +} + +// Update allows IKE policies to be updated. +func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToPolicyUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ikepolicies/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ikepolicies/results.go new file mode 100644 index 000000000..b825f5754 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ikepolicies/results.go @@ -0,0 +1,125 @@ +package ikepolicies + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// Policy is an IKE Policy +type Policy struct { + // TenantID is the ID of the project + TenantID string `json:"tenant_id"` + + // ProjectID is the ID of the project + ProjectID string `json:"project_id"` + + // Description is the human readable description of the policy + Description string `json:"description"` + + // Name is the human readable name of the policy + Name string `json:"name"` + + // AuthAlgorithm is the authentication hash algorithm + AuthAlgorithm string `json:"auth_algorithm"` + + // EncryptionAlgorithm is the encryption algorithm + EncryptionAlgorithm string `json:"encryption_algorithm"` + + // PFS is the Perfect forward secrecy (PFS) mode + PFS string `json:"pfs"` + + // Lifetime is the lifetime of the security association + Lifetime Lifetime `json:"lifetime"` + + // ID is the ID of the policy + ID string `json:"id"` + + // Phase1NegotiationMode is the IKE mode + Phase1NegotiationMode string `json:"phase1_negotiation_mode"` + + // IKEVersion is the IKE version. + IKEVersion string `json:"ike_version"` +} + +type commonResult struct { + gophercloud.Result +} +type Lifetime struct { + // Units is the unit for the lifetime + // Default is seconds + Units string `json:"units"` + + // Value is the lifetime + // Default is 3600 + Value int `json:"value"` +} + +// Extract is a function that accepts a result and extracts an IKE Policy. +func (r commonResult) Extract() (*Policy, error) { + var s struct { + Policy *Policy `json:"ikepolicy"` + } + err := r.ExtractInto(&s) + return s.Policy, err +} + +// PolicyPage is the page returned by a pager when traversing over a +// collection of Policies. +type PolicyPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of IKE policies 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 PolicyPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"ikepolicies_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a PolicyPage struct is empty. +func (r PolicyPage) IsEmpty() (bool, error) { + is, err := ExtractPolicies(r) + return len(is) == 0, err +} + +// ExtractPolicies accepts a Page struct, specifically a Policy struct, +// and extracts the elements into a slice of Policy structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractPolicies(r pagination.Page) ([]Policy, error) { + var s struct { + Policies []Policy `json:"ikepolicies"` + } + err := (r.(PolicyPage)).ExtractInto(&s) + return s.Policies, err +} + +// CreateResult represents the result of a Create operation. Call its Extract method to +// interpret it as a Policy. +type CreateResult struct { + commonResult +} + +// GetResult represents the result of a Get operation. Call its Extract method to +// interpret it as a Policy. +type GetResult struct { + commonResult +} + +// DeleteResult represents the results of a Delete operation. Call its ExtractErr method +// to determine whether the operation succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// UpdateResult represents the result of an update operation. Call its Extract method +// to interpret it as a Policy. +type UpdateResult struct { + commonResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ikepolicies/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ikepolicies/urls.go new file mode 100644 index 000000000..a364a881e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ikepolicies/urls.go @@ -0,0 +1,16 @@ +package ikepolicies + +import "github.com/gophercloud/gophercloud" + +const ( + rootPath = "vpn" + resourcePath = "ikepolicies" +) + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath, resourcePath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ipsecpolicies/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ipsecpolicies/doc.go new file mode 100644 index 000000000..91d5451a6 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ipsecpolicies/doc.go @@ -0,0 +1,56 @@ +/* +Package ipsecpolicies allows management and retrieval of IPSec Policies in the +OpenStack Networking Service. + +Example to Create a Policy + + createOpts := ipsecpolicies.CreateOpts{ + Name: "IPSecPolicy_1", + } + + policy, err := policies.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Policy + + err := ipsecpolicies.Delete(client, "5291b189-fd84-46e5-84bd-78f40c05d69c").ExtractErr() + if err != nil { + panic(err) + } + +Example to Show the details of a specific IPSec policy by ID + + policy, err := ipsecpolicies.Get(client, "f2b08c1e-aa81-4668-8ae1-1401bcb0576c").Extract() + if err != nil { + panic(err) + } + +Example to Update an IPSec policy + + name := "updatedname" + description := "updated policy" + updateOpts := ipsecpolicies.UpdateOpts{ + Name: &name, + Description: &description, + } + updatedPolicy, err := ipsecpolicies.Update(client, "5c561d9d-eaea-45f6-ae3e-08d1a7080828", updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to List IPSec policies + + allPages, err := ipsecpolicies.List(client, nil).AllPages() + if err != nil { + panic(err) + } + + allPolicies, err := ipsecpolicies.ExtractPolicies(allPages) + if err != nil { + panic(err) + } + +*/ +package ipsecpolicies diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ipsecpolicies/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ipsecpolicies/requests.go new file mode 100644 index 000000000..9496365ca --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ipsecpolicies/requests.go @@ -0,0 +1,211 @@ +package ipsecpolicies + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type TransformProtocol string +type AuthAlgorithm string +type EncapsulationMode string +type EncryptionAlgorithm string +type PFS string +type Unit string + +const ( + TransformProtocolESP TransformProtocol = "esp" + TransformProtocolAH TransformProtocol = "ah" + TransformProtocolAHESP TransformProtocol = "ah-esp" + AuthAlgorithmSHA1 AuthAlgorithm = "sha1" + AuthAlgorithmSHA256 AuthAlgorithm = "sha256" + AuthAlgorithmSHA384 AuthAlgorithm = "sha384" + AuthAlgorithmSHA512 AuthAlgorithm = "sha512" + EncryptionAlgorithm3DES EncryptionAlgorithm = "3des" + EncryptionAlgorithmAES128 EncryptionAlgorithm = "aes-128" + EncryptionAlgorithmAES256 EncryptionAlgorithm = "aes-256" + EncryptionAlgorithmAES192 EncryptionAlgorithm = "aes-192" + EncapsulationModeTunnel EncapsulationMode = "tunnel" + EncapsulationModeTransport EncapsulationMode = "transport" + UnitSeconds Unit = "seconds" + UnitKilobytes Unit = "kilobytes" + PFSGroup2 PFS = "group2" + PFSGroup5 PFS = "group5" + PFSGroup14 PFS = "group14" +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToPolicyCreateMap() (map[string]interface{}, error) +} + +// CreateOpts contains all the values needed to create a new IPSec policy +type CreateOpts struct { + // TenantID specifies a tenant to own the IPSec policy. The caller must have + // an admin role in order to set this. Otherwise, this field is left unset + // and the caller will be the owner. + TenantID string `json:"tenant_id,omitempty"` + + // Description is the human readable description of the policy. + Description string `json:"description,omitempty"` + + // Name is the human readable name of the policy. + // Does not have to be unique. + Name string `json:"name,omitempty"` + + // AuthAlgorithm is the authentication hash algorithm. + // Valid values are sha1, sha256, sha384, sha512. + // The default is sha1. + AuthAlgorithm AuthAlgorithm `json:"auth_algorithm,omitempty"` + + // EncapsulationMode is the encapsulation mode. + // A valid value is tunnel or transport. + // Default is tunnel. + EncapsulationMode EncapsulationMode `json:"encapsulation_mode,omitempty"` + + // EncryptionAlgorithm is the encryption algorithm. + // A valid value is 3des, aes-128, aes-192, aes-256, and so on. + // Default is aes-128. + EncryptionAlgorithm EncryptionAlgorithm `json:"encryption_algorithm,omitempty"` + + // PFS is the Perfect forward secrecy mode. + // A valid value is Group2, Group5, Group14, and so on. + // Default is Group5. + PFS PFS `json:"pfs,omitempty"` + + // TransformProtocol is the transform protocol. + // A valid value is ESP, AH, or AH- ESP. + // Default is ESP. + TransformProtocol TransformProtocol `json:"transform_protocol,omitempty"` + + //Lifetime is the lifetime of the security association + Lifetime *LifetimeCreateOpts `json:"lifetime,omitempty"` +} + +// The lifetime consists of a unit and integer value +// You can omit either the unit or value portion of the lifetime +type LifetimeCreateOpts struct { + // Units is the units for the lifetime of the security association + // Default unit is seconds + Units Unit `json:"units,omitempty"` + + // The lifetime value. + // Must be a positive integer. + // Default value is 3600. + Value int `json:"value,omitempty"` +} + +// ToPolicyCreateMap casts a CreateOpts struct to a map. +func (opts CreateOpts) ToPolicyCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "ipsecpolicy") +} + +// Create accepts a CreateOpts struct and uses the values to create a new +// IPSec policy +func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToPolicyCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Post(rootURL(c), b, &r.Body, nil) + return +} + +// Delete will permanently delete a particular IPSec policy based on its +// unique ID. +func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = c.Delete(resourceURL(c, id), nil) + return +} + +// Get retrieves a particular IPSec policy based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) + return +} + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToPolicyListQuery() (string, error) +} + +// ListOpts allows the filtering of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the IPSec policy attributes you want to see returned. +type ListOpts struct { + TenantID string `q:"tenant_id"` + Name string `q:"name"` + Description string `q:"description"` + ProjectID string `q:"project_id"` + AuthAlgorithm string `q:"auth_algorithm"` + EncapsulationMode string `q:"encapsulation_mode"` + EncryptionAlgorithm string `q:"encryption_algorithm"` + PFS string `q:"pfs"` + TransformProtocol string `q:"transform_protocol"` +} + +// ToPolicyListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToPolicyListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of +// IPSec policies. It accepts a ListOpts struct, which allows you to filter +// the returned collection for greater efficiency. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + if opts != nil { + query, err := opts.ToPolicyListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return PolicyPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToPolicyUpdateMap() (map[string]interface{}, error) +} + +type LifetimeUpdateOpts struct { + Units Unit `json:"units,omitempty"` + Value int `json:"value,omitempty"` +} + +// UpdateOpts contains the values used when updating an IPSec policy +type UpdateOpts struct { + Description *string `json:"description,omitempty"` + Name *string `json:"name,omitempty"` + AuthAlgorithm AuthAlgorithm `json:"auth_algorithm,omitempty"` + EncapsulationMode EncapsulationMode `json:"encapsulation_mode,omitempty"` + EncryptionAlgorithm EncryptionAlgorithm `json:"encryption_algorithm,omitempty"` + PFS PFS `json:"pfs,omitempty"` + TransformProtocol TransformProtocol `json:"transform_protocol,omitempty"` + Lifetime *LifetimeUpdateOpts `json:"lifetime,omitempty"` +} + +// ToPolicyUpdateMap casts an UpdateOpts struct to a map. +func (opts UpdateOpts) ToPolicyUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "ipsecpolicy") +} + +// Update allows IPSec policies to be updated. +func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToPolicyUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ipsecpolicies/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ipsecpolicies/results.go new file mode 100644 index 000000000..eda4a1bd2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ipsecpolicies/results.go @@ -0,0 +1,126 @@ +package ipsecpolicies + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// Policy is an IPSec Policy +type Policy struct { + // TenantID is the ID of the project + TenantID string `json:"tenant_id"` + + // ProjectID is the ID of the project + ProjectID string `json:"project_id"` + + // Description is the human readable description of the policy + Description string `json:"description"` + + // Name is the human readable name of the policy + Name string `json:"name"` + + // AuthAlgorithm is the authentication hash algorithm + AuthAlgorithm string `json:"auth_algorithm"` + + // EncapsulationMode is the encapsulation mode + EncapsulationMode string `json:"encapsulation_mode"` + + // EncryptionAlgorithm is the encryption algorithm + EncryptionAlgorithm string `json:"encryption_algorithm"` + + // PFS is the Perfect forward secrecy (PFS) mode + PFS string `json:"pfs"` + + // TransformProtocol is the transform protocol + TransformProtocol string `json:"transform_protocol"` + + // Lifetime is the lifetime of the security association + Lifetime Lifetime `json:"lifetime"` + + // ID is the ID of the policy + ID string `json:"id"` +} + +type Lifetime struct { + // Units is the unit for the lifetime + // Default is seconds + Units string `json:"units"` + + // Value is the lifetime + // Default is 3600 + Value int `json:"value"` +} + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts an IPSec Policy. +func (r commonResult) Extract() (*Policy, error) { + var s struct { + Policy *Policy `json:"ipsecpolicy"` + } + err := r.ExtractInto(&s) + return s.Policy, err +} + +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a Policy. +type CreateResult struct { + commonResult +} + +// CreateResult represents the result of a delete operation. Call its ExtractErr method +// to determine if the operation succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a Policy. +type GetResult struct { + commonResult +} + +// PolicyPage is the page returned by a pager when traversing over a +// collection of Policies. +type PolicyPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of IPSec policies 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 PolicyPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"ipsecpolicies_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a PolicyPage struct is empty. +func (r PolicyPage) IsEmpty() (bool, error) { + is, err := ExtractPolicies(r) + return len(is) == 0, err +} + +// ExtractPolicies accepts a Page struct, specifically a Policy struct, +// and extracts the elements into a slice of Policy structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractPolicies(r pagination.Page) ([]Policy, error) { + var s struct { + Policies []Policy `json:"ipsecpolicies"` + } + err := (r.(PolicyPage)).ExtractInto(&s) + return s.Policies, err +} + +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a Policy. +type UpdateResult struct { + commonResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ipsecpolicies/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ipsecpolicies/urls.go new file mode 100644 index 000000000..8781cc449 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ipsecpolicies/urls.go @@ -0,0 +1,16 @@ +package ipsecpolicies + +import "github.com/gophercloud/gophercloud" + +const ( + rootPath = "vpn" + resourcePath = "ipsecpolicies" +) + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath, resourcePath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/services/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/services/doc.go new file mode 100644 index 000000000..6bd3236c8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/services/doc.go @@ -0,0 +1,68 @@ +/* +Package services allows management and retrieval of VPN services in the +OpenStack Networking Service. + +Example to List Services + + listOpts := services.ListOpts{ + TenantID: "966b3c7d36a24facaf20b7e458bf2192", + } + + allPages, err := services.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allPolicies, err := services.ExtractServices(allPages) + if err != nil { + panic(err) + } + + for _, service := range allServices { + fmt.Printf("%+v\n", service) + } + +Example to Create a Service + + createOpts := services.CreateOpts{ + Name: "vpnservice1", + Description: "A service", + RouterID: "2512e759-e8d7-4eea-a0af-4a85927a2e59", + AdminStateUp: gophercloud.Enabled, + } + + service, err := services.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Service + + serviceID := "38aee955-6283-4279-b091-8b9c828000ec" + + updateOpts := services.UpdateOpts{ + Description: "New Description", + } + + service, err := services.Update(networkClient, serviceID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Service + + serviceID := "38aee955-6283-4279-b091-8b9c828000ec" + err := services.Delete(networkClient, serviceID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Show the details of a specific Service by ID + + service, err := services.Get(client, "f2b08c1e-aa81-4668-8ae1-1401bcb0576c").Extract() + if err != nil { + panic(err) + } + +*/ +package services diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/services/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/services/requests.go new file mode 100644 index 000000000..8d642197e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/services/requests.go @@ -0,0 +1,150 @@ +package services + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToServiceCreateMap() (map[string]interface{}, error) +} + +// CreateOpts contains all the values needed to create a new VPN service +type CreateOpts struct { + // TenantID specifies a tenant to own the VPN service. The caller must have + // an admin role in order to set this. Otherwise, this field is left unset + // and the caller will be the owner. + TenantID string `json:"tenant_id,omitempty"` + + // SubnetID is the ID of the subnet. + SubnetID string `json:"subnet_id,omitempty"` + + // RouterID is the ID of the router. + RouterID string `json:"router_id" required:"true"` + + // Description is the human readable description of the service. + Description string `json:"description,omitempty"` + + // AdminStateUp is the administrative state of the resource, which is up (true) or down (false). + AdminStateUp *bool `json:"admin_state_up"` + + // Name is the human readable name of the service. + Name string `json:"name,omitempty"` + + // The ID of the flavor. + FlavorID string `json:"flavor_id,omitempty"` +} + +// ToServiceCreateMap casts a CreateOpts struct to a map. +func (opts CreateOpts) ToServiceCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "vpnservice") +} + +// Create accepts a CreateOpts struct and uses the values to create a new +// VPN service. +func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToServiceCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Post(rootURL(c), b, &r.Body, nil) + return +} + +// Delete will permanently delete a particular VPN service based on its +// unique ID. +func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = c.Delete(resourceURL(c, id), nil) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToServiceUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts contains the values used when updating a VPN service +type UpdateOpts struct { + // Name is the human readable name of the service. + Name *string `json:"name,omitempty"` + + // Description is the human readable description of the service. + Description *string `json:"description,omitempty"` + + // AdminStateUp is the administrative state of the resource, which is up (true) or down (false). + AdminStateUp *bool `json:"admin_state_up,omitempty"` +} + +// ToServiceUpdateMap casts aa UodateOpts struct to a map. +func (opts UpdateOpts) ToServiceUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "vpnservice") +} + +// Update allows VPN services to be updated. +func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToServiceUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToServiceListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the VPN service attributes you want to see returned. +type ListOpts struct { + TenantID string `q:"tenant_id"` + Name string `q:"name"` + Description string `q:"description"` + AdminStateUp *bool `q:"admin_state_up"` + Status string `q:"status"` + SubnetID string `q:"subnet_id"` + RouterID string `q:"router_id"` + ProjectID string `q:"project_id"` + ExternalV6IP string `q:"external_v6_ip"` + ExternalV4IP string `q:"external_v4_ip"` + FlavorID string `q:"flavor_id"` +} + +// ToServiceListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToServiceListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of +// VPN services. It accepts a ListOpts struct, which allows you to filter +// and sort the returned collection for greater efficiency. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + if opts != nil { + query, err := opts.ToServiceListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return ServicePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves a particular VPN service based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/services/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/services/results.go new file mode 100644 index 000000000..5e555699f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/services/results.go @@ -0,0 +1,121 @@ +package services + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// Service is a VPN Service +type Service struct { + // TenantID is the ID of the project. + TenantID string `json:"tenant_id"` + + // ProjectID is the ID of the project. + ProjectID string `json:"project_id"` + + // SubnetID is the ID of the subnet. + SubnetID string `json:"subnet_id"` + + // RouterID is the ID of the router. + RouterID string `json:"router_id"` + + // Description is a human-readable description for the resource. + // Default is an empty string + Description string `json:"description"` + + // AdminStateUp is the administrative state of the resource, which is up (true) or down (false). + AdminStateUp bool `json:"admin_state_up"` + + // Name is the human readable name of the service. + Name string `json:"name"` + + // Status indicates whether IPsec VPN service is currently operational. + // Values are ACTIVE, DOWN, BUILD, ERROR, PENDING_CREATE, PENDING_UPDATE, or PENDING_DELETE. + Status string `json:"status"` + + // ID is the unique ID of the VPN service. + ID string `json:"id"` + + // ExternalV6IP is the read-only external (public) IPv6 address that is used for the VPN service. + ExternalV6IP string `json:"external_v6_ip"` + + // ExternalV4IP is the read-only external (public) IPv4 address that is used for the VPN service. + ExternalV4IP string `json:"external_v4_ip"` + + // FlavorID is the ID of the flavor. + FlavorID string `json:"flavor_id"` +} + +type commonResult struct { + gophercloud.Result +} + +// ServicePage is the page returned by a pager when traversing over a +// collection of VPN services. +type ServicePage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of VPN services 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 ServicePage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"vpnservices_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a ServicePage struct is empty. +func (r ServicePage) IsEmpty() (bool, error) { + is, err := ExtractServices(r) + return len(is) == 0, err +} + +// ExtractServices accepts a Page struct, specifically a Service struct, +// and extracts the elements into a slice of Service structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractServices(r pagination.Page) ([]Service, error) { + var s struct { + Services []Service `json:"vpnservices"` + } + err := (r.(ServicePage)).ExtractInto(&s) + return s.Services, err +} + +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a Service. +type GetResult struct { + commonResult +} + +// Extract is a function that accepts a result and extracts a VPN service. +func (r commonResult) Extract() (*Service, error) { + var s struct { + Service *Service `json:"vpnservice"` + } + err := r.ExtractInto(&s) + return s.Service, err +} + +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a Service. +type CreateResult struct { + commonResult +} + +// DeleteResult represents the result of a delete operation. Call its +// ExtractErr method to determine if the operation succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a service. +type UpdateResult struct { + commonResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/services/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/services/urls.go new file mode 100644 index 000000000..fe8b343fe --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/services/urls.go @@ -0,0 +1,16 @@ +package services + +import "github.com/gophercloud/gophercloud" + +const ( + rootPath = "vpn" + resourcePath = "vpnservices" +) + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath, resourcePath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/siteconnections/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/siteconnections/doc.go new file mode 100644 index 000000000..66befd3ba --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/siteconnections/doc.go @@ -0,0 +1,68 @@ +/* +Package siteconnections allows management and retrieval of IPSec site connections in the +OpenStack Networking Service. + + +Example to create an IPSec site connection + +createOpts := siteconnections.CreateOpts{ + Name: "Connection1", + PSK: "secret", + Initiator: siteconnections.InitiatorBiDirectional, + AdminStateUp: gophercloud.Enabled, + IPSecPolicyID: "4ab0a72e-64ef-4809-be43-c3f7e0e5239b", + PeerEPGroupID: "5f5801b1-b383-4cf0-bf61-9e85d4044b2d", + IKEPolicyID: "47a880f9-1da9-468c-b289-219c9eca78f0", + VPNServiceID: "692c1ec8-a7cd-44d9-972b-8ed3fe4cc476", + LocalEPGroupID: "498bb96a-1517-47ea-b1eb-c4a53db46a16", + PeerAddress: "172.24.4.233", + PeerID: "172.24.4.233", + MTU: 1500, + } + connection, err := siteconnections.Create(client, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Show the details of a specific IPSec site connection by ID + + conn, err := siteconnections.Get(client, "f2b08c1e-aa81-4668-8ae1-1401bcb0576c").Extract() + if err != nil { + panic(err) + } + +Example to Delete a site connection + + connID := "38aee955-6283-4279-b091-8b9c828000ec" + err := siteconnections.Delete(networkClient, connID).ExtractErr() + if err != nil { + panic(err) + } + +Example to List site connections + + allPages, err := siteconnections.List(client, nil).AllPages() + if err != nil { + panic(err) + } + + allConnections, err := siteconnections.ExtractConnections(allPages) + if err != nil { + panic(err) + } + +Example to Update an IPSec site connection + + description := "updated connection" + name := "updatedname" + updateOpts := siteconnections.UpdateOpts{ + Name: &name, + Description: &description, + } + updatedConnection, err := siteconnections.Update(client, "5c561d9d-eaea-45f6-ae3e-08d1a7080828", updateOpts).Extract() + if err != nil { + panic(err) + } + +*/ +package siteconnections diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/siteconnections/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/siteconnections/requests.go new file mode 100644 index 000000000..15ce54b5f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/siteconnections/requests.go @@ -0,0 +1,243 @@ +package siteconnections + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToConnectionCreateMap() (map[string]interface{}, error) +} +type Action string +type Initiator string + +const ( + ActionHold Action = "hold" + ActionClear Action = "clear" + ActionRestart Action = "restart" + ActionDisabled Action = "disabled" + ActionRestartByPeer Action = "restart-by-peer" + InitiatorBiDirectional Initiator = "bi-directional" + InitiatorResponseOnly Initiator = "response-only" +) + +// DPDCreateOpts contains all the values needed to create a valid configuration for Dead Peer detection protocols +type DPDCreateOpts struct { + // The dead peer detection (DPD) action. + // A valid value is clear, hold, restart, disabled, or restart-by-peer. + // Default value is hold. + Action Action `json:"action,omitempty"` + + // The dead peer detection (DPD) timeout in seconds. + // A valid value is a positive integer that is greater than the DPD interval value. + // Default is 120. + Timeout int `json:"timeout,omitempty"` + + // The dead peer detection (DPD) interval, in seconds. + // A valid value is a positive integer. + // Default is 30. + Interval int `json:"interval,omitempty"` +} + +// CreateOpts contains all the values needed to create a new IPSec site connection +type CreateOpts struct { + // The ID of the IKE policy + IKEPolicyID string `json:"ikepolicy_id"` + + // The ID of the VPN Service + VPNServiceID string `json:"vpnservice_id"` + + // The ID for the endpoint group that contains private subnets for the local side of the connection. + // You must specify this parameter with the peer_ep_group_id parameter unless + // in backward- compatible mode where peer_cidrs is provided with a subnet_id for the VPN service. + LocalEPGroupID string `json:"local_ep_group_id,omitempty"` + + // The ID of the IPsec policy. + IPSecPolicyID string `json:"ipsecpolicy_id"` + + // The peer router identity for authentication. + // A valid value is an IPv4 address, IPv6 address, e-mail address, key ID, or FQDN. + // Typically, this value matches the peer_address value. + PeerID string `json:"peer_id"` + + // The ID of the project + TenantID string `json:"tenant_id,omitempty"` + + // The ID for the endpoint group that contains private CIDRs in the form < net_address > / < prefix > + // for the peer side of the connection. + // You must specify this parameter with the local_ep_group_id parameter unless in backward-compatible mode + // where peer_cidrs is provided with a subnet_id for the VPN service. + PeerEPGroupID string `json:"peer_ep_group_id,omitempty"` + + // An ID to be used instead of the external IP address for a virtual router used in traffic between instances on different networks in east-west traffic. + // Most often, local ID would be domain name, email address, etc. + // If this is not configured then the external IP address will be used as the ID. + LocalID string `json:"local_id,omitempty"` + + // The human readable name of the connection. + // Does not have to be unique. + // Default is an empty string + Name string `json:"name,omitempty"` + + // The human readable description of the connection. + // Does not have to be unique. + // Default is an empty string + Description string `json:"description,omitempty"` + + // The peer gateway public IPv4 or IPv6 address or FQDN. + PeerAddress string `json:"peer_address"` + + // The pre-shared key. + // A valid value is any string. + PSK string `json:"psk"` + + // Indicates whether this VPN can only respond to connections or both respond to and initiate connections. + // A valid value is response-only or bi-directional. Default is bi-directional. + Initiator Initiator `json:"initiator,omitempty"` + + // Unique list of valid peer private CIDRs in the form < net_address > / < prefix > . + PeerCIDRs []string `json:"peer_cidrs,omitempty"` + + // The administrative state of the resource, which is up (true) or down (false). + // Default is false + AdminStateUp *bool `json:"admin_state_up,omitempty"` + + // A dictionary with dead peer detection (DPD) protocol controls. + DPD *DPDCreateOpts `json:"dpd,omitempty"` + + // The maximum transmission unit (MTU) value to address fragmentation. + // Minimum value is 68 for IPv4, and 1280 for IPv6. + MTU int `json:"mtu,omitempty"` +} + +// ToConnectionCreateMap casts a CreateOpts struct to a map. +func (opts CreateOpts) ToConnectionCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "ipsec_site_connection") +} + +// Create accepts a CreateOpts struct and uses the values to create a new +// IPSec site connection. +func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToConnectionCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Post(rootURL(c), b, &r.Body, nil) + + return +} + +// Delete will permanently delete a particular IPSec site connection based on its +// unique ID. +func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = c.Delete(resourceURL(c, id), nil) + return +} + +// Get retrieves a particular IPSec site connection based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) + return +} + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToConnectionListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the IPSec site connection attributes you want to see returned. +type ListOpts struct { + IKEPolicyID string `q:"ikepolicy_id"` + VPNServiceID string `q:"vpnservice_id"` + LocalEPGroupID string `q:"local_ep_group_id"` + IPSecPolicyID string `q:"ipsecpolicy_id"` + PeerID string `q:"peer_id"` + TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` + PeerEPGroupID string `q:"peer_ep_group_id"` + LocalID string `q:"local_id"` + Name string `q:"name"` + Description string `q:"description"` + PeerAddress string `q:"peer_address"` + PSK string `q:"psk"` + Initiator Initiator `q:"initiator"` + AdminStateUp *bool `q:"admin_state_up"` + MTU int `q:"mtu"` +} + +// ToConnectionListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToConnectionListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of +// IPSec site connections. It accepts a ListOpts struct, which allows you to filter +// and sort the returned collection for greater efficiency. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + if opts != nil { + query, err := opts.ToConnectionListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return ConnectionPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToConnectionUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts contains the values used when updating the DPD of an IPSec site connection +type DPDUpdateOpts struct { + Action Action `json:"action,omitempty"` + Timeout int `json:"timeout,omitempty"` + Interval int `json:"interval,omitempty"` +} + +// UpdateOpts contains the values used when updating an IPSec site connection +type UpdateOpts struct { + Description *string `json:"description,omitempty"` + Name *string `json:"name,omitempty"` + LocalID string `json:"local_id,omitempty"` + PeerAddress string `json:"peer_address,omitempty"` + PeerID string `json:"peer_id,omitempty"` + PeerCIDRs []string `json:"peer_cidrs,omitempty"` + LocalEPGroupID string `json:"local_ep_group_id,omitempty"` + PeerEPGroupID string `json:"peer_ep_group_id,omitempty"` + MTU int `json:"mtu,omitempty"` + Initiator Initiator `json:"initiator,omitempty"` + PSK string `json:"psk,omitempty"` + DPD *DPDUpdateOpts `json:"dpd,omitempty"` + AdminStateUp *bool `json:"admin_state_up,omitempty"` +} + +// ToConnectionUpdateMap casts an UpdateOpts struct to a map. +func (opts UpdateOpts) ToConnectionUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "ipsec_site_connection") +} + +// Update allows IPSec site connections to be updated. +func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToConnectionUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/siteconnections/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/siteconnections/results.go new file mode 100644 index 000000000..3c09e4d07 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/siteconnections/results.go @@ -0,0 +1,163 @@ +package siteconnections + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type DPD struct { + // Action is the dead peer detection (DPD) action. + Action string `json:"action"` + + // Timeout is the dead peer detection (DPD) timeout in seconds. + Timeout int `json:"timeout"` + + // Interval is the dead peer detection (DPD) interval in seconds. + Interval int `json:"interval"` +} + +// Connection is an IPSec site connection +type Connection struct { + // IKEPolicyID is the ID of the IKE policy. + IKEPolicyID string `json:"ikepolicy_id"` + + // VPNServiceID is the ID of the VPN service. + VPNServiceID string `json:"vpnservice_id"` + + // LocalEPGroupID is the ID for the endpoint group that contains private subnets for the local side of the connection. + LocalEPGroupID string `json:"local_ep_group_id"` + + // IPSecPolicyID is the ID of the IPSec policy + IPSecPolicyID string `json:"ipsecpolicy_id"` + + // PeerID is the peer router identity for authentication. + PeerID string `json:"peer_id"` + + // TenantID is the ID of the project. + TenantID string `json:"tenant_id"` + + // ProjectID is the ID of the project. + ProjectID string `json:"project_id"` + + // PeerEPGroupID is the ID for the endpoint group that contains private CIDRs in the form < net_address > / < prefix > + // for the peer side of the connection. + PeerEPGroupID string `json:"peer_ep_group_id"` + + // LocalID is an ID to be used instead of the external IP address for a virtual router used in traffic + // between instances on different networks in east-west traffic. + LocalID string `json:"local_id"` + + // Name is the human readable name of the connection. + Name string `json:"name"` + + // Description is the human readable description of the connection. + Description string `json:"description"` + + // PeerAddress is the peer gateway public IPv4 or IPv6 address or FQDN. + PeerAddress string `json:"peer_address"` + + // RouteMode is the route mode. + RouteMode string `json:"route_mode"` + + // PSK is the pre-shared key. + PSK string `json:"psk"` + + // Initiator indicates whether this VPN can only respond to connections or both respond to and initiate connections. + Initiator string `json:"initiator"` + + // PeerCIDRs is a unique list of valid peer private CIDRs in the form < net_address > / < prefix > . + PeerCIDRs []string `json:"peer_cidrs"` + + // AdminStateUp is the administrative state of the connection. + AdminStateUp bool `json:"admin_state_up"` + + // DPD is the dead peer detection (DPD) protocol controls. + DPD DPD `json:"dpd"` + + // AuthMode is the authentication mode. + AuthMode string `json:"auth_mode"` + + // MTU is the maximum transmission unit (MTU) value to address fragmentation. + MTU int `json:"mtu"` + + // Status indicates whether the IPsec connection is currently operational. + // Values are ACTIVE, DOWN, BUILD, ERROR, PENDING_CREATE, PENDING_UPDATE, or PENDING_DELETE. + Status string `json:"status"` + + // ID is the id of the connection + ID string `json:"id"` +} + +type commonResult struct { + gophercloud.Result +} + +// ConnectionPage is the page returned by a pager when traversing over a +// collection of IPSec site connections. +type ConnectionPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of IPSec site connections 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 ConnectionPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"ipsec_site_connections_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a ConnectionPage struct is empty. +func (r ConnectionPage) IsEmpty() (bool, error) { + is, err := ExtractConnections(r) + return len(is) == 0, err +} + +// ExtractConnections accepts a Page struct, specifically a Connection struct, +// and extracts the elements into a slice of Connection structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractConnections(r pagination.Page) ([]Connection, error) { + var s struct { + Connections []Connection `json:"ipsec_site_connections"` + } + err := (r.(ConnectionPage)).ExtractInto(&s) + return s.Connections, err +} + +// Extract is a function that accepts a result and extracts an IPSec site connection. +func (r commonResult) Extract() (*Connection, error) { + var s struct { + Connection *Connection `json:"ipsec_site_connection"` + } + err := r.ExtractInto(&s) + return s.Connection, err +} + +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a Connection. +type CreateResult struct { + commonResult +} + +// DeleteResult represents the result of a delete operation. Call its +// ExtractErr method to determine if the operation succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a Connection. +type GetResult struct { + commonResult +} + +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a connection +type UpdateResult struct { + commonResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/siteconnections/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/siteconnections/urls.go new file mode 100644 index 000000000..5c8ee9a36 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/siteconnections/urls.go @@ -0,0 +1,16 @@ +package siteconnections + +import "github.com/gophercloud/gophercloud" + +const ( + rootPath = "vpn" + resourcePath = "ipsec-site-connections" +) + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath, resourcePath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/doc.go index c87a7ce27..e768b71f8 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/doc.go @@ -1,9 +1,65 @@ -// Package networks contains functionality for working with Neutron network -// resources. A network is an isolated virtual layer-2 broadcast domain that is -// typically reserved for the tenant who created it (unless you configure the -// network to be shared). Tenants can create multiple networks until the -// thresholds per-tenant quota is reached. -// -// In the v2.0 Networking API, the network is the main entity. Ports and subnets -// are always associated with a network. +/* +Package networks contains functionality for working with Neutron network +resources. A network is an isolated virtual layer-2 broadcast domain that is +typically reserved for the tenant who created it (unless you configure the +network to be shared). Tenants can create multiple networks until the +thresholds per-tenant quota is reached. + +In the v2.0 Networking API, the network is the main entity. Ports and subnets +are always associated with a network. + +Example to List Networks + + listOpts := networks.ListOpts{ + TenantID: "a99e9b4e620e4db09a2dfb6e42a01e66", + } + + allPages, err := networks.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allNetworks, err := networks.ExtractNetworks(allPages) + if err != nil { + panic(err) + } + + for _, network := range allNetworks { + fmt.Printf("%+v", network) + } + +Example to Create a Network + + iTrue := true + createOpts := networks.CreateOpts{ + Name: "network_1", + AdminStateUp: &iTrue, + } + + network, err := networks.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Network + + networkID := "484cda0e-106f-4f4b-bb3f-d413710bbe78" + + updateOpts := networks.UpdateOpts{ + Name: "new_name", + } + + network, err := networks.Update(networkClient, networkID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Network + + networkID := "484cda0e-106f-4f4b-bb3f-d413710bbe78" + err := networks.Delete(networkClient, networkID).ExtractErr() + if err != nil { + panic(err) + } +*/ package networks diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/requests.go index 876a00bb0..d52d099a6 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/requests.go @@ -19,14 +19,20 @@ type ListOptsBuilder interface { type ListOpts struct { Status string `q:"status"` Name string `q:"name"` + Description string `q:"description"` AdminStateUp *bool `q:"admin_state_up"` TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` Shared *bool `q:"shared"` ID string `q:"id"` Marker string `q:"marker"` Limit int `q:"limit"` SortKey string `q:"sort_key"` SortDir string `q:"sort_dir"` + Tags string `q:"tags"` + TagsAny string `q:"tags-any"` + NotTags string `q:"not-tags"` + NotTagsAny string `q:"not-tags-any"` } // ToNetworkListQuery formats a ListOpts into a query string. @@ -58,23 +64,24 @@ func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { return } -// CreateOptsBuilder is the interface options structs have to satisfy in order -// to be used in the main Create operation in this package. Since many -// extensions decorate or modify the common logic, it is useful for them to -// satisfy a basic interface in order for them to be used. +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. type CreateOptsBuilder interface { ToNetworkCreateMap() (map[string]interface{}, error) } -// CreateOpts satisfies the CreateOptsBuilder interface +// CreateOpts represents options used to create a network. type CreateOpts struct { - AdminStateUp *bool `json:"admin_state_up,omitempty"` - Name string `json:"name,omitempty"` - Shared *bool `json:"shared,omitempty"` - TenantID string `json:"tenant_id,omitempty"` + AdminStateUp *bool `json:"admin_state_up,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Shared *bool `json:"shared,omitempty"` + TenantID string `json:"tenant_id,omitempty"` + ProjectID string `json:"project_id,omitempty"` + AvailabilityZoneHints []string `json:"availability_zone_hints,omitempty"` } -// ToNetworkCreateMap casts a CreateOpts struct to a map. +// ToNetworkCreateMap builds a request body from CreateOpts. func (opts CreateOpts) ToNetworkCreateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "network") } @@ -96,22 +103,21 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul return } -// UpdateOptsBuilder is the interface options structs have to satisfy in order -// to be used in the main Update operation in this package. Since many -// extensions decorate or modify the common logic, it is useful for them to -// satisfy a basic interface in order for them to be used. +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. type UpdateOptsBuilder interface { ToNetworkUpdateMap() (map[string]interface{}, error) } -// UpdateOpts satisfies the UpdateOptsBuilder interface +// UpdateOpts represents options used to update a network. type UpdateOpts struct { - AdminStateUp *bool `json:"admin_state_up,omitempty"` - Name string `json:"name,omitempty"` - Shared *bool `json:"shared,omitempty"` + AdminStateUp *bool `json:"admin_state_up,omitempty"` + Name string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + Shared *bool `json:"shared,omitempty"` } -// ToNetworkUpdateMap casts a UpdateOpts struct to a map. +// ToNetworkUpdateMap builds a request body from UpdateOpts. func (opts UpdateOpts) ToNetworkUpdateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "network") } @@ -136,11 +142,17 @@ func Delete(c *gophercloud.ServiceClient, networkID string) (r DeleteResult) { return } -// IDFromName is a convenience function that returns a network's ID given its name. +// IDFromName is a convenience function that returns a network's ID, given +// its name. func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { count := 0 id := "" - pages, err := List(client, nil).AllPages() + + listOpts := ListOpts{ + Name: name, + } + + pages, err := List(client, listOpts).AllPages() if err != nil { return "", err } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/results.go index d9289800f..f03067415 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/results.go @@ -11,29 +11,35 @@ type commonResult struct { // Extract is a function that accepts a result and extracts a network resource. func (r commonResult) Extract() (*Network, error) { - var s struct { - Network *Network `json:"network"` - } + var s Network err := r.ExtractInto(&s) - return s.Network, err + return &s, err } -// CreateResult represents the result of a create operation. +func (r commonResult) ExtractInto(v interface{}) error { + return r.Result.ExtractIntoStructPtr(v, "network") +} + +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a Network. type CreateResult struct { commonResult } -// GetResult represents the result of a get operation. +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a Network. type GetResult struct { commonResult } -// UpdateResult represents the result of an update operation. +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a Network. type UpdateResult struct { commonResult } -// DeleteResult represents the result of a delete operation. +// DeleteResult represents the result of a delete operation. Call its +// ExtractErr method to determine if the request succeeded or failed. type DeleteResult struct { gophercloud.ErrResult } @@ -46,21 +52,36 @@ type Network struct { // Human-readable name for the network. Might not be unique. Name string `json:"name"` - // The administrative state of network. If false (down), the network does not forward packets. + // Description for the network + Description string `json:"description"` + + // The administrative state of network. If false (down), the network does not + // forward packets. AdminStateUp bool `json:"admin_state_up"` // Indicates whether network is currently operational. Possible values include - // `ACTIVE', `DOWN', `BUILD', or `ERROR'. Plug-ins might define additional values. + // `ACTIVE', `DOWN', `BUILD', or `ERROR'. Plug-ins might define additional + // values. Status string `json:"status"` // Subnets associated with this network. Subnets []string `json:"subnets"` - // Owner of network. Only admin users can specify a tenant_id other than its own. + // TenantID is the project owner of the network. TenantID string `json:"tenant_id"` - // Specifies whether the network resource can be accessed by any tenant or not. + // ProjectID is the project owner of the network. + ProjectID string `json:"project_id"` + + // Specifies whether the network resource can be accessed by any tenant. Shared bool `json:"shared"` + + // Availability zone hints groups network nodes that run services like DHCP, L3, FW, and others. + // Used to make network resources highly available. + AvailabilityZoneHints []string `json:"availability_zone_hints"` + + // Tags optionally set via extensions/attributestags + Tags []string `json:"tags"` } // NetworkPage is the page returned by a pager when traversing over a @@ -93,9 +114,11 @@ func (r NetworkPage) IsEmpty() (bool, error) { // and extracts the elements into a slice of Network structs. In other words, // a generic collection is mapped into a relevant slice. func ExtractNetworks(r pagination.Page) ([]Network, error) { - var s struct { - Networks []Network `json:"networks"` - } - err := (r.(NetworkPage)).ExtractInto(&s) - return s.Networks, err + var s []Network + err := ExtractNetworksInto(r, &s) + return s, err +} + +func ExtractNetworksInto(r pagination.Page, v interface{}) error { + return r.(NetworkPage).Result.ExtractIntoSlicePtr(v, "networks") } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/doc.go index f16a4bb01..cfb1774fb 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/doc.go @@ -1,8 +1,73 @@ -// Package ports contains functionality for working with Neutron port resources. -// A port represents a virtual switch port on a logical network switch. Virtual -// instances attach their interfaces into ports. The logical port also defines -// the MAC address and the IP address(es) to be assigned to the interfaces -// plugged into them. When IP addresses are associated to a port, this also -// implies the port is associated with a subnet, as the IP address was taken -// from the allocation pool for a specific subnet. +/* +Package ports contains functionality for working with Neutron port resources. + +A port represents a virtual switch port on a logical network switch. Virtual +instances attach their interfaces into ports. The logical port also defines +the MAC address and the IP address(es) to be assigned to the interfaces +plugged into them. When IP addresses are associated to a port, this also +implies the port is associated with a subnet, as the IP address was taken +from the allocation pool for a specific subnet. + +Example to List Ports + + listOpts := ports.ListOpts{ + DeviceID: "b0b89efe-82f8-461d-958b-adbf80f50c7d", + } + + allPages, err := ports.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allPorts, err := ports.ExtractPorts(allPages) + if err != nil { + panic(err) + } + + for _, port := range allPorts { + fmt.Printf("%+v\n", port) + } + +Example to Create a Port + + createOtps := ports.CreateOpts{ + Name: "private-port", + AdminStateUp: &asu, + NetworkID: "a87cc70a-3e15-4acf-8205-9b711a3531b7", + FixedIPs: []ports.IP{ + {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"}, + }, + SecurityGroups: &[]string{"foo"}, + AllowedAddressPairs: []ports.AddressPair{ + {IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"}, + }, + } + + port, err := ports.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Port + + portID := "c34bae2b-7641-49b6-bf6d-d8e473620ed8" + + updateOpts := ports.UpdateOpts{ + Name: "new_name", + SecurityGroups: &[]string{}, + } + + port, err := ports.Update(networkClient, portID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Port + + portID := "c34bae2b-7641-49b6-bf6d-d8e473620ed8" + err := ports.Delete(networkClient, portID).ExtractErr() + if err != nil { + panic(err) + } +*/ package ports diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/requests.go index d353b7ed3..f5f7d761c 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/requests.go @@ -19,9 +19,11 @@ type ListOptsBuilder interface { type ListOpts struct { Status string `q:"status"` Name string `q:"name"` + Description string `q:"description"` AdminStateUp *bool `q:"admin_state_up"` NetworkID string `q:"network_id"` TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` DeviceOwner string `q:"device_owner"` MACAddress string `q:"mac_address"` ID string `q:"id"` @@ -30,6 +32,10 @@ type ListOpts struct { Marker string `q:"marker"` SortKey string `q:"sort_key"` SortDir string `q:"sort_dir"` + Tags string `q:"tags"` + TagsAny string `q:"tags-any"` + NotTags string `q:"not-tags"` + NotTagsAny string `q:"not-tags-any"` } // ToPortListQuery formats a ListOpts into a query string. @@ -65,10 +71,8 @@ func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { return } -// CreateOptsBuilder is the interface options structs have to satisfy in order -// to be used in the main Create operation in this package. Since many -// extensions decorate or modify the common logic, it is useful for them to -// satisfy a basic interface in order for them to be used. +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. type CreateOptsBuilder interface { ToPortCreateMap() (map[string]interface{}, error) } @@ -77,17 +81,19 @@ type CreateOptsBuilder interface { type CreateOpts struct { NetworkID string `json:"network_id" required:"true"` Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` AdminStateUp *bool `json:"admin_state_up,omitempty"` MACAddress string `json:"mac_address,omitempty"` FixedIPs interface{} `json:"fixed_ips,omitempty"` DeviceID string `json:"device_id,omitempty"` DeviceOwner string `json:"device_owner,omitempty"` TenantID string `json:"tenant_id,omitempty"` - SecurityGroups []string `json:"security_groups,omitempty"` + ProjectID string `json:"project_id,omitempty"` + SecurityGroups *[]string `json:"security_groups,omitempty"` AllowedAddressPairs []AddressPair `json:"allowed_address_pairs,omitempty"` } -// ToPortCreateMap casts a CreateOpts struct to a map. +// ToPortCreateMap builds a request body from CreateOpts. func (opts CreateOpts) ToPortCreateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "port") } @@ -104,26 +110,25 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul return } -// UpdateOptsBuilder is the interface options structs have to satisfy in order -// to be used in the main Update operation in this package. Since many -// extensions decorate or modify the common logic, it is useful for them to -// satisfy a basic interface in order for them to be used. +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. type UpdateOptsBuilder interface { ToPortUpdateMap() (map[string]interface{}, error) } // UpdateOpts represents the attributes used when updating an existing port. type UpdateOpts struct { - Name string `json:"name,omitempty"` - AdminStateUp *bool `json:"admin_state_up,omitempty"` - FixedIPs interface{} `json:"fixed_ips,omitempty"` - DeviceID string `json:"device_id,omitempty"` - DeviceOwner string `json:"device_owner,omitempty"` - SecurityGroups []string `json:"security_groups"` - AllowedAddressPairs []AddressPair `json:"allowed_address_pairs"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + AdminStateUp *bool `json:"admin_state_up,omitempty"` + FixedIPs interface{} `json:"fixed_ips,omitempty"` + DeviceID *string `json:"device_id,omitempty"` + DeviceOwner *string `json:"device_owner,omitempty"` + SecurityGroups *[]string `json:"security_groups,omitempty"` + AllowedAddressPairs *[]AddressPair `json:"allowed_address_pairs,omitempty"` } -// ToPortUpdateMap casts an UpdateOpts struct to a map. +// ToPortUpdateMap builds a request body from UpdateOpts. func (opts UpdateOpts) ToPortUpdateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "port") } @@ -148,11 +153,17 @@ func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { return } -// IDFromName is a convenience function that returns a port's ID given its name. +// IDFromName is a convenience function that returns a port's ID, +// given its name. func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { count := 0 id := "" - pages, err := List(client, nil).AllPages() + + listOpts := ListOpts{ + Name: name, + } + + pages, err := List(client, listOpts).AllPages() if err != nil { return "", err } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/results.go index 57a1765c6..3941b6230 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/results.go @@ -11,29 +11,35 @@ type commonResult struct { // Extract is a function that accepts a result and extracts a port resource. func (r commonResult) Extract() (*Port, error) { - var s struct { - Port *Port `json:"port"` - } + var s Port err := r.ExtractInto(&s) - return s.Port, err + return &s, err } -// CreateResult represents the result of a create operation. +func (r commonResult) ExtractInto(v interface{}) error { + return r.Result.ExtractIntoStructPtr(v, "port") +} + +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a Port. type CreateResult struct { commonResult } -// GetResult represents the result of a get operation. +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a Port. type GetResult struct { commonResult } -// UpdateResult represents the result of an update operation. +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a Port. type UpdateResult struct { commonResult } -// DeleteResult represents the result of a delete operation. +// DeleteResult represents the result of a delete operation. Call its +// ExtractErr method to determine if the request succeeded or failed. type DeleteResult struct { gophercloud.ErrResult } @@ -55,30 +61,52 @@ type AddressPair struct { type Port struct { // UUID for the port. ID string `json:"id"` + // Network that this port is associated with. NetworkID string `json:"network_id"` + // Human-readable name for the port. Might not be unique. Name string `json:"name"` - // Administrative state of port. If false (down), port does not forward packets. + + // Describes the port. + Description string `json:"description"` + + // Administrative state of port. If false (down), port does not forward + // packets. AdminStateUp bool `json:"admin_state_up"` + // Indicates whether network is currently operational. Possible values include - // `ACTIVE', `DOWN', `BUILD', or `ERROR'. Plug-ins might define additional values. + // `ACTIVE', `DOWN', `BUILD', or `ERROR'. Plug-ins might define additional + // values. Status string `json:"status"` + // Mac address to use on this port. MACAddress string `json:"mac_address"` + // Specifies IP addresses for the port thus associating the port itself with // the subnets where the IP addresses are picked from FixedIPs []IP `json:"fixed_ips"` - // Owner of network. Only admin users can specify a tenant_id other than its own. + + // TenantID is the project owner of the port. TenantID string `json:"tenant_id"` + + // ProjectID is the project owner of the port. + ProjectID string `json:"project_id"` + // Identifies the entity (e.g.: dhcp agent) using this port. DeviceOwner string `json:"device_owner"` + // Specifies the IDs of any security groups associated with a port. SecurityGroups []string `json:"security_groups"` + // Identifies the device (e.g., virtual server) using this port. DeviceID string `json:"device_id"` + // Identifies the list of IP addresses the port will recognize/accept AllowedAddressPairs []AddressPair `json:"allowed_address_pairs"` + + // Tags optionally set via extensions/attributestags + Tags []string `json:"tags"` } // PortPage is the page returned by a pager when traversing over a collection @@ -111,9 +139,11 @@ func (r PortPage) IsEmpty() (bool, error) { // and extracts the elements into a slice of Port structs. In other words, // a generic collection is mapped into a relevant slice. func ExtractPorts(r pagination.Page) ([]Port, error) { - var s struct { - Ports []Port `json:"ports"` - } - err := (r.(PortPage)).ExtractInto(&s) - return s.Ports, err + var s []Port + err := ExtractPortsInto(r, &s) + return s, err +} + +func ExtractPortsInto(r pagination.Page, v interface{}) error { + return r.(PortPage).Result.ExtractIntoSlicePtr(v, "ports") } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/doc.go index 43e8296c7..d0ed8dff0 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/doc.go @@ -1,10 +1,133 @@ -// Package subnets contains functionality for working with Neutron subnet -// resources. A subnet represents an IP address block that can be used to -// assign IP addresses to virtual instances. Each subnet must have a CIDR and -// must be associated with a network. IPs can either be selected from the whole -// subnet CIDR or from allocation pools specified by the user. -// -// A subnet can also have a gateway, a list of DNS name servers, and host routes. -// This information is pushed to instances whose interfaces are associated with -// the subnet. +/* +Package subnets contains functionality for working with Neutron subnet +resources. A subnet represents an IP address block that can be used to +assign IP addresses to virtual instances. Each subnet must have a CIDR and +must be associated with a network. IPs can either be selected from the whole +subnet CIDR or from allocation pools specified by the user. + +A subnet can also have a gateway, a list of DNS name servers, and host routes. +This information is pushed to instances whose interfaces are associated with +the subnet. + +Example to List Subnets + + listOpts := subnets.ListOpts{ + IPVersion: 4, + } + + allPages, err := subnets.List(networkClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allSubnets, err := subnets.ExtractSubnets(allPages) + if err != nil { + panic(err) + } + + for _, subnet := range allSubnets { + fmt.Printf("%+v\n", subnet) + } + +Example to Create a Subnet With Specified Gateway + + var gatewayIP = "192.168.199.1" + createOpts := subnets.CreateOpts{ + NetworkID: "d32019d3-bc6e-4319-9c1d-6722fc136a22", + IPVersion: 4, + CIDR: "192.168.199.0/24", + GatewayIP: &gatewayIP, + AllocationPools: []subnets.AllocationPool{ + { + Start: "192.168.199.2", + End: "192.168.199.254", + }, + }, + DNSNameservers: []string{"foo"}, + } + + subnet, err := subnets.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Create a Subnet With No Gateway + + var noGateway = "" + + createOpts := subnets.CreateOpts{ + NetworkID: "d32019d3-bc6e-4319-9c1d-6722fc136a23", + IPVersion: 4, + CIDR: "192.168.1.0/24", + GatewayIP: &noGateway, + AllocationPools: []subnets.AllocationPool{ + { + Start: "192.168.1.2", + End: "192.168.1.254", + }, + }, + DNSNameservers: []string{}, + } + + subnet, err := subnets.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Create a Subnet With a Default Gateway + + createOpts := subnets.CreateOpts{ + NetworkID: "d32019d3-bc6e-4319-9c1d-6722fc136a23", + IPVersion: 4, + CIDR: "192.168.1.0/24", + AllocationPools: []subnets.AllocationPool{ + { + Start: "192.168.1.2", + End: "192.168.1.254", + }, + }, + DNSNameservers: []string{}, + } + + subnet, err := subnets.Create(networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Subnet + + subnetID := "db77d064-e34f-4d06-b060-f21e28a61c23" + + updateOpts := subnets.UpdateOpts{ + Name: "new_name", + DNSNameservers: []string{"8.8.8.8}, + } + + subnet, err := subnets.Update(networkClient, subnetID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Remove a Gateway From a Subnet + + var noGateway = "" + subnetID := "db77d064-e34f-4d06-b060-f21e28a61c23" + + updateOpts := subnets.UpdateOpts{ + GatewayIP: &noGateway, + } + + subnet, err := subnets.Update(networkClient, subnetID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Subnet + + subnetID := "db77d064-e34f-4d06-b060-f21e28a61c23" + err := subnets.Delete(networkClient, subnetID).ExtractErr() + if err != nil { + panic(err) + } +*/ package subnets diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/requests.go index ec3769554..d2c4a29f7 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/requests.go @@ -17,18 +17,27 @@ type ListOptsBuilder interface { // by a particular subnet attribute. SortDir sets the direction, and is either // `asc' or `desc'. Marker and Limit are used for pagination. type ListOpts struct { - Name string `q:"name"` - EnableDHCP *bool `q:"enable_dhcp"` - NetworkID string `q:"network_id"` - TenantID string `q:"tenant_id"` - IPVersion int `q:"ip_version"` - GatewayIP string `q:"gateway_ip"` - CIDR string `q:"cidr"` - ID string `q:"id"` - Limit int `q:"limit"` - Marker string `q:"marker"` - SortKey string `q:"sort_key"` - SortDir string `q:"sort_dir"` + Name string `q:"name"` + Description string `q:"description"` + EnableDHCP *bool `q:"enable_dhcp"` + NetworkID string `q:"network_id"` + TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` + IPVersion int `q:"ip_version"` + GatewayIP string `q:"gateway_ip"` + CIDR string `q:"cidr"` + IPv6AddressMode string `q:"ipv6_address_mode"` + IPv6RAMode string `q:"ipv6_ra_mode"` + ID string `q:"id"` + SubnetPoolID string `q:"subnetpool_id"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` + Tags string `q:"tags"` + TagsAny string `q:"tags-any"` + NotTags string `q:"not-tags"` + NotTagsAny string `q:"not-tags-any"` } // ToSubnetListQuery formats a ListOpts into a query string. @@ -64,29 +73,71 @@ func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { return } -// CreateOptsBuilder is the interface options structs have to satisfy in order -// to be used in the main Create operation in this package. Since many -// extensions decorate or modify the common logic, it is useful for them to -// satisfy a basic interface in order for them to be used. +// CreateOptsBuilder allows extensions to add additional parameters to the +// List request. type CreateOptsBuilder interface { ToSubnetCreateMap() (map[string]interface{}, error) } // CreateOpts represents the attributes used when creating a new subnet. type CreateOpts struct { - NetworkID string `json:"network_id" required:"true"` - CIDR string `json:"cidr" required:"true"` - Name string `json:"name,omitempty"` - TenantID string `json:"tenant_id,omitempty"` - AllocationPools []AllocationPool `json:"allocation_pools,omitempty"` - GatewayIP *string `json:"gateway_ip,omitempty"` - IPVersion gophercloud.IPVersion `json:"ip_version,omitempty"` - EnableDHCP *bool `json:"enable_dhcp,omitempty"` - DNSNameservers []string `json:"dns_nameservers,omitempty"` - HostRoutes []HostRoute `json:"host_routes,omitempty"` + // NetworkID is the UUID of the network the subnet will be associated with. + NetworkID string `json:"network_id" required:"true"` + + // CIDR is the address CIDR of the subnet. + CIDR string `json:"cidr,omitempty"` + + // Name is a human-readable name of the subnet. + Name string `json:"name,omitempty"` + + // Description of the subnet. + Description string `json:"description,omitempty"` + + // The UUID of the project who owns the Subnet. Only administrative users + // can specify a project UUID other than their own. + TenantID string `json:"tenant_id,omitempty"` + + // The UUID of the project who owns the Subnet. Only administrative users + // can specify a project UUID other than their own. + ProjectID string `json:"project_id,omitempty"` + + // AllocationPools are IP Address pools that will be available for DHCP. + AllocationPools []AllocationPool `json:"allocation_pools,omitempty"` + + // GatewayIP sets gateway information for the subnet. Setting to nil will + // cause a default gateway to automatically be created. Setting to an empty + // string will cause the subnet to be created with no gateway. Setting to + // an explicit address will set that address as the gateway. + GatewayIP *string `json:"gateway_ip,omitempty"` + + // IPVersion is the IP version for the subnet. + IPVersion gophercloud.IPVersion `json:"ip_version,omitempty"` + + // EnableDHCP will either enable to disable the DHCP service. + EnableDHCP *bool `json:"enable_dhcp,omitempty"` + + // DNSNameservers are the nameservers to be set via DHCP. + DNSNameservers []string `json:"dns_nameservers,omitempty"` + + // HostRoutes are any static host routes to be set via DHCP. + HostRoutes []HostRoute `json:"host_routes,omitempty"` + + // The IPv6 address modes specifies mechanisms for assigning IPv6 IP addresses. + IPv6AddressMode string `json:"ipv6_address_mode,omitempty"` + + // The IPv6 router advertisement specifies whether the networking service + // should transmit ICMPv6 packets. + IPv6RAMode string `json:"ipv6_ra_mode,omitempty"` + + // SubnetPoolID is the id of the subnet pool that subnet should be associated to. + SubnetPoolID string `json:"subnetpool_id,omitempty"` + + // Prefixlen is used when user creates a subnet from the subnetpool. It will + // overwrite the "default_prefixlen" value of the referenced subnetpool. + Prefixlen int `json:"prefixlen,omitempty"` } -// ToSubnetCreateMap casts a CreateOpts struct to a map. +// ToSubnetCreateMap builds a request body from CreateOpts. func (opts CreateOpts) ToSubnetCreateMap() (map[string]interface{}, error) { b, err := gophercloud.BuildRequestBody(opts, "subnet") if err != nil { @@ -101,7 +152,8 @@ func (opts CreateOpts) ToSubnetCreateMap() (map[string]interface{}, error) { } // Create accepts a CreateOpts struct and creates a new subnet using the values -// provided. You must remember to provide a valid NetworkID, CIDR and IP version. +// provided. You must remember to provide a valid NetworkID, CIDR and IP +// version. func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { b, err := opts.ToSubnetCreateMap() if err != nil { @@ -120,15 +172,32 @@ type UpdateOptsBuilder interface { // UpdateOpts represents the attributes used when updating an existing subnet. type UpdateOpts struct { - Name string `json:"name,omitempty"` + // Name is a human-readable name of the subnet. + Name string `json:"name,omitempty"` + + // Description of the subnet. + Description *string `json:"description,omitempty"` + + // AllocationPools are IP Address pools that will be available for DHCP. AllocationPools []AllocationPool `json:"allocation_pools,omitempty"` - GatewayIP *string `json:"gateway_ip,omitempty"` - DNSNameservers []string `json:"dns_nameservers,omitempty"` - HostRoutes []HostRoute `json:"host_routes,omitempty"` - EnableDHCP *bool `json:"enable_dhcp,omitempty"` + + // GatewayIP sets gateway information for the subnet. Setting to nil will + // cause a default gateway to automatically be created. Setting to an empty + // string will cause the subnet to be created with no gateway. Setting to + // an explicit address will set that address as the gateway. + GatewayIP *string `json:"gateway_ip,omitempty"` + + // DNSNameservers are the nameservers to be set via DHCP. + DNSNameservers []string `json:"dns_nameservers,omitempty"` + + // HostRoutes are any static host routes to be set via DHCP. + HostRoutes *[]HostRoute `json:"host_routes,omitempty"` + + // EnableDHCP will either enable to disable the DHCP service. + EnableDHCP *bool `json:"enable_dhcp,omitempty"` } -// ToSubnetUpdateMap casts an UpdateOpts struct to a map. +// ToSubnetUpdateMap builds a request body from UpdateOpts. func (opts UpdateOpts) ToSubnetUpdateMap() (map[string]interface{}, error) { b, err := gophercloud.BuildRequestBody(opts, "subnet") if err != nil { @@ -162,11 +231,17 @@ func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { return } -// IDFromName is a convenience function that returns a subnet's ID given its name. +// IDFromName is a convenience function that returns a subnet's ID, +// given its name. func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { count := 0 id := "" - pages, err := List(client, nil).AllPages() + + listOpts := ListOpts{ + Name: name, + } + + pages, err := List(client, listOpts).AllPages() if err != nil { return "", err } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/results.go index ab5cce124..cf0397019 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/results.go @@ -18,22 +18,26 @@ func (r commonResult) Extract() (*Subnet, error) { return s.Subnet, err } -// CreateResult represents the result of a create operation. +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a Subnet. type CreateResult struct { commonResult } -// GetResult represents the result of a get operation. +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a Subnet. type GetResult struct { commonResult } -// UpdateResult represents the result of an update operation. +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a Subnet. type UpdateResult struct { commonResult } -// DeleteResult represents the result of a delete operation. +// DeleteResult represents the result of a delete operation. Call its +// ExtractErr method to determine if the request succeeded or failed. type DeleteResult struct { gophercloud.ErrResult } @@ -55,28 +59,59 @@ type HostRoute struct { // Subnet represents a subnet. See package documentation for a top-level // description of what this is. type Subnet struct { - // UUID representing the subnet + // UUID representing the subnet. ID string `json:"id"` - // UUID of the parent network + + // UUID of the parent network. NetworkID string `json:"network_id"` + // Human-readable name for the subnet. Might not be unique. Name string `json:"name"` - // IP version, either `4' or `6' + + // Description for the subnet. + Description string `json:"description"` + + // IP version, either `4' or `6'. IPVersion int `json:"ip_version"` - // CIDR representing IP range for this subnet, based on IP version + + // CIDR representing IP range for this subnet, based on IP version. CIDR string `json:"cidr"` - // Default gateway used by devices in this subnet + + // Default gateway used by devices in this subnet. GatewayIP string `json:"gateway_ip"` + // DNS name servers used by hosts in this subnet. DNSNameservers []string `json:"dns_nameservers"` - // Sub-ranges of CIDR available for dynamic allocation to ports. See AllocationPool. + + // Sub-ranges of CIDR available for dynamic allocation to ports. + // See AllocationPool. AllocationPools []AllocationPool `json:"allocation_pools"` - // Routes that should be used by devices with IPs from this subnet (not including local subnet route). + + // Routes that should be used by devices with IPs from this subnet + // (not including local subnet route). HostRoutes []HostRoute `json:"host_routes"` + // Specifies whether DHCP is enabled for this subnet or not. EnableDHCP bool `json:"enable_dhcp"` - // Owner of network. Only admin users can specify a tenant_id other than its own. + + // TenantID is the project owner of the subnet. TenantID string `json:"tenant_id"` + + // ProjectID is the project owner of the subnet. + ProjectID string `json:"project_id"` + + // The IPv6 address modes specifies mechanisms for assigning IPv6 IP addresses. + IPv6AddressMode string `json:"ipv6_address_mode"` + + // The IPv6 router advertisement specifies whether the networking service + // should transmit ICMPv6 packets. + IPv6RAMode string `json:"ipv6_ra_mode"` + + // SubnetPoolID is the id of the subnet pool associated with the subnet. + SubnetPoolID string `json:"subnetpool_id"` + + // Tags optionally set via extensions/attributestags + Tags []string `json:"tags"` } // SubnetPage is the page returned by a pager when traversing over a collection diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/doc.go index f5f894a9e..0fa1c083a 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/doc.go @@ -1,8 +1,29 @@ -// Package accounts contains functionality for working with Object Storage -// account resources. An account is the top-level resource the object storage -// hierarchy: containers belong to accounts, objects belong to containers. -// -// Another way of thinking of an account is like a namespace for all your -// resources. It is synonymous with a project or tenant in other OpenStack -// services. +/* +Package accounts contains functionality for working with Object Storage +account resources. An account is the top-level resource the object storage +hierarchy: containers belong to accounts, objects belong to containers. + +Another way of thinking of an account is like a namespace for all your +resources. It is synonymous with a project or tenant in other OpenStack +services. + +Example to Get an Account + + account, err := accounts.Get(objectStorageClient, nil).Extract() + fmt.Printf("%+v\n", account) + +Example to Update an Account + + metadata := map[string]string{ + "some": "metadata", + } + + updateOpts := accounts.UpdateOpts{ + Metadata: metadata, + } + + updateResult, err := accounts.Update(objectStorageClient, updateOpts).Extract() + fmt.Printf("%+v\n", updateResult) + +*/ package accounts diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/requests.go index b5beef28b..452a331c7 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/requests.go @@ -22,7 +22,7 @@ func (opts GetOpts) ToAccountGetMap() (map[string]string, error) { // Get is a function that retrieves an account's metadata. To extract just the // custom metadata, call the ExtractMetadata method on the GetResult. To extract // all the headers that are returned (including the metadata), call the -// ExtractHeader method on the GetResult. +// Extract method on the GetResult. func Get(c *gophercloud.ServiceClient, opts GetOptsBuilder) (r GetResult) { h := make(map[string]string) if opts != nil { @@ -35,7 +35,7 @@ func Get(c *gophercloud.ServiceClient, opts GetOptsBuilder) (r GetResult) { h[k] = v } } - resp, err := c.Request("HEAD", getURL(c), &gophercloud.RequestOpts{ + resp, err := c.Head(getURL(c), &gophercloud.RequestOpts{ MoreHeaders: h, OkCodes: []int{204}, }) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/results.go index 9bc834047..10661e671 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/results.go @@ -14,7 +14,8 @@ type UpdateResult struct { gophercloud.HeaderResult } -// UpdateHeader represents the headers returned in the response from an Update request. +// UpdateHeader represents the headers returned in the response from an Update +// request. type UpdateHeader struct { ContentLength int64 `json:"-"` ContentType string `json:"Content-Type"` @@ -51,8 +52,8 @@ func (r *UpdateHeader) UnmarshalJSON(b []byte) error { return err } -// Extract will return a struct of headers returned from a call to Get. To obtain -// a map of headers, call the ExtractHeader method on the GetResult. +// Extract will return a struct of headers returned from a call to Get. To +// obtain a map of headers, call the Extract method on the GetResult. func (r UpdateResult) Extract() (*UpdateHeader, error) { var s *UpdateHeader err := r.ExtractInto(&s) @@ -62,6 +63,7 @@ func (r UpdateResult) Extract() (*UpdateHeader, error) { // GetHeader represents the headers returned in the response from a Get request. type GetHeader struct { BytesUsed int64 `json:"-"` + QuotaBytes *int64 `json:"-"` ContainerCount int64 `json:"-"` ContentLength int64 `json:"-"` ObjectCount int64 `json:"-"` @@ -77,6 +79,7 @@ func (r *GetHeader) UnmarshalJSON(b []byte) error { var s struct { tmp BytesUsed string `json:"X-Account-Bytes-Used"` + QuotaBytes string `json:"X-Account-Meta-Quota-Bytes"` ContentLength string `json:"Content-Length"` ContainerCount string `json:"X-Account-Container-Count"` ObjectCount string `json:"X-Account-Object-Count"` @@ -99,6 +102,17 @@ func (r *GetHeader) UnmarshalJSON(b []byte) error { } } + switch s.QuotaBytes { + case "": + r.QuotaBytes = nil + default: + v, err := strconv.ParseInt(s.QuotaBytes, 10, 64) + if err != nil { + return err + } + r.QuotaBytes = &v + } + switch s.ContentLength { case "": r.ContentLength = 0 @@ -141,15 +155,14 @@ type GetResult struct { gophercloud.HeaderResult } -// Extract will return a struct of headers returned from a call to Get. To obtain -// a map of headers, call the ExtractHeader method on the GetResult. +// Extract will return a struct of headers returned from a call to Get. func (r GetResult) Extract() (*GetHeader, error) { var s *GetHeader err := r.ExtractInto(&s) return s, err } -// ExtractMetadata is a function that takes a GetResult (of type *htts.Response) +// ExtractMetadata is a function that takes a GetResult (of type *http.Response) // and returns the custom metatdata associated with the account. func (r GetResult) ExtractMetadata() (map[string]string, error) { if r.Err != nil { diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/doc.go index 5fed5537f..9e5f66419 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/doc.go @@ -1,8 +1,92 @@ -// Package containers contains functionality for working with Object Storage -// container resources. A container serves as a logical namespace for objects -// that are placed inside it - an object with the same name in two different -// containers represents two different objects. -// -// In addition to containing objects, you can also use the container to control -// access to objects by using an access control list (ACL). +/* +Package containers contains functionality for working with Object Storage +container resources. A container serves as a logical namespace for objects +that are placed inside it - an object with the same name in two different +containers represents two different objects. + +In addition to containing objects, you can also use the container to control +access to objects by using an access control list (ACL). + +Note: When referencing the Object Storage API docs, some of the API actions +are listed under "accounts" rather than "containers". This was an intentional +design in Gophercloud to make some container actions feel more natural. + +Example to List Containers + + listOpts := containers.ListOpts{ + Full: true, + } + + allPages, err := containers.List(objectStorageClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allContainers, err := containers.ExtractInfo(allPages) + if err != nil { + panic(err) + } + + for _, container := range allContainers { + fmt.Printf("%+v\n", container) + } + +Example to List Only Container Names + + listOpts := containers.ListOpts{ + Full: false, + } + + allPages, err := containers.List(objectStorageClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allContainers, err := containers.ExtractNames(allPages) + if err != nil { + panic(err) + } + + for _, container := range allContainers { + fmt.Printf("%+v\n", container) + } + +Example to Create a Container + + createOpts := containers.CreateOpts{ + ContentType: "application/json", + Metadata: map[string]string{ + "foo": "bar", + }, + } + + container, err := containers.Create(objectStorageClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Container + + containerName := "my_container" + + updateOpts := containers.UpdateOpts{ + Metadata: map[string]string{ + "bar": "baz", + }, + } + + container, err := containers.Update(objectStorageClient, containerName, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Container + + containerName := "my_container" + + container, err := containers.Delete(objectStorageClient, containerName).Extract() + if err != nil { + panic(err) + } +*/ package containers diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/requests.go index a66867394..89aa99683 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/requests.go @@ -74,6 +74,7 @@ type CreateOpts struct { DetectContentType bool `h:"X-Detect-Content-Type"` IfNoneMatch string `h:"If-None-Match"` VersionsLocation string `h:"X-Versions-Location"` + HistoryLocation string `h:"X-History-Location"` } // ToContainerCreateMap formats a CreateOpts into a map of headers. @@ -107,6 +108,7 @@ func Create(c *gophercloud.ServiceClient, containerName string, opts CreateOptsB }) if resp != nil { r.Header = resp.Header + resp.Body.Close() } r.Err = err return @@ -136,9 +138,11 @@ type UpdateOpts struct { DetectContentType bool `h:"X-Detect-Content-Type"` RemoveVersionsLocation string `h:"X-Remove-Versions-Location"` VersionsLocation string `h:"X-Versions-Location"` + RemoveHistoryLocation string `h:"X-Remove-History-Location"` + HistoryLocation string `h:"X-History-Location"` } -// ToContainerUpdateMap formats a CreateOpts into a map of headers. +// ToContainerUpdateMap formats a UpdateOpts into a map of headers. func (opts UpdateOpts) ToContainerUpdateMap() (map[string]string, error) { h, err := gophercloud.BuildHeaders(opts) if err != nil { @@ -176,12 +180,41 @@ func Update(c *gophercloud.ServiceClient, containerName string, opts UpdateOptsB return } +// GetOptsBuilder allows extensions to add additional parameters to the Get +// request. +type GetOptsBuilder interface { + ToContainerGetMap() (map[string]string, error) +} + +// GetOpts is a structure that holds options for listing containers. +type GetOpts struct { + Newest bool `h:"X-Newest"` +} + +// ToContainerGetMap formats a GetOpts into a map of headers. +func (opts GetOpts) ToContainerGetMap() (map[string]string, error) { + return gophercloud.BuildHeaders(opts) +} + // Get is a function that retrieves the metadata of a container. To extract just // the custom metadata, pass the GetResult response to the ExtractMetadata // function. -func Get(c *gophercloud.ServiceClient, containerName string) (r GetResult) { - resp, err := c.Request("HEAD", getURL(c, containerName), &gophercloud.RequestOpts{ - OkCodes: []int{200, 204}, +func Get(c *gophercloud.ServiceClient, containerName string, opts GetOptsBuilder) (r GetResult) { + h := make(map[string]string) + if opts != nil { + headers, err := opts.ToContainerGetMap() + if err != nil { + r.Err = err + return + } + + for k, v := range headers { + h[k] = v + } + } + resp, err := c.Head(getURL(c, containerName), &gophercloud.RequestOpts{ + MoreHeaders: h, + OkCodes: []int{200, 204}, }) if resp != nil { r.Header = resp.Header diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/results.go index 8c11b8c83..cce2190ff 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/results.go @@ -47,14 +47,16 @@ func (r ContainerPage) LastMarker() (string, error) { return names[len(names)-1], nil } -// ExtractInfo is a function that takes a ListResult and returns the containers' information. +// ExtractInfo is a function that takes a ListResult and returns the +// containers' information. func ExtractInfo(r pagination.Page) ([]Container, error) { var s []Container err := (r.(ContainerPage)).ExtractInto(&s) return s, err } -// ExtractNames is a function that takes a ListResult and returns the containers' names. +// ExtractNames is a function that takes a ListResult and returns the +// containers' names. func ExtractNames(page pagination.Page) ([]string, error) { casted := page.(ContainerPage) ct := casted.Header.Get("Content-Type") @@ -98,7 +100,9 @@ type GetHeader struct { Read []string `json:"-"` TransID string `json:"X-Trans-Id"` VersionsLocation string `json:"X-Versions-Location"` + HistoryLocation string `json:"X-History-Location"` Write []string `json:"-"` + StoragePolicy string `json:"X-Storage-Policy"` } func (r *GetHeader) UnmarshalJSON(b []byte) error { @@ -162,15 +166,14 @@ type GetResult struct { gophercloud.HeaderResult } -// Extract will return a struct of headers returned from a call to Get. To obtain -// a map of headers, call the ExtractHeader method on the GetResult. +// Extract will return a struct of headers returned from a call to Get. func (r GetResult) Extract() (*GetHeader, error) { var s *GetHeader err := r.ExtractInto(&s) return s, err } -// ExtractMetadata is a function that takes a GetResult (of type *stts.Response) +// ExtractMetadata is a function that takes a GetResult (of type *http.Response) // and returns the custom metadata associated with the container. func (r GetResult) ExtractMetadata() (map[string]string, error) { if r.Err != nil { @@ -186,7 +189,8 @@ func (r GetResult) ExtractMetadata() (map[string]string, error) { return metadata, nil } -// CreateHeader represents the headers returned in the response from a Create request. +// CreateHeader represents the headers returned in the response from a Create +// request. type CreateHeader struct { ContentLength int64 `json:"-"` ContentType string `json:"Content-Type"` @@ -224,21 +228,21 @@ func (r *CreateHeader) UnmarshalJSON(b []byte) error { } // CreateResult represents the result of a create operation. To extract the -// the headers from the HTTP response, you can invoke the 'ExtractHeader' -// method on the result struct. +// the headers from the HTTP response, call its Extract method. type CreateResult struct { gophercloud.HeaderResult } -// Extract will return a struct of headers returned from a call to Create. To obtain -// a map of headers, call the ExtractHeader method on the CreateResult. +// Extract will return a struct of headers returned from a call to Create. +// To extract the headers from the HTTP response, call its Extract method. func (r CreateResult) Extract() (*CreateHeader, error) { var s *CreateHeader err := r.ExtractInto(&s) return s, err } -// UpdateHeader represents the headers returned in the response from a Update request. +// UpdateHeader represents the headers returned in the response from a Update +// request. type UpdateHeader struct { ContentLength int64 `json:"-"` ContentType string `json:"Content-Type"` @@ -276,21 +280,20 @@ func (r *UpdateHeader) UnmarshalJSON(b []byte) error { } // UpdateResult represents the result of an update operation. To extract the -// the headers from the HTTP response, you can invoke the 'ExtractHeader' -// method on the result struct. +// the headers from the HTTP response, call its Extract method. type UpdateResult struct { gophercloud.HeaderResult } -// Extract will return a struct of headers returned from a call to Update. To obtain -// a map of headers, call the ExtractHeader method on the UpdateResult. +// Extract will return a struct of headers returned from a call to Update. func (r UpdateResult) Extract() (*UpdateHeader, error) { var s *UpdateHeader err := r.ExtractInto(&s) return s, err } -// DeleteHeader represents the headers returned in the response from a Delete request. +// DeleteHeader represents the headers returned in the response from a Delete +// request. type DeleteHeader struct { ContentLength int64 `json:"-"` ContentType string `json:"Content-Type"` @@ -328,14 +331,12 @@ func (r *DeleteHeader) UnmarshalJSON(b []byte) error { } // DeleteResult represents the result of a delete operation. To extract the -// the headers from the HTTP response, you can invoke the 'ExtractHeader' -// method on the result struct. +// the headers from the HTTP response, call its Extract method. type DeleteResult struct { gophercloud.HeaderResult } -// Extract will return a struct of headers returned from a call to Delete. To obtain -// a map of headers, call the ExtractHeader method on the DeleteResult. +// Extract will return a struct of headers returned from a call to Delete. func (r DeleteResult) Extract() (*DeleteHeader, error) { var s *DeleteHeader err := r.ExtractInto(&s) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/doc.go index 30a9adde1..e9b4b8a9f 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/doc.go @@ -1,5 +1,106 @@ -// Package objects contains functionality for working with Object Storage -// object resources. An object is a resource that represents and contains data -// - such as documents, images, and so on. You can also store custom metadata -// with an object. +/* +Package objects contains functionality for working with Object Storage +object resources. An object is a resource that represents and contains data +- such as documents, images, and so on. You can also store custom metadata +with an object. + +Note: When referencing the Object Storage API docs, some of the API actions +are listed under "containers" rather than "objects". This was an intentional +design in Gophercloud to make some object actions feel more natural. + +Example to List Objects + + containerName := "my_container" + + listOpts := objects.ListOpts{ + Full: true, + } + + allPages, err := objects.List(objectStorageClient, containerName, listOpts).AllPages() + if err != nil { + panic(err) + } + + allObjects, err := objects.ExtractInfo(allPages) + if err != nil { + panic(err) + } + + for _, object := range allObjects { + fmt.Printf("%+v\n", object) + } + +Example to List Object Names + + containerName := "my_container" + + listOpts := objects.ListOpts{ + Full: false, + } + + allPages, err := objects.List(objectStorageClient, containerName, listOpts).AllPages() + if err != nil { + panic(err) + } + + allObjects, err := objects.ExtractNames(allPages) + if err != nil { + panic(err) + } + + for _, object := range allObjects { + fmt.Printf("%+v\n", object) + } + +Example to Create an Object + + content := "some object content" + objectName := "my_object" + containerName := "my_container" + + createOpts := objects.CreateOpts{ + ContentType: "text/plain" + Content: strings.NewReader(content), + } + + object, err := objects.Create(objectStorageClient, containerName, objectName, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Copy an Object + + objectName := "my_object" + containerName := "my_container" + + copyOpts := objects.CopyOpts{ + Destination: "/newContainer/newObject", + } + + object, err := objects.Copy(objectStorageClient, containerName, objectName, copyOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete an Object + + objectName := "my_object" + containerName := "my_container" + + object, err := objects.Delete(objectStorageClient, containerName, objectName).Extract() + if err != nil { + panic(err) + } + +Example to Download an Object's Data + + objectName := "my_object" + containerName := "my_container" + + object := objects.Download(objectStorageClient, containerName, objectName, nil) + content, err := object.ExtractContent() + if err != nil { + panic(err) + } +*/ package objects diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/requests.go index 0ab5e1711..7325cd7d0 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/requests.go @@ -7,6 +7,7 @@ import ( "crypto/sha1" "fmt" "io" + "io/ioutil" "strings" "time" @@ -24,9 +25,9 @@ type ListOptsBuilder interface { // ListOpts is a structure that holds parameters for listing objects. type ListOpts struct { // Full is a true/false value that represents the amount of object information - // returned. If Full is set to true, then the content-type, number of bytes, hash - // date last modified, and name are returned. If set to false or not set, then - // only the object names are returned. + // returned. If Full is set to true, then the content-type, number of bytes, + // hash date last modified, and name are returned. If set to false or not set, + // then only the object names are returned. Full bool Limit int `q:"limit"` Marker string `q:"marker"` @@ -44,9 +45,10 @@ func (opts ListOpts) ToObjectListParams() (bool, string, error) { return opts.Full, q.String(), err } -// List is a function that retrieves all objects in a container. It also returns the details -// for the container. To extract only the object information or names, pass the ListResult -// response to the ExtractInfo or ExtractNames function, respectively. +// List is a function that retrieves all objects in a container. It also returns +// the details for the container. To extract only the object information or names, +// pass the ListResult response to the ExtractInfo or ExtractNames function, +// respectively. func List(c *gophercloud.ServiceClient, containerName string, opts ListOptsBuilder) pagination.Pager { headers := map[string]string{"Accept": "text/plain", "Content-Type": "text/plain"} @@ -84,6 +86,7 @@ type DownloadOpts struct { IfModifiedSince time.Time `h:"If-Modified-Since"` IfNoneMatch string `h:"If-None-Match"` IfUnmodifiedSince time.Time `h:"If-Unmodified-Since"` + Newest bool `h:"X-Newest"` Range string `h:"Range"` Expires string `q:"expires"` MultipartManifest string `q:"multipart-manifest"` @@ -124,7 +127,7 @@ func Download(c *gophercloud.ServiceClient, containerName, objectName string, op resp, err := c.Get(url, nil, &gophercloud.RequestOpts{ MoreHeaders: h, - OkCodes: []int{200, 304}, + OkCodes: []int{200, 206, 304}, }) if resp != nil { r.Header = resp.Header @@ -144,6 +147,7 @@ type CreateOptsBuilder interface { type CreateOpts struct { Content io.Reader Metadata map[string]string + NoETag bool CacheControl string `h:"Cache-Control"` ContentDisposition string `h:"Content-Disposition"` ContentEncoding string `h:"Content-Encoding"` @@ -178,20 +182,43 @@ func (opts CreateOpts) ToObjectCreateParams() (io.Reader, map[string]string, str h["X-Object-Meta-"+k] = v } + if opts.NoETag { + delete(h, "etag") + return opts.Content, h, q.String(), nil + } + + if h["ETag"] != "" { + return opts.Content, h, q.String(), nil + } + + // When we're dealing with big files an io.ReadSeeker allows us to efficiently calculate + // the md5 sum. An io.Reader is only readable once which means we have to copy the entire + // file content into memory first. + readSeeker, isReadSeeker := opts.Content.(io.ReadSeeker) + if !isReadSeeker { + data, err := ioutil.ReadAll(opts.Content) + if err != nil { + return nil, nil, "", err + } + readSeeker = bytes.NewReader(data) + } + hash := md5.New() - buf := bytes.NewBuffer([]byte{}) - _, err = io.Copy(io.MultiWriter(hash, buf), opts.Content) - if err != nil { + // io.Copy into md5 is very efficient as it's done in small chunks. + if _, err := io.Copy(hash, readSeeker); err != nil { return nil, nil, "", err } - localChecksum := fmt.Sprintf("%x", hash.Sum(nil)) - h["ETag"] = localChecksum + readSeeker.Seek(0, io.SeekStart) - return buf, h, q.String(), nil + h["ETag"] = fmt.Sprintf("%x", hash.Sum(nil)) + + return readSeeker, h, q.String(), nil } -// Create is a function that creates a new object or replaces an existing object. If the returned response's ETag -// header fails to match the local checksum, the failed request will automatically be retried up to a maximum of 3 times. +// Create is a function that creates a new object or replaces an existing +// object. If the returned response's ETag header fails to match the local +// checksum, the failed request will automatically be retried up to a maximum +// of 3 times. func Create(c *gophercloud.ServiceClient, containerName, objectName string, opts CreateOptsBuilder) (r CreateResult) { url := createURL(c, containerName, objectName) h := make(map[string]string) @@ -312,35 +339,51 @@ func Delete(c *gophercloud.ServiceClient, containerName, objectName string, opts // GetOptsBuilder allows extensions to add additional parameters to the // Get request. type GetOptsBuilder interface { - ToObjectGetQuery() (string, error) + ToObjectGetParams() (map[string]string, string, error) } -// GetOpts is a structure that holds parameters for getting an object's metadata. +// GetOpts is a structure that holds parameters for getting an object's +// metadata. type GetOpts struct { + Newest bool `h:"X-Newest"` Expires string `q:"expires"` Signature string `q:"signature"` } -// ToObjectGetQuery formats a GetOpts into a query string. -func (opts GetOpts) ToObjectGetQuery() (string, error) { +// ToObjectGetParams formats a GetOpts into a query string and a map of headers. +func (opts GetOpts) ToObjectGetParams() (map[string]string, string, error) { q, err := gophercloud.BuildQueryString(opts) - return q.String(), err + if err != nil { + return nil, "", err + } + h, err := gophercloud.BuildHeaders(opts) + if err != nil { + return nil, q.String(), err + } + return h, q.String(), nil } -// Get is a function that retrieves the metadata of an object. To extract just the custom -// metadata, pass the GetResult response to the ExtractMetadata function. +// Get is a function that retrieves the metadata of an object. To extract just +// the custom metadata, pass the GetResult response to the ExtractMetadata +// function. func Get(c *gophercloud.ServiceClient, containerName, objectName string, opts GetOptsBuilder) (r GetResult) { url := getURL(c, containerName, objectName) + h := make(map[string]string) if opts != nil { - query, err := opts.ToObjectGetQuery() + headers, query, err := opts.ToObjectGetParams() if err != nil { r.Err = err return } + for k, v := range headers { + h[k] = v + } url += query } - resp, err := c.Request("HEAD", url, &gophercloud.RequestOpts{ - OkCodes: []int{200, 204}, + + resp, err := c.Head(url, &gophercloud.RequestOpts{ + MoreHeaders: h, + OkCodes: []int{200, 204}, }) if resp != nil { r.Header = resp.Header @@ -355,8 +398,8 @@ type UpdateOptsBuilder interface { ToObjectUpdateMap() (map[string]string, error) } -// UpdateOpts is a structure that holds parameters for updating, creating, or deleting an -// object's metadata. +// UpdateOpts is a structure that holds parameters for updating, creating, or +// deleting an object's metadata. type UpdateOpts struct { Metadata map[string]string ContentDisposition string `h:"Content-Disposition"` @@ -410,17 +453,20 @@ type HTTPMethod string var ( // GET represents an HTTP "GET" method. GET HTTPMethod = "GET" + // POST represents an HTTP "POST" method. POST HTTPMethod = "POST" ) // CreateTempURLOpts are options for creating a temporary URL for an object. type CreateTempURLOpts struct { - // (REQUIRED) Method is the HTTP method to allow for users of the temp URL. Valid values - // are "GET" and "POST". + // (REQUIRED) Method is the HTTP method to allow for users of the temp URL. + // Valid values are "GET" and "POST". Method HTTPMethod + // (REQUIRED) TTL is the number of seconds the temp URL should be active. TTL int + // (Optional) Split is the string on which to split the object URL. Since only // the object path is used in the hash, the object URL needs to be parsed. If // empty, the default OpenStack URL split point will be used ("/v1/"). diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/results.go index 0dcdbe2fb..dd7c7044d 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/results.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "io/ioutil" + "net/url" "strconv" "strings" "time" @@ -24,19 +25,21 @@ type Object struct { // Hash represents the MD5 checksum value of the object's content. Hash string `json:"hash"` - // LastModified is the time the object was last modified, represented - // as a string. + // LastModified is the time the object was last modified. LastModified time.Time `json:"-"` // Name is the unique name for the object. Name string `json:"name"` + + // Subdir denotes if the result contains a subdir. + Subdir string `json:"subdir"` } func (r *Object) UnmarshalJSON(b []byte) error { type tmp Object var s *struct { tmp - LastModified gophercloud.JSONRFC3339MilliNoZ `json:"last_modified"` + LastModified string `json:"last_modified"` } err := json.Unmarshal(b, &s) @@ -46,10 +49,18 @@ func (r *Object) UnmarshalJSON(b []byte) error { *r = Object(s.tmp) - r.LastModified = time.Time(s.LastModified) + if s.LastModified != "" { + t, err := time.Parse(gophercloud.RFC3339MilliNoZ, s.LastModified) + if err != nil { + t, err = time.Parse(gophercloud.RFC3339Milli, s.LastModified) + if err != nil { + return err + } + } + r.LastModified = t + } return nil - } // ObjectPage is a single page of objects that is returned from a call to the @@ -66,24 +77,19 @@ func (r ObjectPage) IsEmpty() (bool, error) { // LastMarker returns the last object name in a ListResult. func (r ObjectPage) LastMarker() (string, error) { - names, err := ExtractNames(r) - if err != nil { - return "", err - } - if len(names) == 0 { - return "", nil - } - return names[len(names)-1], nil + return extractLastMarker(r) } -// ExtractInfo is a function that takes a page of objects and returns their full information. +// ExtractInfo is a function that takes a page of objects and returns their +// full information. func ExtractInfo(r pagination.Page) ([]Object, error) { var s []Object err := (r.(ObjectPage)).ExtractInto(&s) return s, err } -// ExtractNames is a function that takes a page of objects and returns only their names. +// ExtractNames is a function that takes a page of objects and returns only +// their names. func ExtractNames(r pagination.Page) ([]string, error) { casted := r.(ObjectPage) ct := casted.Header.Get("Content-Type") @@ -96,7 +102,11 @@ func ExtractNames(r pagination.Page) ([]string, error) { names := make([]string, 0, len(parsed)) for _, object := range parsed { - names = append(names, object.Name) + if object.Subdir != "" { + names = append(names, object.Subdir) + } else { + names = append(names, object.Name) + } } return names, nil @@ -118,7 +128,8 @@ func ExtractNames(r pagination.Page) ([]string, error) { } } -// DownloadHeader represents the headers returned in the response from a Download request. +// DownloadHeader represents the headers returned in the response from a +// Download request. type DownloadHeader struct { AcceptRanges string `json:"Accept-Ranges"` ContentDisposition string `json:"Content-Disposition"` @@ -130,7 +141,7 @@ type DownloadHeader struct { ETag string `json:"Etag"` LastModified time.Time `json:"-"` ObjectManifest string `json:"X-Object-Manifest"` - StaticLargeObject bool `json:"X-Static-Large-Object"` + StaticLargeObject bool `json:"-"` TransID string `json:"X-Trans-Id"` } @@ -138,10 +149,11 @@ func (r *DownloadHeader) UnmarshalJSON(b []byte) error { type tmp DownloadHeader var s struct { tmp - ContentLength string `json:"Content-Length"` - Date gophercloud.JSONRFC1123 `json:"Date"` - DeleteAt gophercloud.JSONUnix `json:"X-Delete-At"` - LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"` + ContentLength string `json:"Content-Length"` + Date gophercloud.JSONRFC1123 `json:"Date"` + DeleteAt gophercloud.JSONUnix `json:"X-Delete-At"` + LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"` + StaticLargeObject interface{} `json:"X-Static-Large-Object"` } err := json.Unmarshal(b, &s) if err != nil { @@ -160,6 +172,15 @@ func (r *DownloadHeader) UnmarshalJSON(b []byte) error { } } + switch t := s.StaticLargeObject.(type) { + case string: + if t == "True" || t == "true" { + r.StaticLargeObject = true + } + case bool: + r.StaticLargeObject = t + } + r.Date = time.Time(s.Date) r.DeleteAt = time.Time(s.DeleteAt) r.LastModified = time.Time(s.LastModified) @@ -167,14 +188,14 @@ func (r *DownloadHeader) UnmarshalJSON(b []byte) error { return nil } -// DownloadResult is a *http.Response that is returned from a call to the Download function. +// DownloadResult is a *http.Response that is returned from a call to the +// Download function. type DownloadResult struct { gophercloud.HeaderResult Body io.ReadCloser } -// Extract will return a struct of headers returned from a call to Download. To obtain -// a map of headers, call the ExtractHeader method on the DownloadResult. +// Extract will return a struct of headers returned from a call to Download. func (r DownloadResult) Extract() (*DownloadHeader, error) { var s *DownloadHeader err := r.ExtractInto(&s) @@ -210,7 +231,7 @@ type GetHeader struct { ETag string `json:"Etag"` LastModified time.Time `json:"-"` ObjectManifest string `json:"X-Object-Manifest"` - StaticLargeObject bool `json:"X-Static-Large-Object"` + StaticLargeObject bool `json:"-"` TransID string `json:"X-Trans-Id"` } @@ -218,10 +239,11 @@ func (r *GetHeader) UnmarshalJSON(b []byte) error { type tmp GetHeader var s struct { tmp - ContentLength string `json:"Content-Length"` - Date gophercloud.JSONRFC1123 `json:"Date"` - DeleteAt gophercloud.JSONUnix `json:"X-Delete-At"` - LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"` + ContentLength string `json:"Content-Length"` + Date gophercloud.JSONRFC1123 `json:"Date"` + DeleteAt gophercloud.JSONUnix `json:"X-Delete-At"` + LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"` + StaticLargeObject interface{} `json:"X-Static-Large-Object"` } err := json.Unmarshal(b, &s) if err != nil { @@ -240,6 +262,15 @@ func (r *GetHeader) UnmarshalJSON(b []byte) error { } } + switch t := s.StaticLargeObject.(type) { + case string: + if t == "True" || t == "true" { + r.StaticLargeObject = true + } + case bool: + r.StaticLargeObject = t + } + r.Date = time.Time(s.Date) r.DeleteAt = time.Time(s.DeleteAt) r.LastModified = time.Time(s.LastModified) @@ -247,13 +278,13 @@ func (r *GetHeader) UnmarshalJSON(b []byte) error { return nil } -// GetResult is a *http.Response that is returned from a call to the Get function. +// GetResult is a *http.Response that is returned from a call to the Get +// function. type GetResult struct { gophercloud.HeaderResult } -// Extract will return a struct of headers returned from a call to Get. To obtain -// a map of headers, call the ExtractHeader method on the GetResult. +// Extract will return a struct of headers returned from a call to Get. func (r GetResult) Extract() (*GetHeader, error) { var s *GetHeader err := r.ExtractInto(&s) @@ -276,7 +307,8 @@ func (r GetResult) ExtractMetadata() (map[string]string, error) { return metadata, nil } -// CreateHeader represents the headers returned in the response from a Create request. +// CreateHeader represents the headers returned in the response from a +// Create request. type CreateHeader struct { ContentLength int64 `json:"-"` ContentType string `json:"Content-Type"` @@ -323,8 +355,7 @@ type CreateResult struct { gophercloud.HeaderResult } -// Extract will return a struct of headers returned from a call to Create. To obtain -// a map of headers, call the ExtractHeader method on the CreateResult. +// Extract will return a struct of headers returned from a call to Create. func (r CreateResult) Extract() (*CreateHeader, error) { //if r.Header.Get("ETag") != fmt.Sprintf("%x", localChecksum) { // return nil, ErrWrongChecksum{} @@ -334,7 +365,8 @@ func (r CreateResult) Extract() (*CreateHeader, error) { return s, err } -// UpdateHeader represents the headers returned in the response from a Update request. +// UpdateHeader represents the headers returned in the response from a +// Update request. type UpdateHeader struct { ContentLength int64 `json:"-"` ContentType string `json:"Content-Type"` @@ -376,17 +408,17 @@ type UpdateResult struct { gophercloud.HeaderResult } -// Extract will return a struct of headers returned from a call to Update. To obtain -// a map of headers, call the ExtractHeader method on the UpdateResult. +// Extract will return a struct of headers returned from a call to Update. func (r UpdateResult) Extract() (*UpdateHeader, error) { var s *UpdateHeader err := r.ExtractInto(&s) return s, err } -// DeleteHeader represents the headers returned in the response from a Delete request. +// DeleteHeader represents the headers returned in the response from a +// Delete request. type DeleteHeader struct { - ContentLength int64 `json:"Content-Length"` + ContentLength int64 `json:"-"` ContentType string `json:"Content-Type"` Date time.Time `json:"-"` TransID string `json:"X-Trans-Id"` @@ -426,15 +458,15 @@ type DeleteResult struct { gophercloud.HeaderResult } -// Extract will return a struct of headers returned from a call to Delete. To obtain -// a map of headers, call the ExtractHeader method on the DeleteResult. +// Extract will return a struct of headers returned from a call to Delete. func (r DeleteResult) Extract() (*DeleteHeader, error) { var s *DeleteHeader err := r.ExtractInto(&s) return s, err } -// CopyHeader represents the headers returned in the response from a Copy request. +// CopyHeader represents the headers returned in the response from a +// Copy request. type CopyHeader struct { ContentLength int64 `json:"-"` ContentType string `json:"Content-Type"` @@ -484,10 +516,65 @@ type CopyResult struct { gophercloud.HeaderResult } -// Extract will return a struct of headers returned from a call to Copy. To obtain -// a map of headers, call the ExtractHeader method on the CopyResult. +// Extract will return a struct of headers returned from a call to Copy. func (r CopyResult) Extract() (*CopyHeader, error) { var s *CopyHeader err := r.ExtractInto(&s) return s, err } + +// extractLastMarker is a function that takes a page of objects and returns the +// marker for the page. This can either be a subdir or the last object's name. +func extractLastMarker(r pagination.Page) (string, error) { + casted := r.(ObjectPage) + + // If a delimiter was requested, check if a subdir exists. + queryParams, err := url.ParseQuery(casted.URL.RawQuery) + if err != nil { + return "", err + } + + var delimeter bool + if v, ok := queryParams["delimiter"]; ok && len(v) > 0 { + delimeter = true + } + + ct := casted.Header.Get("Content-Type") + switch { + case strings.HasPrefix(ct, "application/json"): + parsed, err := ExtractInfo(r) + if err != nil { + return "", err + } + + var lastObject Object + if len(parsed) > 0 { + lastObject = parsed[len(parsed)-1] + } + + if !delimeter { + return lastObject.Name, nil + } + + if lastObject.Name != "" { + return lastObject.Name, nil + } + + return lastObject.Subdir, nil + case strings.HasPrefix(ct, "text/plain"): + names := make([]string, 0, 50) + + body := string(r.(ObjectPage).Body.([]uint8)) + for _, name := range strings.Split(body, "\n") { + if len(name) > 0 { + names = append(names, name) + } + } + + return names[len(names)-1], err + case strings.HasPrefix(ct, "text/html"): + return "", nil + default: + return "", fmt.Errorf("Cannot extract names from response with content-type: [%s]", ct) + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/swauth/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/swauth/doc.go new file mode 100644 index 000000000..989dc4ece --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/swauth/doc.go @@ -0,0 +1,16 @@ +/* +Package swauth implements Swift's built-in authentication. + +Example to Authenticate with swauth + + authOpts := swauth.AuthOpts{ + User: "project:user", + Key: "password", + } + + swiftClient, err := swauth.NewObjectStorageV1(providerClient, authOpts) + if err != nil { + panic(err) + } +*/ +package swauth diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/swauth/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/swauth/requests.go index e8589ae0c..29bdcbcf7 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/swauth/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/swauth/requests.go @@ -3,7 +3,6 @@ package swauth import "github.com/gophercloud/gophercloud" // AuthOptsBuilder describes struct types that can be accepted by the Auth call. -// The AuthOpts struct in this package does. type AuthOptsBuilder interface { ToAuthOptsMap() (map[string]string, error) } @@ -12,6 +11,7 @@ type AuthOptsBuilder interface { type AuthOpts struct { // User is an Swauth-based username in username:tenant format. User string `h:"X-Auth-User" required:"true"` + // Key is a secret/password to authenticate the User with. Key string `h:"X-Auth-Key" required:"true"` } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/swauth/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/swauth/results.go index 294c43c07..f442f4725 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/swauth/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/swauth/results.go @@ -4,8 +4,8 @@ import ( "github.com/gophercloud/gophercloud" ) -// GetAuthResult temporarily contains the response from a Swauth -// authentication call. +// GetAuthResult contains the response from the Auth request. Call its Extract +// method to interpret it as an AuthResult. type GetAuthResult struct { gophercloud.HeaderResult } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/errors/errors.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/errors/errors.go new file mode 100644 index 000000000..cd88a8ac1 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/errors/errors.go @@ -0,0 +1,44 @@ +package errors + +import ( + "encoding/json" + "fmt" + + "github.com/gophercloud/gophercloud" +) + +type ManilaError struct { + Code int `json:"code"` + Message string `json:"message"` + Details string `json:"details"` +} + +type ErrorDetails map[string]ManilaError + +// error types from provider_client.go +func ExtractErrorInto(rawError error, errorDetails *ErrorDetails) (err error) { + switch e := rawError.(type) { + case gophercloud.ErrDefault400: + err = json.Unmarshal(e.ErrUnexpectedResponseCode.Body, errorDetails) + case gophercloud.ErrDefault401: + err = json.Unmarshal(e.ErrUnexpectedResponseCode.Body, errorDetails) + case gophercloud.ErrDefault403: + err = json.Unmarshal(e.ErrUnexpectedResponseCode.Body, errorDetails) + case gophercloud.ErrDefault404: + err = json.Unmarshal(e.ErrUnexpectedResponseCode.Body, errorDetails) + case gophercloud.ErrDefault405: + err = json.Unmarshal(e.ErrUnexpectedResponseCode.Body, errorDetails) + case gophercloud.ErrDefault408: + err = json.Unmarshal(e.ErrUnexpectedResponseCode.Body, errorDetails) + case gophercloud.ErrDefault429: + err = json.Unmarshal(e.ErrUnexpectedResponseCode.Body, errorDetails) + case gophercloud.ErrDefault500: + err = json.Unmarshal(e.ErrUnexpectedResponseCode.Body, errorDetails) + case gophercloud.ErrDefault503: + err = json.Unmarshal(e.ErrUnexpectedResponseCode.Body, errorDetails) + default: + err = fmt.Errorf("Unable to extract detailed error message") + } + + return err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/requests.go new file mode 100644 index 000000000..44e0d94bd --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/requests.go @@ -0,0 +1,72 @@ +package messages + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// Delete will delete the existing Message with the provided ID. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} + +// ListOptsBuilder allows extensions to add additional parameters to the List +// request. +type ListOptsBuilder interface { + ToMessageListQuery() (string, error) +} + +// ListOpts holds options for listing Messages. It is passed to the +// messages.List function. +type ListOpts struct { + // The message ID + ID string `q:"id"` + // The ID of the action during which the message was created + ActionID string `q:"action_id"` + // The ID of the message detail + DetailID string `q:"detail_id"` + // The message level + MessageLevel string `q:"message_level"` + // The UUID of the request during which the message was created + RequestID string `q:"request_id"` + // The UUID of the resource for which the message was created + ResourceID string `q:"resource_id"` + // The type of the resource for which the message was created + ResourceType string `q:"resource_type"` + // The key to sort a list of messages + SortKey string `q:"sort_key"` + // The key to sort a list of messages + SortDir string `q:"sort_dir"` + // The maximum number of messages to return + Limit int `q:"limit"` +} + +// ToMessageListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToMessageListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns Messages 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.ToMessageListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return MessagePage{pagination.SinglePageBase(r)} + }) +} + +// Get retrieves the Message with the provided ID. To extract the Message +// 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 +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/results.go new file mode 100644 index 000000000..9c48ea07b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/results.go @@ -0,0 +1,99 @@ +package messages + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// Message contains all the information associated with an OpenStack +// Message. +type Message struct { + // The message ID + ID string `json:"id"` + // The UUID of the project where the message was created + ProjectID string `json:"project_id"` + // The ID of the action during which the message was created + ActionID string `json:"action_id"` + // The ID of the message detail + DetailID string `json:"detail_id"` + // The message level + MessageLevel string `json:"message_level"` + // The UUID of the request during which the message was created + RequestID string `json:"request_id"` + // The UUID of the resource for which the message was created + ResourceID string `json:"resource_id"` + // The type of the resource for which the message was created + ResourceType string `json:"resource_type"` + // The message text + UserMessage string `json:"user_message"` + // The date and time stamp when the message was created + CreatedAt time.Time `json:"-"` + // The date and time stamp when the message will expire + ExpiresAt time.Time `json:"-"` +} + +func (r *Message) UnmarshalJSON(b []byte) error { + type tmp Message + var s struct { + tmp + CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"` + ExpiresAt gophercloud.JSONRFC3339MilliNoZ `json:"expires_at"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Message(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + r.ExpiresAt = time.Time(s.ExpiresAt) + + return nil +} + +type commonResult struct { + gophercloud.Result +} + +// MessagePage is a pagination.pager that is returned from a call to the List function. +type MessagePage struct { + pagination.SinglePageBase +} + +// IsEmpty returns true if a ListResult contains no Messages. +func (r MessagePage) IsEmpty() (bool, error) { + messages, err := ExtractMessages(r) + return len(messages) == 0, err +} + +// ExtractMessages extracts and returns Messages. It is used while +// iterating over a messages.List call. +func ExtractMessages(r pagination.Page) ([]Message, error) { + var s struct { + Messages []Message `json:"messages"` + } + err := (r.(MessagePage)).ExtractInto(&s) + return s.Messages, err +} + +// Extract will get the Message object out of the commonResult object. +func (r commonResult) Extract() (*Message, error) { + var s struct { + Message *Message `json:"message"` + } + err := r.ExtractInto(&s) + return s.Message, err +} + +// DeleteResult contains the response body and error from a Delete request. +type DeleteResult struct { + gophercloud.ErrResult +} + +// GetResult contains the response body and error from a Get request. +type GetResult struct { + commonResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/urls.go new file mode 100644 index 000000000..7c2c54a87 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages/urls.go @@ -0,0 +1,15 @@ +package messages + +import "github.com/gophercloud/gophercloud" + +func listURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("messages") +} + +func getURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("messages", id) +} + +func deleteURL(c *gophercloud.ServiceClient, id string) string { + return getURL(c, id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/securityservices/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/securityservices/requests.go new file mode 100644 index 000000000..8bfec43cf --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/securityservices/requests.go @@ -0,0 +1,182 @@ +package securityservices + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type SecurityServiceType string + +// Valid security service types +const ( + LDAP SecurityServiceType = "ldap" + Kerberos SecurityServiceType = "kerberos" + ActiveDirectory SecurityServiceType = "active_directory" +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToSecurityServiceCreateMap() (map[string]interface{}, error) +} + +// CreateOpts contains options for creating a SecurityService. This object is +// passed to the securityservices.Create function. For more information about +// these parameters, see the SecurityService object. +type CreateOpts struct { + // The security service type. A valid value is ldap, kerberos, or active_directory + Type SecurityServiceType `json:"type" required:"true"` + // The security service name + Name string `json:"name,omitempty"` + // The security service description + Description string `json:"description,omitempty"` + // The DNS IP address that is used inside the tenant network + DNSIP string `json:"dns_ip,omitempty"` + // The security service organizational unit (OU). Minimum supported microversion for OU is 2.44. + OU string `json:"ou,omitempty"` + // The security service user or group name that is used by the tenant + User string `json:"user,omitempty"` + // The user password, if you specify a user + Password string `json:"password,omitempty"` + // The security service domain + Domain string `json:"domain,omitempty"` + // The security service host name or IP address + Server string `json:"server,omitempty"` +} + +// ToSecurityServicesCreateMap assembles a request body based on the contents of a +// CreateOpts. +func (opts CreateOpts) ToSecurityServiceCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "security_service") +} + +// Create will create a new SecurityService based on the values in CreateOpts. To +// extract the SecurityService object from the response, call the Extract method +// on the CreateResult. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToSecurityServiceCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Delete will delete the existing SecurityService with the provided ID. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} + +// ListOptsBuilder allows extensions to add additional parameters to the List +// request. +type ListOptsBuilder interface { + ToSecurityServiceListQuery() (string, error) +} + +// ListOpts holds options for listing SecurityServices. It is passed to the +// securityservices.List function. +type ListOpts struct { + // admin-only option. Set it to true to see all tenant security services. + AllTenants bool `q:"all_tenants"` + // The security service ID + ID string `q:"id"` + // The security service domain + Domain string `q:"domain"` + // The security service type. A valid value is ldap, kerberos, or active_directory + Type SecurityServiceType `q:"type"` + // The security service name + Name string `q:"name"` + // The DNS IP address that is used inside the tenant network + DNSIP string `q:"dns_ip"` + // The security service organizational unit (OU). Minimum supported microversion for OU is 2.44. + OU string `q:"ou"` + // The security service user or group name that is used by the tenant + User string `q:"user"` + // The security service host name or IP address + Server string `q:"server"` + // The ID of the share network using security services + ShareNetworkID string `q:"share_network_id"` +} + +// ToSecurityServiceListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToSecurityServiceListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns SecurityServices 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.ToSecurityServiceListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return SecurityServicePage{pagination.SinglePageBase(r)} + }) +} + +// Get retrieves the SecurityService with the provided ID. To extract the SecurityService +// 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 +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToSecurityServiceUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts contain options for updating an existing SecurityService. This object is passed +// to the securityservices.Update function. For more information about the parameters, see +// the SecurityService object. +type UpdateOpts struct { + // The security service name + Name *string `json:"name"` + // The security service description + Description *string `json:"description,omitempty"` + // The security service type. A valid value is ldap, kerberos, or active_directory + Type string `json:"type,omitempty"` + // The DNS IP address that is used inside the tenant network + DNSIP *string `json:"dns_ip,omitempty"` + // The security service organizational unit (OU). Minimum supported microversion for OU is 2.44. + OU *string `json:"ou,omitempty"` + // The security service user or group name that is used by the tenant + User *string `json:"user,omitempty"` + // The user password, if you specify a user + Password *string `json:"password,omitempty"` + // The security service domain + Domain *string `json:"domain,omitempty"` + // The security service host name or IP address + Server *string `json:"server,omitempty"` +} + +// ToSecurityServiceUpdateMap assembles a request body based on the contents of an +// UpdateOpts. +func (opts UpdateOpts) ToSecurityServiceUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "security_service") +} + +// Update will update the SecurityService with provided information. To extract the updated +// SecurityService from the response, call the Extract method on the UpdateResult. +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToSecurityServiceUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/securityservices/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/securityservices/results.go new file mode 100644 index 000000000..355f7c76a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/securityservices/results.go @@ -0,0 +1,115 @@ +package securityservices + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// SecurityService contains all the information associated with an OpenStack +// SecurityService. +type SecurityService struct { + // The security service ID + ID string `json:"id"` + // The UUID of the project where the security service was created + ProjectID string `json:"project_id"` + // The security service domain + Domain string `json:"domain"` + // The security service status + Status string `json:"status"` + // The security service type. A valid value is ldap, kerberos, or active_directory + Type string `json:"type"` + // The security service name + Name string `json:"name"` + // The security service description + Description string `json:"description"` + // The DNS IP address that is used inside the tenant network + DNSIP string `json:"dns_ip"` + // The security service organizational unit (OU) + OU string `json:"ou"` + // The security service user or group name that is used by the tenant + User string `json:"user"` + // The user password, if you specify a user + Password string `json:"password"` + // The security service host name or IP address + Server string `json:"server"` + // The date and time stamp when the security service was created + CreatedAt time.Time `json:"-"` + // The date and time stamp when the security service was updated + UpdatedAt time.Time `json:"-"` +} + +func (r *SecurityService) UnmarshalJSON(b []byte) error { + type tmp SecurityService + 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 = SecurityService(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + r.UpdatedAt = time.Time(s.UpdatedAt) + + return nil +} + +type commonResult struct { + gophercloud.Result +} + +// SecurityServicePage is a pagination.pager that is returned from a call to the List function. +type SecurityServicePage struct { + pagination.SinglePageBase +} + +// IsEmpty returns true if a ListResult contains no SecurityServices. +func (r SecurityServicePage) IsEmpty() (bool, error) { + securityServices, err := ExtractSecurityServices(r) + return len(securityServices) == 0, err +} + +// ExtractSecurityServices extracts and returns SecurityServices. It is used while +// iterating over a securityservices.List call. +func ExtractSecurityServices(r pagination.Page) ([]SecurityService, error) { + var s struct { + SecurityServices []SecurityService `json:"security_services"` + } + err := (r.(SecurityServicePage)).ExtractInto(&s) + return s.SecurityServices, err +} + +// Extract will get the SecurityService object out of the commonResult object. +func (r commonResult) Extract() (*SecurityService, error) { + var s struct { + SecurityService *SecurityService `json:"security_service"` + } + err := r.ExtractInto(&s) + return s.SecurityService, err +} + +// CreateResult contains the response body and error from a Create request. +type CreateResult struct { + commonResult +} + +// DeleteResult contains the response body and error from a Delete request. +type DeleteResult struct { + gophercloud.ErrResult +} + +// 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 +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/securityservices/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/securityservices/urls.go new file mode 100644 index 000000000..c19b1f106 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/securityservices/urls.go @@ -0,0 +1,23 @@ +package securityservices + +import "github.com/gophercloud/gophercloud" + +func createURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("security-services") +} + +func deleteURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("security-services", id) +} + +func listURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("security-services", "detail") +} + +func getURL(c *gophercloud.ServiceClient, id string) string { + return deleteURL(c, id) +} + +func updateURL(c *gophercloud.ServiceClient, id string) string { + return deleteURL(c, id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharenetworks/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharenetworks/requests.go new file mode 100644 index 000000000..15e664ec3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharenetworks/requests.go @@ -0,0 +1,233 @@ +package sharenetworks + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToShareNetworkCreateMap() (map[string]interface{}, error) +} + +// CreateOpts contains options for creating a ShareNetwork. This object is +// passed to the sharenetworks.Create function. For more information about +// these parameters, see the ShareNetwork object. +type CreateOpts struct { + // The UUID of the Neutron network to set up for share servers + NeutronNetID string `json:"neutron_net_id,omitempty"` + // The UUID of the Neutron subnet to set up for share servers + NeutronSubnetID string `json:"neutron_subnet_id,omitempty"` + // The UUID of the nova network to set up for share servers + NovaNetID string `json:"nova_net_id,omitempty"` + // The share network name + Name string `json:"name"` + // The share network description + Description string `json:"description"` +} + +// ToShareNetworkCreateMap assembles a request body based on the contents of a +// CreateOpts. +func (opts CreateOpts) ToShareNetworkCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "share_network") +} + +// Create will create a new ShareNetwork based on the values in CreateOpts. To +// extract the ShareNetwork object from the response, call the Extract method +// on the CreateResult. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToShareNetworkCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 202}, + }) + return +} + +// Delete will delete the existing ShareNetwork with the provided ID. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} + +// ListOptsBuilder allows extensions to add additional parameters to the List +// request. +type ListOptsBuilder interface { + ToShareNetworkListQuery() (string, error) +} + +// ListOpts holds options for listing ShareNetworks. It is passed to the +// sharenetworks.List function. +type ListOpts struct { + // admin-only option. Set it to true to see all tenant share networks. + AllTenants bool `q:"all_tenants"` + // The UUID of the project where the share network was created + ProjectID string `q:"project_id"` + // The neutron network ID + NeutronNetID string `q:"neutron_net_id"` + // The neutron subnet ID + NeutronSubnetID string `q:"neutron_subnet_id"` + // The nova network ID + NovaNetID string `q:"nova_net_id"` + // The network type. A valid value is VLAN, VXLAN, GRE or flat + NetworkType string `q:"network_type"` + // The Share Network name + Name string `q:"name"` + // The Share Network description + Description string `q:"description"` + // The Share Network IP version + IPVersion gophercloud.IPVersion `q:"ip_version"` + // The Share Network segmentation ID + SegmentationID int `q:"segmentation_id"` + // List all share networks created after the given date + CreatedSince string `q:"created_since"` + // List all share networks created before the given date + CreatedBefore string `q:"created_before"` + // Limit specifies the page size. + Limit int `q:"limit"` + // Limit specifies the page number. + Offset int `q:"offset"` +} + +// ToShareNetworkListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToShareNetworkListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// ListDetail returns ShareNetworks optionally limited by the conditions provided in ListOpts. +func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listDetailURL(client) + if opts != nil { + query, err := opts.ToShareNetworkListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + p := ShareNetworkPage{pagination.MarkerPageBase{PageResult: r}} + p.MarkerPageBase.Owner = p + return p + }) +} + +// Get retrieves the ShareNetwork with the provided ID. To extract the ShareNetwork +// 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 +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToShareNetworkUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts contain options for updating an existing ShareNetwork. This object is passed +// to the sharenetworks.Update function. For more information about the parameters, see +// the ShareNetwork object. +type UpdateOpts struct { + // The share network name + Name *string `json:"name,omitempty"` + // The share network description + Description *string `json:"description,omitempty"` + // The UUID of the Neutron network to set up for share servers + NeutronNetID string `json:"neutron_net_id,omitempty"` + // The UUID of the Neutron subnet to set up for share servers + NeutronSubnetID string `json:"neutron_subnet_id,omitempty"` + // The UUID of the nova network to set up for share servers + NovaNetID string `json:"nova_net_id,omitempty"` +} + +// ToShareNetworkUpdateMap assembles a request body based on the contents of an +// UpdateOpts. +func (opts UpdateOpts) ToShareNetworkUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "share_network") +} + +// Update will update the ShareNetwork with provided information. To extract the updated +// ShareNetwork from the response, call the Extract method on the UpdateResult. +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToShareNetworkUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// AddSecurityServiceOptsBuilder allows extensions to add additional parameters to the +// AddSecurityService request. +type AddSecurityServiceOptsBuilder interface { + ToShareNetworkAddSecurityServiceMap() (map[string]interface{}, error) +} + +// AddSecurityServiceOpts contain options for adding a security service to an +// existing ShareNetwork. This object is passed to the sharenetworks.AddSecurityService +// function. For more information about the parameters, see the ShareNetwork object. +type AddSecurityServiceOpts struct { + SecurityServiceID string `json:"security_service_id"` +} + +// ToShareNetworkAddSecurityServiceMap assembles a request body based on the contents of an +// AddSecurityServiceOpts. +func (opts AddSecurityServiceOpts) ToShareNetworkAddSecurityServiceMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "add_security_service") +} + +// AddSecurityService will add the security service to a ShareNetwork. To extract the updated +// ShareNetwork from the response, call the Extract method on the UpdateResult. +func AddSecurityService(client *gophercloud.ServiceClient, id string, opts AddSecurityServiceOptsBuilder) (r UpdateResult) { + b, err := opts.ToShareNetworkAddSecurityServiceMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(addSecurityServiceURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// RemoveSecurityServiceOptsBuilder allows extensions to add additional parameters to the +// RemoveSecurityService request. +type RemoveSecurityServiceOptsBuilder interface { + ToShareNetworkRemoveSecurityServiceMap() (map[string]interface{}, error) +} + +// RemoveSecurityServiceOpts contain options for removing a security service from an +// existing ShareNetwork. This object is passed to the sharenetworks.RemoveSecurityService +// function. For more information about the parameters, see the ShareNetwork object. +type RemoveSecurityServiceOpts struct { + SecurityServiceID string `json:"security_service_id"` +} + +// ToShareNetworkRemoveSecurityServiceMap assembles a request body based on the contents of an +// RemoveSecurityServiceOpts. +func (opts RemoveSecurityServiceOpts) ToShareNetworkRemoveSecurityServiceMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "remove_security_service") +} + +// RemoveSecurityService will remove the security service from a ShareNetwork. To extract the updated +// ShareNetwork from the response, call the Extract method on the UpdateResult. +func RemoveSecurityService(client *gophercloud.ServiceClient, id string, opts RemoveSecurityServiceOptsBuilder) (r UpdateResult) { + b, err := opts.ToShareNetworkRemoveSecurityServiceMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(removeSecurityServiceURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharenetworks/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharenetworks/results.go new file mode 100644 index 000000000..fdb725695 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharenetworks/results.go @@ -0,0 +1,182 @@ +package sharenetworks + +import ( + "encoding/json" + "net/url" + "strconv" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ShareNetwork contains all the information associated with an OpenStack +// ShareNetwork. +type ShareNetwork struct { + // The Share Network ID + ID string `json:"id"` + // The UUID of the project where the share network was created + ProjectID string `json:"project_id"` + // The neutron network ID + NeutronNetID string `json:"neutron_net_id"` + // The neutron subnet ID + NeutronSubnetID string `json:"neutron_subnet_id"` + // The nova network ID + NovaNetID string `json:"nova_net_id"` + // The network type. A valid value is VLAN, VXLAN, GRE or flat + NetworkType string `json:"network_type"` + // The segmentation ID + SegmentationID int `json:"segmentation_id"` + // The IP block from which to allocate the network, in CIDR notation + CIDR string `json:"cidr"` + // The IP version of the network. A valid value is 4 or 6 + IPVersion int `json:"ip_version"` + // The Share Network name + Name string `json:"name"` + // The Share Network description + Description string `json:"description"` + // The date and time stamp when the Share Network was created + CreatedAt time.Time `json:"-"` + // The date and time stamp when the Share Network was updated + UpdatedAt time.Time `json:"-"` +} + +func (r *ShareNetwork) UnmarshalJSON(b []byte) error { + type tmp ShareNetwork + 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 = ShareNetwork(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + r.UpdatedAt = time.Time(s.UpdatedAt) + + return nil +} + +type commonResult struct { + gophercloud.Result +} + +// ShareNetworkPage is a pagination.pager that is returned from a call to the List function. +type ShareNetworkPage struct { + pagination.MarkerPageBase +} + +// NextPageURL generates the URL for the page of results after this one. +func (r ShareNetworkPage) NextPageURL() (string, error) { + currentURL := r.URL + mark, err := r.Owner.LastMarker() + if err != nil { + return "", err + } + + q := currentURL.Query() + q.Set("offset", mark) + currentURL.RawQuery = q.Encode() + return currentURL.String(), nil +} + +// LastMarker returns the last offset in a ListResult. +func (r ShareNetworkPage) LastMarker() (string, error) { + maxInt := strconv.Itoa(int(^uint(0) >> 1)) + shareNetworks, err := ExtractShareNetworks(r) + if err != nil { + return maxInt, err + } + if len(shareNetworks) == 0 { + return maxInt, nil + } + + u, err := url.Parse(r.URL.String()) + if err != nil { + return maxInt, err + } + queryParams := u.Query() + offset := queryParams.Get("offset") + limit := queryParams.Get("limit") + + // Limit is not present, only one page required + if limit == "" { + return maxInt, nil + } + + iOffset := 0 + if offset != "" { + iOffset, err = strconv.Atoi(offset) + if err != nil { + return maxInt, err + } + } + iLimit, err := strconv.Atoi(limit) + if err != nil { + return maxInt, err + } + iOffset = iOffset + iLimit + offset = strconv.Itoa(iOffset) + + return offset, nil +} + +// IsEmpty satisifies the IsEmpty method of the Page interface +func (r ShareNetworkPage) IsEmpty() (bool, error) { + shareNetworks, err := ExtractShareNetworks(r) + return len(shareNetworks) == 0, err +} + +// ExtractShareNetworks extracts and returns ShareNetworks. It is used while +// iterating over a sharenetworks.List call. +func ExtractShareNetworks(r pagination.Page) ([]ShareNetwork, error) { + var s struct { + ShareNetworks []ShareNetwork `json:"share_networks"` + } + err := (r.(ShareNetworkPage)).ExtractInto(&s) + return s.ShareNetworks, err +} + +// Extract will get the ShareNetwork object out of the commonResult object. +func (r commonResult) Extract() (*ShareNetwork, error) { + var s struct { + ShareNetwork *ShareNetwork `json:"share_network"` + } + err := r.ExtractInto(&s) + return s.ShareNetwork, err +} + +// CreateResult contains the response body and error from a Create request. +type CreateResult struct { + commonResult +} + +// DeleteResult contains the response body and error from a Delete request. +type DeleteResult struct { + gophercloud.ErrResult +} + +// 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 +} + +// AddSecurityServiceResult contains the response body and error from a security +// service addition request. +type AddSecurityServiceResult struct { + commonResult +} + +// RemoveSecurityServiceResult contains the response body and error from a security +// service removal request. +type RemoveSecurityServiceResult struct { + commonResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharenetworks/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharenetworks/urls.go new file mode 100644 index 000000000..667bd2a52 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharenetworks/urls.go @@ -0,0 +1,31 @@ +package sharenetworks + +import "github.com/gophercloud/gophercloud" + +func createURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("share-networks") +} + +func deleteURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("share-networks", id) +} + +func listDetailURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("share-networks", "detail") +} + +func getURL(c *gophercloud.ServiceClient, id string) string { + return deleteURL(c, id) +} + +func updateURL(c *gophercloud.ServiceClient, id string) string { + return deleteURL(c, id) +} + +func addSecurityServiceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("share-networks", id, "action") +} + +func removeSecurityServiceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("share-networks", id, "action") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/requests.go new file mode 100644 index 000000000..2028b4831 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/requests.go @@ -0,0 +1,381 @@ +package shares + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToShareCreateMap() (map[string]interface{}, error) +} + +// CreateOpts contains the options for create a Share. This object is +// passed to shares.Create(). For more information about these parameters, +// please refer to the Share object, or the shared file systems API v2 +// documentation +type CreateOpts struct { + // Defines the share protocol to use + ShareProto string `json:"share_proto" required:"true"` + // Size in GB + Size int `json:"size" required:"true"` + // Defines the share name + Name string `json:"name,omitempty"` + // Share description + Description string `json:"description,omitempty"` + // DisplayName is equivalent to Name. The API supports using both + // This is an inherited attribute from the block storage API + DisplayName string `json:"display_name,omitempty"` + // DisplayDescription is equivalent to Description. The API supports using both + // This is an inherited attribute from the block storage API + DisplayDescription string `json:"display_description,omitempty"` + // ShareType defines the sharetype. If omitted, a default share type is used + ShareType string `json:"share_type,omitempty"` + // VolumeType is deprecated but supported. Either ShareType or VolumeType can be used + VolumeType string `json:"volume_type,omitempty"` + // The UUID from which to create a share + SnapshotID string `json:"snapshot_id,omitempty"` + // Determines whether or not the share is public + IsPublic *bool `json:"is_public,omitempty"` + // Key value pairs of user defined metadata + Metadata map[string]string `json:"metadata,omitempty"` + // The UUID of the share network to which the share belongs to + ShareNetworkID string `json:"share_network_id,omitempty"` + // The UUID of the consistency group to which the share belongs to + ConsistencyGroupID string `json:"consistency_group_id,omitempty"` + // The availability zone of the share + AvailabilityZone string `json:"availability_zone,omitempty"` +} + +// ToShareCreateMap assembles a request body based on the contents of a +// CreateOpts. +func (opts CreateOpts) ToShareCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "share") +} + +// Create will create a new Share based on the values in CreateOpts. To extract +// the Share object from the response, call the Extract method on the +// CreateResult. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToShareCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201}, + }) + return +} + +// ListOpts holds options for listing Shares. It is passed to the +// shares.List function. +type ListOpts struct { + // (Admin only). Defines whether to list the requested resources for all projects. + AllTenants bool `q:"all_tenants"` + // The share name. + Name string `q:"name"` + // Filters by a share status. + Status string `q:"status"` + // The UUID of the share server. + ShareServerID string `q:"share_server_id"` + // One or more metadata key and value pairs as a dictionary of strings. + Metadata map[string]string `q:"metadata"` + // The extra specifications for the share type. + ExtraSpecs map[string]string `q:"extra_specs"` + // The UUID of the share type. + ShareTypeID string `q:"share_type_id"` + // The maximum number of shares to return. + Limit int `q:"limit"` + // The offset to define start point of share or share group listing. + Offset int `q:"offset"` + // The key to sort a list of shares. + SortKey string `q:"sort_key"` + // The direction to sort a list of shares. + SortDir string `q:"sort_dir"` + // The UUID of the share’s base snapshot to filter the request based on. + SnapshotID string `q:"snapshot_id"` + // The share host name. + Host string `q:"host"` + // The share network ID. + ShareNetworkID string `q:"share_network_id"` + // The UUID of the project in which the share was created. Useful with all_tenants parameter. + ProjectID string `q:"project_id"` + // The level of visibility for the share. + IsPublic *bool `q:"is_public"` + // The UUID of a share group to filter resource. + ShareGroupID string `q:"share_group_id"` + // The export location UUID that can be used to filter shares or share instances. + ExportLocationID string `q:"export_location_id"` + // The export location path that can be used to filter shares or share instances. + ExportLocationPath string `q:"export_location_path"` + // The name pattern that can be used to filter shares, share snapshots, share networks or share groups. + NamePattern string `q:"name~"` + // The description pattern that can be used to filter shares, share snapshots, share networks or share groups. + DescriptionPattern string `q:"description~"` + // Whether to show count in API response or not, default is False. + WithCount bool `q:"with_count"` + // DisplayName is equivalent to Name. The API supports using both + // This is an inherited attribute from the block storage API + DisplayName string `q:"display_name"` + // Equivalent to NamePattern. + DisplayNamePattern string `q:"display_name~"` + // VolumeTypeID is deprecated but supported. Either ShareTypeID or VolumeTypeID can be used + VolumeTypeID string `q:"volume_type_id"` + // The UUID of the share group snapshot. + ShareGroupSnapshotID string `q:"share_group_snapshot_id"` + // DisplayDescription is equivalent to Description. The API supports using both + // This is an inherited attribute from the block storage API + DisplayDescription string `q:"display_description"` + // Equivalent to DescriptionPattern + DisplayDescriptionPattern string `q:"display_description~"` +} + +// ListOptsBuilder allows extensions to add additional parameters to the List +// request. +type ListOptsBuilder interface { + ToShareListQuery() (string, error) +} + +// ToShareListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToShareListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// ListDetail returns []Share optionally limited by the conditions provided in ListOpts. +func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listDetailURL(client) + if opts != nil { + query, err := opts.ToShareListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + p := SharePage{pagination.MarkerPageBase{PageResult: r}} + p.MarkerPageBase.Owner = p + return p + }) +} + +// Delete will delete an existing Share with the given UUID. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} + +// Get will get a single share with given UUID +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + return +} + +// GetExportLocations will get shareID's export locations. +// Client must have Microversion set; minimum supported microversion for GetExportLocations is 2.14. +func GetExportLocations(client *gophercloud.ServiceClient, id string) (r GetExportLocationsResult) { + _, r.Err = client.Get(getExportLocationsURL(client, id), &r.Body, nil) + return +} + +// GrantAccessOptsBuilder allows extensions to add additional parameters to the +// GrantAccess request. +type GrantAccessOptsBuilder interface { + ToGrantAccessMap() (map[string]interface{}, error) +} + +// GrantAccessOpts contains the options for creation of an GrantAccess request. +// For more information about these parameters, please, refer to the shared file systems API v2, +// Share Actions, Grant Access documentation +type GrantAccessOpts struct { + // The access rule type that can be "ip", "cert" or "user". + AccessType string `json:"access_type"` + // The value that defines the access that can be a valid format of IP, cert or user. + AccessTo string `json:"access_to"` + // The access level to the share is either "rw" or "ro". + AccessLevel string `json:"access_level"` +} + +// ToGrantAccessMap assembles a request body based on the contents of a +// GrantAccessOpts. +func (opts GrantAccessOpts) ToGrantAccessMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "allow_access") +} + +// GrantAccess will grant access to a Share based on the values in GrantAccessOpts. To extract +// the GrantAccess object from the response, call the Extract method on the GrantAccessResult. +// Client must have Microversion set; minimum supported microversion for GrantAccess is 2.7. +func GrantAccess(client *gophercloud.ServiceClient, id string, opts GrantAccessOptsBuilder) (r GrantAccessResult) { + b, err := opts.ToGrantAccessMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(grantAccessURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// RevokeAccessOptsBuilder allows extensions to add additional parameters to the +// RevokeAccess request. +type RevokeAccessOptsBuilder interface { + ToRevokeAccessMap() (map[string]interface{}, error) +} + +// RevokeAccessOpts contains the options for creation of a RevokeAccess request. +// For more information about these parameters, please, refer to the shared file systems API v2, +// Share Actions, Revoke Access documentation +type RevokeAccessOpts struct { + AccessID string `json:"access_id"` +} + +// ToRevokeAccessMap assembles a request body based on the contents of a +// RevokeAccessOpts. +func (opts RevokeAccessOpts) ToRevokeAccessMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "deny_access") +} + +// RevokeAccess will revoke an existing access to a Share based on the values in RevokeAccessOpts. +// RevokeAccessResult contains only the error. To extract it, call the ExtractErr method on +// the RevokeAccessResult. Client must have Microversion set; minimum supported microversion +// for RevokeAccess is 2.7. +func RevokeAccess(client *gophercloud.ServiceClient, id string, opts RevokeAccessOptsBuilder) (r RevokeAccessResult) { + b, err := opts.ToRevokeAccessMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Post(revokeAccessURL(client, id), b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{200, 202}, + }) + + return +} + +// ListAccessRights lists all access rules assigned to a Share based on its id. To extract +// the AccessRight slice from the response, call the Extract method on the ListAccessRightsResult. +// Client must have Microversion set; minimum supported microversion for ListAccessRights is 2.7. +func ListAccessRights(client *gophercloud.ServiceClient, id string) (r ListAccessRightsResult) { + requestBody := map[string]interface{}{"access_list": nil} + _, r.Err = client.Post(listAccessRightsURL(client, id), requestBody, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// ExtendOptsBuilder allows extensions to add additional parameters to the +// Extend request. +type ExtendOptsBuilder interface { + ToShareExtendMap() (map[string]interface{}, error) +} + +// ExtendOpts contains options for extending a Share. +// For more information about these parameters, please, refer to the shared file systems API v2, +// Share Actions, Extend share documentation +type ExtendOpts struct { + // New size in GBs. + NewSize int `json:"new_size"` +} + +// ToShareExtendMap assembles a request body based on the contents of a +// ExtendOpts. +func (opts ExtendOpts) ToShareExtendMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "extend") +} + +// Extend will extend the capacity of an existing share. ExtendResult contains only the error. +// To extract it, call the ExtractErr method on the ExtendResult. +// Client must have Microversion set; minimum supported microversion for Extend is 2.7. +func Extend(client *gophercloud.ServiceClient, id string, opts ExtendOptsBuilder) (r ExtendResult) { + b, err := opts.ToShareExtendMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Post(extendURL(client, id), b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + + return +} + +// ShrinkOptsBuilder allows extensions to add additional parameters to the +// Shrink request. +type ShrinkOptsBuilder interface { + ToShareShrinkMap() (map[string]interface{}, error) +} + +// ShrinkOpts contains options for shrinking a Share. +// For more information about these parameters, please, refer to the shared file systems API v2, +// Share Actions, Shrink share documentation +type ShrinkOpts struct { + // New size in GBs. + NewSize int `json:"new_size"` +} + +// ToShareShrinkMap assembles a request body based on the contents of a +// ShrinkOpts. +func (opts ShrinkOpts) ToShareShrinkMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "shrink") +} + +// Shrink will shrink the capacity of an existing share. ShrinkResult contains only the error. +// To extract it, call the ExtractErr method on the ShrinkResult. +// Client must have Microversion set; minimum supported microversion for Shrink is 2.7. +func Shrink(client *gophercloud.ServiceClient, id string, opts ShrinkOptsBuilder) (r ShrinkResult) { + b, err := opts.ToShareShrinkMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Post(shrinkURL(client, id), b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToShareUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts contain options for updating an existing Share. This object is passed +// to the share.Update function. For more information about the parameters, see +// the Share object. +type UpdateOpts struct { + // Share name. Manila share update logic doesn't have a "name" alias. + DisplayName *string `json:"display_name,omitempty"` + // Share description. Manila share update logic doesn't have a "description" alias. + DisplayDescription *string `json:"display_description,omitempty"` + // Determines whether or not the share is public + IsPublic *bool `json:"is_public,omitempty"` +} + +// ToShareUpdateMap assembles a request body based on the contents of an +// UpdateOpts. +func (opts UpdateOpts) ToShareUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "share") +} + +// Update will update the Share with provided information. To extract the updated +// Share from the response, call the Extract method on the UpdateResult. +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToShareUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/results.go new file mode 100644 index 000000000..0deae373b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/results.go @@ -0,0 +1,326 @@ +package shares + +import ( + "encoding/json" + "net/url" + "strconv" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +const ( + invalidMarker = "-1" +) + +// Share contains all information associated with an OpenStack Share +type Share struct { + // The availability zone of the share + AvailabilityZone string `json:"availability_zone"` + // A description of the share + Description string `json:"description,omitempty"` + // DisplayDescription is inherited from BlockStorage API. + // Both Description and DisplayDescription can be used + DisplayDescription string `json:"display_description,omitempty"` + // DisplayName is inherited from BlockStorage API + // Both DisplayName and Name can be used + DisplayName string `json:"display_name,omitempty"` + // Indicates whether a share has replicas or not. + HasReplicas bool `json:"has_replicas"` + // The host name of the share + Host string `json:"host"` + // The UUID of the share + ID string `json:"id"` + // Indicates the visibility of the share + IsPublic bool `json:"is_public,omitempty"` + // Share links for pagination + Links []map[string]string `json:"links"` + // Key, value -pairs of custom metadata + Metadata map[string]string `json:"metadata,omitempty"` + // The name of the share + Name string `json:"name,omitempty"` + // The UUID of the project to which this share belongs to + ProjectID string `json:"project_id"` + // The share replication type + ReplicationType string `json:"replication_type,omitempty"` + // The UUID of the share network + ShareNetworkID string `json:"share_network_id"` + // The shared file system protocol + ShareProto string `json:"share_proto"` + // The UUID of the share server + ShareServerID string `json:"share_server_id"` + // The UUID of the share type. + ShareType string `json:"share_type"` + // The name of the share type. + ShareTypeName string `json:"share_type_name"` + // Size of the share in GB + Size int `json:"size"` + // UUID of the snapshot from which to create the share + SnapshotID string `json:"snapshot_id"` + // The share status + Status string `json:"status"` + // The task state, used for share migration + TaskState string `json:"task_state"` + // The type of the volume + VolumeType string `json:"volume_type,omitempty"` + // The UUID of the consistency group this share belongs to + ConsistencyGroupID string `json:"consistency_group_id"` + // Used for filtering backends which either support or do not support share snapshots + SnapshotSupport bool `json:"snapshot_support"` + SourceCgsnapshotMemberID string `json:"source_cgsnapshot_member_id"` + // Timestamp when the share was created + CreatedAt time.Time `json:"-"` + // Timestamp when the share was updated + UpdatedAt time.Time `json:"-"` +} + +func (r *Share) UnmarshalJSON(b []byte) error { + type tmp Share + 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 = Share(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + r.UpdatedAt = time.Time(s.UpdatedAt) + + return nil +} + +type commonResult struct { + gophercloud.Result +} + +// Extract will get the Share object from the commonResult +func (r commonResult) Extract() (*Share, error) { + var s struct { + Share *Share `json:"share"` + } + err := r.ExtractInto(&s) + return s.Share, err +} + +// CreateResult contains the response body and error from a Create request. +type CreateResult struct { + commonResult +} + +// SharePage is a pagination.pager that is returned from a call to the List function. +type SharePage struct { + pagination.MarkerPageBase +} + +// NextPageURL generates the URL for the page of results after this one. +func (r SharePage) NextPageURL() (string, error) { + currentURL := r.URL + mark, err := r.Owner.LastMarker() + if err != nil { + return "", err + } + if mark == invalidMarker { + return "", nil + } + + q := currentURL.Query() + q.Set("offset", mark) + currentURL.RawQuery = q.Encode() + return currentURL.String(), nil +} + +// LastMarker returns the last offset in a ListResult. +func (r SharePage) LastMarker() (string, error) { + shares, err := ExtractShares(r) + if err != nil { + return invalidMarker, err + } + if len(shares) == 0 { + return invalidMarker, nil + } + + u, err := url.Parse(r.URL.String()) + if err != nil { + return invalidMarker, err + } + queryParams := u.Query() + offset := queryParams.Get("offset") + limit := queryParams.Get("limit") + + // Limit is not present, only one page required + if limit == "" { + return invalidMarker, nil + } + + iOffset := 0 + if offset != "" { + iOffset, err = strconv.Atoi(offset) + if err != nil { + return invalidMarker, err + } + } + iLimit, err := strconv.Atoi(limit) + if err != nil { + return invalidMarker, err + } + iOffset = iOffset + iLimit + offset = strconv.Itoa(iOffset) + + return offset, nil +} + +// IsEmpty satisifies the IsEmpty method of the Page interface +func (r SharePage) IsEmpty() (bool, error) { + shares, err := ExtractShares(r) + return len(shares) == 0, err +} + +// ExtractShares extracts and returns a Share slice. It is used while +// iterating over a shares.List call. +func ExtractShares(r pagination.Page) ([]Share, error) { + var s struct { + Shares []Share `json:"shares"` + } + + err := (r.(SharePage)).ExtractInto(&s) + + return s.Shares, err +} + +// DeleteResult contains the response body and error from a Delete request. +type DeleteResult struct { + gophercloud.ErrResult +} + +// 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 +} + +// IDFromName is a convenience function that returns a share's ID given its name. +func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { + r, err := ListDetail(client, &ListOpts{Name: name}).AllPages() + if err != nil { + return "", err + } + + ss, err := ExtractShares(r) + if err != nil { + return "", err + } + + switch len(ss) { + case 0: + return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "share"} + case 1: + return ss[0].ID, nil + default: + return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: len(ss), ResourceType: "share"} + } +} + +// GetExportLocationsResult contains the result body and error from an +// GetExportLocations request. +type GetExportLocationsResult struct { + gophercloud.Result +} + +// ExportLocation contains all information associated with a share export location +type ExportLocation struct { + // The export location path that should be used for mount operation. + Path string `json:"path"` + // The UUID of the share instance that this export location belongs to. + ShareInstanceID string `json:"share_instance_id"` + // Defines purpose of an export location. + // If set to true, then it is expected to be used for service needs + // and by administrators only. + // If it is set to false, then this export location can be used by end users. + IsAdminOnly bool `json:"is_admin_only"` + // The share export location UUID. + ID string `json:"id"` + // Drivers may use this field to identify which export locations are + // most efficient and should be used preferentially by clients. + // By default it is set to false value. New in version 2.14 + Preferred bool `json:"preferred"` +} + +// Extract will get the Export Locations from the commonResult +func (r GetExportLocationsResult) Extract() ([]ExportLocation, error) { + var s struct { + ExportLocations []ExportLocation `json:"export_locations"` + } + err := r.ExtractInto(&s) + return s.ExportLocations, err +} + +// AccessRight contains all information associated with an OpenStack share +// Grant Access Response +type AccessRight struct { + // The UUID of the share to which you are granted or denied access. + ShareID string `json:"share_id"` + // The access rule type that can be "ip", "cert" or "user". + AccessType string `json:"access_type,omitempty"` + // The value that defines the access that can be a valid format of IP, cert or user. + AccessTo string `json:"access_to,omitempty"` + // The access credential of the entity granted share access. + AccessKey string `json:"access_key,omitempty"` + // The access level to the share is either "rw" or "ro". + AccessLevel string `json:"access_level,omitempty"` + // The state of the access rule + State string `json:"state,omitempty"` + // The access rule ID. + ID string `json:"id"` +} + +// Extract will get the GrantAccess object from the commonResult +func (r GrantAccessResult) Extract() (*AccessRight, error) { + var s struct { + AccessRight *AccessRight `json:"access"` + } + err := r.ExtractInto(&s) + return s.AccessRight, err +} + +// GrantAccessResult contains the result body and error from an GrantAccess request. +type GrantAccessResult struct { + gophercloud.Result +} + +// RevokeAccessResult contains the response body and error from a Revoke access request. +type RevokeAccessResult struct { + gophercloud.ErrResult +} + +// Extract will get a slice of AccessRight objects from the commonResult +func (r ListAccessRightsResult) Extract() ([]AccessRight, error) { + var s struct { + AccessRights []AccessRight `json:"access_list"` + } + err := r.ExtractInto(&s) + return s.AccessRights, err +} + +// ListAccessRightsResult contains the result body and error from a ListAccessRights request. +type ListAccessRightsResult struct { + gophercloud.Result +} + +// ExtendResult contains the response body and error from an Extend request. +type ExtendResult struct { + gophercloud.ErrResult +} + +// ShrinkResult contains the response body and error from a Shrink request. +type ShrinkResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/urls.go new file mode 100644 index 000000000..02fba24d2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares/urls.go @@ -0,0 +1,47 @@ +package shares + +import "github.com/gophercloud/gophercloud" + +func createURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("shares") +} + +func listDetailURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("shares", "detail") +} + +func deleteURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("shares", id) +} + +func getURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("shares", id) +} + +func updateURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("shares", id) +} + +func getExportLocationsURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("shares", id, "export_locations") +} + +func grantAccessURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("shares", id, "action") +} + +func revokeAccessURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("shares", id, "action") +} + +func listAccessRightsURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("shares", id, "action") +} + +func extendURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("shares", id, "action") +} + +func shrinkURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("shares", id, "action") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/requests.go new file mode 100644 index 000000000..1f2f7d7cc --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/requests.go @@ -0,0 +1,161 @@ +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 the options for create a Snapshot. This object is +// passed to snapshots.Create(). For more information about these parameters, +// please refer to the Snapshot object, or the shared file systems API v2 +// documentation +type CreateOpts struct { + // The UUID of the share from which to create a snapshot + ShareID string `json:"share_id" required:"true"` + // Defines the snapshot name + Name string `json:"name,omitempty"` + // Defines the snapshot description + Description string `json:"description,omitempty"` + // DisplayName is equivalent to Name. The API supports using both + // This is an inherited attribute from the block storage API + DisplayName string `json:"display_name,omitempty"` + // DisplayDescription is equivalent to Description. The API supports using both + // This is an inherited attribute from the block storage API + DisplayDescription string `json:"display_description,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{200, 201, 202}, + }) + return +} + +// ListOpts holds options for listing Snapshots. It is passed to the +// snapshots.List function. +type ListOpts struct { + // (Admin only). Defines whether to list the requested resources for all projects. + AllTenants bool `q:"all_tenants"` + // The snapshot name. + Name string `q:"name"` + // Filter by a snapshot description. + Description string `q:"description"` + // Filters by a share from which the snapshot was created. + ShareID string `q:"share_id"` + // Filters by a snapshot size in GB. + Size int `q:"size"` + // Filters by a snapshot status. + Status string `q:"status"` + // The maximum number of snapshots to return. + Limit int `q:"limit"` + // The offset to define start point of snapshot or snapshot group listing. + Offset int `q:"offset"` + // The key to sort a list of snapshots. + SortKey string `q:"sort_key"` + // The direction to sort a list of snapshots. + SortDir string `q:"sort_dir"` + // The UUID of the project in which the snapshot was created. Useful with all_tenants parameter. + ProjectID string `q:"project_id"` + // The name pattern that can be used to filter snapshots, snapshot snapshots, snapshot networks or snapshot groups. + NamePattern string `q:"name~"` + // The description pattern that can be used to filter snapshots, snapshot snapshots, snapshot networks or snapshot groups. + DescriptionPattern string `q:"description~"` +} + +// ListOptsBuilder allows extensions to add additional parameters to the List +// request. +type ListOptsBuilder interface { + ToSnapshotListQuery() (string, error) +} + +// ToSnapshotListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToSnapshotListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// ListDetail returns []Snapshot optionally limited by the conditions provided in ListOpts. +func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listDetailURL(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 { + p := SnapshotPage{pagination.MarkerPageBase{PageResult: r}} + p.MarkerPageBase.Owner = p + return p + }) +} + +// Delete will delete an existing Snapshot with the given UUID. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} + +// Get will get a single snapshot with given UUID +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToSnapshotUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts contain options for updating an existing Snapshot. This object is passed +// to the snapshot.Update function. For more information about the parameters, see +// the Snapshot object. +type UpdateOpts struct { + // Snapshot name. Manila snapshot update logic doesn't have a "name" alias. + DisplayName *string `json:"display_name,omitempty"` + // Snapshot description. Manila snapshot update logic doesn't have a "description" alias. + DisplayDescription *string `json:"display_description,omitempty"` +} + +// ToSnapshotUpdateMap assembles a request body based on the contents of an +// UpdateOpts. +func (opts UpdateOpts) ToSnapshotUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "snapshot") +} + +// Update will update the Snapshot with provided information. To extract the updated +// Snapshot from the response, call the Extract method on the UpdateResult. +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToSnapshotUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/results.go new file mode 100644 index 000000000..4952dbd34 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/results.go @@ -0,0 +1,193 @@ +package snapshots + +import ( + "encoding/json" + "net/url" + "strconv" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +const ( + invalidMarker = "-1" +) + +// Snapshot contains all information associated with an OpenStack Snapshot +type Snapshot struct { + // The UUID of the snapshot + ID string `json:"id"` + // The name of the snapshot + Name string `json:"name,omitempty"` + // A description of the snapshot + Description string `json:"description,omitempty"` + // UUID of the share from which the snapshot was created + ShareID string `json:"share_id"` + // The shared file system protocol + ShareProto string `json:"share_proto"` + // Size of the snapshot share in GB + ShareSize int `json:"share_size"` + // Size of the snapshot in GB + Size int `json:"size"` + // The snapshot status + Status string `json:"status"` + // The UUID of the project in which the snapshot was created + ProjectID string `json:"project_id"` + // Timestamp when the snapshot was created + CreatedAt time.Time `json:"-"` + // Snapshot links for pagination + Links []map[string]string `json:"links"` +} + +func (r *Snapshot) UnmarshalJSON(b []byte) error { + type tmp Snapshot + var s struct { + tmp + CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Snapshot(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + + return nil +} + +type commonResult struct { + gophercloud.Result +} + +// Extract will get the Snapshot object from the commonResult +func (r commonResult) Extract() (*Snapshot, error) { + var s struct { + Snapshot *Snapshot `json:"snapshot"` + } + err := r.ExtractInto(&s) + return s.Snapshot, err +} + +// CreateResult contains the response body and error from a Create request. +type CreateResult struct { + commonResult +} + +// SnapshotPage is a pagination.pager that is returned from a call to the List function. +type SnapshotPage struct { + pagination.MarkerPageBase +} + +// NextPageURL generates the URL for the page of results after this one. +func (r SnapshotPage) NextPageURL() (string, error) { + currentURL := r.URL + mark, err := r.Owner.LastMarker() + if err != nil { + return "", err + } + if mark == invalidMarker { + return "", nil + } + + q := currentURL.Query() + q.Set("offset", mark) + currentURL.RawQuery = q.Encode() + return currentURL.String(), nil +} + +// LastMarker returns the last offset in a ListResult. +func (r SnapshotPage) LastMarker() (string, error) { + snapshots, err := ExtractSnapshots(r) + if err != nil { + return invalidMarker, err + } + if len(snapshots) == 0 { + return invalidMarker, nil + } + + u, err := url.Parse(r.URL.String()) + if err != nil { + return invalidMarker, err + } + queryParams := u.Query() + offset := queryParams.Get("offset") + limit := queryParams.Get("limit") + + // Limit is not present, only one page required + if limit == "" { + return invalidMarker, nil + } + + iOffset := 0 + if offset != "" { + iOffset, err = strconv.Atoi(offset) + if err != nil { + return invalidMarker, err + } + } + iLimit, err := strconv.Atoi(limit) + if err != nil { + return invalidMarker, err + } + iOffset = iOffset + iLimit + offset = strconv.Itoa(iOffset) + + return offset, nil +} + +// IsEmpty satisifies the IsEmpty method of the Page interface +func (r SnapshotPage) IsEmpty() (bool, error) { + snapshots, err := ExtractSnapshots(r) + return len(snapshots) == 0, err +} + +// ExtractSnapshots extracts and returns a Snapshot slice. 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 +} + +// DeleteResult contains the response body and error from a Delete request. +type DeleteResult struct { + gophercloud.ErrResult +} + +// 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 +} + +// IDFromName is a convenience function that returns a snapshot's ID given its name. +func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { + r, err := ListDetail(client, &ListOpts{Name: name}).AllPages() + if err != nil { + return "", err + } + + ss, err := ExtractSnapshots(r) + if err != nil { + return "", err + } + + switch len(ss) { + case 0: + return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "snapshot"} + case 1: + return ss[0].ID, nil + default: + return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: len(ss), ResourceType: "snapshot"} + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/urls.go new file mode 100644 index 000000000..a07e3ec87 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots/urls.go @@ -0,0 +1,23 @@ +package snapshots + +import "github.com/gophercloud/gophercloud" + +func createURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("snapshots") +} + +func listDetailURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("snapshots", "detail") +} + +func deleteURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("snapshots", id) +} + +func getURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("snapshots", id) +} + +func updateURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("snapshots", id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/utils/base_endpoint.go b/vendor/github.com/gophercloud/gophercloud/openstack/utils/base_endpoint.go new file mode 100644 index 000000000..40080f7af --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/utils/base_endpoint.go @@ -0,0 +1,28 @@ +package utils + +import ( + "net/url" + "regexp" + "strings" +) + +// BaseEndpoint will return a URL without the /vX.Y +// portion of the URL. +func BaseEndpoint(endpoint string) (string, error) { + u, err := url.Parse(endpoint) + if err != nil { + return "", err + } + + u.RawQuery, u.Fragment = "", "" + + path := u.Path + versionRe := regexp.MustCompile("v[0-9.]+/?") + + if version := versionRe.FindString(path); version != "" { + versionIndex := strings.Index(path, version) + u.Path = path[:versionIndex] + } + + return u.String(), nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/utils/choose_version.go b/vendor/github.com/gophercloud/gophercloud/openstack/utils/choose_version.go index c605d0844..27da19f91 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/utils/choose_version.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/utils/choose_version.go @@ -68,11 +68,6 @@ func ChooseVersion(client *gophercloud.ProviderClient, recognized []*Version) (* return nil, "", err } - byID := make(map[string]*Version) - for _, version := range recognized { - byID[version.ID] = version - } - var highest *Version var endpoint string @@ -84,20 +79,22 @@ func ChooseVersion(client *gophercloud.ProviderClient, recognized []*Version) (* } } - if matching, ok := byID[value.ID]; ok { - // Prefer a version that exactly matches the provided endpoint. - if href == identityEndpoint { - if href == "" { - return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", value.ID, client.IdentityBase) + for _, version := range recognized { + if strings.Contains(value.ID, version.ID) { + // Prefer a version that exactly matches the provided endpoint. + if href == identityEndpoint { + if href == "" { + return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", value.ID, client.IdentityBase) + } + return version, href, nil } - return matching, href, nil - } - // Otherwise, find the highest-priority version with a whitelisted status. - if goodStatus[strings.ToLower(value.Status)] { - if highest == nil || matching.Priority > highest.Priority { - highest = matching - endpoint = href + // Otherwise, find the highest-priority version with a whitelisted status. + if goodStatus[strings.ToLower(value.Status)] { + if highest == nil || version.Priority > highest.Priority { + highest = version + endpoint = href + } } } } diff --git a/vendor/github.com/gophercloud/gophercloud/pagination/http.go b/vendor/github.com/gophercloud/gophercloud/pagination/http.go index cb4b4ae6b..757295c42 100644 --- a/vendor/github.com/gophercloud/gophercloud/pagination/http.go +++ b/vendor/github.com/gophercloud/gophercloud/pagination/http.go @@ -55,6 +55,6 @@ func PageResultFromParsed(resp *http.Response, body interface{}) PageResult { func Request(client *gophercloud.ServiceClient, headers map[string]string, url string) (*http.Response, error) { return client.Get(url, nil, &gophercloud.RequestOpts{ MoreHeaders: headers, - OkCodes: []int{200, 204}, + OkCodes: []int{200, 204, 300}, }) } diff --git a/vendor/github.com/gophercloud/gophercloud/pagination/pager.go b/vendor/github.com/gophercloud/gophercloud/pagination/pager.go index 6f1609ef2..42c0b2dbe 100644 --- a/vendor/github.com/gophercloud/gophercloud/pagination/pager.go +++ b/vendor/github.com/gophercloud/gophercloud/pagination/pager.go @@ -22,7 +22,6 @@ var ( // Depending on the pagination strategy of a particular resource, there may be an additional subinterface that the result type // will need to implement. type Page interface { - // NextPageURL generates the URL for the page of data that follows this collection. // Return "" if no such page exists. NextPageURL() (string, error) @@ -42,6 +41,8 @@ type Pager struct { createPage func(r PageResult) Page + firstPage Page + Err error // Headers supplies additional HTTP headers to populate on each paged request. @@ -90,9 +91,18 @@ func (p Pager) EachPage(handler func(Page) (bool, error)) error { } currentURL := p.initialURL for { - currentPage, err := p.fetchNextPage(currentURL) - if err != nil { - return err + var currentPage Page + + // if first page has already been fetched, no need to fetch it again + if p.firstPage != nil { + currentPage = p.firstPage + p.firstPage = nil + } else { + var err error + currentPage, err = p.fetchNextPage(currentURL) + if err != nil { + return err + } } empty, err := currentPage.IsEmpty() @@ -129,23 +139,26 @@ func (p Pager) AllPages() (Page, error) { // body will contain the final concatenated Page body. var body reflect.Value - // Grab a test page to ascertain the page body type. - testPage, err := p.fetchNextPage(p.initialURL) + // Grab a first page to ascertain the page body type. + firstPage, err := p.fetchNextPage(p.initialURL) if err != nil { return nil, err } // Store the page type so we can use reflection to create a new mega-page of // that type. - pageType := reflect.TypeOf(testPage) + pageType := reflect.TypeOf(firstPage) - // if it's a single page, just return the testPage (first page) + // if it's a single page, just return the firstPage (first page) if _, found := pageType.FieldByName("SinglePageBase"); found { - return testPage, nil + return firstPage, nil } + // store the first page to avoid getting it twice + p.firstPage = firstPage + // Switch on the page body type. Recognized types are `map[string]interface{}`, // `[]byte`, and `[]interface{}`. - switch pb := testPage.GetBody().(type) { + switch pb := firstPage.GetBody().(type) { case map[string]interface{}: // key is the map key for the page body if the body type is `map[string]interface{}`. var key string diff --git a/vendor/github.com/gophercloud/gophercloud/params.go b/vendor/github.com/gophercloud/gophercloud/params.go index e484fe1c1..b9986660c 100644 --- a/vendor/github.com/gophercloud/gophercloud/params.go +++ b/vendor/github.com/gophercloud/gophercloud/params.go @@ -10,10 +10,28 @@ import ( "time" ) -// BuildRequestBody builds a map[string]interface from the given `struct`. If -// parent is not the empty string, the final map[string]interface returned will -// encapsulate the built one -// +/* +BuildRequestBody builds a map[string]interface from the given `struct`. If +parent is not an empty string, the final map[string]interface returned will +encapsulate the built one. For example: + + disk := 1 + createOpts := flavors.CreateOpts{ + ID: "1", + Name: "m1.tiny", + Disk: &disk, + RAM: 512, + VCPUs: 1, + RxTxFactor: 1.0, + } + + body, err := gophercloud.BuildRequestBody(createOpts, "flavor") + +The above example can be run as-is, however it is recommended to look at how +BuildRequestBody is used within Gophercloud to more fully understand how it +fits within the request process as a whole rather than use it directly as shown +above. +*/ func BuildRequestBody(opts interface{}, parent string) (map[string]interface{}, error) { optsValue := reflect.ValueOf(opts) if optsValue.Kind() == reflect.Ptr { @@ -97,10 +115,31 @@ func BuildRequestBody(opts interface{}, parent string) (map[string]interface{}, } } + jsonTag := f.Tag.Get("json") + if jsonTag == "-" { + continue + } + + if v.Kind() == reflect.Slice || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Slice) { + sliceValue := v + if sliceValue.Kind() == reflect.Ptr { + sliceValue = sliceValue.Elem() + } + + for i := 0; i < sliceValue.Len(); i++ { + element := sliceValue.Index(i) + if element.Kind() == reflect.Struct || (element.Kind() == reflect.Ptr && element.Elem().Kind() == reflect.Struct) { + _, err := BuildRequestBody(element.Interface(), "") + if err != nil { + return nil, err + } + } + } + } if v.Kind() == reflect.Struct || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct) { if zero { //fmt.Printf("value before change: %+v\n", optsValue.Field(i)) - if jsonTag := f.Tag.Get("json"); jsonTag != "" { + if jsonTag != "" { jsonTagPieces := strings.Split(jsonTag, ",") if len(jsonTagPieces) > 1 && jsonTagPieces[1] == "omitempty" { if v.CanSet() { @@ -329,12 +368,20 @@ func BuildQueryString(opts interface{}) (*url.URL, error) { params.Add(tags[0], v.Index(i).String()) } } + case reflect.Map: + if v.Type().Key().Kind() == reflect.String && v.Type().Elem().Kind() == reflect.String { + var s []string + for _, k := range v.MapKeys() { + value := v.MapIndex(k).String() + s = append(s, fmt.Sprintf("'%s':'%s'", k.String(), value)) + } + params.Add(tags[0], fmt.Sprintf("{%s}", strings.Join(s, ", "))) + } } } else { - // Otherwise, the field is not set. - if len(tags) == 2 && tags[1] == "required" { - // And the field is required. Return an error. - return nil, fmt.Errorf("Required query parameter [%s] not set.", f.Name) + // if the field has a 'required' tag, it can't have a zero-value + if requiredTag := f.Tag.Get("required"); requiredTag == "true" { + return &url.URL{}, fmt.Errorf("Required query parameter [%s] not set.", f.Name) } } } @@ -407,10 +454,9 @@ func BuildHeaders(opts interface{}) (map[string]string, error) { optsMap[tags[0]] = strconv.FormatBool(v.Bool()) } } else { - // Otherwise, the field is not set. - if len(tags) == 2 && tags[1] == "required" { - // And the field is required. Return an error. - return optsMap, fmt.Errorf("Required header not set.") + // if the field has a 'required' tag, it can't have a zero-value + if requiredTag := f.Tag.Get("required"); requiredTag == "true" { + return optsMap, fmt.Errorf("Required header [%s] not set.", f.Name) } } } diff --git a/vendor/github.com/gophercloud/gophercloud/provider_client.go b/vendor/github.com/gophercloud/gophercloud/provider_client.go index f88682381..365b4cbcd 100644 --- a/vendor/github.com/gophercloud/gophercloud/provider_client.go +++ b/vendor/github.com/gophercloud/gophercloud/provider_client.go @@ -3,10 +3,12 @@ package gophercloud import ( "bytes" "encoding/json" + "errors" "io" "io/ioutil" "net/http" "strings" + "sync" ) // DefaultUserAgent is the default User-Agent string set in the request header. @@ -51,6 +53,8 @@ type ProviderClient struct { IdentityEndpoint string // TokenID is the ID of the most recently issued valid token. + // NOTE: Aside from within a custom ReauthFunc, this field shouldn't be set by an application. + // To safely read or write this value, call `Token` or `SetToken`, respectively TokenID string // EndpointLocator describes how this provider discovers the endpoints for @@ -68,16 +72,187 @@ type ProviderClient struct { // authentication functions for different Identity service versions. ReauthFunc func() error - Debug bool + // Throwaway determines whether if this client is a throw-away client. It's a copy of user's provider client + // with the token and reauth func zeroed. Such client can be used to perform reauthorization. + Throwaway bool + + mut *sync.RWMutex + + reauthmut *reauthlock + + authResult AuthResult +} + +type reauthlock struct { + sync.RWMutex + reauthing bool + reauthingErr error + done *sync.Cond } // AuthenticatedHeaders returns a map of HTTP headers that are common for all -// authenticated service requests. -func (client *ProviderClient) AuthenticatedHeaders() map[string]string { - if client.TokenID == "" { - return map[string]string{} +// authenticated service requests. Blocks if Reauthenticate is in progress. +func (client *ProviderClient) AuthenticatedHeaders() (m map[string]string) { + if client.IsThrowaway() { + return } - return map[string]string{"X-Auth-Token": client.TokenID} + if client.reauthmut != nil { + client.reauthmut.Lock() + for client.reauthmut.reauthing { + client.reauthmut.done.Wait() + } + client.reauthmut.Unlock() + } + t := client.Token() + if t == "" { + return + } + return map[string]string{"X-Auth-Token": t} +} + +// UseTokenLock creates a mutex that is used to allow safe concurrent access to the auth token. +// If the application's ProviderClient is not used concurrently, this doesn't need to be called. +func (client *ProviderClient) UseTokenLock() { + client.mut = new(sync.RWMutex) + client.reauthmut = new(reauthlock) +} + +// GetAuthResult returns the result from the request that was used to obtain a +// provider client's Keystone token. +// +// The result is nil when authentication has not yet taken place, when the token +// was set manually with SetToken(), or when a ReauthFunc was used that does not +// record the AuthResult. +func (client *ProviderClient) GetAuthResult() AuthResult { + if client.mut != nil { + client.mut.RLock() + defer client.mut.RUnlock() + } + return client.authResult +} + +// Token safely reads the value of the auth token from the ProviderClient. Applications should +// call this method to access the token instead of the TokenID field +func (client *ProviderClient) Token() string { + if client.mut != nil { + client.mut.RLock() + defer client.mut.RUnlock() + } + return client.TokenID +} + +// SetToken safely sets the value of the auth token in the ProviderClient. Applications may +// use this method in a custom ReauthFunc. +// +// WARNING: This function is deprecated. Use SetTokenAndAuthResult() instead. +func (client *ProviderClient) SetToken(t string) { + if client.mut != nil { + client.mut.Lock() + defer client.mut.Unlock() + } + client.TokenID = t + client.authResult = nil +} + +// SetTokenAndAuthResult safely sets the value of the auth token in the +// ProviderClient and also records the AuthResult that was returned from the +// token creation request. Applications may call this in a custom ReauthFunc. +func (client *ProviderClient) SetTokenAndAuthResult(r AuthResult) error { + tokenID := "" + var err error + if r != nil { + tokenID, err = r.ExtractTokenID() + if err != nil { + return err + } + } + + if client.mut != nil { + client.mut.Lock() + defer client.mut.Unlock() + } + client.TokenID = tokenID + client.authResult = r + return nil +} + +// CopyTokenFrom safely copies the token from another ProviderClient into the +// this one. +func (client *ProviderClient) CopyTokenFrom(other *ProviderClient) { + if client.mut != nil { + client.mut.Lock() + defer client.mut.Unlock() + } + if other.mut != nil && other.mut != client.mut { + other.mut.RLock() + defer other.mut.RUnlock() + } + client.TokenID = other.TokenID + client.authResult = other.authResult +} + +// IsThrowaway safely reads the value of the client Throwaway field. +func (client *ProviderClient) IsThrowaway() bool { + if client.reauthmut != nil { + client.reauthmut.RLock() + defer client.reauthmut.RUnlock() + } + return client.Throwaway +} + +// SetThrowaway safely sets the value of the client Throwaway field. +func (client *ProviderClient) SetThrowaway(v bool) { + if client.reauthmut != nil { + client.reauthmut.Lock() + defer client.reauthmut.Unlock() + } + client.Throwaway = v +} + +// Reauthenticate calls client.ReauthFunc in a thread-safe way. If this is +// called because of a 401 response, the caller may pass the previous token. In +// this case, the reauthentication can be skipped if another thread has already +// reauthenticated in the meantime. If no previous token is known, an empty +// string should be passed instead to force unconditional reauthentication. +func (client *ProviderClient) Reauthenticate(previousToken string) (err error) { + if client.ReauthFunc == nil { + return nil + } + + if client.mut == nil { + return client.ReauthFunc() + } + + client.reauthmut.Lock() + if client.reauthmut.reauthing { + for !client.reauthmut.reauthing { + client.reauthmut.done.Wait() + } + err = client.reauthmut.reauthingErr + client.reauthmut.Unlock() + return err + } + client.reauthmut.Unlock() + + client.mut.Lock() + defer client.mut.Unlock() + + client.reauthmut.Lock() + client.reauthmut.reauthing = true + client.reauthmut.done = sync.NewCond(client.reauthmut) + client.reauthmut.reauthingErr = nil + client.reauthmut.Unlock() + + if previousToken == "" || client.TokenID == previousToken { + err = client.ReauthFunc() + } + + client.reauthmut.Lock() + client.reauthmut.reauthing = false + client.reauthmut.reauthingErr = err + client.reauthmut.done.Broadcast() + client.reauthmut.Unlock() + return } // RequestOpts customizes the behavior of the provider.Request() method. @@ -116,7 +291,7 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts) // io.ReadSeeker as-is. Default the content-type to application/json. if options.JSONBody != nil { if options.RawBody != nil { - panic("Please provide only one of JSONBody or RawBody to gophercloud.Request().") + return nil, errors.New("please provide only one of JSONBody or RawBody to gophercloud.Request()") } rendered, err := json.Marshal(options.JSONBody) @@ -145,10 +320,6 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts) } req.Header.Set("Accept", applicationJSON) - for k, v := range client.AuthenticatedHeaders() { - req.Header.Add(k, v) - } - // Set the User-Agent header req.Header.Set("User-Agent", client.UserAgent.Join()) @@ -162,9 +333,16 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts) } } + // get latest token from client + for k, v := range client.AuthenticatedHeaders() { + req.Header.Set(k, v) + } + // Set connection parameter to close the connection immediately when we've got the response req.Close = true + prereqtok := req.Header.Get("X-Auth-Token") + // Issue the request. resp, err := client.HTTPClient.Do(req) if err != nil { @@ -172,13 +350,14 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts) } // Allow default OkCodes if none explicitly set - if options.OkCodes == nil { - options.OkCodes = defaultOkCodes(method) + okc := options.OkCodes + if okc == nil { + okc = defaultOkCodes(method) } // Validate the HTTP response status. var ok bool - for _, code := range options.OkCodes { + for _, code := range okc { if resp.StatusCode == code { ok = true break @@ -188,9 +367,6 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts) if !ok { body, _ := ioutil.ReadAll(resp.Body) resp.Body.Close() - //pc := make([]uintptr, 1) - //runtime.Callers(2, pc) - //f := runtime.FuncForPC(pc[0]) respErr := ErrUnexpectedResponseCode{ URL: url, Method: method, @@ -198,7 +374,6 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts) Actual: resp.StatusCode, Body: body, } - //respErr.Function = "gophercloud.ProviderClient.Request" errType := options.ErrorContext switch resp.StatusCode { @@ -209,7 +384,7 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts) } case http.StatusUnauthorized: if client.ReauthFunc != nil { - err = client.ReauthFunc() + err = client.Reauthenticate(prereqtok) if err != nil { e := &ErrUnableToReauthenticate{} e.ErrOriginal = respErr @@ -239,6 +414,11 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts) if error401er, ok := errType.(Err401er); ok { err = error401er.Error401(respErr) } + case http.StatusForbidden: + err = ErrDefault403{respErr} + if error403er, ok := errType.(Err403er); ok { + err = error403er.Error403(respErr) + } case http.StatusNotFound: err = ErrDefault404{respErr} if error404er, ok := errType.(Err404er); ok { @@ -298,7 +478,7 @@ func defaultOkCodes(method string) []int { case method == "PUT": return []int{201, 202} case method == "PATCH": - return []int{200, 204} + return []int{200, 202, 204} case method == "DELETE": return []int{202, 204} } diff --git a/vendor/github.com/gophercloud/gophercloud/results.go b/vendor/github.com/gophercloud/gophercloud/results.go index 76c16ef8f..94a16bff0 100644 --- a/vendor/github.com/gophercloud/gophercloud/results.go +++ b/vendor/github.com/gophercloud/gophercloud/results.go @@ -78,6 +78,77 @@ func (r Result) extractIntoPtr(to interface{}, label string) error { return err } + toValue := reflect.ValueOf(to) + if toValue.Kind() == reflect.Ptr { + toValue = toValue.Elem() + } + + switch toValue.Kind() { + case reflect.Slice: + typeOfV := toValue.Type().Elem() + if typeOfV.Kind() == reflect.Struct { + if typeOfV.NumField() > 0 && typeOfV.Field(0).Anonymous { + newSlice := reflect.MakeSlice(reflect.SliceOf(typeOfV), 0, 0) + + if mSlice, ok := m[label].([]interface{}); ok { + for _, v := range mSlice { + // For each iteration of the slice, we create a new struct. + // This is to work around a bug where elements of a slice + // are reused and not overwritten when the same copy of the + // struct is used: + // + // https://github.com/golang/go/issues/21092 + // https://github.com/golang/go/issues/24155 + // https://play.golang.org/p/NHo3ywlPZli + newType := reflect.New(typeOfV).Elem() + + b, err := json.Marshal(v) + if err != nil { + return err + } + + // This is needed for structs with an UnmarshalJSON method. + // Technically this is just unmarshalling the response into + // a struct that is never used, but it's good enough to + // trigger the UnmarshalJSON method. + for i := 0; i < newType.NumField(); i++ { + s := newType.Field(i).Addr().Interface() + + // Unmarshal is used rather than NewDecoder to also work + // around the above-mentioned bug. + err = json.Unmarshal(b, s) + if err != nil { + return err + } + } + + newSlice = reflect.Append(newSlice, newType) + } + } + + // "to" should now be properly modeled to receive the + // JSON response body and unmarshal into all the correct + // fields of the struct or composed extension struct + // at the end of this method. + toValue.Set(newSlice) + } + } + case reflect.Struct: + typeOfV := toValue.Type() + if typeOfV.NumField() > 0 && typeOfV.Field(0).Anonymous { + for i := 0; i < toValue.NumField(); i++ { + toField := toValue.Field(i) + if toField.Kind() == reflect.Struct { + s := toField.Addr().Interface() + err = json.NewDecoder(bytes.NewReader(b)).Decode(s) + if err != nil { + return err + } + } + } + } + } + err = json.Unmarshal(b, &to) return err } @@ -177,9 +248,8 @@ type HeaderResult struct { Result } -// ExtractHeader will return the http.Header and error from the HeaderResult. -// -// header, err := objects.Create(client, "my_container", objects.CreateOpts{}).ExtractHeader() +// ExtractInto allows users to provide an object into which `Extract` will +// extract the http.Header headers of the result. func (r HeaderResult) ExtractInto(to interface{}) error { if r.Err != nil { return r.Err @@ -299,6 +369,48 @@ func (jt *JSONRFC3339NoZ) UnmarshalJSON(data []byte) error { return nil } +// RFC3339ZNoT is the time format used in Zun (Containers Service). +const RFC3339ZNoT = "2006-01-02 15:04:05-07:00" + +type JSONRFC3339ZNoT time.Time + +func (jt *JSONRFC3339ZNoT) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + if s == "" { + return nil + } + t, err := time.Parse(RFC3339ZNoT, s) + if err != nil { + return err + } + *jt = JSONRFC3339ZNoT(t) + return nil +} + +// RFC3339ZNoTNoZ is another time format used in Zun (Containers Service). +const RFC3339ZNoTNoZ = "2006-01-02 15:04:05" + +type JSONRFC3339ZNoTNoZ time.Time + +func (jt *JSONRFC3339ZNoTNoZ) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + if s == "" { + return nil + } + t, err := time.Parse(RFC3339ZNoTNoZ, s) + if err != nil { + return err + } + *jt = JSONRFC3339ZNoTNoZ(t) + return nil +} + /* Link is an internal type to be used in packages of collection resources that are paginated in a certain way. diff --git a/vendor/github.com/gophercloud/gophercloud/service_client.go b/vendor/github.com/gophercloud/gophercloud/service_client.go index 7484c67e5..2734510e1 100644 --- a/vendor/github.com/gophercloud/gophercloud/service_client.go +++ b/vendor/github.com/gophercloud/gophercloud/service_client.go @@ -21,7 +21,17 @@ type ServiceClient struct { // as-is, instead. ResourceBase string + // This is the service client type (e.g. compute, sharev2). + // NOTE: FOR INTERNAL USE ONLY. DO NOT SET. GOPHERCLOUD WILL SET THIS. + // It is only exported because it gets set in a different package. + Type string + + // The microversion of the service to use. Set this to use a particular microversion. Microversion string + + // MoreHeaders allows users (or Gophercloud) to set service-wide headers on requests. Put another way, + // values set in this field will be set on all the HTTP requests the service client sends. + MoreHeaders map[string]string } // ResourceBaseURL returns the base URL of any resources used by this service. It MUST end with a /. @@ -37,11 +47,13 @@ func (client *ServiceClient) ServiceURL(parts ...string) string { return client.ResourceBaseURL() + strings.Join(parts, "/") } -// Get calls `Request` with the "GET" HTTP verb. -func (client *ServiceClient) Get(url string, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) { - if opts == nil { - opts = &RequestOpts{} +func (client *ServiceClient) initReqOpts(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) { + if v, ok := (JSONBody).(io.Reader); ok { + opts.RawBody = v + } else if JSONBody != nil { + opts.JSONBody = JSONBody } + if JSONResponse != nil { opts.JSONResponse = JSONResponse } @@ -49,93 +61,90 @@ func (client *ServiceClient) Get(url string, JSONResponse interface{}, opts *Req if opts.MoreHeaders == nil { opts.MoreHeaders = make(map[string]string) } - opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion + if client.Microversion != "" { + client.setMicroversionHeader(opts) + } +} + +// Get calls `Request` with the "GET" HTTP verb. +func (client *ServiceClient) Get(url string, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) { + if opts == nil { + opts = new(RequestOpts) + } + client.initReqOpts(url, nil, JSONResponse, opts) return client.Request("GET", url, opts) } // Post calls `Request` with the "POST" HTTP verb. func (client *ServiceClient) Post(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) { if opts == nil { - opts = &RequestOpts{} + opts = new(RequestOpts) } - - if v, ok := (JSONBody).(io.Reader); ok { - opts.RawBody = v - } else if JSONBody != nil { - opts.JSONBody = JSONBody - } - - if JSONResponse != nil { - opts.JSONResponse = JSONResponse - } - - if opts.MoreHeaders == nil { - opts.MoreHeaders = make(map[string]string) - } - opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion - + client.initReqOpts(url, JSONBody, JSONResponse, opts) return client.Request("POST", url, opts) } // Put calls `Request` with the "PUT" HTTP verb. func (client *ServiceClient) Put(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) { if opts == nil { - opts = &RequestOpts{} + opts = new(RequestOpts) } - - if v, ok := (JSONBody).(io.Reader); ok { - opts.RawBody = v - } else if JSONBody != nil { - opts.JSONBody = JSONBody - } - - if JSONResponse != nil { - opts.JSONResponse = JSONResponse - } - - if opts.MoreHeaders == nil { - opts.MoreHeaders = make(map[string]string) - } - opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion - + client.initReqOpts(url, JSONBody, JSONResponse, opts) return client.Request("PUT", url, opts) } // Patch calls `Request` with the "PATCH" HTTP verb. func (client *ServiceClient) Patch(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) { if opts == nil { - opts = &RequestOpts{} + opts = new(RequestOpts) } - - if v, ok := (JSONBody).(io.Reader); ok { - opts.RawBody = v - } else if JSONBody != nil { - opts.JSONBody = JSONBody - } - - if JSONResponse != nil { - opts.JSONResponse = JSONResponse - } - - if opts.MoreHeaders == nil { - opts.MoreHeaders = make(map[string]string) - } - opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion - + client.initReqOpts(url, JSONBody, JSONResponse, opts) return client.Request("PATCH", url, opts) } // Delete calls `Request` with the "DELETE" HTTP verb. func (client *ServiceClient) Delete(url string, opts *RequestOpts) (*http.Response, error) { if opts == nil { - opts = &RequestOpts{} + opts = new(RequestOpts) } - - if opts.MoreHeaders == nil { - opts.MoreHeaders = make(map[string]string) - } - opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion - + client.initReqOpts(url, nil, nil, opts) return client.Request("DELETE", url, opts) } + +// Head calls `Request` with the "HEAD" HTTP verb. +func (client *ServiceClient) Head(url string, opts *RequestOpts) (*http.Response, error) { + if opts == nil { + opts = new(RequestOpts) + } + client.initReqOpts(url, nil, nil, opts) + return client.Request("HEAD", url, opts) +} + +func (client *ServiceClient) setMicroversionHeader(opts *RequestOpts) { + switch client.Type { + case "compute": + opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion + case "sharev2": + opts.MoreHeaders["X-OpenStack-Manila-API-Version"] = client.Microversion + case "volume": + opts.MoreHeaders["X-OpenStack-Volume-API-Version"] = client.Microversion + } + + if client.Type != "" { + opts.MoreHeaders["OpenStack-API-Version"] = client.Type + " " + client.Microversion + } +} + +// Request carries out the HTTP operation for the service client +func (client *ServiceClient) Request(method, url string, options *RequestOpts) (*http.Response, error) { + if len(client.MoreHeaders) > 0 { + if options == nil { + options = new(RequestOpts) + } + for k, v := range client.MoreHeaders { + options.MoreHeaders[k] = v + } + } + return client.ProviderClient.Request(method, url, options) +} diff --git a/vendor/github.com/gophercloud/utils/LICENSE b/vendor/github.com/gophercloud/utils/LICENSE new file mode 100644 index 000000000..8dada3eda --- /dev/null +++ b/vendor/github.com/gophercloud/utils/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/gophercloud/utils/openstack/clientconfig/doc.go b/vendor/github.com/gophercloud/utils/openstack/clientconfig/doc.go new file mode 100644 index 000000000..1f3be2128 --- /dev/null +++ b/vendor/github.com/gophercloud/utils/openstack/clientconfig/doc.go @@ -0,0 +1,49 @@ +/* +Package clientconfig provides convienent functions for creating OpenStack +clients. It is based on the Python os-client-config library. + +See https://docs.openstack.org/os-client-config/latest for details. + +Example to Create a Provider Client From clouds.yaml + + opts := &clientconfig.ClientOpts{ + Name: "hawaii", + } + + pClient, err := clientconfig.AuthenticatedClient(opts) + if err != nil { + panic(err) + } + + +Example to Manually Create a Provider Client + + opts := &clientconfig.ClientOpts{ + AuthInfo: &clientconfig.AuthInfo{ + AuthURL: "https://hi.example.com:5000/v3", + Username: "jdoe", + Password: "password", + ProjectName: "Some Project", + DomainName: "default", + }, + } + + pClient, err := clientconfig.AuthenticatedClient(opts) + if err != nil { + panic(err) + } + + +Example to Create a Service Client from clouds.yaml + + opts := &clientconfig.ClientOpts{ + Name: "hawaii", + } + + computeClient, err := clientconfig.NewServiceClient("compute", opts) + if err != nil { + panic(err) + } + +*/ +package clientconfig diff --git a/vendor/github.com/gophercloud/utils/openstack/clientconfig/requests.go b/vendor/github.com/gophercloud/utils/openstack/clientconfig/requests.go new file mode 100644 index 000000000..ad1c33d9a --- /dev/null +++ b/vendor/github.com/gophercloud/utils/openstack/clientconfig/requests.go @@ -0,0 +1,804 @@ +package clientconfig + +import ( + "fmt" + "os" + "reflect" + "strings" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack" + + yaml "gopkg.in/yaml.v2" +) + +// AuthType respresents a valid method of authentication. +type AuthType string + +const ( + // AuthPassword defines an unknown version of the password + AuthPassword AuthType = "password" + // AuthToken defined an unknown version of the token + AuthToken AuthType = "token" + + // AuthV2Password defines version 2 of the password + AuthV2Password AuthType = "v2password" + // AuthV2Token defines version 2 of the token + AuthV2Token AuthType = "v2token" + + // AuthV3Password defines version 3 of the password + AuthV3Password AuthType = "v3password" + // AuthV3Token defines version 3 of the token + AuthV3Token AuthType = "v3token" + + // AuthV3ApplicationCredential defines version 3 of the application credential + AuthV3ApplicationCredential AuthType = "v3applicationcredential" +) + +// ClientOpts represents options to customize the way a client is +// configured. +type ClientOpts struct { + // Cloud is the cloud entry in clouds.yaml to use. + Cloud string + + // EnvPrefix allows a custom environment variable prefix to be used. + EnvPrefix string + + // AuthType specifies the type of authentication to use. + // By default, this is "password". + AuthType AuthType + + // AuthInfo defines the authentication information needed to + // authenticate to a cloud when clouds.yaml isn't used. + AuthInfo *AuthInfo + + // RegionName is the region to create a Service Client in. + // This will override a region in clouds.yaml or can be used + // when authenticating directly with AuthInfo. + RegionName string +} + +// LoadCloudsYAML will load a clouds.yaml file and return the full config. +func LoadCloudsYAML() (map[string]Cloud, error) { + content, err := findAndReadCloudsYAML() + if err != nil { + return nil, err + } + + var clouds Clouds + err = yaml.Unmarshal(content, &clouds) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal yaml: %v", err) + } + + return clouds.Clouds, nil +} + +// LoadSecureCloudsYAML will load a secure.yaml file and return the full config. +func LoadSecureCloudsYAML() (map[string]Cloud, error) { + var secureClouds Clouds + + content, err := findAndReadSecureCloudsYAML() + if err != nil { + if err.Error() == "no secure.yaml file found" { + // secure.yaml is optional so just ignore read error + return secureClouds.Clouds, nil + } + return nil, err + } + + err = yaml.Unmarshal(content, &secureClouds) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal yaml: %v", err) + } + + return secureClouds.Clouds, nil +} + +// LoadPublicCloudsYAML will load a public-clouds.yaml file and return the full config. +func LoadPublicCloudsYAML() (map[string]Cloud, error) { + var publicClouds PublicClouds + + content, err := findAndReadPublicCloudsYAML() + if err != nil { + if err.Error() == "no clouds-public.yaml file found" { + // clouds-public.yaml is optional so just ignore read error + return publicClouds.Clouds, nil + } + + return nil, err + } + + err = yaml.Unmarshal(content, &publicClouds) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal yaml: %v", err) + } + + return publicClouds.Clouds, nil +} + +// GetCloudFromYAML will return a cloud entry from a clouds.yaml file. +func GetCloudFromYAML(opts *ClientOpts) (*Cloud, error) { + clouds, err := LoadCloudsYAML() + if err != nil { + return nil, fmt.Errorf("unable to load clouds.yaml: %s", err) + } + + // Determine which cloud to use. + // First see if a cloud name was explicitly set in opts. + var cloudName string + if opts != nil && opts.Cloud != "" { + cloudName = opts.Cloud + } + + // Next see if a cloud name was specified as an environment variable. + // This is supposed to override an explicit opts setting. + envPrefix := "OS_" + if opts.EnvPrefix != "" { + envPrefix = opts.EnvPrefix + } + + if v := os.Getenv(envPrefix + "CLOUD"); v != "" { + cloudName = v + } + + var cloud *Cloud + if cloudName != "" { + v, ok := clouds[cloudName] + if !ok { + return nil, fmt.Errorf("cloud %s does not exist in clouds.yaml", cloudName) + } + cloud = &v + } + + // If a cloud was not specified, and clouds only contains + // a single entry, use that entry. + if cloudName == "" && len(clouds) == 1 { + for _, v := range clouds { + cloud = &v + } + } + + var cloudIsInCloudsYaml bool + if cloud == nil { + // not an immediate error as it might still be defined in secure.yaml + cloudIsInCloudsYaml = false + } else { + cloudIsInCloudsYaml = true + } + + publicClouds, err := LoadPublicCloudsYAML() + if err != nil { + return nil, fmt.Errorf("unable to load clouds-public.yaml: %s", err) + } + + var profileName = defaultIfEmpty(cloud.Profile, cloud.Cloud) + if profileName != "" { + publicCloud, ok := publicClouds[profileName] + if !ok { + return nil, fmt.Errorf("cloud %s does not exist in clouds-public.yaml", profileName) + } + cloud, err = mergeClouds(cloud, publicCloud) + if err != nil { + return nil, fmt.Errorf("Could not merge information from clouds.yaml and clouds-public.yaml for cloud %s", profileName) + } + } + + secureClouds, err := LoadSecureCloudsYAML() + if err != nil { + return nil, fmt.Errorf("unable to load secure.yaml: %s", err) + } + + if secureClouds != nil { + // If no entry was found in clouds.yaml, no cloud name was specified, + // and only one secureCloud entry exists, use that as the cloud entry. + if !cloudIsInCloudsYaml && cloudName == "" && len(secureClouds) == 1 { + for _, v := range secureClouds { + cloud = &v + } + } + + secureCloud, ok := secureClouds[cloudName] + if !ok && cloud == nil { + // cloud == nil serves two purposes here: + // if no entry in clouds.yaml was found and + // if a single-entry secureCloud wasn't used. + // At this point, no entry could be determined at all. + return nil, fmt.Errorf("Could not find cloud %s", cloudName) + } + + // If secureCloud has content and it differs from the cloud entry, + // merge the two together. + if !reflect.DeepEqual((Cloud{}), secureCloud) && !reflect.DeepEqual(cloud, secureCloud) { + cloud, err = mergeClouds(secureCloud, cloud) + if err != nil { + return nil, fmt.Errorf("unable to merge information from clouds.yaml and secure.yaml") + } + } + } + + // Default is to verify SSL API requests + if cloud.Verify == nil { + iTrue := true + cloud.Verify = &iTrue + } + + // TODO: this is where reading vendor files should go be considered when not found in + // clouds-public.yml + // https://github.com/openstack/openstacksdk/tree/master/openstack/config/vendors + + return cloud, nil +} + +// AuthOptions creates a gophercloud.AuthOptions structure with the +// settings found in a specific cloud entry of a clouds.yaml file or +// based on authentication settings given in ClientOpts. +// +// This attempts to be a single point of entry for all OpenStack authentication. +// +// See http://docs.openstack.org/developer/os-client-config and +// https://github.com/openstack/os-client-config/blob/master/os_client_config/config.py. +func AuthOptions(opts *ClientOpts) (*gophercloud.AuthOptions, error) { + cloud := new(Cloud) + + // If no opts were passed in, create an empty ClientOpts. + if opts == nil { + opts = new(ClientOpts) + } + + // Determine if a clouds.yaml entry should be retrieved. + // Start by figuring out the cloud name. + // First check if one was explicitly specified in opts. + var cloudName string + if opts.Cloud != "" { + cloudName = opts.Cloud + } + + // Next see if a cloud name was specified as an environment variable. + envPrefix := "OS_" + if opts.EnvPrefix != "" { + envPrefix = opts.EnvPrefix + } + + if v := os.Getenv(envPrefix + "CLOUD"); v != "" { + cloudName = v + } + + // If a cloud name was determined, try to look it up in clouds.yaml. + if cloudName != "" { + // Get the requested cloud. + var err error + cloud, err = GetCloudFromYAML(opts) + if err != nil { + return nil, err + } + } + + // If cloud.AuthInfo is nil, then no cloud was specified. + if cloud.AuthInfo == nil { + // If opts.Auth is not nil, then try using the auth settings from it. + if opts.AuthInfo != nil { + cloud.AuthInfo = opts.AuthInfo + } + + // If cloud.AuthInfo is still nil, then set it to an empty Auth struct + // and rely on environment variables to do the authentication. + if cloud.AuthInfo == nil { + cloud.AuthInfo = new(AuthInfo) + } + } + + identityAPI := determineIdentityAPI(cloud, opts) + switch identityAPI { + case "2.0", "2": + return v2auth(cloud, opts) + case "3": + return v3auth(cloud, opts) + } + + return nil, fmt.Errorf("Unable to build AuthOptions") +} + +func determineIdentityAPI(cloud *Cloud, opts *ClientOpts) string { + var identityAPI string + if cloud.IdentityAPIVersion != "" { + identityAPI = cloud.IdentityAPIVersion + } + + envPrefix := "OS_" + if opts != nil && opts.EnvPrefix != "" { + envPrefix = opts.EnvPrefix + } + + if v := os.Getenv(envPrefix + "IDENTITY_API_VERSION"); v != "" { + identityAPI = v + } + + if identityAPI == "" { + if cloud.AuthInfo != nil { + if strings.Contains(cloud.AuthInfo.AuthURL, "v2.0") { + identityAPI = "2.0" + } + + if strings.Contains(cloud.AuthInfo.AuthURL, "v3") { + identityAPI = "3" + } + } + } + + if identityAPI == "" { + switch cloud.AuthType { + case AuthV2Password: + identityAPI = "2.0" + case AuthV2Token: + identityAPI = "2.0" + case AuthV3Password: + identityAPI = "3" + case AuthV3Token: + identityAPI = "3" + case AuthV3ApplicationCredential: + identityAPI = "3" + } + } + + // If an Identity API version could not be determined, + // default to v3. + if identityAPI == "" { + identityAPI = "3" + } + + return identityAPI +} + +// v2auth creates a v2-compatible gophercloud.AuthOptions struct. +func v2auth(cloud *Cloud, opts *ClientOpts) (*gophercloud.AuthOptions, error) { + // Environment variable overrides. + envPrefix := "OS_" + if opts != nil && opts.EnvPrefix != "" { + envPrefix = opts.EnvPrefix + } + + if cloud.AuthInfo.AuthURL == "" { + if v := os.Getenv(envPrefix + "AUTH_URL"); v != "" { + cloud.AuthInfo.AuthURL = v + } + } + + if cloud.AuthInfo.Token == "" { + if v := os.Getenv(envPrefix + "TOKEN"); v != "" { + cloud.AuthInfo.Token = v + } + + if v := os.Getenv(envPrefix + "AUTH_TOKEN"); v != "" { + cloud.AuthInfo.Token = v + } + } + + if cloud.AuthInfo.Username == "" { + if v := os.Getenv(envPrefix + "USERNAME"); v != "" { + cloud.AuthInfo.Username = v + } + } + + if cloud.AuthInfo.Password == "" { + if v := os.Getenv(envPrefix + "PASSWORD"); v != "" { + cloud.AuthInfo.Password = v + } + } + + if cloud.AuthInfo.ProjectID == "" { + if v := os.Getenv(envPrefix + "TENANT_ID"); v != "" { + cloud.AuthInfo.ProjectID = v + } + + if v := os.Getenv(envPrefix + "PROJECT_ID"); v != "" { + cloud.AuthInfo.ProjectID = v + } + } + + if cloud.AuthInfo.ProjectName == "" { + if v := os.Getenv(envPrefix + "TENANT_NAME"); v != "" { + cloud.AuthInfo.ProjectName = v + } + + if v := os.Getenv(envPrefix + "PROJECT_NAME"); v != "" { + cloud.AuthInfo.ProjectName = v + } + } + + ao := &gophercloud.AuthOptions{ + IdentityEndpoint: cloud.AuthInfo.AuthURL, + TokenID: cloud.AuthInfo.Token, + Username: cloud.AuthInfo.Username, + Password: cloud.AuthInfo.Password, + TenantID: cloud.AuthInfo.ProjectID, + TenantName: cloud.AuthInfo.ProjectName, + } + + return ao, nil +} + +// v3auth creates a v3-compatible gophercloud.AuthOptions struct. +func v3auth(cloud *Cloud, opts *ClientOpts) (*gophercloud.AuthOptions, error) { + // Environment variable overrides. + envPrefix := "OS_" + if opts != nil && opts.EnvPrefix != "" { + envPrefix = opts.EnvPrefix + } + + if cloud.AuthInfo.AuthURL == "" { + if v := os.Getenv(envPrefix + "AUTH_URL"); v != "" { + cloud.AuthInfo.AuthURL = v + } + } + + if cloud.AuthInfo.Token == "" { + if v := os.Getenv(envPrefix + "TOKEN"); v != "" { + cloud.AuthInfo.Token = v + } + + if v := os.Getenv(envPrefix + "AUTH_TOKEN"); v != "" { + cloud.AuthInfo.Token = v + } + } + + if cloud.AuthInfo.Username == "" { + if v := os.Getenv(envPrefix + "USERNAME"); v != "" { + cloud.AuthInfo.Username = v + } + } + + if cloud.AuthInfo.UserID == "" { + if v := os.Getenv(envPrefix + "USER_ID"); v != "" { + cloud.AuthInfo.UserID = v + } + } + + if cloud.AuthInfo.Password == "" { + if v := os.Getenv(envPrefix + "PASSWORD"); v != "" { + cloud.AuthInfo.Password = v + } + } + + if cloud.AuthInfo.ProjectID == "" { + if v := os.Getenv(envPrefix + "TENANT_ID"); v != "" { + cloud.AuthInfo.ProjectID = v + } + + if v := os.Getenv(envPrefix + "PROJECT_ID"); v != "" { + cloud.AuthInfo.ProjectID = v + } + } + + if cloud.AuthInfo.ProjectName == "" { + if v := os.Getenv(envPrefix + "TENANT_NAME"); v != "" { + cloud.AuthInfo.ProjectName = v + } + + if v := os.Getenv(envPrefix + "PROJECT_NAME"); v != "" { + cloud.AuthInfo.ProjectName = v + } + } + + if cloud.AuthInfo.DomainID == "" { + if v := os.Getenv(envPrefix + "DOMAIN_ID"); v != "" { + cloud.AuthInfo.DomainID = v + } + } + + if cloud.AuthInfo.DomainName == "" { + if v := os.Getenv(envPrefix + "DOMAIN_NAME"); v != "" { + cloud.AuthInfo.DomainName = v + } + } + + if cloud.AuthInfo.DefaultDomain == "" { + if v := os.Getenv(envPrefix + "DEFAULT_DOMAIN"); v != "" { + cloud.AuthInfo.DefaultDomain = v + } + } + + if cloud.AuthInfo.ProjectDomainID == "" { + if v := os.Getenv(envPrefix + "PROJECT_DOMAIN_ID"); v != "" { + cloud.AuthInfo.ProjectDomainID = v + } + } + + if cloud.AuthInfo.ProjectDomainName == "" { + if v := os.Getenv(envPrefix + "PROJECT_DOMAIN_NAME"); v != "" { + cloud.AuthInfo.ProjectDomainName = v + } + } + + if cloud.AuthInfo.UserDomainID == "" { + if v := os.Getenv(envPrefix + "USER_DOMAIN_ID"); v != "" { + cloud.AuthInfo.UserDomainID = v + } + } + + if cloud.AuthInfo.UserDomainName == "" { + if v := os.Getenv(envPrefix + "USER_DOMAIN_NAME"); v != "" { + cloud.AuthInfo.UserDomainName = v + } + } + + if cloud.AuthInfo.ApplicationCredentialID == "" { + if v := os.Getenv(envPrefix + "APPLICATION_CREDENTIAL_ID"); v != "" { + cloud.AuthInfo.ApplicationCredentialID = v + } + } + + if cloud.AuthInfo.ApplicationCredentialName == "" { + if v := os.Getenv(envPrefix + "APPLICATION_CREDENTIAL_NAME"); v != "" { + cloud.AuthInfo.ApplicationCredentialName = v + } + } + + if cloud.AuthInfo.ApplicationCredentialSecret == "" { + if v := os.Getenv(envPrefix + "APPLICATION_CREDENTIAL_SECRET"); v != "" { + cloud.AuthInfo.ApplicationCredentialSecret = v + } + } + + // Build a scope and try to do it correctly. + // https://github.com/openstack/os-client-config/blob/master/os_client_config/config.py#L595 + scope := new(gophercloud.AuthScope) + + // Application credentials don't support scope + if isApplicationCredential(cloud.AuthInfo) { + // If Domain* is set, but UserDomain* or ProjectDomain* aren't, + // then use Domain* as the default setting. + cloud = setDomainIfNeeded(cloud) + } else { + if !isProjectScoped(cloud.AuthInfo) { + if cloud.AuthInfo.DomainID != "" { + scope.DomainID = cloud.AuthInfo.DomainID + } else if cloud.AuthInfo.DomainName != "" { + scope.DomainName = cloud.AuthInfo.DomainName + } + } else { + // If Domain* is set, but UserDomain* or ProjectDomain* aren't, + // then use Domain* as the default setting. + cloud = setDomainIfNeeded(cloud) + + if cloud.AuthInfo.ProjectID != "" { + scope.ProjectID = cloud.AuthInfo.ProjectID + } else { + scope.ProjectName = cloud.AuthInfo.ProjectName + scope.DomainID = cloud.AuthInfo.ProjectDomainID + scope.DomainName = cloud.AuthInfo.ProjectDomainName + } + } + } + + ao := &gophercloud.AuthOptions{ + Scope: scope, + IdentityEndpoint: cloud.AuthInfo.AuthURL, + TokenID: cloud.AuthInfo.Token, + Username: cloud.AuthInfo.Username, + UserID: cloud.AuthInfo.UserID, + Password: cloud.AuthInfo.Password, + TenantID: cloud.AuthInfo.ProjectID, + TenantName: cloud.AuthInfo.ProjectName, + DomainID: cloud.AuthInfo.UserDomainID, + DomainName: cloud.AuthInfo.UserDomainName, + ApplicationCredentialID: cloud.AuthInfo.ApplicationCredentialID, + ApplicationCredentialName: cloud.AuthInfo.ApplicationCredentialName, + ApplicationCredentialSecret: cloud.AuthInfo.ApplicationCredentialSecret, + } + + // If an auth_type of "token" was specified, then make sure + // Gophercloud properly authenticates with a token. This involves + // unsetting a few other auth options. The reason this is done + // here is to wait until all auth settings (both in clouds.yaml + // and via environment variables) are set and then unset them. + if strings.Contains(string(cloud.AuthType), "token") || ao.TokenID != "" { + ao.Username = "" + ao.Password = "" + ao.UserID = "" + ao.DomainID = "" + ao.DomainName = "" + } + + // Check for absolute minimum requirements. + if ao.IdentityEndpoint == "" { + err := gophercloud.ErrMissingInput{Argument: "auth_url"} + return nil, err + } + + return ao, nil +} + +// AuthenticatedClient is a convenience function to get a new provider client +// based on a clouds.yaml entry. +func AuthenticatedClient(opts *ClientOpts) (*gophercloud.ProviderClient, error) { + ao, err := AuthOptions(opts) + if err != nil { + return nil, err + } + + return openstack.AuthenticatedClient(*ao) +} + +// NewServiceClient is a convenience function to get a new service client. +func NewServiceClient(service string, opts *ClientOpts) (*gophercloud.ServiceClient, error) { + cloud := new(Cloud) + + // If no opts were passed in, create an empty ClientOpts. + if opts == nil { + opts = new(ClientOpts) + } + + // Determine if a clouds.yaml entry should be retrieved. + // Start by figuring out the cloud name. + // First check if one was explicitly specified in opts. + var cloudName string + if opts.Cloud != "" { + cloudName = opts.Cloud + } + + // Next see if a cloud name was specified as an environment variable. + envPrefix := "OS_" + if opts.EnvPrefix != "" { + envPrefix = opts.EnvPrefix + } + + if v := os.Getenv(envPrefix + "CLOUD"); v != "" { + cloudName = v + } + + // If a cloud name was determined, try to look it up in clouds.yaml. + if cloudName != "" { + // Get the requested cloud. + var err error + cloud, err = GetCloudFromYAML(opts) + if err != nil { + return nil, err + } + } + + // Get a Provider Client + pClient, err := AuthenticatedClient(opts) + if err != nil { + return nil, err + } + + // Determine the region to use. + // First, check if the REGION_NAME environment variable is set. + var region string + if v := os.Getenv(envPrefix + "REGION_NAME"); v != "" { + region = v + } + + // Next, check if the cloud entry sets a region. + if v := cloud.RegionName; v != "" { + region = v + } + + // Finally, see if one was specified in the ClientOpts. + // If so, this takes precedence. + if v := opts.RegionName; v != "" { + region = v + } + + eo := gophercloud.EndpointOpts{ + Region: region, + } + + switch service { + case "clustering": + return openstack.NewClusteringV1(pClient, eo) + case "compute": + return openstack.NewComputeV2(pClient, eo) + case "container": + return openstack.NewContainerV1(pClient, eo) + case "database": + return openstack.NewDBV1(pClient, eo) + case "dns": + return openstack.NewDNSV2(pClient, eo) + case "identity": + identityVersion := "3" + if v := cloud.IdentityAPIVersion; v != "" { + identityVersion = v + } + + switch identityVersion { + case "v2", "2", "2.0": + return openstack.NewIdentityV2(pClient, eo) + case "v3", "3": + return openstack.NewIdentityV3(pClient, eo) + default: + return nil, fmt.Errorf("invalid identity API version") + } + case "image": + return openstack.NewImageServiceV2(pClient, eo) + case "load-balancer": + return openstack.NewLoadBalancerV2(pClient, eo) + case "network": + return openstack.NewNetworkV2(pClient, eo) + case "object-store": + return openstack.NewObjectStorageV1(pClient, eo) + case "orchestration": + return openstack.NewOrchestrationV1(pClient, eo) + case "sharev2": + return openstack.NewSharedFileSystemV2(pClient, eo) + case "volume": + volumeVersion := "2" + if v := cloud.VolumeAPIVersion; v != "" { + volumeVersion = v + } + + switch volumeVersion { + case "v1", "1": + return openstack.NewBlockStorageV1(pClient, eo) + case "v2", "2": + return openstack.NewBlockStorageV2(pClient, eo) + case "v3", "3": + return openstack.NewBlockStorageV3(pClient, eo) + default: + return nil, fmt.Errorf("invalid volume API version") + } + } + + return nil, fmt.Errorf("unable to create a service client for %s", service) +} + +// isProjectScoped determines if an auth struct is project scoped. +func isProjectScoped(authInfo *AuthInfo) bool { + if authInfo.ProjectID == "" && authInfo.ProjectName == "" { + return false + } + + return true +} + +// setDomainIfNeeded will set a DomainID and DomainName +// to ProjectDomain* and UserDomain* if not already set. +func setDomainIfNeeded(cloud *Cloud) *Cloud { + if cloud.AuthInfo.DomainID != "" { + if cloud.AuthInfo.UserDomainID == "" { + cloud.AuthInfo.UserDomainID = cloud.AuthInfo.DomainID + } + + if cloud.AuthInfo.ProjectDomainID == "" { + cloud.AuthInfo.ProjectDomainID = cloud.AuthInfo.DomainID + } + + cloud.AuthInfo.DomainID = "" + } + + if cloud.AuthInfo.DomainName != "" { + if cloud.AuthInfo.UserDomainName == "" { + cloud.AuthInfo.UserDomainName = cloud.AuthInfo.DomainName + } + + if cloud.AuthInfo.ProjectDomainName == "" { + cloud.AuthInfo.ProjectDomainName = cloud.AuthInfo.DomainName + } + + cloud.AuthInfo.DomainName = "" + } + + // If Domain fields are still not set, and if DefaultDomain has a value, + // set UserDomainID and ProjectDomainID to DefaultDomain. + // https://github.com/openstack/osc-lib/blob/86129e6f88289ef14bfaa3f7c9cdfbea8d9fc944/osc_lib/cli/client_config.py#L117-L146 + if cloud.AuthInfo.DefaultDomain != "" { + if cloud.AuthInfo.UserDomainName == "" && cloud.AuthInfo.UserDomainID == "" { + cloud.AuthInfo.UserDomainID = cloud.AuthInfo.DefaultDomain + } + + if cloud.AuthInfo.ProjectDomainName == "" && cloud.AuthInfo.ProjectDomainID == "" { + cloud.AuthInfo.ProjectDomainID = cloud.AuthInfo.DefaultDomain + } + } + + return cloud +} + +// isApplicationCredential determines if an application credential is used to auth. +func isApplicationCredential(authInfo *AuthInfo) bool { + if authInfo.ApplicationCredentialID == "" && authInfo.ApplicationCredentialName == "" && authInfo.ApplicationCredentialSecret == "" { + return false + } + return true +} diff --git a/vendor/github.com/gophercloud/utils/openstack/clientconfig/results.go b/vendor/github.com/gophercloud/utils/openstack/clientconfig/results.go new file mode 100644 index 000000000..292ff5dc4 --- /dev/null +++ b/vendor/github.com/gophercloud/utils/openstack/clientconfig/results.go @@ -0,0 +1,121 @@ +package clientconfig + +// PublicClouds represents a collection of PublicCloud entries in clouds-public.yaml file. +// The format of the clouds-public.yml is documented at +// https://docs.openstack.org/python-openstackclient/latest/configuration/ +type PublicClouds struct { + Clouds map[string]Cloud `yaml:"public-clouds" json:"public-clouds"` +} + +// Clouds represents a collection of Cloud entries in a clouds.yaml file. +// The format of clouds.yaml is documented at +// https://docs.openstack.org/os-client-config/latest/user/configuration.html. +type Clouds struct { + Clouds map[string]Cloud `yaml:"clouds" json:"clouds"` +} + +// Cloud represents an entry in a clouds.yaml/public-clouds.yaml/secure.yaml file. +type Cloud struct { + Cloud string `yaml:"cloud" json:"cloud"` + Profile string `yaml:"profile" json:"profile"` + AuthInfo *AuthInfo `yaml:"auth" json:"auth"` + AuthType AuthType `yaml:"auth_type" json:"auth_type"` + RegionName string `yaml:"region_name" json:"region_name"` + Regions []interface{} `yaml:"regions" json:"regions"` + + // API Version overrides. + IdentityAPIVersion string `yaml:"identity_api_version" json:"identity_api_version"` + VolumeAPIVersion string `yaml:"volume_api_version" json:"volume_api_version"` + + // Verify whether or not SSL API requests should be verified. + Verify *bool `yaml:"verify" json:"verify"` + + // CACertFile a path to a CA Cert bundle that can be used as part of + // verifying SSL API requests. + CACertFile string `yaml:"cacert" json:"cacert"` + + // ClientCertFile a path to a client certificate to use as part of the SSL + // transaction. + ClientCertFile string `yaml:"cert" json:"cert"` + + // ClientKeyFile a path to a client key to use as part of the SSL + // transaction. + ClientKeyFile string `yaml:"key" json:"key"` +} + +// AuthInfo represents the auth section of a cloud entry or +// auth options entered explicitly in ClientOpts. +type AuthInfo struct { + // AuthURL is the keystone/identity endpoint URL. + AuthURL string `yaml:"auth_url" json:"auth_url"` + + // Token is a pre-generated authentication token. + Token string `yaml:"token" json:"token"` + + // Username is the username of the user. + Username string `yaml:"username" json:"username"` + + // UserID is the unique ID of a user. + UserID string `yaml:"user_id" json:"user_id"` + + // Password is the password of the user. + Password string `yaml:"password" json:"password"` + + // Application Credential ID to login with. + ApplicationCredentialID string `yaml:"application_credential_id" json:"application_credential_id"` + + // Application Credential name to login with. + ApplicationCredentialName string `yaml:"application_credential_name" json:"application_credential_name"` + + // Application Credential secret to login with. + ApplicationCredentialSecret string `yaml:"application_credential_secret" json:"application_credential_secret"` + + // ProjectName is the common/human-readable name of a project. + // Users can be scoped to a project. + // ProjectName on its own is not enough to ensure a unique scope. It must + // also be combined with either a ProjectDomainName or ProjectDomainID. + // ProjectName cannot be combined with ProjectID in a scope. + ProjectName string `yaml:"project_name" json:"project_name"` + + // ProjectID is the unique ID of a project. + // It can be used to scope a user to a specific project. + ProjectID string `yaml:"project_id" json:"project_id"` + + // UserDomainName is the name of the domain where a user resides. + // It is used to identify the source domain of a user. + UserDomainName string `yaml:"user_domain_name" json:"user_domain_name"` + + // UserDomainID is the unique ID of the domain where a user resides. + // It is used to identify the source domain of a user. + UserDomainID string `yaml:"user_domain_id" json:"user_domain_id"` + + // ProjectDomainName is the name of the domain where a project resides. + // It is used to identify the source domain of a project. + // ProjectDomainName can be used in addition to a ProjectName when scoping + // a user to a specific project. + ProjectDomainName string `yaml:"project_domain_name" json:"project_domain_name"` + + // ProjectDomainID is the name of the domain where a project resides. + // It is used to identify the source domain of a project. + // ProjectDomainID can be used in addition to a ProjectName when scoping + // a user to a specific project. + ProjectDomainID string `yaml:"project_domain_id" json:"project_domain_id"` + + // DomainName is the name of a domain which can be used to identify the + // source domain of either a user or a project. + // If UserDomainName and ProjectDomainName are not specified, then DomainName + // is used as a default choice. + // It can also be used be used to specify a domain-only scope. + DomainName string `yaml:"domain_name" json:"domain_name"` + + // DomainID is the unique ID of a domain which can be used to identify the + // source domain of eitehr a user or a project. + // If UserDomainID and ProjectDomainID are not specified, then DomainID is + // used as a default choice. + // It can also be used be used to specify a domain-only scope. + DomainID string `yaml:"domain_id" json:"domain_id"` + + // DefaultDomain is the domain ID to fall back on if no other domain has + // been specified and a domain is required for scope. + DefaultDomain string `yaml:"default_domain" json:"default_domain"` +} diff --git a/vendor/github.com/gophercloud/utils/openstack/clientconfig/utils.go b/vendor/github.com/gophercloud/utils/openstack/clientconfig/utils.go new file mode 100644 index 000000000..884e5644e --- /dev/null +++ b/vendor/github.com/gophercloud/utils/openstack/clientconfig/utils.go @@ -0,0 +1,155 @@ +package clientconfig + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "os/user" + "path/filepath" + "reflect" +) + +// defaultIfEmpty is a helper function to make it cleaner to set default value +// for strings. +func defaultIfEmpty(value string, defaultValue string) string { + if value == "" { + return defaultValue + } + return value +} + +// mergeCLouds merges two Clouds recursively (the AuthInfo also gets merged). +// In case both Clouds define a value, the value in the 'override' cloud takes precedence +func mergeClouds(override, cloud interface{}) (*Cloud, error) { + overrideJson, err := json.Marshal(override) + if err != nil { + return nil, err + } + cloudJson, err := json.Marshal(cloud) + if err != nil { + return nil, err + } + var overrideInterface interface{} + err = json.Unmarshal(overrideJson, &overrideInterface) + if err != nil { + return nil, err + } + var cloudInterface interface{} + err = json.Unmarshal(cloudJson, &cloudInterface) + if err != nil { + return nil, err + } + var mergedCloud Cloud + mergedInterface := mergeInterfaces(overrideInterface, cloudInterface) + mergedJson, err := json.Marshal(mergedInterface) + json.Unmarshal(mergedJson, &mergedCloud) + return &mergedCloud, nil +} + +// merges two interfaces. In cases where a value is defined for both 'overridingInterface' and +// 'inferiorInterface' the value in 'overridingInterface' will take precedence. +func mergeInterfaces(overridingInterface, inferiorInterface interface{}) interface{} { + switch overriding := overridingInterface.(type) { + case map[string]interface{}: + interfaceMap, ok := inferiorInterface.(map[string]interface{}) + if !ok { + return overriding + } + for k, v := range interfaceMap { + if overridingValue, ok := overriding[k]; ok { + overriding[k] = mergeInterfaces(overridingValue, v) + } else { + overriding[k] = v + } + } + case []interface{}: + list, ok := inferiorInterface.([]interface{}) + if !ok { + return overriding + } + for i := range list { + overriding = append(overriding, list[i]) + } + return overriding + case nil: + // mergeClouds(nil, map[string]interface{...}) -> map[string]interface{...} + v, ok := inferiorInterface.(map[string]interface{}) + if ok { + return v + } + } + // We don't want to override with empty values + if reflect.DeepEqual(overridingInterface, nil) || reflect.DeepEqual(reflect.Zero(reflect.TypeOf(overridingInterface)).Interface(), overridingInterface) { + return inferiorInterface + } else { + return overridingInterface + } +} + +// findAndReadCloudsYAML attempts to locate a clouds.yaml file in the following +// locations: +// +// 1. OS_CLIENT_CONFIG_FILE +// 2. Current directory. +// 3. unix-specific user_config_dir (~/.config/openstack/clouds.yaml) +// 4. unix-specific site_config_dir (/etc/openstack/clouds.yaml) +// +// If found, the contents of the file is returned. +func findAndReadCloudsYAML() ([]byte, error) { + // OS_CLIENT_CONFIG_FILE + if v := os.Getenv("OS_CLIENT_CONFIG_FILE"); v != "" { + if ok := fileExists(v); ok { + return ioutil.ReadFile(v) + } + } + + return findAndReadYAML("clouds.yaml") +} + +func findAndReadPublicCloudsYAML() ([]byte, error) { + return findAndReadYAML("clouds-public.yaml") +} + +func findAndReadSecureCloudsYAML() ([]byte, error) { + return findAndReadYAML("secure.yaml") +} + +func findAndReadYAML(yamlFile string) ([]byte, error) { + // current directory + cwd, err := os.Getwd() + if err != nil { + return nil, fmt.Errorf("unable to determine working directory: %s", err) + } + + filename := filepath.Join(cwd, yamlFile) + if ok := fileExists(filename); ok { + return ioutil.ReadFile(filename) + } + + // unix user config directory: ~/.config/openstack. + if currentUser, err := user.Current(); err == nil { + homeDir := currentUser.HomeDir + if homeDir != "" { + filename := filepath.Join(homeDir, ".config/openstack/"+yamlFile) + if ok := fileExists(filename); ok { + return ioutil.ReadFile(filename) + } + } + } + + // unix-specific site config directory: /etc/openstack. + if ok := fileExists("/etc/openstack/" + yamlFile); ok { + return ioutil.ReadFile("/etc/openstack/" + yamlFile) + } + + return nil, fmt.Errorf("no " + yamlFile + " file found") +} + +// fileExists checks for the existence of a file at a given location. +func fileExists(filename string) bool { + if _, err := os.Stat(filename); err == nil { + return true + } + return false +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/blockstorage_snapshot_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/blockstorage_snapshot_v2.go new file mode 100644 index 000000000..7274b51cf --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/blockstorage_snapshot_v2.go @@ -0,0 +1,31 @@ +package openstack + +import ( + "sort" + + "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots" +) + +// blockStorageV2SnapshotSort represents a sortable slice of block storage +// v2 snapshots. +type blockStorageV2SnapshotSort []snapshots.Snapshot + +func (snaphot blockStorageV2SnapshotSort) Len() int { + return len(snaphot) +} + +func (snaphot blockStorageV2SnapshotSort) Swap(i, j int) { + snaphot[i], snaphot[j] = snaphot[j], snaphot[i] +} + +func (snaphot blockStorageV2SnapshotSort) Less(i, j int) bool { + itime := snaphot[i].CreatedAt + jtime := snaphot[j].CreatedAt + return itime.Unix() < jtime.Unix() +} + +func dataSourceBlockStorageV2MostRecentSnapshot(snapshots []snapshots.Snapshot) snapshots.Snapshot { + sortedSnapshots := snapshots + sort.Sort(blockStorageV2SnapshotSort(sortedSnapshots)) + return sortedSnapshots[len(sortedSnapshots)-1] +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/blockstorage_snapshot_v3.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/blockstorage_snapshot_v3.go new file mode 100644 index 000000000..4ea63c8d5 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/blockstorage_snapshot_v3.go @@ -0,0 +1,31 @@ +package openstack + +import ( + "sort" + + "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots" +) + +// blockStorageV3SnapshotSort represents a sortable slice of block storage +// v3 snapshots. +type blockStorageV3SnapshotSort []snapshots.Snapshot + +func (snaphot blockStorageV3SnapshotSort) Len() int { + return len(snaphot) +} + +func (snaphot blockStorageV3SnapshotSort) Swap(i, j int) { + snaphot[i], snaphot[j] = snaphot[j], snaphot[i] +} + +func (snaphot blockStorageV3SnapshotSort) Less(i, j int) bool { + itime := snaphot[i].CreatedAt + jtime := snaphot[j].CreatedAt + return itime.Unix() < jtime.Unix() +} + +func dataSourceBlockStorageV3MostRecentSnapshot(snapshots []snapshots.Snapshot) snapshots.Snapshot { + sortedSnapshots := snapshots + sort.Sort(blockStorageV3SnapshotSort(sortedSnapshots)) + return sortedSnapshots[len(sortedSnapshots)-1] +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/blockstorage_volume_attach_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/blockstorage_volume_attach_v2.go new file mode 100644 index 000000000..e6922cc0d --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/blockstorage_volume_attach_v2.go @@ -0,0 +1,35 @@ +package openstack + +import ( + "fmt" + "strings" + + "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions" +) + +func expandBlockStorageV2AttachMode(v string) (volumeactions.AttachMode, error) { + var attachMode volumeactions.AttachMode + var attachError error + + switch v { + case "": + attachMode = "" + case "ro": + attachMode = volumeactions.ReadOnly + case "rw": + attachMode = volumeactions.ReadWrite + default: + attachError = fmt.Errorf("Invalid attach_mode specified") + } + + return attachMode, attachError +} + +func blockStorageVolumeAttachV2ParseID(id string) (string, string, error) { + parts := strings.Split(id, "/") + if len(parts) < 2 { + return "", "", fmt.Errorf("Unable to determine openstack_blockstorage_volume_attach_v2 ID") + } + + return parts[0], parts[1], nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/blockstorage_volume_attach_v3.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/blockstorage_volume_attach_v3.go new file mode 100644 index 000000000..f547a8137 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/blockstorage_volume_attach_v3.go @@ -0,0 +1,35 @@ +package openstack + +import ( + "fmt" + "strings" + + "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions" +) + +func expandBlockStorageV3AttachMode(v string) (volumeactions.AttachMode, error) { + var attachMode volumeactions.AttachMode + var attachError error + + switch v { + case "": + attachMode = "" + case "ro": + attachMode = volumeactions.ReadOnly + case "rw": + attachMode = volumeactions.ReadWrite + default: + attachError = fmt.Errorf("Invalid attach_mode specified") + } + + return attachMode, attachError +} + +func blockStorageVolumeAttachV3ParseID(id string) (string, string, error) { + parts := strings.Split(id, "/") + if len(parts) < 2 { + return "", "", fmt.Errorf("Unable to determine openstack_blockstorage_volume_attach_v3 ID") + } + + return parts[0], parts[1], nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/blockstorage_volume_v1.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/blockstorage_volume_v1.go new file mode 100644 index 000000000..91f9be43d --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/blockstorage_volume_v1.go @@ -0,0 +1,54 @@ +package openstack + +import ( + "bytes" + "fmt" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes" + + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/resource" +) + +func flattenBlockStorageVolumeV1Attachments(v []map[string]interface{}) []map[string]interface{} { + attachments := make([]map[string]interface{}, len(v)) + for i, attachment := range v { + attachments[i] = make(map[string]interface{}) + attachments[i]["id"] = attachment["id"] + attachments[i]["instance_id"] = attachment["server_id"] + attachments[i]["device"] = attachment["device"] + } + + return attachments +} + +func blockStorageVolumeV1StateRefreshFunc(client *gophercloud.ServiceClient, volumeID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + v, err := volumes.Get(client, volumeID).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + return v, "deleted", nil + } + + return nil, "", err + } + + if v.Status == "error" { + return v, v.Status, fmt.Errorf("The volume is in error status. " + + "Please check with your cloud admin or check the Block Storage " + + "API logs to see why this error occurred.") + } + + return v, v.Status, nil + } +} + +func blockStorageVolumeV1AttachmentHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + if m["instance_id"] != nil { + buf.WriteString(fmt.Sprintf("%s-", m["instance_id"].(string))) + } + return hashcode.String(buf.String()) +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/blockstorage_volume_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/blockstorage_volume_v2.go new file mode 100644 index 000000000..4afb8027e --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/blockstorage_volume_v2.go @@ -0,0 +1,54 @@ +package openstack + +import ( + "bytes" + "fmt" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes" + + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/resource" +) + +func flattenBlockStorageVolumeV2Attachments(v []volumes.Attachment) []map[string]interface{} { + attachments := make([]map[string]interface{}, len(v)) + for i, attachment := range v { + attachments[i] = make(map[string]interface{}) + attachments[i]["id"] = attachment.ID + attachments[i]["instance_id"] = attachment.ServerID + attachments[i]["device"] = attachment.Device + } + + return attachments +} + +func blockStorageVolumeV2StateRefreshFunc(client *gophercloud.ServiceClient, volumeID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + v, err := volumes.Get(client, volumeID).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + return v, "deleted", nil + } + + return nil, "", err + } + + if v.Status == "error" { + return v, v.Status, fmt.Errorf("The volume is in error status. " + + "Please check with your cloud admin or check the Block Storage " + + "API logs to see why this error occurred.") + } + + return v, v.Status, nil + } +} + +func blockStorageVolumeV2AttachmentHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + if m["instance_id"] != nil { + buf.WriteString(fmt.Sprintf("%s-", m["instance_id"].(string))) + } + return hashcode.String(buf.String()) +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/blockstorage_volume_v3.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/blockstorage_volume_v3.go new file mode 100644 index 000000000..afa71ad7b --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/blockstorage_volume_v3.go @@ -0,0 +1,54 @@ +package openstack + +import ( + "bytes" + "fmt" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes" + + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/resource" +) + +func flattenBlockStorageVolumeV3Attachments(v []volumes.Attachment) []map[string]interface{} { + attachments := make([]map[string]interface{}, len(v)) + for i, attachment := range v { + attachments[i] = make(map[string]interface{}) + attachments[i]["id"] = attachment.ID + attachments[i]["instance_id"] = attachment.ServerID + attachments[i]["device"] = attachment.Device + } + + return attachments +} + +func blockStorageVolumeV3StateRefreshFunc(client *gophercloud.ServiceClient, volumeID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + v, err := volumes.Get(client, volumeID).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + return v, "deleted", nil + } + + return nil, "", err + } + + if v.Status == "error" { + return v, v.Status, fmt.Errorf("The volume is in error status. " + + "Please check with your cloud admin or check the Block Storage " + + "API logs to see why this error occurred.") + } + + return v, v.Status, nil + } +} + +func blockStorageVolumeV3AttachmentHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + if m["instance_id"] != nil { + buf.WriteString(fmt.Sprintf("%s-", m["instance_id"].(string))) + } + return hashcode.String(buf.String()) +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/compute_flavor_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/compute_flavor_v2.go new file mode 100644 index 000000000..322db2981 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/compute_flavor_v2.go @@ -0,0 +1,14 @@ +package openstack + +import ( + "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors" +) + +func expandComputeFlavorV2ExtraSpecs(raw map[string]interface{}) flavors.ExtraSpecsOpts { + extraSpecs := make(flavors.ExtraSpecsOpts, len(raw)) + for k, v := range raw { + extraSpecs[k] = v.(string) + } + + return extraSpecs +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/compute_instance_v2_networking.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/compute_instance_v2_networking.go new file mode 100644 index 000000000..931057d82 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/compute_instance_v2_networking.go @@ -0,0 +1,530 @@ +// This set of code handles all functions required to configure networking +// on an openstack_compute_instance_v2 resource. +// +// This is a complicated task because it's not possible to obtain all +// information in a single API call. In fact, it even traverses multiple +// OpenStack services. +// +// The end result, from the user's point of view, is a structured set of +// understandable network information within the instance resource. +package openstack + +import ( + "fmt" + "log" + "os" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/tenantnetworks" + "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" + "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" + "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" + "github.com/hashicorp/terraform/helper/schema" +) + +// InstanceNIC is a structured representation of a Gophercloud servers.Server +// virtual NIC. +type InstanceNIC struct { + FixedIPv4 string + FixedIPv6 string + MAC string +} + +// InstanceAddresses is a collection of InstanceNICs, grouped by the +// network name. An instance/server could have multiple NICs on the same +// network. +type InstanceAddresses struct { + NetworkName string + InstanceNICs []InstanceNIC +} + +// InstanceNetwork represents a collection of network information that a +// Terraform instance needs to satisfy all network information requirements. +type InstanceNetwork struct { + UUID string + Name string + Port string + FixedIP string + AccessNetwork bool +} + +// getAllInstanceNetworks loops through the networks defined in the Terraform +// configuration and structures that information into something standard that +// can be consumed by both OpenStack and Terraform. +// +// This would be simple, except we have ensure both the network name and +// network ID have been determined. This isn't just for the convenience of a +// user specifying a human-readable network name, but the network information +// returned by an OpenStack instance only has the network name set! So if a +// user specified a network ID, there's no way to correlate it to the instance +// unless we know both the name and ID. +// +// Not only that, but we have to account for two OpenStack network services +// running: nova-network (legacy) and Neutron (current). +// +// In addition, if a port was specified, not all of the port information +// will be displayed, such as multiple fixed and floating IPs. This resource +// isn't currently configured for that type of flexibility. It's better to +// reference the actual port resource itself. +// +// So, let's begin the journey. +func getAllInstanceNetworks(d *schema.ResourceData, meta interface{}) ([]InstanceNetwork, error) { + var instanceNetworks []InstanceNetwork + + networks := d.Get("network").([]interface{}) + for _, v := range networks { + network := v.(map[string]interface{}) + networkID := network["uuid"].(string) + networkName := network["name"].(string) + portID := network["port"].(string) + + if networkID == "" && networkName == "" && portID == "" { + return nil, fmt.Errorf( + "At least one of network.uuid, network.name, or network.port must be set.") + } + + // If a user specified both an ID and name, that makes things easy + // since both name and ID are already satisfied. No need to query + // further. + if networkID != "" && networkName != "" { + v := InstanceNetwork{ + UUID: networkID, + Name: networkName, + Port: portID, + FixedIP: network["fixed_ip_v4"].(string), + AccessNetwork: network["access_network"].(bool), + } + instanceNetworks = append(instanceNetworks, v) + continue + } + + // But if at least one of name or ID was missing, we have to query + // for that other piece. + // + // Priority is given to a port since a network ID or name usually isn't + // specified when using a port. + // + // Next priority is given to the network ID since it's guaranteed to be + // an exact match. + queryType := "name" + queryTerm := networkName + if networkID != "" { + queryType = "id" + queryTerm = networkID + } + if portID != "" { + queryType = "port" + queryTerm = portID + } + + networkInfo, err := getInstanceNetworkInfo(d, meta, queryType, queryTerm) + if err != nil { + return nil, err + } + + v := InstanceNetwork{ + Port: portID, + FixedIP: network["fixed_ip_v4"].(string), + AccessNetwork: network["access_network"].(bool), + } + if networkInfo["uuid"] != nil { + v.UUID = networkInfo["uuid"].(string) + } + if networkInfo["name"] != nil { + v.Name = networkInfo["name"].(string) + } + + instanceNetworks = append(instanceNetworks, v) + } + + log.Printf("[DEBUG] getAllInstanceNetworks: %#v", instanceNetworks) + return instanceNetworks, nil +} + +// getInstanceNetworkInfo will query for network information in order to make +// an accurate determination of a network's name and a network's ID. +// +// We will try to first query the Neutron network service and fall back to the +// legacy nova-network service if that fails. +// +// If OS_NOVA_NETWORK is set, query nova-network even if Neutron is available. +// This is to be able to explicitly test the nova-network API. +func getInstanceNetworkInfo( + d *schema.ResourceData, meta interface{}, queryType, queryTerm string) (map[string]interface{}, error) { + + config := meta.(*Config) + + if _, ok := os.LookupEnv("OS_NOVA_NETWORK"); !ok { + networkClient, err := config.networkingV2Client(GetRegion(d, config)) + if err == nil { + networkInfo, err := getInstanceNetworkInfoNeutron(networkClient, queryType, queryTerm) + if err != nil { + return nil, fmt.Errorf("Error trying to get network information from the Network API: %s", err) + } + + return networkInfo, nil + } + } + + log.Printf("[DEBUG] Unable to obtain a network client") + + computeClient, err := config.computeV2Client(GetRegion(d, config)) + if err != nil { + return nil, fmt.Errorf("Error creating OpenStack compute client: %s", err) + } + + networkInfo, err := getInstanceNetworkInfoNovaNet(computeClient, queryType, queryTerm) + if err != nil { + return nil, fmt.Errorf("Error trying to get network information from the Nova API: %s", err) + } + + return networkInfo, nil +} + +// getInstanceNetworkInfoNovaNet will query the os-tenant-networks API for +// the network information. +func getInstanceNetworkInfoNovaNet( + client *gophercloud.ServiceClient, queryType, queryTerm string) (map[string]interface{}, error) { + + // If somehow a port ended up here, we should just error out. + if queryType == "port" { + return nil, fmt.Errorf( + "Unable to query a port (%s) using the Nova API", queryTerm) + } + + // test to see if the tenantnetworks api is available + log.Printf("[DEBUG] testing for os-tenant-networks") + tenantNetworksAvailable := true + + allPages, err := tenantnetworks.List(client).AllPages() + if err != nil { + switch err.(type) { + case gophercloud.ErrDefault404: + tenantNetworksAvailable = false + case gophercloud.ErrDefault403: + tenantNetworksAvailable = false + case gophercloud.ErrUnexpectedResponseCode: + tenantNetworksAvailable = false + default: + return nil, fmt.Errorf( + "An error occurred while querying the Nova API for network information: %s", err) + } + } + + if !tenantNetworksAvailable { + // we can't query the APIs for more information, but in some cases + // the information provided is enough + log.Printf("[DEBUG] os-tenant-networks disabled.") + return map[string]interface{}{queryType: queryTerm}, nil + } + + networkList, err := tenantnetworks.ExtractNetworks(allPages) + if err != nil { + return nil, fmt.Errorf( + "An error occurred while querying the Nova API for network information: %s", err) + } + + var networkFound bool + var network tenantnetworks.Network + + for _, v := range networkList { + if queryType == "id" && v.ID == queryTerm { + networkFound = true + network = v + break + } + + if queryType == "name" && v.Name == queryTerm { + networkFound = true + network = v + break + } + } + + if networkFound { + v := map[string]interface{}{ + "uuid": network.ID, + "name": network.Name, + } + + log.Printf("[DEBUG] getInstanceNetworkInfoNovaNet: %#v", v) + return v, nil + } + + return nil, fmt.Errorf("Could not find any matching network for %s %s", queryType, queryTerm) +} + +// getInstanceNetworkInfoNeutron will query the neutron API for the network +// information. +func getInstanceNetworkInfoNeutron( + client *gophercloud.ServiceClient, queryType, queryTerm string) (map[string]interface{}, error) { + + // If a port was specified, use it to look up the network ID + // and then query the network as if a network ID was originally used. + if queryType == "port" { + listOpts := ports.ListOpts{ + ID: queryTerm, + } + allPages, err := ports.List(client, listOpts).AllPages() + if err != nil { + return nil, fmt.Errorf("Unable to retrieve networks from the Network API: %s", err) + } + + allPorts, err := ports.ExtractPorts(allPages) + if err != nil { + return nil, fmt.Errorf("Unable to retrieve networks from the Network API: %s", err) + } + + var port ports.Port + switch len(allPorts) { + case 0: + return nil, fmt.Errorf("Could not find any matching port for %s %s", queryType, queryTerm) + case 1: + port = allPorts[0] + default: + return nil, fmt.Errorf("More than one port found for %s %s", queryType, queryTerm) + } + + queryType = "id" + queryTerm = port.NetworkID + } + + listOpts := networks.ListOpts{ + Status: "ACTIVE", + } + + switch queryType { + case "name": + listOpts.Name = queryTerm + default: + listOpts.ID = queryTerm + } + + allPages, err := networks.List(client, listOpts).AllPages() + if err != nil { + return nil, fmt.Errorf("Unable to retrieve networks from the Network API: %s", err) + } + + allNetworks, err := networks.ExtractNetworks(allPages) + if err != nil { + return nil, fmt.Errorf("Unable to retrieve networks from the Network API: %s", err) + } + + var network networks.Network + switch len(allNetworks) { + case 0: + return nil, fmt.Errorf("Could not find any matching network for %s %s", queryType, queryTerm) + case 1: + network = allNetworks[0] + default: + return nil, fmt.Errorf("More than one network found for %s %s", queryType, queryTerm) + } + + v := map[string]interface{}{ + "uuid": network.ID, + "name": network.Name, + } + + log.Printf("[DEBUG] getInstanceNetworkInfoNeutron: %#v", v) + return v, nil +} + +// getInstanceAddresses parses a Gophercloud server.Server's Address field into +// a structured InstanceAddresses struct. +func getInstanceAddresses(addresses map[string]interface{}) []InstanceAddresses { + var allInstanceAddresses []InstanceAddresses + + for networkName, v := range addresses { + instanceAddresses := InstanceAddresses{ + NetworkName: networkName, + } + + for _, v := range v.([]interface{}) { + instanceNIC := InstanceNIC{} + var exists bool + + v := v.(map[string]interface{}) + if v, ok := v["OS-EXT-IPS-MAC:mac_addr"].(string); ok { + instanceNIC.MAC = v + } + + if v["OS-EXT-IPS:type"] == "fixed" || v["OS-EXT-IPS:type"] == nil { + switch v["version"].(float64) { + case 6: + instanceNIC.FixedIPv6 = fmt.Sprintf("[%s]", v["addr"].(string)) + default: + instanceNIC.FixedIPv4 = v["addr"].(string) + } + } + + // To associate IPv4 and IPv6 on the right NIC, + // key on the mac address and fill in the blanks. + for i, v := range instanceAddresses.InstanceNICs { + if v.MAC == instanceNIC.MAC { + exists = true + if instanceNIC.FixedIPv6 != "" { + instanceAddresses.InstanceNICs[i].FixedIPv6 = instanceNIC.FixedIPv6 + } + if instanceNIC.FixedIPv4 != "" { + instanceAddresses.InstanceNICs[i].FixedIPv4 = instanceNIC.FixedIPv4 + } + } + } + + if !exists { + instanceAddresses.InstanceNICs = append(instanceAddresses.InstanceNICs, instanceNIC) + } + } + + allInstanceAddresses = append(allInstanceAddresses, instanceAddresses) + } + + log.Printf("[DEBUG] Addresses: %#v", addresses) + log.Printf("[DEBUG] allInstanceAddresses: %#v", allInstanceAddresses) + + return allInstanceAddresses +} + +// expandInstanceNetworks takes network information found in []InstanceNetwork +// and builds a Gophercloud []servers.Network for use in creating an Instance. +func expandInstanceNetworks(allInstanceNetworks []InstanceNetwork) []servers.Network { + var networks []servers.Network + for _, v := range allInstanceNetworks { + n := servers.Network{ + UUID: v.UUID, + Port: v.Port, + FixedIP: v.FixedIP, + } + networks = append(networks, n) + } + + return networks +} + +// flattenInstanceNetworks collects instance network information from different +// sources and aggregates it all together into a map array. +func flattenInstanceNetworks( + d *schema.ResourceData, meta interface{}) ([]map[string]interface{}, error) { + + config := meta.(*Config) + computeClient, err := config.computeV2Client(GetRegion(d, config)) + if err != nil { + return nil, fmt.Errorf("Error creating OpenStack compute client: %s", err) + } + + server, err := servers.Get(computeClient, d.Id()).Extract() + if err != nil { + return nil, CheckDeleted(d, err, "server") + } + + allInstanceAddresses := getInstanceAddresses(server.Addresses) + allInstanceNetworks, err := getAllInstanceNetworks(d, meta) + if err != nil { + return nil, err + } + + networks := []map[string]interface{}{} + + // If there were no instance networks returned, this means that there + // was not a network specified in the Terraform configuration. When this + // happens, the instance will be launched on a "default" network, if one + // is available. If there isn't, the instance will fail to launch, so + // this is a safe assumption at this point. + if len(allInstanceNetworks) == 0 { + for _, instanceAddresses := range allInstanceAddresses { + for _, instanceNIC := range instanceAddresses.InstanceNICs { + v := map[string]interface{}{ + "name": instanceAddresses.NetworkName, + "fixed_ip_v4": instanceNIC.FixedIPv4, + "fixed_ip_v6": instanceNIC.FixedIPv6, + "mac": instanceNIC.MAC, + } + + // Use the same method as getAllInstanceNetworks to get the network uuid + networkInfo, err := getInstanceNetworkInfo(d, meta, "name", instanceAddresses.NetworkName) + if err != nil { + log.Printf("[WARN] Error getting default network uuid: %s", err) + } else { + if v["uuid"] != nil { + v["uuid"] = networkInfo["uuid"].(string) + } else { + log.Printf("[WARN] Could not get default network uuid") + } + } + + networks = append(networks, v) + } + } + + log.Printf("[DEBUG] flattenInstanceNetworks: %#v", networks) + return networks, nil + } + + // Loop through all networks and addresses, merge relevant address details. + for _, instanceNetwork := range allInstanceNetworks { + for _, instanceAddresses := range allInstanceAddresses { + // Skip if instanceAddresses has no NICs + if len(instanceAddresses.InstanceNICs) == 0 { + continue + } + + if instanceNetwork.Name == instanceAddresses.NetworkName { + // Only use one NIC since it's possible the user defined another NIC + // on this same network in another Terraform network block. + instanceNIC := instanceAddresses.InstanceNICs[0] + copy(instanceAddresses.InstanceNICs, instanceAddresses.InstanceNICs[1:]) + v := map[string]interface{}{ + "name": instanceAddresses.NetworkName, + "fixed_ip_v4": instanceNIC.FixedIPv4, + "fixed_ip_v6": instanceNIC.FixedIPv6, + "mac": instanceNIC.MAC, + "uuid": instanceNetwork.UUID, + "port": instanceNetwork.Port, + "access_network": instanceNetwork.AccessNetwork, + } + networks = append(networks, v) + } + } + } + + log.Printf("[DEBUG] flattenInstanceNetworks: %#v", networks) + return networks, nil +} + +// getInstanceAccessAddresses determines the best IP address to communicate +// with the instance. It does this by looping through all networks and looking +// for a valid IP address. Priority is given to a network that was flagged as +// an access_network. +func getInstanceAccessAddresses( + d *schema.ResourceData, networks []map[string]interface{}) (string, string) { + + var hostv4, hostv6 string + + // Loop through all networks + // If the network has a valid fixed v4 or fixed v6 address + // and hostv4 or hostv6 is not set, set hostv4/hostv6. + // If the network is an "access_network" overwrite hostv4/hostv6. + for _, n := range networks { + var accessNetwork bool + + if an, ok := n["access_network"].(bool); ok && an { + accessNetwork = true + } + + if fixedIPv4, ok := n["fixed_ip_v4"].(string); ok && fixedIPv4 != "" { + if hostv4 == "" || accessNetwork { + hostv4 = fixedIPv4 + } + } + + if fixedIPv6, ok := n["fixed_ip_v6"].(string); ok && fixedIPv6 != "" { + if hostv6 == "" || accessNetwork { + hostv6 = fixedIPv6 + } + } + } + + log.Printf("[DEBUG] OpenStack Instance Network Access Addresses: %s, %s", hostv4, hostv6) + + return hostv4, hostv6 +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/compute_interface_attach_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/compute_interface_attach_v2.go new file mode 100644 index 000000000..e1e44a472 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/compute_interface_attach_v2.go @@ -0,0 +1,71 @@ +package openstack + +import ( + "fmt" + "log" + "strings" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces" + + "github.com/hashicorp/terraform/helper/resource" +) + +func computeInterfaceAttachV2AttachFunc( + computeClient *gophercloud.ServiceClient, instanceId, attachmentId string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + va, err := attachinterfaces.Get(computeClient, instanceId, attachmentId).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + return va, "ATTACHING", nil + } + return va, "", err + } + + return va, "ATTACHED", nil + } +} + +func computeInterfaceAttachV2DetachFunc( + computeClient *gophercloud.ServiceClient, instanceId, attachmentId string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + log.Printf("[DEBUG] Attempting to detach openstack_compute_interface_attach_v2 %s from instance %s", + attachmentId, instanceId) + + va, err := attachinterfaces.Get(computeClient, instanceId, attachmentId).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + return va, "DETACHED", nil + } + return va, "", err + } + + err = attachinterfaces.Delete(computeClient, instanceId, attachmentId).ExtractErr() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + return va, "DETACHED", nil + } + + if _, ok := err.(gophercloud.ErrDefault400); ok { + return nil, "", nil + } + + return nil, "", err + } + + log.Printf("[DEBUG] openstack_compute_interface_attach_v2 %s is still active.", attachmentId) + return nil, "", nil + } +} + +func computeInterfaceAttachV2ParseID(id string) (string, string, error) { + idParts := strings.Split(id, "/") + if len(idParts) < 2 { + return "", "", fmt.Errorf("Unable to determine openstack_compute_interface_attach_v2 %s ID", id) + } + + instanceId := idParts[0] + attachmentId := idParts[1] + + return instanceId, attachmentId, nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/compute_keypair_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/compute_keypair_v2.go new file mode 100644 index 000000000..ff70500c4 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/compute_keypair_v2.go @@ -0,0 +1,17 @@ +package openstack + +import ( + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs" +) + +// ComputeKeyPairV2CreateOpts is a custom KeyPair struct to include the ValueSpecs field. +type ComputeKeyPairV2CreateOpts struct { + keypairs.CreateOpts + ValueSpecs map[string]string `json:"value_specs,omitempty"` +} + +// ToKeyPairCreateMap casts a CreateOpts struct to a map. +// It overrides keypairs.ToKeyPairCreateMap to add the ValueSpecs field. +func (opts ComputeKeyPairV2CreateOpts) ToKeyPairCreateMap() (map[string]interface{}, error) { + return BuildRequest(opts, "keypair") +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/compute_secgroup_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/compute_secgroup_v2.go new file mode 100644 index 000000000..a2d057153 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/compute_secgroup_v2.go @@ -0,0 +1,171 @@ +package openstack + +import ( + "bytes" + "fmt" + "log" + "strings" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func computeSecGroupV2RulesCheckForErrors(d *schema.ResourceData) error { + rawRules := d.Get("rule").(*schema.Set).List() + + for _, rawRule := range rawRules { + rawRuleMap := rawRule.(map[string]interface{}) + + // only one of cidr, from_group_id, or self can be set + cidr := rawRuleMap["cidr"].(string) + groupId := rawRuleMap["from_group_id"].(string) + self := rawRuleMap["self"].(bool) + errorMessage := fmt.Errorf("Only one of cidr, from_group_id, or self can be set.") + + // if cidr is set, from_group_id and self cannot be set + if cidr != "" { + if groupId != "" || self { + return errorMessage + } + } + + // if from_group_id is set, cidr and self cannot be set + if groupId != "" { + if cidr != "" || self { + return errorMessage + } + } + + // if self is set, cidr and from_group_id cannot be set + if self { + if cidr != "" || groupId != "" { + return errorMessage + } + } + } + + return nil +} + +func expandComputeSecGroupV2CreateRules(d *schema.ResourceData) []secgroups.CreateRuleOpts { + rawRules := d.Get("rule").(*schema.Set).List() + createRuleOptsList := make([]secgroups.CreateRuleOpts, len(rawRules)) + + for i, rawRule := range rawRules { + createRuleOptsList[i] = expandComputeSecGroupV2CreateRule(d, rawRule) + } + + return createRuleOptsList +} + +func expandComputeSecGroupV2CreateRule(d *schema.ResourceData, rawRule interface{}) secgroups.CreateRuleOpts { + rawRuleMap := rawRule.(map[string]interface{}) + groupId := rawRuleMap["from_group_id"].(string) + if rawRuleMap["self"].(bool) { + groupId = d.Id() + } + return secgroups.CreateRuleOpts{ + ParentGroupID: d.Id(), + FromPort: rawRuleMap["from_port"].(int), + ToPort: rawRuleMap["to_port"].(int), + IPProtocol: rawRuleMap["ip_protocol"].(string), + CIDR: rawRuleMap["cidr"].(string), + FromGroupID: groupId, + } +} + +func expandComputeSecGroupV2Rule(d *schema.ResourceData, rawRule interface{}) secgroups.Rule { + rawRuleMap := rawRule.(map[string]interface{}) + return secgroups.Rule{ + ID: rawRuleMap["id"].(string), + ParentGroupID: d.Id(), + FromPort: rawRuleMap["from_port"].(int), + ToPort: rawRuleMap["to_port"].(int), + IPProtocol: rawRuleMap["ip_protocol"].(string), + IPRange: secgroups.IPRange{CIDR: rawRuleMap["cidr"].(string)}, + } +} + +func flattenComputeSecGroupV2Rules(computeClient *gophercloud.ServiceClient, d *schema.ResourceData, sgrs []secgroups.Rule) ([]map[string]interface{}, error) { + sgrMap := make([]map[string]interface{}, len(sgrs)) + for i, sgr := range sgrs { + groupId := "" + self := false + if sgr.Group.Name != "" { + if sgr.Group.Name == d.Get("name").(string) { + self = true + } else { + // Since Nova only returns the secgroup Name (and not the ID) for the group attribute, + // we need to look up all security groups and match the name. + // Nevermind that Nova wants the ID when setting the Group *and* that multiple groups + // with the same name can exist... + allPages, err := secgroups.List(computeClient).AllPages() + if err != nil { + return nil, err + } + securityGroups, err := secgroups.ExtractSecurityGroups(allPages) + if err != nil { + return nil, err + } + + for _, sg := range securityGroups { + if sg.Name == sgr.Group.Name { + groupId = sg.ID + } + } + } + } + + sgrMap[i] = map[string]interface{}{ + "id": sgr.ID, + "from_port": sgr.FromPort, + "to_port": sgr.ToPort, + "ip_protocol": sgr.IPProtocol, + "cidr": sgr.IPRange.CIDR, + "self": self, + "from_group_id": groupId, + } + } + return sgrMap, nil +} + +func computeSecGroupV2RuleHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int))) + buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int))) + buf.WriteString(fmt.Sprintf("%s-", m["ip_protocol"].(string))) + buf.WriteString(fmt.Sprintf("%s-", strings.ToLower(m["cidr"].(string)))) + buf.WriteString(fmt.Sprintf("%s-", m["from_group_id"].(string))) + buf.WriteString(fmt.Sprintf("%t-", m["self"].(bool))) + + return hashcode.String(buf.String()) +} + +func computeSecGroupV2StateRefreshFunc(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + log.Printf("[DEBUG] Attempting to delete openstack_compute_secgroup_v2 %s", d.Id()) + + err := secgroups.Delete(computeClient, d.Id()).ExtractErr() + if err != nil { + return nil, "", err + } + + s, err := secgroups.Get(computeClient, d.Id()).Extract() + if err != nil { + err = CheckDeleted(d, err, "Error retrieving openstack_compute_secgroup_v2") + if err != nil { + return s, "", err + } + + log.Printf("[DEBUG] Successfully deleted openstack_compute_secgroup_v2 %s", d.Id()) + return s, "DELETED", nil + } + + log.Printf("[DEBUG] openstack_compute_secgroup_v2 %s still active", d.Id()) + return s, "ACTIVE", nil + } +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/compute_servergroup_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/compute_servergroup_v2.go new file mode 100644 index 000000000..44a117909 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/compute_servergroup_v2.go @@ -0,0 +1,39 @@ +package openstack + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups" +) + +const ( + softAntiAffinityPolicy = "soft-anti-affinity" + softAffinityPolicy = "soft-affinity" +) + +// ServerGroupCreateOpts is a custom ServerGroup struct to include the +// ValueSpecs field. +type ComputeServerGroupV2CreateOpts struct { + servergroups.CreateOpts + ValueSpecs map[string]string `json:"value_specs,omitempty"` +} + +// ToServerGroupCreateMap casts a CreateOpts struct to a map. +// It overrides routers.ToServerGroupCreateMap to add the ValueSpecs field. +func (opts ComputeServerGroupV2CreateOpts) ToServerGroupCreateMap() (map[string]interface{}, error) { + return BuildRequest(opts, "server_group") +} + +func expandComputeServerGroupV2Policies(client *gophercloud.ServiceClient, raw []interface{}) []string { + policies := make([]string, len(raw)) + for i, v := range raw { + policy := v.(string) + policies[i] = policy + + // Set microversion for new policies. + if policy == softAntiAffinityPolicy || policy == softAffinityPolicy { + client.Microversion = "2.15" + } + } + + return policies +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/compute_volume_attach_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/compute_volume_attach_v2.go new file mode 100644 index 000000000..74c5d3384 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/compute_volume_attach_v2.go @@ -0,0 +1,71 @@ +package openstack + +import ( + "fmt" + "log" + "strings" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach" + + "github.com/hashicorp/terraform/helper/resource" +) + +func computeVolumeAttachV2ParseID(id string) (string, string, error) { + parts := strings.Split(id, "/") + if len(parts) < 2 { + return "", "", fmt.Errorf("unable to determine openstack_compute_volume_attach_v2 ID") + } + + instanceID := parts[0] + attachmentID := parts[1] + + return instanceID, attachmentID, nil +} + +func computeVolumeAttachV2AttachFunc( + computeClient *gophercloud.ServiceClient, instanceId, attachmentId string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + va, err := volumeattach.Get(computeClient, instanceId, attachmentId).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + return va, "ATTACHING", nil + } + return va, "", err + } + + return va, "ATTACHED", nil + } +} + +func computeVolumeAttachV2DetachFunc( + computeClient *gophercloud.ServiceClient, instanceId, attachmentId string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + log.Printf("[DEBUG] openstack_compute_volume_attach_v2 attempting to detach OpenStack volume %s from instance %s", + attachmentId, instanceId) + + va, err := volumeattach.Get(computeClient, instanceId, attachmentId).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + return va, "DETACHED", nil + } + return va, "", err + } + + err = volumeattach.Delete(computeClient, instanceId, attachmentId).ExtractErr() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + return va, "DETACHED", nil + } + + if _, ok := err.(gophercloud.ErrDefault400); ok { + return nil, "", nil + } + + return nil, "", err + } + + log.Printf("[DEBUG] openstack_compute_volume_attach_v2 (%s/%s) is still active.", instanceId, attachmentId) + return nil, "", nil + } +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/config.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/config.go index 8b0e806ef..b6ceb850a 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/config.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/config.go @@ -11,32 +11,55 @@ import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack" "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/swauth" + "github.com/gophercloud/utils/openstack/clientconfig" "github.com/hashicorp/terraform/helper/pathorcontents" "github.com/hashicorp/terraform/terraform" ) type Config struct { - CACertFile string - ClientCertFile string - ClientKeyFile string - DomainID string - DomainName string - EndpointType string - IdentityEndpoint string - Insecure bool - Password string - Region string - Swauth bool - TenantID string - TenantName string - Token string - Username string - UserID string + CACertFile string + ClientCertFile string + ClientKeyFile string + Cloud string + DefaultDomain string + DomainID string + DomainName string + EndpointOverrides map[string]interface{} + EndpointType string + IdentityEndpoint string + Insecure *bool + Password string + ProjectDomainName string + ProjectDomainID string + Region string + Swauth bool + TenantID string + TenantName string + Token string + UserDomainName string + UserDomainID string + Username string + UserID string + ApplicationCredentialID string + ApplicationCredentialName string + ApplicationCredentialSecret string + useOctavia bool + MaxRetries int OsClient *gophercloud.ProviderClient } +// LoadAndValidate performs the authentication and initial configuration +// of an OpenStack Provider Client. This sets up the HTTP client and +// authenticates to an OpenStack cloud. +// +// Individual Service Clients are created later in this file. func (c *Config) LoadAndValidate() error { + // Make sure at least one of auth_url or cloud was specified. + if c.IdentityEndpoint == "" && c.Cloud == "" { + return fmt.Errorf("One of 'auth_url' or 'cloud' must be specified") + } + validEndpoint := false validEndpoints := []string{ "internal", "internalURL", @@ -55,16 +78,63 @@ func (c *Config) LoadAndValidate() error { return fmt.Errorf("Invalid endpoint type provided") } - ao := gophercloud.AuthOptions{ - DomainID: c.DomainID, - DomainName: c.DomainName, - IdentityEndpoint: c.IdentityEndpoint, - Password: c.Password, - TenantID: c.TenantID, - TenantName: c.TenantName, - TokenID: c.Token, - Username: c.Username, - UserID: c.UserID, + clientOpts := new(clientconfig.ClientOpts) + + // If a cloud entry was given, base AuthOptions on a clouds.yaml file. + if c.Cloud != "" { + clientOpts.Cloud = c.Cloud + + cloud, err := clientconfig.GetCloudFromYAML(clientOpts) + if err != nil { + return err + } + + if c.Region == "" && cloud.RegionName != "" { + c.Region = cloud.RegionName + } + + if c.CACertFile == "" && cloud.CACertFile != "" { + c.CACertFile = cloud.CACertFile + } + + if c.ClientCertFile == "" && cloud.ClientCertFile != "" { + c.ClientCertFile = cloud.ClientCertFile + } + + if c.ClientKeyFile == "" && cloud.ClientKeyFile != "" { + c.ClientKeyFile = cloud.ClientKeyFile + } + + if c.Insecure == nil && cloud.Verify != nil { + v := (!*cloud.Verify) + c.Insecure = &v + } + } else { + authInfo := &clientconfig.AuthInfo{ + AuthURL: c.IdentityEndpoint, + DefaultDomain: c.DefaultDomain, + DomainID: c.DomainID, + DomainName: c.DomainName, + Password: c.Password, + ProjectDomainID: c.ProjectDomainID, + ProjectDomainName: c.ProjectDomainName, + ProjectID: c.TenantID, + ProjectName: c.TenantName, + Token: c.Token, + UserDomainID: c.UserDomainID, + UserDomainName: c.UserDomainName, + Username: c.Username, + UserID: c.UserID, + ApplicationCredentialID: c.ApplicationCredentialID, + ApplicationCredentialName: c.ApplicationCredentialName, + ApplicationCredentialSecret: c.ApplicationCredentialSecret, + } + clientOpts.AuthInfo = authInfo + } + + ao, err := clientconfig.AuthOptions(clientOpts) + if err != nil { + return err } client, err := openstack.NewClient(ao.IdentityEndpoint) @@ -87,8 +157,10 @@ func (c *Config) LoadAndValidate() error { config.RootCAs = caCertPool } - if c.Insecure { - config.InsecureSkipVerify = true + if c.Insecure == nil { + config.InsecureSkipVerify = false + } else { + config.InsecureSkipVerify = *c.Insecure } if c.ClientCertFile != "" && c.ClientKeyFile != "" { @@ -119,24 +191,49 @@ func (c *Config) LoadAndValidate() error { transport := &http.Transport{Proxy: http.ProxyFromEnvironment, TLSClientConfig: config} client.HTTPClient = http.Client{ Transport: &LogRoundTripper{ - Rt: transport, - OsDebug: osDebug, + Rt: transport, + OsDebug: osDebug, + MaxRetries: c.MaxRetries, }, } // If using Swift Authentication, there's no need to validate authentication normally. if !c.Swauth { - err = openstack.Authenticate(client, ao) + err = openstack.Authenticate(client, *ao) if err != nil { return err } } + if c.MaxRetries < 0 { + return fmt.Errorf("max_retries should be a positive value") + } + c.OsClient = client return nil } +// determineEndpoint is a helper method to determine if the user wants to +// override an endpoint returned from the catalog. +func (c *Config) determineEndpoint(client *gophercloud.ServiceClient, service string) *gophercloud.ServiceClient { + finalEndpoint := client.ResourceBaseURL() + + if v, ok := c.EndpointOverrides[service]; ok { + if endpoint, ok := v.(string); ok && endpoint != "" { + finalEndpoint = endpoint + client.Endpoint = endpoint + client.ResourceBase = "" + } + } + + log.Printf("[DEBUG] OpenStack Endpoint for %s: %s", service, finalEndpoint) + + return client +} + +// determineRegion is a helper method to determine the region based on +// the user's settings. func (c *Config) determineRegion(region string) string { // If a resource-level region was not specified, and a provider-level region was set, // use the provider-level region. @@ -148,63 +245,8 @@ func (c *Config) determineRegion(region string) string { return region } -func (c *Config) blockStorageV1Client(region string) (*gophercloud.ServiceClient, error) { - return openstack.NewBlockStorageV1(c.OsClient, gophercloud.EndpointOpts{ - Region: c.determineRegion(region), - Availability: c.getEndpointType(), - }) -} - -func (c *Config) blockStorageV2Client(region string) (*gophercloud.ServiceClient, error) { - return openstack.NewBlockStorageV2(c.OsClient, gophercloud.EndpointOpts{ - Region: c.determineRegion(region), - Availability: c.getEndpointType(), - }) -} - -func (c *Config) computeV2Client(region string) (*gophercloud.ServiceClient, error) { - return openstack.NewComputeV2(c.OsClient, gophercloud.EndpointOpts{ - Region: c.determineRegion(region), - Availability: c.getEndpointType(), - }) -} - -func (c *Config) dnsV2Client(region string) (*gophercloud.ServiceClient, error) { - return openstack.NewDNSV2(c.OsClient, gophercloud.EndpointOpts{ - Region: c.determineRegion(region), - Availability: c.getEndpointType(), - }) -} - -func (c *Config) imageV2Client(region string) (*gophercloud.ServiceClient, error) { - return openstack.NewImageServiceV2(c.OsClient, gophercloud.EndpointOpts{ - Region: c.determineRegion(region), - Availability: c.getEndpointType(), - }) -} - -func (c *Config) networkingV2Client(region string) (*gophercloud.ServiceClient, error) { - return openstack.NewNetworkV2(c.OsClient, gophercloud.EndpointOpts{ - Region: c.determineRegion(region), - Availability: c.getEndpointType(), - }) -} - -func (c *Config) objectStorageV1Client(region string) (*gophercloud.ServiceClient, error) { - // If Swift Authentication is being used, return a swauth client. - if c.Swauth { - return swauth.NewObjectStorageV1(c.OsClient, swauth.AuthOpts{ - User: c.Username, - Key: c.Password, - }) - } - - return openstack.NewObjectStorageV1(c.OsClient, gophercloud.EndpointOpts{ - Region: c.determineRegion(region), - Availability: c.getEndpointType(), - }) -} - +// getEndpointType is a helper method to determine the endpoint type +// requested by the user. func (c *Config) getEndpointType() gophercloud.Availability { if c.EndpointType == "internal" || c.EndpointType == "internalURL" { return gophercloud.AvailabilityInternal @@ -214,3 +256,226 @@ func (c *Config) getEndpointType() gophercloud.Availability { } return gophercloud.AvailabilityPublic } + +// The following methods assist with the creation of individual Service Clients +// which interact with the various OpenStack services. + +func (c *Config) blockStorageV1Client(region string) (*gophercloud.ServiceClient, error) { + client, err := openstack.NewBlockStorageV1(c.OsClient, gophercloud.EndpointOpts{ + Region: c.determineRegion(region), + Availability: c.getEndpointType(), + }) + + if err != nil { + return client, err + } + + // Check if an endpoint override was specified for the volume service. + client = c.determineEndpoint(client, "volume") + + return client, nil +} + +func (c *Config) blockStorageV2Client(region string) (*gophercloud.ServiceClient, error) { + client, err := openstack.NewBlockStorageV2(c.OsClient, gophercloud.EndpointOpts{ + Region: c.determineRegion(region), + Availability: c.getEndpointType(), + }) + + if err != nil { + return client, err + } + + // Check if an endpoint override was specified for the volumev2 service. + client = c.determineEndpoint(client, "volumev2") + + return client, nil +} + +func (c *Config) blockStorageV3Client(region string) (*gophercloud.ServiceClient, error) { + client, err := openstack.NewBlockStorageV3(c.OsClient, gophercloud.EndpointOpts{ + Region: c.determineRegion(region), + Availability: c.getEndpointType(), + }) + + if err != nil { + return client, err + } + + // Check if an endpoint override was specified for the volumev3 service. + client = c.determineEndpoint(client, "volumev3") + + return client, nil +} + +func (c *Config) computeV2Client(region string) (*gophercloud.ServiceClient, error) { + client, err := openstack.NewComputeV2(c.OsClient, gophercloud.EndpointOpts{ + Region: c.determineRegion(region), + Availability: c.getEndpointType(), + }) + + if err != nil { + return client, err + } + + // Check if an endpoint override was specified for the compute service. + client = c.determineEndpoint(client, "compute") + + return client, nil +} + +func (c *Config) dnsV2Client(region string) (*gophercloud.ServiceClient, error) { + client, err := openstack.NewDNSV2(c.OsClient, gophercloud.EndpointOpts{ + Region: c.determineRegion(region), + Availability: c.getEndpointType(), + }) + + if err != nil { + return client, err + } + + // Check if an endpoint override was specified for the dns service. + client = c.determineEndpoint(client, "dns") + + return client, nil +} + +func (c *Config) identityV3Client(region string) (*gophercloud.ServiceClient, error) { + client, err := openstack.NewIdentityV3(c.OsClient, gophercloud.EndpointOpts{ + Region: c.determineRegion(region), + Availability: c.getEndpointType(), + }) + + if err != nil { + return client, err + } + + // Check if an endpoint override was specified for the identity service. + client = c.determineEndpoint(client, "identity") + + return client, nil +} + +func (c *Config) imageV2Client(region string) (*gophercloud.ServiceClient, error) { + client, err := openstack.NewImageServiceV2(c.OsClient, gophercloud.EndpointOpts{ + Region: c.determineRegion(region), + Availability: c.getEndpointType(), + }) + + if err != nil { + return client, err + } + + // Check if an endpoint override was specified for the image service. + client = c.determineEndpoint(client, "image") + + return client, nil +} + +func (c *Config) networkingV2Client(region string) (*gophercloud.ServiceClient, error) { + client, err := openstack.NewNetworkV2(c.OsClient, gophercloud.EndpointOpts{ + Region: c.determineRegion(region), + Availability: c.getEndpointType(), + }) + + if err != nil { + return client, err + } + + // Check if an endpoint override was specified for the network service. + client = c.determineEndpoint(client, "network") + + return client, nil +} + +func (c *Config) objectStorageV1Client(region string) (*gophercloud.ServiceClient, error) { + var client *gophercloud.ServiceClient + var err error + + // If Swift Authentication is being used, return a swauth client. + // Otherwise, use a Keystone-based client. + if c.Swauth { + client, err = swauth.NewObjectStorageV1(c.OsClient, swauth.AuthOpts{ + User: c.Username, + Key: c.Password, + }) + } else { + client, err = openstack.NewObjectStorageV1(c.OsClient, gophercloud.EndpointOpts{ + Region: c.determineRegion(region), + Availability: c.getEndpointType(), + }) + } + + if err != nil { + return client, err + } + + // Check if an endpoint override was specified for the object-store service. + client = c.determineEndpoint(client, "object-store") + + return client, nil +} + +func (c *Config) loadBalancerV2Client(region string) (*gophercloud.ServiceClient, error) { + client, err := openstack.NewLoadBalancerV2(c.OsClient, gophercloud.EndpointOpts{ + Region: c.determineRegion(region), + Availability: c.getEndpointType(), + }) + + if err != nil { + return client, err + } + + // Check if an endpoint override was specified for the octavia service. + client = c.determineEndpoint(client, "octavia") + + return client, nil +} + +func (c *Config) databaseV1Client(region string) (*gophercloud.ServiceClient, error) { + client, err := openstack.NewDBV1(c.OsClient, gophercloud.EndpointOpts{ + Region: c.determineRegion(region), + Availability: c.getEndpointType(), + }) + + if err != nil { + return client, err + } + + // Check if an endpoint override was specified for the database service. + client = c.determineEndpoint(client, "database") + + return client, nil +} + +func (c *Config) containerInfraV1Client(region string) (*gophercloud.ServiceClient, error) { + client, err := openstack.NewContainerInfraV1(c.OsClient, gophercloud.EndpointOpts{ + Region: c.determineRegion(region), + Availability: c.getEndpointType(), + }) + + if err != nil { + return client, err + } + + // Check if an endpoint override was specified for the container-infra service. + client = c.determineEndpoint(client, "container-infra") + + return client, nil +} + +func (c *Config) sharedfilesystemV2Client(region string) (*gophercloud.ServiceClient, error) { + client, err := openstack.NewSharedFileSystemV2(c.OsClient, gophercloud.EndpointOpts{ + Region: c.determineRegion(region), + Availability: c.getEndpointType(), + }) + + if err != nil { + return client, err + } + + // Check if an endpoint override was specified for the sharev2 service. + client = c.determineEndpoint(client, "sharev2") + + return client, nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/containerinfra_shared_v1.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/containerinfra_shared_v1.go new file mode 100644 index 000000000..bb64afc21 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/containerinfra_shared_v1.go @@ -0,0 +1,120 @@ +package openstack + +import ( + "fmt" + "os" + "strings" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters" + "github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func expandContainerInfraV1LabelsMap(v map[string]interface{}) (map[string]string, error) { + m := make(map[string]string) + for key, val := range v { + labelValue, ok := val.(string) + if !ok { + return nil, fmt.Errorf("label %s value should be string", key) + } + m[key] = labelValue + } + return m, nil +} + +func expandContainerInfraV1LabelsString(v map[string]interface{}) (string, error) { + var formattedLabels string + for key, val := range v { + labelValue, ok := val.(string) + if !ok { + return "", fmt.Errorf("label %s value should be string", key) + } + formattedLabels = strings.Join([]string{ + formattedLabels, + fmt.Sprintf("%s=%s", key, labelValue), + }, ",") + } + formattedLabels = strings.Trim(formattedLabels, ",") + + return formattedLabels, nil +} + +func containerInfraClusterTemplateV1AppendUpdateOpts(updateOpts []clustertemplates.UpdateOptsBuilder, attribute, value string) []clustertemplates.UpdateOptsBuilder { + if value == "" { + updateOpts = append(updateOpts, clustertemplates.UpdateOpts{ + Op: clustertemplates.RemoveOp, + Path: strings.Join([]string{"/", attribute}, ""), + }) + } else { + updateOpts = append(updateOpts, clustertemplates.UpdateOpts{ + Op: clustertemplates.ReplaceOp, + Path: strings.Join([]string{"/", attribute}, ""), + Value: value, + }) + } + return updateOpts +} + +// ContainerInfraClusterV1StateRefreshFunc returns a resource.StateRefreshFunc +// that is used to watch a container infra Cluster. +func containerInfraClusterV1StateRefreshFunc(client *gophercloud.ServiceClient, clusterID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + c, err := clusters.Get(client, clusterID).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + return c, "DELETE_COMPLETE", nil + } + return nil, "", err + } + + errorStatuses := []string{ + "CREATE_FAILED", + "UPDATE_FAILED", + "DELETE_FAILED", + "RESUME_FAILED", + "ROLLBACK_FAILED", + } + for _, errorStatus := range errorStatuses { + if c.Status == errorStatus { + err = fmt.Errorf("openstack_containerinfra_cluster_v1 is in an error state: %s", c.StatusReason) + return c, c.Status, err + } + } + + return c, c.Status, nil + } +} + +// containerInfraClusterV1Flavor will determine the flavor for a container infra +// cluster based on either what was set in the configuration or environment +// variable. +func containerInfraClusterV1Flavor(d *schema.ResourceData) (string, error) { + if flavor := d.Get("flavor").(string); flavor != "" { + return flavor, nil + } + // Try the OS_MAGNUM_FLAVOR environment variable + if v := os.Getenv("OS_MAGNUM_FLAVOR"); v != "" { + return v, nil + } + + return "", nil +} + +// containerInfraClusterV1Flavor will determine the master flavor for a +// container infra cluster based on either what was set in the configuration +// or environment variable. +func containerInfraClusterV1MasterFlavor(d *schema.ResourceData) (string, error) { + if flavor := d.Get("master_flavor").(string); flavor != "" { + return flavor, nil + } + + // Try the OS_MAGNUM_FLAVOR environment variable + if v := os.Getenv("OS_MAGNUM_MASTER_FLAVOR"); v != "" { + return v, nil + } + + return "", nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_blockstorage_snapshot_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_blockstorage_snapshot_v2.go new file mode 100644 index 000000000..0cf556337 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_blockstorage_snapshot_v2.go @@ -0,0 +1,124 @@ +package openstack + +import ( + "fmt" + "log" + + "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceBlockStorageSnapshotV2() *schema.Resource { + return &schema.Resource{ + Read: dataSourceBlockStorageSnapshotV2Read, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "status": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "volume_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "most_recent": { + Type: schema.TypeBool, + Optional: true, + }, + + // Computed values + "description": { + Type: schema.TypeString, + Computed: true, + }, + + "size": { + Type: schema.TypeInt, + Computed: true, + }, + + "metadata": { + Type: schema.TypeMap, + Computed: true, + }, + }, + } +} + +func dataSourceBlockStorageSnapshotV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + client, err := config.blockStorageV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack block storage client: %s", err) + } + + listOpts := snapshots.ListOpts{ + Name: d.Get("name").(string), + Status: d.Get("status").(string), + VolumeID: d.Get("volume_id").(string), + } + + allPages, err := snapshots.List(client, listOpts).AllPages() + if err != nil { + return fmt.Errorf("Unable to query openstack_blockstorage_snapshot_v2: %s", err) + } + + allSnapshots, err := snapshots.ExtractSnapshots(allPages) + if err != nil { + return fmt.Errorf("Unable to retrieve openstack_blockstorage_snapshot_v2: %s", err) + } + + if len(allSnapshots) < 1 { + return fmt.Errorf("Your openstack_blockstorage_snapshot_v2 query returned no results. " + + "Please change your search criteria and try again.") + } + + var snapshot snapshots.Snapshot + if len(allSnapshots) > 1 { + recent := d.Get("most_recent").(bool) + + if recent { + snapshot = dataSourceBlockStorageV2MostRecentSnapshot(allSnapshots) + } else { + log.Printf("[DEBUG] Multiple openstack_blockstorage_snapshot_v2 results found: %#v", allSnapshots) + + return fmt.Errorf("Your query returned more than one result. Please try a more " + + "specific search criteria, or set `most_recent` attribute to true.") + } + } else { + snapshot = allSnapshots[0] + } + + return dataSourceBlockStorageSnapshotV2Attributes(d, snapshot) +} + +func dataSourceBlockStorageSnapshotV2Attributes(d *schema.ResourceData, snapshot snapshots.Snapshot) error { + d.SetId(snapshot.ID) + d.Set("name", snapshot.Name) + d.Set("description", snapshot.Description) + d.Set("size", snapshot.Size) + d.Set("status", snapshot.Status) + d.Set("volume_id", snapshot.VolumeID) + + if err := d.Set("metadata", snapshot.Metadata); err != nil { + log.Printf("[DEBUG] Unable to set metadata for snapshot %s: %s", snapshot.ID, err) + } + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_blockstorage_snapshot_v3.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_blockstorage_snapshot_v3.go new file mode 100644 index 000000000..f7be3ffab --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_blockstorage_snapshot_v3.go @@ -0,0 +1,124 @@ +package openstack + +import ( + "fmt" + "log" + + "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceBlockStorageSnapshotV3() *schema.Resource { + return &schema.Resource{ + Read: dataSourceBlockStorageSnapshotV3Read, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "status": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "volume_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "most_recent": { + Type: schema.TypeBool, + Optional: true, + }, + + // Computed values + "description": { + Type: schema.TypeString, + Computed: true, + }, + + "size": { + Type: schema.TypeInt, + Computed: true, + }, + + "metadata": { + Type: schema.TypeMap, + Computed: true, + }, + }, + } +} + +func dataSourceBlockStorageSnapshotV3Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + client, err := config.blockStorageV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack block storage client: %s", err) + } + + listOpts := snapshots.ListOpts{ + Name: d.Get("name").(string), + Status: d.Get("status").(string), + VolumeID: d.Get("volume_id").(string), + } + + allPages, err := snapshots.List(client, listOpts).AllPages() + if err != nil { + return fmt.Errorf("Unable to query openstack_blockstorage_snapshots_v3: %s", err) + } + + allSnapshots, err := snapshots.ExtractSnapshots(allPages) + if err != nil { + return fmt.Errorf("Unable to retrieve openstack_blockstorage_snapshots_v3: %s", err) + } + + if len(allSnapshots) < 1 { + return fmt.Errorf("Your openstack_blockstorage_snapshot_v3 query returned no results. " + + "Please change your search criteria and try again.") + } + + var snapshot snapshots.Snapshot + if len(allSnapshots) > 1 { + recent := d.Get("most_recent").(bool) + + if recent { + snapshot = dataSourceBlockStorageV3MostRecentSnapshot(allSnapshots) + } else { + log.Printf("[DEBUG] Multiple openstack_blockstorage_snapshot_v3 results found: %#v", allSnapshots) + + return fmt.Errorf("Your query returned more than one result. Please try a more " + + "specific search criteria, or set `most_recent` attribute to true.") + } + } else { + snapshot = allSnapshots[0] + } + + return dataSourceBlockStorageSnapshotV3Attributes(d, snapshot) +} + +func dataSourceBlockStorageSnapshotV3Attributes(d *schema.ResourceData, snapshot snapshots.Snapshot) error { + d.SetId(snapshot.ID) + d.Set("name", snapshot.Name) + d.Set("description", snapshot.Description) + d.Set("size", snapshot.Size) + d.Set("status", snapshot.Status) + d.Set("volume_id", snapshot.VolumeID) + + if err := d.Set("metadata", snapshot.Metadata); err != nil { + log.Printf("[DEBUG] Unable to set metadata for snapshot %s: %s", snapshot.ID, err) + } + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_compute_flavor_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_compute_flavor_v2.go new file mode 100644 index 000000000..9e0dcb73b --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_compute_flavor_v2.go @@ -0,0 +1,223 @@ +package openstack + +import ( + "fmt" + "log" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors" + + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceComputeFlavorV2() *schema.Resource { + return &schema.Resource{ + Read: dataSourceComputeFlavorV2Read, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "flavor_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"name", "min_ram", "min_disk"}, + }, + + "name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"flavor_id"}, + }, + + "min_ram": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"flavor_id"}, + }, + + "ram": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + + "vcpus": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + + "min_disk": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"flavor_id"}, + }, + + "disk": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + + "swap": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + + "rx_tx_factor": { + Type: schema.TypeFloat, + Optional: true, + ForceNew: true, + }, + + // Computed values + "extra_specs": { + Type: schema.TypeMap, + Computed: true, + }, + + "is_public": { + Type: schema.TypeBool, + Computed: true, + }, + }, + } +} + +// dataSourceComputeFlavorV2Read performs the flavor lookup. +func dataSourceComputeFlavorV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + computeClient, err := config.computeV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } + + var allFlavors []flavors.Flavor + if v := d.Get("flavor_id").(string); v != "" { + flavor, err := flavors.Get(computeClient, v).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + return fmt.Errorf("No Flavor found") + } + return fmt.Errorf("Unable to retrieve OpenStack %s flavor: %s", v, err) + } + + allFlavors = append(allFlavors, *flavor) + } else { + listOpts := flavors.ListOpts{ + MinDisk: d.Get("min_disk").(int), + MinRAM: d.Get("min_ram").(int), + AccessType: flavors.PublicAccess, + } + + log.Printf("[DEBUG] openstack_compute_flavor_v2 ListOpts: %#v", listOpts) + + allPages, err := flavors.ListDetail(computeClient, listOpts).AllPages() + if err != nil { + return fmt.Errorf("Unable to query OpenStack flavors: %s", err) + } + + allFlavors, err = flavors.ExtractFlavors(allPages) + if err != nil { + return fmt.Errorf("Unable to retrieve OpenStack flavors: %s", err) + } + } + + // Loop through all flavors to find a more specific one. + if len(allFlavors) > 0 { + var filteredFlavors []flavors.Flavor + for _, flavor := range allFlavors { + if v := d.Get("name").(string); v != "" { + if flavor.Name != v { + continue + } + } + + // d.GetOk is used because 0 might be a valid choice. + if v, ok := d.GetOk("ram"); ok { + if flavor.RAM != v.(int) { + continue + } + } + + if v, ok := d.GetOk("vcpus"); ok { + if flavor.VCPUs != v.(int) { + continue + } + } + + if v, ok := d.GetOk("disk"); ok { + if flavor.Disk != v.(int) { + continue + } + } + + if v, ok := d.GetOk("swap"); ok { + if flavor.Swap != v.(int) { + continue + } + } + + if v, ok := d.GetOk("rx_tx_factor"); ok { + if flavor.RxTxFactor != v.(float64) { + continue + } + } + + filteredFlavors = append(filteredFlavors, flavor) + } + + allFlavors = filteredFlavors + } + + if len(allFlavors) < 1 { + return fmt.Errorf("Your query returned no results. " + + "Please change your search criteria and try again.") + } + + if len(allFlavors) > 1 { + log.Printf("[DEBUG] Multiple results found: %#v", allFlavors) + return fmt.Errorf("Your query returned more than one result. " + + "Please try a more specific search criteria") + } + + return dataSourceComputeFlavorV2Attributes(d, computeClient, &allFlavors[0]) +} + +// dataSourceComputeFlavorV2Attributes populates the fields of a Flavor resource. +func dataSourceComputeFlavorV2Attributes( + d *schema.ResourceData, computeClient *gophercloud.ServiceClient, flavor *flavors.Flavor) error { + + log.Printf("[DEBUG] Retrieved openstack_compute_flavor_v2 %s: %#v", flavor.ID, flavor) + + d.SetId(flavor.ID) + d.Set("name", flavor.Name) + d.Set("flavor_id", flavor.ID) + d.Set("disk", flavor.Disk) + d.Set("ram", flavor.RAM) + d.Set("rx_tx_factor", flavor.RxTxFactor) + d.Set("swap", flavor.Swap) + d.Set("vcpus", flavor.VCPUs) + d.Set("is_public", flavor.IsPublic) + + es, err := flavors.ListExtraSpecs(computeClient, d.Id()).Extract() + if err != nil { + return err + } + + if err := d.Set("extra_specs", es); err != nil { + log.Printf("[WARN] Unable to set extra_specs for openstack_compute_flavor_v2 %s: %s", d.Id(), err) + } + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_compute_keypair_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_compute_keypair_v2.go new file mode 100644 index 000000000..9c8dfbffe --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_compute_keypair_v2.go @@ -0,0 +1,63 @@ +package openstack + +import ( + "fmt" + "log" + + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceComputeKeypairV2() *schema.Resource { + return &schema.Resource{ + Read: dataSourceComputeKeypairV2Read, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "name": { + Type: schema.TypeString, + Required: true, + }, + + // computed-only + "fingerprint": { + Type: schema.TypeString, + Computed: true, + }, + + "public_key": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceComputeKeypairV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + computeClient, err := config.computeV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } + + name := d.Get("name").(string) + kp, err := keypairs.Get(computeClient, name).Extract() + if err != nil { + return fmt.Errorf("Error retrieving openstack_compute_keypair_v2 %s: %s", name, err) + } + + d.SetId(name) + + log.Printf("[DEBUG] Retrieved openstack_compute_keypair_v2 %s: %#v", d.Id(), kp) + + d.Set("fingerprint", kp.Fingerprint) + d.Set("public_key", kp.PublicKey) + d.Set("region", GetRegion(d, config)) + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_containerinfra_cluster_v1.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_containerinfra_cluster_v1.go new file mode 100644 index 000000000..1daf1c264 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_containerinfra_cluster_v1.go @@ -0,0 +1,176 @@ +package openstack + +import ( + "fmt" + "log" + "time" + + "github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceContainerInfraCluster() *schema.Resource { + return &schema.Resource{ + Read: dataSourceContainerInfraClusterRead, + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "name": { + Type: schema.TypeString, + Required: true, + }, + + "project_id": { + Type: schema.TypeString, + Computed: true, + }, + + "user_id": { + Type: schema.TypeString, + Computed: true, + }, + + "created_at": { + Type: schema.TypeString, + Computed: true, + }, + + "updated_at": { + Type: schema.TypeString, + Computed: true, + }, + + "api_address": { + Type: schema.TypeString, + Computed: true, + }, + + "coe_version": { + Type: schema.TypeString, + Computed: true, + }, + + "cluster_template_id": { + Type: schema.TypeString, + Computed: true, + }, + + "container_version": { + Type: schema.TypeString, + Computed: true, + }, + + "create_timeout": { + Type: schema.TypeInt, + Computed: true, + }, + + "discovery_url": { + Type: schema.TypeString, + Computed: true, + }, + + "docker_volume_size": { + Type: schema.TypeInt, + Computed: true, + }, + + "flavor": { + Type: schema.TypeString, + Computed: true, + }, + + "master_flavor": { + Type: schema.TypeString, + Computed: true, + }, + + "keypair": { + Type: schema.TypeString, + Computed: true, + }, + + "labels": { + Type: schema.TypeMap, + Computed: true, + }, + + "master_count": { + Type: schema.TypeInt, + Computed: true, + }, + + "node_count": { + Type: schema.TypeInt, + Computed: true, + }, + + "master_addresses": { + Type: schema.TypeString, + Computed: true, + }, + + "node_addresses": { + Type: schema.TypeString, + Computed: true, + }, + + "stack_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceContainerInfraClusterRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + containerInfraClient, err := config.containerInfraV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack container infra client: %s", err) + } + + name := d.Get("name").(string) + c, err := clusters.Get(containerInfraClient, name).Extract() + if err != nil { + return fmt.Errorf("Error getting openstack_containerinfra_cluster_v1 %s: %s", name, err) + } + + d.SetId(c.UUID) + + d.Set("project_id", c.ProjectID) + d.Set("user_id", c.UserID) + d.Set("api_address", c.APIAddress) + d.Set("coe_version", c.COEVersion) + d.Set("cluster_template_id", c.ClusterTemplateID) + d.Set("container_version", c.ContainerVersion) + d.Set("create_timeout", c.CreateTimeout) + d.Set("discovery_url", c.DiscoveryURL) + d.Set("docker_volume_size", c.DockerVolumeSize) + d.Set("flavor", c.FlavorID) + d.Set("master_flavor", c.MasterFlavorID) + d.Set("keypair", c.KeyPair) + d.Set("master_count", c.MasterCount) + d.Set("node_count", c.NodeCount) + d.Set("master_addresses", c.MasterAddresses) + d.Set("node_addresses", c.NodeAddresses) + d.Set("stack_id", c.StackID) + + if err := d.Set("labels", c.Labels); err != nil { + log.Printf("[DEBUG] Unable to set labels for openstack_containerinfra_cluster_v1 %s: %s", c.UUID, err) + } + if err := d.Set("created_at", c.CreatedAt.Format(time.RFC3339)); err != nil { + log.Printf("[DEBUG] Unable to set created_at for openstack_containerinfra_cluster_v1 %s: %s", c.UUID, err) + } + if err := d.Set("updated_at", c.UpdatedAt.Format(time.RFC3339)); err != nil { + log.Printf("[DEBUG] Unable to set updated_at for openstack_containerinfra_cluster_v1 %s: %s", c.UUID, err) + } + + d.Set("region", GetRegion(d, config)) + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_containerinfra_clustertemplate_v1.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_containerinfra_clustertemplate_v1.go new file mode 100644 index 000000000..c91ce6f8f --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_containerinfra_clustertemplate_v1.go @@ -0,0 +1,234 @@ +package openstack + +import ( + "fmt" + "log" + "time" + + "github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceContainerInfraClusterTemplateV1() *schema.Resource { + return &schema.Resource{ + Read: dataSourceContainerInfraClusterTemplateV1Read, + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "name": { + Type: schema.TypeString, + Required: true, + }, + + "project_id": { + Type: schema.TypeString, + Computed: true, + }, + + "user_id": { + Type: schema.TypeString, + Computed: true, + }, + + "created_at": { + Type: schema.TypeString, + Computed: true, + }, + + "updated_at": { + Type: schema.TypeString, + Computed: true, + }, + + "apiserver_port": { + Type: schema.TypeInt, + Computed: true, + }, + + "coe": { + Type: schema.TypeString, + Computed: true, + }, + + "cluster_distro": { + Type: schema.TypeString, + Computed: true, + }, + + "dns_nameserver": { + Type: schema.TypeString, + Computed: true, + }, + + "docker_storage_driver": { + Type: schema.TypeString, + Computed: true, + }, + + "docker_volume_size": { + Type: schema.TypeInt, + Computed: true, + }, + + "external_network_id": { + Type: schema.TypeString, + Computed: true, + }, + + "fixed_network": { + Type: schema.TypeString, + Computed: true, + }, + + "fixed_subnet": { + Type: schema.TypeString, + Computed: true, + }, + + "flavor": { + Type: schema.TypeString, + Computed: true, + }, + + "master_flavor": { + Type: schema.TypeString, + Computed: true, + }, + + "floating_ip_enabled": { + Type: schema.TypeBool, + Computed: true, + }, + + "http_proxy": { + Type: schema.TypeString, + Computed: true, + }, + + "https_proxy": { + Type: schema.TypeString, + Computed: true, + }, + + "image": { + Type: schema.TypeString, + Computed: true, + }, + + "insecure_registry": { + Type: schema.TypeString, + Computed: true, + }, + + "keypair_id": { + Type: schema.TypeString, + Computed: true, + }, + + "labels": { + Type: schema.TypeMap, + Computed: true, + }, + + "master_lb_enabled": { + Type: schema.TypeBool, + Computed: true, + }, + + "network_driver": { + Type: schema.TypeString, + Computed: true, + }, + + "no_proxy": { + Type: schema.TypeString, + Computed: true, + }, + + "public": { + Type: schema.TypeBool, + Computed: true, + }, + + "registry_enabled": { + Type: schema.TypeBool, + Computed: true, + }, + + "server_type": { + Type: schema.TypeString, + Computed: true, + }, + + "tls_disabled": { + Type: schema.TypeBool, + Computed: true, + }, + + "volume_driver": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceContainerInfraClusterTemplateV1Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + containerInfraClient, err := config.containerInfraV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack container infra client: %s", err) + } + + name := d.Get("name").(string) + ct, err := clustertemplates.Get(containerInfraClient, name).Extract() + if err != nil { + return fmt.Errorf("Error getting openstack_containerinfra_clustertemplate_v1 %s: %s", name, err) + } + + d.SetId(ct.UUID) + + d.Set("project_id", ct.ProjectID) + d.Set("user_id", ct.UserID) + d.Set("apiserver_port", ct.APIServerPort) + d.Set("coe", ct.COE) + d.Set("cluster_distro", ct.ClusterDistro) + d.Set("dns_nameserver", ct.DNSNameServer) + d.Set("docker_storage_driver", ct.DockerStorageDriver) + d.Set("docker_volume_size", ct.DockerVolumeSize) + d.Set("external_network_id", ct.ExternalNetworkID) + d.Set("fixed_network", ct.FixedNetwork) + d.Set("fixed_subnet", ct.FixedSubnet) + d.Set("flavor", ct.FlavorID) + d.Set("master_flavor", ct.MasterFlavorID) + d.Set("floating_ip_enabled", ct.FloatingIPEnabled) + d.Set("http_proxy", ct.HTTPProxy) + d.Set("https_proxy", ct.HTTPSProxy) + d.Set("image", ct.ImageID) + d.Set("insecure_registry", ct.InsecureRegistry) + d.Set("keypair_id", ct.KeyPairID) + d.Set("labels", ct.Labels) + d.Set("master_lb_enabled", ct.MasterLBEnabled) + d.Set("network_driver", ct.NetworkDriver) + d.Set("no_proxy", ct.NoProxy) + d.Set("public", ct.Public) + d.Set("registry_enabled", ct.RegistryEnabled) + d.Set("server_type", ct.ServerType) + d.Set("tls_disabled", ct.TLSDisabled) + d.Set("volume_driver", ct.VolumeDriver) + + if err := d.Set("created_at", ct.CreatedAt.Format(time.RFC3339)); err != nil { + log.Printf("[DEBUG] Unable to set openstack_containerinfra_clustertemplate_v1 created_at: %s", err) + } + if err := d.Set("updated_at", ct.UpdatedAt.Format(time.RFC3339)); err != nil { + log.Printf("[DEBUG] Unable to set openstack_containerinfra_clustertemplate_v1 updated_at: %s", err) + } + + d.Set("region", GetRegion(d, config)) + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_dns_zone_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_dns_zone_v2.go new file mode 100644 index 000000000..8ffe96dad --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_dns_zone_v2.go @@ -0,0 +1,204 @@ +package openstack + +import ( + "fmt" + "log" + "time" + + "github.com/gophercloud/gophercloud/openstack/dns/v2/zones" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceDNSZoneV2() *schema.Resource { + return &schema.Resource{ + Read: dataSourceDNSZoneV2Read, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "name": { + Type: schema.TypeString, + Optional: true, + }, + + "pool_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "project_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "email": { + Type: schema.TypeString, + Optional: true, + }, + + "description": { + Type: schema.TypeString, + Optional: true, + }, + + "status": { + Type: schema.TypeString, + Optional: true, + }, + + "type": { + Type: schema.TypeString, + Optional: true, + }, + + "ttl": { + Type: schema.TypeInt, + Optional: true, + }, + + "version": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + + "serial": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + + "created_at": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "updated_at": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "transferred_at": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "attributes": { + Type: schema.TypeMap, + Optional: true, + Computed: true, + }, + + "masters": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func dataSourceDNSZoneV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + dnsClient, err := config.dnsV2Client(GetRegion(d, config)) + if err != nil { + return err + } + + listOpts := zones.ListOpts{} + + if v, ok := d.GetOk("name"); ok { + listOpts.Name = v.(string) + } + + if v, ok := d.GetOk("description"); ok { + listOpts.Description = v.(string) + } + + if v, ok := d.GetOk("email"); ok { + listOpts.Email = v.(string) + } + + if v, ok := d.GetOk("status"); ok { + listOpts.Status = v.(string) + } + + if v, ok := d.GetOk("ttl"); ok { + listOpts.TTL = v.(int) + } + + if v, ok := d.GetOk("type"); ok { + listOpts.Type = v.(string) + } + + pages, err := zones.List(dnsClient, listOpts).AllPages() + if err != nil { + return fmt.Errorf("Unable to retrieve zones: %s", err) + } + + allZones, err := zones.ExtractZones(pages) + if err != nil { + return fmt.Errorf("Unable to extract zones: %s", err) + } + + if len(allZones) < 1 { + return fmt.Errorf("Your query returned no results. " + + "Please change your search criteria and try again.") + } + + if len(allZones) > 1 { + return fmt.Errorf("Your query returned more than one result." + + " Please try a more specific search criteria") + } + + zone := allZones[0] + + log.Printf("[DEBUG] Retrieved DNS Zone %s: %+v", zone.ID, zone) + d.SetId(zone.ID) + + // strings + d.Set("name", zone.Name) + d.Set("pool_id", zone.PoolID) + d.Set("project_id", zone.ProjectID) + d.Set("email", zone.Email) + d.Set("description", zone.Description) + d.Set("status", zone.Status) + d.Set("type", zone.Type) + d.Set("region", GetRegion(d, config)) + + // ints + d.Set("ttl", zone.TTL) + d.Set("version", zone.Version) + d.Set("serial", zone.Serial) + + // time.Times + d.Set("created_at", zone.CreatedAt.Format(time.RFC3339)) + d.Set("updated_at", zone.UpdatedAt.Format(time.RFC3339)) + d.Set("transferred_at", zone.TransferredAt.Format(time.RFC3339)) + + // maps + err = d.Set("attributes", zone.Attributes) + if err != nil { + log.Printf("[DEBUG] Unable to set attributes: %s", err) + return err + } + + // slices + err = d.Set("masters", zone.Masters) + if err != nil { + log.Printf("[DEBUG] Unable to set masters: %s", err) + return err + } + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_fw_policy_v1.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_fw_policy_v1.go new file mode 100644 index 000000000..52e21fd58 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_fw_policy_v1.go @@ -0,0 +1,102 @@ +package openstack + +import ( + "fmt" + "log" + + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceFWPolicyV1() *schema.Resource { + return &schema.Resource{ + Read: dataSourceFWPolicyV1Read, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "policy_id": { + Type: schema.TypeString, + Optional: true, + }, + "name": { + Type: schema.TypeString, + Optional: true, + }, + "tenant_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Computed: true, + }, + "audited": { + Type: schema.TypeBool, + Computed: true, + }, + "shared": { + Type: schema.TypeBool, + Computed: true, + }, + "rules": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func dataSourceFWPolicyV1Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + listOpts := policies.ListOpts{ + ID: d.Get("policy_id").(string), + Name: d.Get("name").(string), + TenantID: d.Get("tenant_id").(string), + } + + pages, err := policies.List(networkingClient, listOpts).AllPages() + if err != nil { + return err + } + + allFWPolicies, err := policies.ExtractPolicies(pages) + if err != nil { + return fmt.Errorf("Unable to retrieve firewall policies: %s", err) + } + + if len(allFWPolicies) < 1 { + return fmt.Errorf("No firewall policies found with name: %s", d.Get("name")) + } + + if len(allFWPolicies) > 1 { + return fmt.Errorf("More than one firewall policies found with name: %s", d.Get("name")) + } + + policy := allFWPolicies[0] + + log.Printf("[DEBUG] Retrieved firewall policies %s: %+v", policy.ID, policy) + d.SetId(policy.ID) + + d.Set("name", policy.Name) + d.Set("tenant_id", policy.TenantID) + d.Set("description", policy.Description) + d.Set("audited", policy.Audited) + d.Set("shared", policy.Shared) + d.Set("rules", policy.Rules) + d.Set("region", GetRegion(d, config)) + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_identity_auth_scope_v3.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_identity_auth_scope_v3.go new file mode 100644 index 000000000..d062f734b --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_identity_auth_scope_v3.go @@ -0,0 +1,135 @@ +package openstack + +import ( + "log" + + "github.com/hashicorp/terraform/helper/schema" + + "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens" +) + +func dataSourceIdentityAuthScopeV3() *schema.Resource { + return &schema.Resource{ + Read: dataSourceIdentityAuthScopeV3Read, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + // computed attributes + "user_id": { + Type: schema.TypeString, + Computed: true, + }, + + "user_name": { + Type: schema.TypeString, + Computed: true, + }, + + "user_domain_id": { + Type: schema.TypeString, + Computed: true, + }, + + "user_domain_name": { + Type: schema.TypeString, + Computed: true, + }, + + "project_id": { + Type: schema.TypeString, + Computed: true, + }, + + "project_name": { + Type: schema.TypeString, + Computed: true, + }, + + "project_domain_id": { + Type: schema.TypeString, + Computed: true, + }, + + "project_domain_name": { + Type: schema.TypeString, + Computed: true, + }, + + "roles": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "role_id": { + Type: schema.TypeString, + Computed: true, + }, + "role_name": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceIdentityAuthScopeV3Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + identityClient, err := config.identityV3Client(GetRegion(d, config)) + tokenID := config.OsClient.TokenID + + d.SetId(d.Get("name").(string)) + + result := tokens.Get(identityClient, tokenID) + if result.Err != nil { + return result.Err + } + + user, err := result.ExtractUser() + if err != nil { + return err + } + + d.Set("user_name", user.Name) + d.Set("user_id", user.Name) + d.Set("user_domain_name", user.Domain.Name) + d.Set("user_domain_id", user.Domain.ID) + + project, err := result.ExtractProject() + if err != nil { + return err + } + + d.Set("project_name", project.Name) + d.Set("project_id", project.ID) + d.Set("project_domain_name", project.Domain.Name) + d.Set("project_domain_id", project.Domain.ID) + + roles, err := result.ExtractRoles() + if err != nil { + return err + } + + allRoles := flattenIdentityAuthScopeV3Roles(roles) + if err := d.Set("roles", allRoles); err != nil { + log.Printf("[DEBUG] Unable to set openstack_identity_auth_scope_v3 roles: %s", err) + } + + d.Set("region", GetRegion(d, config)) + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_identity_endpoint_v3.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_identity_endpoint_v3.go new file mode 100644 index 000000000..542a057c5 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_identity_endpoint_v3.go @@ -0,0 +1,144 @@ +package openstack + +import ( + "fmt" + "log" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/identity/v3/endpoints" + "github.com/gophercloud/gophercloud/openstack/identity/v3/services" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" +) + +func dataSourceIdentityEndpointV3() *schema.Resource { + return &schema.Resource{ + Read: dataSourceIdentityEndpointV3Read, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "service_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "service_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "interface": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "public", + ValidateFunc: validation.StringInSlice([]string{ + "public", "internal", "admin", + }, false), + }, + + "url": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +// dataSourceIdentityEndpointV3Read performs the endpoint lookup. +func dataSourceIdentityEndpointV3Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + identityClient, err := config.identityV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack identity client: %s", err) + } + + availability := gophercloud.AvailabilityPublic + switch d.Get("interface") { + case "internal": + availability = gophercloud.AvailabilityInternal + case "admin": + availability = gophercloud.AvailabilityAdmin + } + + listOpts := endpoints.ListOpts{ + Availability: availability, + ServiceID: d.Get("service_id").(string), + } + + log.Printf("[DEBUG] openstack_identity_endpoint_v3 list options: %#v", listOpts) + + var endpoint endpoints.Endpoint + allPages, err := endpoints.List(identityClient, listOpts).AllPages() + if err != nil { + return fmt.Errorf("Unable to query openstack_identity_endpoint_v3: %s", err) + } + + allEndpoints, err := endpoints.ExtractEndpoints(allPages) + if err != nil { + return fmt.Errorf("Unable to retrieve openstack_identity_endpoint_v3: %s", err) + } + + serviceName := d.Get("service_name").(string) + if len(allEndpoints) > 1 && serviceName != "" { + var filteredEndpoints []endpoints.Endpoint + + // Query all services to further filter results + allServicePages, err := services.List(identityClient, nil).AllPages() + if err != nil { + return fmt.Errorf("Unable to query openstack_identity_endpoint_v3 services: %s", err) + } + + allServices, err := services.ExtractServices(allServicePages) + if err != nil { + return fmt.Errorf("Unable to retrieve openstack_identity_endpoint_v3 services: %s", err) + } + + for _, endpoint := range allEndpoints { + for _, service := range allServices { + if v, ok := service.Extra["name"].(string); ok { + if endpoint.ServiceID == service.ID && serviceName == v { + endpoint.Name = v + filteredEndpoints = append(filteredEndpoints, endpoint) + } + } + } + } + + allEndpoints = filteredEndpoints + } + + if len(allEndpoints) < 1 { + return fmt.Errorf("Your openstack_identity_endpoint_v3 query returned no results. " + + "Please change your search criteria and try again.") + } + + if len(allEndpoints) > 1 { + return fmt.Errorf("Your openstack_identity_endpoint_v3 query returned more than one result") + } + endpoint = allEndpoints[0] + + return dataSourceIdentityEndpointV3Attributes(d, &endpoint) +} + +// dataSourceIdentityEndpointV3Attributes populates the fields of an Endpoint resource. +func dataSourceIdentityEndpointV3Attributes(d *schema.ResourceData, endpoint *endpoints.Endpoint) error { + log.Printf("[DEBUG] openstack_identity_endpoint_v3 details: %#v", endpoint) + + d.SetId(endpoint.ID) + d.Set("interface", endpoint.Availability) + d.Set("region", endpoint.Region) + d.Set("service_id", endpoint.ServiceID) + d.Set("service_name", endpoint.Name) + d.Set("url", endpoint.URL) + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_identity_group_v3.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_identity_group_v3.go new file mode 100644 index 000000000..ef5020fda --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_identity_group_v3.go @@ -0,0 +1,87 @@ +package openstack + +import ( + "fmt" + "log" + + "github.com/gophercloud/gophercloud/openstack/identity/v3/groups" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceIdentityGroupV3() *schema.Resource { + return &schema.Resource{ + Read: dataSourceIdentityGroupV3Read, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "domain_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "name": { + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +// dataSourceIdentityGroupV3Read performs the group lookup. +func dataSourceIdentityGroupV3Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + identityClient, err := config.identityV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack identity client: %s", err) + } + + listOpts := groups.ListOpts{ + DomainID: d.Get("domain_id").(string), + Name: d.Get("name").(string), + } + + log.Printf("[DEBUG] openstack_identity_group_v3 list options: %#v", listOpts) + + var group groups.Group + allPages, err := groups.List(identityClient, listOpts).AllPages() + if err != nil { + return fmt.Errorf("Unable to query openstack_identity_group_v3: %s", err) + } + + allGroups, err := groups.ExtractGroups(allPages) + if err != nil { + return fmt.Errorf("Unable to retrieve openstack_identity_group_v3: %s", err) + } + + if len(allGroups) < 1 { + return fmt.Errorf("Your openstack_identity_group_v3 query returned no results. " + + "Please change your search criteria and try again.") + } + + if len(allGroups) > 1 { + return fmt.Errorf("Your openstack_identity_group_v3 query returned more than one result.") + } + + group = allGroups[0] + + return dataSourceIdentityGroupV3Attributes(d, config, &group) +} + +// dataSourceIdentityRoleV3Attributes populates the fields of an Role resource. +func dataSourceIdentityGroupV3Attributes(d *schema.ResourceData, config *Config, group *groups.Group) error { + log.Printf("[DEBUG] openstack_identity_group_v3 details: %#v", group) + + d.SetId(group.ID) + d.Set("name", group.Name) + d.Set("domain_id", group.DomainID) + d.Set("region", GetRegion(d, config)) + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_identity_project_v3.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_identity_project_v3.go new file mode 100644 index 000000000..7fd58e25b --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_identity_project_v3.go @@ -0,0 +1,117 @@ +package openstack + +import ( + "fmt" + "log" + + "github.com/gophercloud/gophercloud/openstack/identity/v3/projects" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceIdentityProjectV3() *schema.Resource { + return &schema.Resource{ + Read: dataSourceIdentityProjectV3Read, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "description": { + Type: schema.TypeString, + Computed: true, + }, + + "domain_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + + "is_domain": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "name": { + Type: schema.TypeString, + Optional: true, + }, + + "parent_id": { + Type: schema.TypeString, + Optional: true, + }, + }, + } +} + +// dataSourceIdentityProjectV3Read performs the project lookup. +func dataSourceIdentityProjectV3Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + identityClient, err := config.identityV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack identity client: %s", err) + } + + enabled := d.Get("enabled").(bool) + isDomain := d.Get("is_domain").(bool) + listOpts := projects.ListOpts{ + DomainID: d.Get("domain_id").(string), + Enabled: &enabled, + IsDomain: &isDomain, + Name: d.Get("name").(string), + ParentID: d.Get("parent_id").(string), + } + + log.Printf("[DEBUG] openstack_identity_project_v3 list options: %#v", listOpts) + + var project projects.Project + allPages, err := projects.List(identityClient, listOpts).AllPages() + if err != nil { + return fmt.Errorf("Unable to query openstack_identity_project_v3: %s", err) + } + + allProjects, err := projects.ExtractProjects(allPages) + if err != nil { + return fmt.Errorf("Unable to retrieve openstack_identity_project_v3: %s", err) + } + + if len(allProjects) < 1 { + return fmt.Errorf("Your openstack_identity_project_v3 query returned no results. " + + "Please change your search criteria and try again.") + } + + if len(allProjects) > 1 { + return fmt.Errorf("Your openstack_identity_project_v3 query returned more than one result.") + } + + project = allProjects[0] + + return dataSourceIdentityProjectV3Attributes(d, &project) +} + +// dataSourceIdentityProjectV3Attributes populates the fields of an Project resource. +func dataSourceIdentityProjectV3Attributes(d *schema.ResourceData, project *projects.Project) error { + log.Printf("[DEBUG] openstack_identity_project_v3 details: %#v", project) + + d.SetId(project.ID) + d.Set("is_domain", project.IsDomain) + d.Set("description", project.Description) + d.Set("domain_id", project.DomainID) + d.Set("enabled", project.Enabled) + d.Set("name", project.Name) + d.Set("parent_id", project.ParentID) + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_identity_role_v3.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_identity_role_v3.go new file mode 100644 index 000000000..95afa7760 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_identity_role_v3.go @@ -0,0 +1,86 @@ +package openstack + +import ( + "fmt" + "log" + + "github.com/gophercloud/gophercloud/openstack/identity/v3/roles" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceIdentityRoleV3() *schema.Resource { + return &schema.Resource{ + Read: dataSourceIdentityRoleV3Read, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "domain_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "name": { + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +// dataSourceIdentityRoleV3Read performs the role lookup. +func dataSourceIdentityRoleV3Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + identityClient, err := config.identityV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack identity client: %s", err) + } + + listOpts := roles.ListOpts{ + DomainID: d.Get("domain_id").(string), + Name: d.Get("name").(string), + } + + log.Printf("[DEBUG] openstack_identity_role_v3 list options: %#v", listOpts) + + var role roles.Role + allPages, err := roles.List(identityClient, listOpts).AllPages() + if err != nil { + return fmt.Errorf("Unable to query openstack_identity_role_v3: %s", err) + } + + allRoles, err := roles.ExtractRoles(allPages) + if err != nil { + return fmt.Errorf("Unable to retrieve openstack_identity_role_v3: %s", err) + } + + if len(allRoles) < 1 { + return fmt.Errorf("Your openstack_identity_role_v3 query returned no results.") + } + + if len(allRoles) > 1 { + return fmt.Errorf("Your openstack_identity_role_v3 query returned more than one result.") + } + + role = allRoles[0] + + return dataSourceIdentityRoleV3Attributes(d, config, &role) +} + +// dataSourceIdentityRoleV3Attributes populates the fields of an Role resource. +func dataSourceIdentityRoleV3Attributes(d *schema.ResourceData, config *Config, role *roles.Role) error { + log.Printf("[DEBUG] openstack_identity_role_v3 details: %#v", role) + + d.SetId(role.ID) + d.Set("name", role.Name) + d.Set("domain_id", role.DomainID) + d.Set("region", GetRegion(d, config)) + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_identity_user_v3.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_identity_user_v3.go new file mode 100644 index 000000000..3bd20f27e --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_identity_user_v3.go @@ -0,0 +1,129 @@ +package openstack + +import ( + "fmt" + "log" + "time" + + "github.com/gophercloud/gophercloud/openstack/identity/v3/users" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceIdentityUserV3() *schema.Resource { + return &schema.Resource{ + Read: dataSourceIdentityUserV3Read, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "default_project_id": { + Type: schema.TypeString, + Computed: true, + }, + + "domain_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + + "idp_id": { + Type: schema.TypeString, + Optional: true, + }, + + "name": { + Type: schema.TypeString, + Optional: true, + }, + + "password_expires_at": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validatePasswordExpiresAtQuery, + }, + + "protocol_id": { + Type: schema.TypeString, + Optional: true, + }, + + "unique_id": { + Type: schema.TypeString, + Optional: true, + }, + }, + } +} + +// dataSourceIdentityUserV3Read performs the user lookup. +func dataSourceIdentityUserV3Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + identityClient, err := config.identityV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack identity client: %s", err) + } + + enabled := d.Get("enabled").(bool) + listOpts := users.ListOpts{ + DomainID: d.Get("domain_id").(string), + Enabled: &enabled, + IdPID: d.Get("idp_id").(string), + Name: d.Get("name").(string), + PasswordExpiresAt: d.Get("password_expires_at").(string), + ProtocolID: d.Get("protocol_id").(string), + UniqueID: d.Get("unique_id").(string), + } + + log.Printf("[DEBUG] openstack_identity_user_v3 list options: %#v", listOpts) + + var user users.User + allPages, err := users.List(identityClient, listOpts).AllPages() + if err != nil { + return fmt.Errorf("Unable to query openstack_identity_user_v3: %s", err) + } + + allUsers, err := users.ExtractUsers(allPages) + if err != nil { + return fmt.Errorf("Unable to retrieve openstack_identity_user_v3: %s", err) + } + + if len(allUsers) < 1 { + return fmt.Errorf("Your openstack_identity_user_v3 query returned no results. " + + "Please change your search criteria and try again.") + } + + if len(allUsers) > 1 { + return fmt.Errorf("Your openstack_identity_user_v3 query returned more than one result.") + } + + user = allUsers[0] + + return dataSourceIdentityUserV3Attributes(d, &user) +} + +// dataSourceIdentityUserV3Attributes populates the fields of an User resource. +func dataSourceIdentityUserV3Attributes(d *schema.ResourceData, user *users.User) error { + log.Printf("[DEBUG] openstack_identity_user_v3 details: %#v", user) + + d.SetId(user.ID) + d.Set("default_project_id", user.DefaultProjectID) + d.Set("description", user.Description) + d.Set("domain_id", user.DomainID) + d.Set("enabled", user.Enabled) + d.Set("name", user.Name) + d.Set("password_expires_at", user.PasswordExpiresAt.Format(time.RFC3339)) + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_images_image_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_images_image_v2.go index 7e5bf2d1a..f6ae228d5 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_images_image_v2.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_images_image_v2.go @@ -4,9 +4,9 @@ import ( "fmt" "log" "sort" + "time" "github.com/gophercloud/gophercloud/openstack/imageservice/v2/images" - "github.com/gophercloud/gophercloud/pagination" "github.com/hashicorp/terraform/helper/schema" ) @@ -16,7 +16,7 @@ func dataSourceImagesImageV2() *schema.Resource { Read: dataSourceImagesImageV2Read, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, @@ -35,6 +35,12 @@ func dataSourceImagesImageV2() *schema.Resource { ForceNew: true, }, + "member_status": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "owner": { Type: schema.TypeString, Optional: true, @@ -81,6 +87,12 @@ func dataSourceImagesImageV2() *schema.Resource { ForceNew: true, }, + "properties": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + }, + // Computed values "container_format": { Type: schema.TypeString, @@ -122,6 +134,11 @@ func dataSourceImagesImageV2() *schema.Resource { Computed: true, }, + "created_at": { + Type: schema.TypeString, + Computed: true, + }, + "updated_at": { Type: schema.TypeString, Computed: true, @@ -136,6 +153,13 @@ func dataSourceImagesImageV2() *schema.Resource { Type: schema.TypeString, Computed: true, }, + + "tags": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, }, } } @@ -149,49 +173,80 @@ func dataSourceImagesImageV2Read(d *schema.ResourceData, meta interface{}) error } visibility := resourceImagesImageV2VisibilityFromString(d.Get("visibility").(string)) + member_status := resourceImagesImageV2MemberStatusFromString(d.Get("member_status").(string)) - listOpts := images.ListOpts{ - Name: d.Get("name").(string), - Visibility: visibility, - Owner: d.Get("owner").(string), - Status: images.ImageStatusActive, - SizeMin: int64(d.Get("size_min").(int)), - SizeMax: int64(d.Get("size_max").(int)), - SortKey: d.Get("sort_key").(string), - SortDir: d.Get("sort_direction").(string), - Tag: d.Get("tag").(string), + var tags []string + tag := d.Get("tag").(string) + if tag != "" { + tags = append(tags, tag) } - var allImages []images.Image - pager := images.List(imageClient, listOpts) - err = pager.EachPage(func(page pagination.Page) (bool, error) { - images, err := images.ExtractImages(page) - if err != nil { - return false, err - } + listOpts := images.ListOpts{ + Name: d.Get("name").(string), + Visibility: visibility, + Owner: d.Get("owner").(string), + Status: images.ImageStatusActive, + SizeMin: int64(d.Get("size_min").(int)), + SizeMax: int64(d.Get("size_max").(int)), + SortKey: d.Get("sort_key").(string), + SortDir: d.Get("sort_direction").(string), + Tags: tags, + MemberStatus: member_status, + } - for _, i := range images { - allImages = append(allImages, i) - } + log.Printf("[DEBUG] List Options: %#v", listOpts) - return true, nil - }) + var image images.Image + allPages, err := images.List(imageClient, listOpts).AllPages() + if err != nil { + return fmt.Errorf("Unable to query images: %s", err) + } + allImages, err := images.ExtractImages(allPages) if err != nil { return fmt.Errorf("Unable to retrieve images: %s", err) } - var image images.Image + properties := d.Get("properties").(map[string]interface{}) + imageProperties := resourceImagesImageV2ExpandProperties(properties) + if len(allImages) > 1 && len(imageProperties) > 0 { + var filteredImages []images.Image + for _, image := range allImages { + if len(image.Properties) > 0 { + match := true + for searchKey, searchValue := range imageProperties { + imageValue, ok := image.Properties[searchKey] + if !ok { + match = false + break + } + + if searchValue != imageValue { + match = false + break + } + } + + if match { + filteredImages = append(filteredImages, image) + } + } + } + allImages = filteredImages + } + if len(allImages) < 1 { - return fmt.Errorf("Your query returned no results. Please change your search criteria and try again.") + return fmt.Errorf("Your query returned no results. " + + "Please change your search criteria and try again.") } if len(allImages) > 1 { recent := d.Get("most_recent").(bool) - log.Printf("[DEBUG] openstack_images_image: multiple results found and `most_recent` is set to: %t", recent) + log.Printf("[DEBUG] Multiple results found and `most_recent` is set to: %t", recent) if recent { image = mostRecentImage(allImages) } else { + log.Printf("[DEBUG] Multiple results found: %#v", allImages) return fmt.Errorf("Your query returned more than one result. Please try a more " + "specific search criteria, or set `most_recent` attribute to true.") } @@ -199,7 +254,7 @@ func dataSourceImagesImageV2Read(d *schema.ResourceData, meta interface{}) error image = allImages[0] } - log.Printf("[DEBUG] openstack_images_image: Single Image found: %s", image.ID) + log.Printf("[DEBUG] Single Image found: %s", image.ID) return dataSourceImagesImageV2Attributes(d, &image) } @@ -220,8 +275,8 @@ func dataSourceImagesImageV2Attributes(d *schema.ResourceData, image *images.Ima d.Set("checksum", image.Checksum) d.Set("size_bytes", image.SizeBytes) d.Set("metadata", image.Metadata) - d.Set("created_at", image.CreatedAt) - d.Set("updated_at", image.UpdatedAt) + d.Set("created_at", image.CreatedAt.Format(time.RFC3339)) + d.Set("updated_at", image.UpdatedAt.Format(time.RFC3339)) d.Set("file", image.File) d.Set("schema", image.Schema) @@ -233,8 +288,8 @@ type imageSort []images.Image func (a imageSort) Len() int { return len(a) } func (a imageSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a imageSort) Less(i, j int) bool { - itime := a[i].UpdatedAt - jtime := a[j].UpdatedAt + itime := a[i].CreatedAt + jtime := a[j].CreatedAt return itime.Unix() < jtime.Unix() } diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_networking_floatingip_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_networking_floatingip_v2.go new file mode 100644 index 000000000..144e91584 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_networking_floatingip_v2.go @@ -0,0 +1,149 @@ +package openstack + +import ( + "fmt" + "log" + "strings" + + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" + + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceNetworkingFloatingIPV2() *schema.Resource { + return &schema.Resource{ + Read: dataSourceNetworkingFloatingIPV2Read, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + }, + + "description": { + Type: schema.TypeString, + Optional: true, + }, + + "address": { + Type: schema.TypeString, + Optional: true, + }, + + "pool": { + Type: schema.TypeString, + Optional: true, + }, + + "port_id": { + Type: schema.TypeString, + Optional: true, + }, + + "tenant_id": { + Type: schema.TypeString, + Optional: true, + }, + + "fixed_ip": { + Type: schema.TypeString, + Optional: true, + }, + + "status": { + Type: schema.TypeString, + Optional: true, + }, + + "tags": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "all_tags": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func dataSourceNetworkingFloatingIPV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + listOpts := floatingips.ListOpts{} + + if v, ok := d.GetOk("description"); ok { + listOpts.Description = v.(string) + } + + if v, ok := d.GetOk("address"); ok { + listOpts.FloatingIP = v.(string) + } + + if v, ok := d.GetOk("tenant_id"); ok { + listOpts.TenantID = v.(string) + } + + if v, ok := d.GetOk("pool"); ok { + listOpts.FloatingNetworkID = v.(string) + } + + if v, ok := d.GetOk("port_id"); ok { + listOpts.PortID = v.(string) + } + + if v, ok := d.GetOk("fixed_ip"); ok { + listOpts.FixedIP = v.(string) + } + + if v, ok := d.GetOk("status"); ok { + listOpts.Status = v.(string) + } + + tags := networkV2AttributesTags(d) + if len(tags) > 0 { + listOpts.Tags = strings.Join(tags, ",") + } + + pages, err := floatingips.List(networkingClient, listOpts).AllPages() + if err != nil { + return fmt.Errorf("Unable to list openstack_networking_floatingips_v2: %s", err) + } + + allFloatingIPs, err := floatingips.ExtractFloatingIPs(pages) + if err != nil { + return fmt.Errorf("Unable to retrieve openstack_networking_floatingips_v2: %s", err) + } + + if len(allFloatingIPs) < 1 { + return fmt.Errorf("No openstack_networking_floatingip_v2 found") + } + + if len(allFloatingIPs) > 1 { + return fmt.Errorf("More than one openstack_networking_floatingip_v2 found") + } + + fip := allFloatingIPs[0] + + log.Printf("[DEBUG] Retrieved openstack_networking_floatingip_v2 %s: %+v", fip.ID, fip) + d.SetId(fip.ID) + + d.Set("description", fip.Description) + d.Set("address", fip.FloatingIP) + d.Set("pool", fip.FloatingNetworkID) + d.Set("port_id", fip.PortID) + d.Set("fixed_ip", fip.FixedIP) + d.Set("tenant_id", fip.TenantID) + d.Set("status", fip.Status) + d.Set("all_tags", fip.Tags) + d.Set("region", GetRegion(d, config)) + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_networking_network_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_networking_network_v2.go index 06e2db28b..50b626785 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_networking_network_v2.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_networking_network_v2.go @@ -4,10 +4,13 @@ import ( "fmt" "log" "strconv" + "strings" "github.com/hashicorp/terraform/helper/schema" "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent" "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" "github.com/gophercloud/gophercloud/openstack/networking/v2/subnets" ) @@ -17,41 +20,81 @@ func dataSourceNetworkingNetworkV2() *schema.Resource { Read: dataSourceNetworkingNetworkV2Read, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "network_id": &schema.Schema{ + + "network_id": { Type: schema.TypeString, Optional: true, }, - "name": &schema.Schema{ + + "name": { Type: schema.TypeString, Optional: true, }, - "matching_subnet_cidr": &schema.Schema{ + + "description": { Type: schema.TypeString, Optional: true, }, - "tenant_id": &schema.Schema{ + + "status": { Type: schema.TypeString, Optional: true, - DefaultFunc: schema.MultiEnvDefaultFunc([]string{ - "OS_TENANT_ID", - "OS_PROJECT_ID", - }, ""), + }, + + "matching_subnet_cidr": { + Type: schema.TypeString, + Optional: true, + }, + + "tenant_id": { + Type: schema.TypeString, + Optional: true, Description: descriptions["tenant_id"], }, - "admin_state_up": &schema.Schema{ + + "admin_state_up": { Type: schema.TypeString, Computed: true, }, - "shared": &schema.Schema{ + + "shared": { Type: schema.TypeString, Computed: true, }, + + "external": { + Type: schema.TypeBool, + Optional: true, + }, + + "availability_zone_hints": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "transparent_vlan": { + Type: schema.TypeBool, + Optional: true, + }, + + "tags": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "all_tags": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, }, } } @@ -59,21 +102,78 @@ func dataSourceNetworkingNetworkV2() *schema.Resource { func dataSourceNetworkingNetworkV2Read(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } - listOpts := networks.ListOpts{ - ID: d.Get("network_id").(string), - Name: d.Get("name").(string), - TenantID: d.Get("tenant_id").(string), - Status: "ACTIVE", + // Prepare basic listOpts. + var listOpts networks.ListOptsBuilder + + var status string + if v, ok := d.GetOk("status"); ok { + status = v.(string) + } + + listOpts = networks.ListOpts{ + ID: d.Get("network_id").(string), + Name: d.Get("name").(string), + Description: d.Get("description").(string), + TenantID: d.Get("tenant_id").(string), + Status: status, + } + + // Add the external attribute if specified. + if v, ok := d.GetOkExists("external"); ok { + isExternal := v.(bool) + listOpts = external.ListOptsExt{ + ListOptsBuilder: listOpts, + External: &isExternal, + } + } + + // Add the transparent VLAN attribute if specified. + if v, ok := d.GetOkExists("transparent_vlan"); ok { + isVLANTransparent := v.(bool) + listOpts = vlantransparent.ListOptsExt{ + ListOptsBuilder: listOpts, + VLANTransparent: &isVLANTransparent, + } + } + + tags := networkV2AttributesTags(d) + if len(tags) > 0 { + listOpts = networks.ListOpts{Tags: strings.Join(tags, ",")} } pages, err := networks.List(networkingClient, listOpts).AllPages() - allNetworks, err := networks.ExtractNetworks(pages) if err != nil { - return fmt.Errorf("Unable to retrieve networks: %s", err) + return err } - var refinedNetworks []networks.Network + // First extract into a normal networks.Network in order to see if + // there were any results at all. + tmpAllNetworks, err := networks.ExtractNetworks(pages) + if err != nil { + return err + } + + if len(tmpAllNetworks) < 1 { + return fmt.Errorf("Your query returned no results. " + + "Please change your search criteria and try again.") + } + + type networkWithExternalExt struct { + networks.Network + external.NetworkExternalExt + vlantransparent.TransparentExt + } + var allNetworks []networkWithExternalExt + err = networks.ExtractNetworksInto(pages, &allNetworks) + if err != nil { + return fmt.Errorf("Unable to retrieve openstack_networking_networks_v2: %s", err) + } + + var refinedNetworks []networkWithExternalExt if cidr := d.Get("matching_subnet_cidr").(string); cidr != "" { for _, n := range allNetworks { for _, s := range n.Subnets { @@ -82,7 +182,7 @@ func dataSourceNetworkingNetworkV2Read(d *schema.ResourceData, meta interface{}) if _, ok := err.(gophercloud.ErrDefault404); ok { continue } - return fmt.Errorf("Unable to retrieve network subnet: %s", err) + return fmt.Errorf("Unable to retrieve openstack_networking_network_v2 subnet: %s", err) } if cidr == subnet.CIDR { refinedNetworks = append(refinedNetworks, n) @@ -105,13 +205,21 @@ func dataSourceNetworkingNetworkV2Read(d *schema.ResourceData, meta interface{}) network := refinedNetworks[0] - log.Printf("[DEBUG] Retrieved Network %s: %+v", network.ID, network) + if err = d.Set("availability_zone_hints", network.AvailabilityZoneHints); err != nil { + log.Printf("[DEBUG] Unable to set availability_zone_hints for openstack_networking_network_v2 %s: %s", network.ID, err) + } + + log.Printf("[DEBUG] Retrieved openstack_networking_network_v2 %s: %+v", network.ID, network) d.SetId(network.ID) d.Set("name", network.Name) + d.Set("description", network.Description) d.Set("admin_state_up", strconv.FormatBool(network.AdminStateUp)) d.Set("shared", strconv.FormatBool(network.Shared)) + d.Set("external", network.External) d.Set("tenant_id", network.TenantID) + d.Set("transparent_vlan", network.VLANTransparent) + d.Set("all_tags", network.Tags) d.Set("region", GetRegion(d, config)) return nil diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_networking_port_ids_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_networking_port_ids_v2.go new file mode 100644 index 000000000..f84c24e94 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_networking_port_ids_v2.go @@ -0,0 +1,254 @@ +package openstack + +import ( + "fmt" + "log" + "strings" + + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + + "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" +) + +func dataSourceNetworkingPortIDsV2() *schema.Resource { + return &schema.Resource{ + Read: dataSourceNetworkingPortIDsV2Read, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "description": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "admin_state_up": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + + "network_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "tenant_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "project_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "device_owner": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "mac_address": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "device_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "fixed_ip": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.SingleIP(), + }, + + "status": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "security_group_ids": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + + "tags": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "sort_key": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "sort_direction": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + "asc", "desc", + }, true), + }, + + "ids": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func dataSourceNetworkingPortIDsV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + listOpts := ports.ListOpts{} + + if v, ok := d.GetOk("sort_key"); ok { + listOpts.SortKey = v.(string) + } + + if v, ok := d.GetOk("sort_direction"); ok { + listOpts.SortDir = v.(string) + } + + if v, ok := d.GetOk("name"); ok { + listOpts.Name = v.(string) + } + + if v, ok := d.GetOk("description"); ok { + listOpts.Description = v.(string) + } + + if v, ok := d.GetOkExists("admin_state_up"); ok { + asu := v.(bool) + listOpts.AdminStateUp = &asu + } + + if v, ok := d.GetOk("network_id"); ok { + listOpts.NetworkID = v.(string) + } + + if v, ok := d.GetOk("status"); ok { + listOpts.Status = v.(string) + } + + if v, ok := d.GetOk("tenant_id"); ok { + listOpts.TenantID = v.(string) + } + + if v, ok := d.GetOk("project_id"); ok { + listOpts.ProjectID = v.(string) + } + + if v, ok := d.GetOk("device_owner"); ok { + listOpts.DeviceOwner = v.(string) + } + + if v, ok := d.GetOk("mac_address"); ok { + listOpts.MACAddress = v.(string) + } + + if v, ok := d.GetOk("device_id"); ok { + listOpts.DeviceID = v.(string) + } + + tags := networkV2AttributesTags(d) + if len(tags) > 0 { + listOpts.Tags = strings.Join(tags, ",") + } + + allPages, err := ports.List(networkingClient, listOpts).AllPages() + if err != nil { + return fmt.Errorf("Unable to list openstack_networking_port_ids_v2: %s", err) + } + + allPorts, err := ports.ExtractPorts(allPages) + if err != nil { + return fmt.Errorf("Unable to retrieve openstack_networking_port_ids_v2: %s", err) + } + + if len(allPorts) == 0 { + log.Printf("[DEBUG] No ports in openstack_networking_port_ids_v2 found") + } + + var portsList []ports.Port + var portIDs []string + + // Filter returned Fixed IPs by a "fixed_ip". + if v, ok := d.GetOk("fixed_ip"); ok { + for _, p := range allPorts { + for _, ipObject := range p.FixedIPs { + if v.(string) == ipObject.IPAddress { + portsList = append(portsList, p) + } + } + } + if len(portsList) == 0 { + log.Printf("[DEBUG] No ports in openstack_networking_port_ids_v2 found after the 'fixed_ip' filter") + } + } else { + portsList = allPorts + } + + securityGroups := expandToStringSlice(d.Get("security_group_ids").(*schema.Set).List()) + if len(securityGroups) > 0 { + var sgPorts []ports.Port + for _, p := range portsList { + for _, sg := range p.SecurityGroups { + if strSliceContains(securityGroups, sg) { + sgPorts = append(sgPorts, p) + } + } + } + if len(sgPorts) == 0 { + log.Printf("[DEBUG] No ports in openstack_networking_port_ids_v2 found after the 'security_group_ids' filter") + } + portsList = sgPorts + } + + for _, p := range portsList { + portIDs = append(portIDs, p.ID) + } + + log.Printf("[DEBUG] Retrieved %d ports in openstack_networking_port_ids_v2: %+v", len(portsList), portsList) + + d.SetId(fmt.Sprintf("%d", hashcode.String(strings.Join(portIDs, "")))) + d.Set("ids", portIDs) + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_networking_port_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_networking_port_v2.go new file mode 100644 index 000000000..87e571376 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_networking_port_v2.go @@ -0,0 +1,304 @@ +package openstack + +import ( + "fmt" + "log" + "strings" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/extradhcpopts" + "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" +) + +type extraPort struct { + ports.Port + extradhcpopts.ExtraDHCPOptsExt +} + +func dataSourceNetworkingPortV2() *schema.Resource { + return &schema.Resource{ + Read: dataSourceNetworkingPortV2Read, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + }, + + "port_id": { + Type: schema.TypeString, + Optional: true, + }, + + "name": { + Type: schema.TypeString, + Optional: true, + }, + + "description": { + Type: schema.TypeString, + Optional: true, + }, + + "admin_state_up": { + Type: schema.TypeBool, + Optional: true, + }, + + "network_id": { + Type: schema.TypeString, + Optional: true, + }, + + "tenant_id": { + Type: schema.TypeString, + Optional: true, + }, + + "project_id": { + Type: schema.TypeString, + Optional: true, + }, + + "device_owner": { + Type: schema.TypeString, + Optional: true, + }, + + "mac_address": { + Type: schema.TypeString, + Optional: true, + }, + + "device_id": { + Type: schema.TypeString, + Optional: true, + }, + + "fixed_ip": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.SingleIP(), + }, + + "status": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "security_group_ids": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + + "tags": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "allowed_address_pairs": { + Type: schema.TypeSet, + Computed: true, + Set: resourceNetworkingPortV2AllowedAddressPairsHash, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ip_address": { + Type: schema.TypeString, + Computed: true, + }, + "mac_address": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + + "all_fixed_ips": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "all_security_group_ids": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + + "all_tags": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "extra_dhcp_option": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + "value": { + Type: schema.TypeString, + Computed: true, + }, + "ip_version": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceNetworkingPortV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + listOpts := ports.ListOpts{} + + if v, ok := d.GetOk("port_id"); ok { + listOpts.ID = v.(string) + } + + if v, ok := d.GetOk("name"); ok { + listOpts.Name = v.(string) + } + + if v, ok := d.GetOk("description"); ok { + listOpts.Description = v.(string) + } + + if v, ok := d.GetOkExists("admin_state_up"); ok { + asu := v.(bool) + listOpts.AdminStateUp = &asu + } + + if v, ok := d.GetOk("network_id"); ok { + listOpts.NetworkID = v.(string) + } + + if v, ok := d.GetOk("status"); ok { + listOpts.Status = v.(string) + } + + if v, ok := d.GetOk("tenant_id"); ok { + listOpts.TenantID = v.(string) + } + + if v, ok := d.GetOk("project_id"); ok { + listOpts.ProjectID = v.(string) + } + + if v, ok := d.GetOk("device_owner"); ok { + listOpts.DeviceOwner = v.(string) + } + + if v, ok := d.GetOk("mac_address"); ok { + listOpts.MACAddress = v.(string) + } + + if v, ok := d.GetOk("device_id"); ok { + listOpts.DeviceID = v.(string) + } + + tags := networkV2AttributesTags(d) + if len(tags) > 0 { + listOpts.Tags = strings.Join(tags, ",") + } + + allPages, err := ports.List(networkingClient, listOpts).AllPages() + if err != nil { + return fmt.Errorf("Unable to list openstack_networking_ports_v2: %s", err) + } + + var allPorts []extraPort + + err = ports.ExtractPortsInto(allPages, &allPorts) + if err != nil { + return fmt.Errorf("Unable to retrieve openstack_networking_ports_v2: %s", err) + } + + if len(allPorts) == 0 { + return fmt.Errorf("No openstack_networking_port_v2 found") + } + + var portsList []extraPort + + // Filter returned Fixed IPs by a "fixed_ip". + if v, ok := d.GetOk("fixed_ip"); ok { + for _, p := range allPorts { + for _, ipObject := range p.FixedIPs { + if v.(string) == ipObject.IPAddress { + portsList = append(portsList, p) + } + } + } + if len(portsList) == 0 { + log.Printf("No openstack_networking_port_v2 found after the 'fixed_ip' filter") + return fmt.Errorf("No openstack_networking_port_v2 found") + } + } else { + portsList = allPorts + } + + securityGroups := expandToStringSlice(d.Get("security_group_ids").(*schema.Set).List()) + if len(securityGroups) > 0 { + var sgPorts []extraPort + for _, p := range portsList { + for _, sg := range p.SecurityGroups { + if strSliceContains(securityGroups, sg) { + sgPorts = append(sgPorts, p) + } + } + } + if len(sgPorts) == 0 { + log.Printf("[DEBUG] No openstack_networking_port_v2 found after the 'security_group_ids' filter") + return fmt.Errorf("No openstack_networking_port_v2 found") + } + portsList = sgPorts + } + + if len(portsList) > 1 { + return fmt.Errorf("More than one openstack_networking_port_v2 found (%d)", len(portsList)) + } + + port := portsList[0] + + log.Printf("[DEBUG] Retrieved openstack_networking_port_v2 %s: %+v", port.ID, port) + d.SetId(port.ID) + + d.Set("port_id", port.ID) + d.Set("name", port.Name) + d.Set("description", port.Description) + d.Set("admin_state_up", port.AdminStateUp) + d.Set("network_id", port.NetworkID) + d.Set("tenant_id", port.TenantID) + d.Set("project_id", port.ProjectID) + d.Set("device_owner", port.DeviceOwner) + d.Set("mac_address", port.MACAddress) + d.Set("device_id", port.DeviceID) + d.Set("region", GetRegion(d, config)) + d.Set("all_tags", port.Tags) + d.Set("all_security_group_ids", port.SecurityGroups) + d.Set("all_fixed_ips", expandNetworkingPortFixedIPToStringSlice(port.FixedIPs)) + d.Set("allowed_address_pairs", flattenNetworkingPortAllowedAddressPairsV2(port.MACAddress, port.AllowedAddressPairs)) + d.Set("extra_dhcp_option", flattenNetworkingPortDHCPOptsV2(port.ExtraDHCPOptsExt)) + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_networking_router_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_networking_router_v2.go new file mode 100644 index 000000000..db056cb3f --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_networking_router_v2.go @@ -0,0 +1,187 @@ +package openstack + +import ( + "fmt" + "log" + "strings" + + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers" + + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceNetworkingRouterV2() *schema.Resource { + return &schema.Resource{ + Read: dataSourceNetworkingRouterV2Read, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + }, + "router_id": { + Type: schema.TypeString, + Optional: true, + }, + "name": { + Type: schema.TypeString, + Optional: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "admin_state_up": { + Type: schema.TypeBool, + Optional: true, + }, + "distributed": { + Type: schema.TypeBool, + Optional: true, + }, + "status": { + Type: schema.TypeString, + Optional: true, + }, + "tenant_id": { + Type: schema.TypeString, + Optional: true, + }, + "external_network_id": { + Type: schema.TypeString, + Computed: true, + }, + "enable_snat": { + Type: schema.TypeBool, + Computed: true, + Optional: true, + }, + "availability_zone_hints": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "external_fixed_ip": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "subnet_id": { + Type: schema.TypeString, + Optional: true, + }, + "ip_address": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "tags": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "all_tags": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func dataSourceNetworkingRouterV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + listOpts := routers.ListOpts{} + + if v, ok := d.GetOk("router_id"); ok { + listOpts.ID = v.(string) + } + + if v, ok := d.GetOk("name"); ok { + listOpts.Name = v.(string) + } + + if v, ok := d.GetOk("description"); ok { + listOpts.Description = v.(string) + } + + if v, ok := d.GetOkExists("admin_state_up"); ok { + asu := v.(bool) + listOpts.AdminStateUp = &asu + } + + if v, ok := d.GetOkExists("distributed"); ok { + dist := v.(bool) + listOpts.Distributed = &dist + } + + if v, ok := d.GetOk("status"); ok { + listOpts.Status = v.(string) + } + + if v, ok := d.GetOk("tenant_id"); ok { + listOpts.TenantID = v.(string) + } + + tags := networkV2AttributesTags(d) + if len(tags) > 0 { + listOpts.Tags = strings.Join(tags, ",") + } + + pages, err := routers.List(networkingClient, listOpts).AllPages() + if err != nil { + return fmt.Errorf("Unable to list Routers: %s", err) + } + + allRouters, err := routers.ExtractRouters(pages) + if err != nil { + return fmt.Errorf("Unable to retrieve Routers: %s", err) + } + + if len(allRouters) < 1 { + return fmt.Errorf("No Router found") + } + + if len(allRouters) > 1 { + return fmt.Errorf("More than one Router found") + } + + router := allRouters[0] + + log.Printf("[DEBUG] Retrieved Router %s: %+v", router.ID, router) + d.SetId(router.ID) + + d.Set("name", router.Name) + d.Set("description", router.Description) + d.Set("admin_state_up", router.AdminStateUp) + d.Set("distributed", router.Distributed) + d.Set("status", router.Status) + d.Set("tenant_id", router.TenantID) + d.Set("external_network_id", router.GatewayInfo.NetworkID) + d.Set("enable_snat", router.GatewayInfo.EnableSNAT) + d.Set("all_tags", router.Tags) + d.Set("region", GetRegion(d, config)) + + if err := d.Set("availability_zone_hints", router.AvailabilityZoneHints); err != nil { + log.Printf("[DEBUG] Unable to set availability_zone_hints: %s", err) + } + + var externalFixedIPs []map[string]string + for _, v := range router.GatewayInfo.ExternalFixedIPs { + externalFixedIPs = append(externalFixedIPs, map[string]string{ + "subnet_id": v.SubnetID, + "ip_address": v.IPAddress, + }) + } + if err = d.Set("external_fixed_ip", externalFixedIPs); err != nil { + log.Printf("[DEBUG] Unable to set external_fixed_ip: %s", err) + } + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_networking_secgroup_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_networking_secgroup_v2.go new file mode 100644 index 000000000..fbfc4bb6b --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_networking_secgroup_v2.go @@ -0,0 +1,105 @@ +package openstack + +import ( + "fmt" + "log" + "strings" + + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups" + + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceNetworkingSecGroupV2() *schema.Resource { + return &schema.Resource{ + Read: dataSourceNetworkingSecGroupV2Read, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "secgroup_id": { + Type: schema.TypeString, + Optional: true, + }, + "name": { + Type: schema.TypeString, + Optional: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "tenant_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + "tags": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "all_tags": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func dataSourceNetworkingSecGroupV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + listOpts := groups.ListOpts{ + ID: d.Get("secgroup_id").(string), + Name: d.Get("name").(string), + Description: d.Get("description").(string), + TenantID: d.Get("tenant_id").(string), + } + + tags := networkV2AttributesTags(d) + if len(tags) > 0 { + listOpts.Tags = strings.Join(tags, ",") + } + + pages, err := groups.List(networkingClient, listOpts).AllPages() + if err != nil { + return err + } + + allSecGroups, err := groups.ExtractGroups(pages) + if err != nil { + return fmt.Errorf("Unable to retrieve security groups: %s", err) + } + + if len(allSecGroups) < 1 { + return fmt.Errorf("No Security Group found with name: %s", d.Get("name")) + } + + if len(allSecGroups) > 1 { + return fmt.Errorf("More than one Security Group found with name: %s", d.Get("name")) + } + + secGroup := allSecGroups[0] + + log.Printf("[DEBUG] Retrieved Security Group %s: %+v", secGroup.ID, secGroup) + d.SetId(secGroup.ID) + + d.Set("name", secGroup.Name) + d.Set("description", secGroup.Description) + d.Set("tenant_id", secGroup.TenantID) + d.Set("all_tags", secGroup.Tags) + d.Set("region", GetRegion(d, config)) + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_networking_subnet_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_networking_subnet_v2.go new file mode 100644 index 000000000..fe79d7250 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_networking_subnet_v2.go @@ -0,0 +1,305 @@ +package openstack + +import ( + "fmt" + "log" + "strings" + + "github.com/hashicorp/terraform/helper/schema" + + "github.com/gophercloud/gophercloud/openstack/networking/v2/subnets" +) + +func dataSourceNetworkingSubnetV2() *schema.Resource { + return &schema.Resource{ + Read: dataSourceNetworkingSubnetV2Read, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "name": { + Type: schema.TypeString, + Computed: true, + Optional: true, + }, + + "description": { + Type: schema.TypeString, + Computed: true, + Optional: true, + }, + + "dhcp_enabled": { + Type: schema.TypeBool, + ConflictsWith: []string{"dhcp_disabled"}, + Optional: true, + }, + + "dhcp_disabled": { + Type: schema.TypeBool, + ConflictsWith: []string{"dhcp_enabled"}, + Optional: true, + }, + + "network_id": { + Type: schema.TypeString, + Computed: true, + Optional: true, + }, + + "tenant_id": { + Type: schema.TypeString, + Computed: true, + Optional: true, + Description: descriptions["tenant_id"], + }, + + "ip_version": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + value := v.(int) + if value != 4 && value != 6 { + errors = append(errors, fmt.Errorf( + "Only 4 and 6 are supported values for 'ip_version'")) + } + return + }, + }, + + "gateway_ip": { + Type: schema.TypeString, + Computed: true, + Optional: true, + }, + + "cidr": { + Type: schema.TypeString, + Computed: true, + Optional: true, + }, + + "subnet_id": { + Type: schema.TypeString, + Computed: true, + Optional: true, + }, + + // Computed values + "allocation_pools": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "start": { + Type: schema.TypeString, + Computed: true, + }, + "end": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + + "enable_dhcp": { + Type: schema.TypeBool, + Computed: true, + }, + + "dns_nameservers": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + + "host_routes": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "destination_cidr": { + Type: schema.TypeString, + Computed: true, + }, + "next_hop": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "ipv6_address_mode": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + ValidateFunc: validateSubnetV2IPv6Mode, + }, + "ipv6_ra_mode": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + ValidateFunc: validateSubnetV2IPv6Mode, + }, + "subnetpool_id": { + Type: schema.TypeString, + Computed: true, + Optional: true, + }, + "tags": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "all_tags": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func dataSourceNetworkingSubnetV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + listOpts := subnets.ListOpts{} + + if v, ok := d.GetOk("name"); ok { + listOpts.Name = v.(string) + } + + if v, ok := d.GetOk("description"); ok { + listOpts.Description = v.(string) + } + + if _, ok := d.GetOk("dhcp_enabled"); ok { + enableDHCP := true + listOpts.EnableDHCP = &enableDHCP + } + + if _, ok := d.GetOk("dhcp_disabled"); ok { + enableDHCP := false + listOpts.EnableDHCP = &enableDHCP + } + + if v, ok := d.GetOk("network_id"); ok { + listOpts.NetworkID = v.(string) + } + + if v, ok := d.GetOk("tenant_id"); ok { + listOpts.TenantID = v.(string) + } + + if v, ok := d.GetOk("ip_version"); ok { + listOpts.IPVersion = v.(int) + } + + if v, ok := d.GetOk("gateway_ip"); ok { + listOpts.GatewayIP = v.(string) + } + + if v, ok := d.GetOk("cidr"); ok { + listOpts.CIDR = v.(string) + } + + if v, ok := d.GetOk("subnet_id"); ok { + listOpts.ID = v.(string) + } + + if v, ok := d.GetOk("ipv6_address_mode"); ok { + listOpts.IPv6AddressMode = v.(string) + } + + if v, ok := d.GetOk("ipv6_ra_mode"); ok { + listOpts.IPv6RAMode = v.(string) + } + + if v, ok := d.GetOk("subnetpool_id"); ok { + listOpts.SubnetPoolID = v.(string) + } + + tags := networkV2AttributesTags(d) + if len(tags) > 0 { + listOpts.Tags = strings.Join(tags, ",") + } + + pages, err := subnets.List(networkingClient, listOpts).AllPages() + if err != nil { + return fmt.Errorf("Unable to retrieve subnets: %s", err) + } + + allSubnets, err := subnets.ExtractSubnets(pages) + if err != nil { + return fmt.Errorf("Unable to extract subnets: %s", err) + } + + if len(allSubnets) < 1 { + return fmt.Errorf("Your query returned no results. " + + "Please change your search criteria and try again.") + } + + if len(allSubnets) > 1 { + return fmt.Errorf("Your query returned more than one result." + + " Please try a more specific search criteria") + } + + subnet := allSubnets[0] + + log.Printf("[DEBUG] Retrieved Subnet %s: %+v", subnet.ID, subnet) + d.SetId(subnet.ID) + + d.Set("name", subnet.Name) + d.Set("description", subnet.Description) + d.Set("tenant_id", subnet.TenantID) + d.Set("network_id", subnet.NetworkID) + d.Set("cidr", subnet.CIDR) + d.Set("ip_version", subnet.IPVersion) + d.Set("ipv6_address_mode", subnet.IPv6AddressMode) + d.Set("ipv6_ra_mode", subnet.IPv6RAMode) + d.Set("gateway_ip", subnet.GatewayIP) + d.Set("enable_dhcp", subnet.EnableDHCP) + d.Set("subnetpool_id", subnet.SubnetPoolID) + d.Set("all_tags", subnet.Tags) + d.Set("region", GetRegion(d, config)) + + err = d.Set("dns_nameservers", subnet.DNSNameservers) + if err != nil { + log.Printf("[DEBUG] Unable to set dns_nameservers: %s", err) + } + + err = d.Set("host_routes", subnet.HostRoutes) + if err != nil { + log.Printf("[DEBUG] Unable to set host_routes: %s", err) + } + + // Set the allocation_pools + var allocationPools []map[string]interface{} + for _, v := range subnet.AllocationPools { + pool := make(map[string]interface{}) + pool["start"] = v.Start + pool["end"] = v.End + + allocationPools = append(allocationPools, pool) + } + err = d.Set("allocation_pools", allocationPools) + if err != nil { + log.Printf("[DEBUG] Unable to set allocation_pools: %s", err) + } + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_networking_subnetpool_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_networking_subnetpool_v2.go new file mode 100644 index 000000000..a09ee0a68 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_networking_subnetpool_v2.go @@ -0,0 +1,261 @@ +package openstack + +import ( + "fmt" + "log" + "strings" + "time" + + "github.com/hashicorp/terraform/helper/schema" + + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/subnetpools" +) + +func dataSourceNetworkingSubnetPoolV2() *schema.Resource { + return &schema.Resource{ + Read: dataSourceNetworkingSubnetPoolV2Read, + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: false, + }, + + "default_quota": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: false, + }, + + "project_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "created_at": { + Type: schema.TypeString, + Computed: true, + ForceNew: false, + }, + + "updated_at": { + Type: schema.TypeString, + Computed: true, + ForceNew: false, + }, + + "prefixes": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + ForceNew: false, + }, + + "default_prefixlen": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: false, + }, + + "min_prefixlen": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: false, + }, + + "max_prefixlen": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: false, + }, + + "address_scope_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: false, + }, + + "ip_version": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + value := v.(int) + if value != 4 && value != 6 { + errors = append(errors, fmt.Errorf( + "Only 4 and 6 are supported values for 'ip_version'")) + } + return + }, + }, + + "shared": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + ForceNew: false, + }, + + "description": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: false, + }, + + "is_default": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + ForceNew: false, + }, + + "revision_number": { + Type: schema.TypeInt, + Computed: true, + ForceNew: false, + }, + + "tags": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "all_tags": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func dataSourceNetworkingSubnetPoolV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + listOpts := subnetpools.ListOpts{} + + if v, ok := d.GetOk("name"); ok { + listOpts.Name = v.(string) + } + + if v, ok := d.GetOk("default_quota"); ok { + listOpts.DefaultQuota = v.(int) + } + + if v, ok := d.GetOk("project_id"); ok { + listOpts.ProjectID = v.(string) + } + + if v, ok := d.GetOk("default_prefixlen"); ok { + listOpts.DefaultPrefixLen = v.(int) + } + + if v, ok := d.GetOk("min_prefixlen"); ok { + listOpts.MinPrefixLen = v.(int) + } + + if v, ok := d.GetOk("max_prefixlen"); ok { + listOpts.MaxPrefixLen = v.(int) + } + + if v, ok := d.GetOk("address_scope_id"); ok { + listOpts.AddressScopeID = v.(string) + } + + if v, ok := d.GetOk("ip_version"); ok { + listOpts.IPVersion = v.(int) + } + + if v, ok := d.GetOk("shared"); ok { + shared := v.(bool) + listOpts.Shared = &shared + } + + if v, ok := d.GetOk("description"); ok { + listOpts.Description = v.(string) + } + + if v, ok := d.GetOk("is_default"); ok { + isDefault := v.(bool) + listOpts.IsDefault = &isDefault + } + + tags := networkV2AttributesTags(d) + if len(tags) > 0 { + listOpts.Tags = strings.Join(tags, ",") + } + + pages, err := subnetpools.List(networkingClient, listOpts).AllPages() + if err != nil { + return fmt.Errorf("Unable to retrieve openstack_networking_subnetpool_v2: %s", err) + } + + allSubnetPools, err := subnetpools.ExtractSubnetPools(pages) + if err != nil { + return fmt.Errorf("Unable to extract openstack_networking_subnetpool_v2: %s", err) + } + + if len(allSubnetPools) < 1 { + return fmt.Errorf("Your query returned no openstack_networking_subnetpool_v2. " + + "Please change your search criteria and try again.") + } + + if len(allSubnetPools) > 1 { + return fmt.Errorf("Your query returned more than one openstack_networking_subnetpool_v2." + + " Please try a more specific search criteria") + } + + subnetPool := allSubnetPools[0] + + log.Printf("[DEBUG] Retrieved openstack_networking_subnetpool_v2 %s: %+v", subnetPool.ID, subnetPool) + d.SetId(subnetPool.ID) + + d.Set("name", subnetPool.Name) + d.Set("default_quota", subnetPool.DefaultQuota) + d.Set("project_id", subnetPool.ProjectID) + d.Set("default_prefixlen", subnetPool.DefaultPrefixLen) + d.Set("min_prefixlen", subnetPool.MinPrefixLen) + d.Set("max_prefixlen", subnetPool.MaxPrefixLen) + d.Set("address_scope_id", subnetPool.AddressScopeID) + d.Set("ip_version", subnetPool.IPversion) + d.Set("shared", subnetPool.Shared) + d.Set("is_default", subnetPool.IsDefault) + d.Set("description", subnetPool.Description) + d.Set("revision_number", subnetPool.RevisionNumber) + d.Set("all_tags", subnetPool.Tags) + d.Set("region", GetRegion(d, config)) + + if err := d.Set("created_at", subnetPool.CreatedAt.Format(time.RFC3339)); err != nil { + log.Printf("[DEBUG] Unable to set openstack_networking_subnetpool_v2 created_at: %s", err) + } + if err := d.Set("updated_at", subnetPool.UpdatedAt.Format(time.RFC3339)); err != nil { + log.Printf("[DEBUG] Unable to set openstack_networking_subnetpool_v2 updated_at: %s", err) + } + if err := d.Set("prefixes", subnetPool.Prefixes); err != nil { + log.Printf("[WARN] unable to set prefixes: %s", err) + } + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_sharedfilesystem_share_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_sharedfilesystem_share_v2.go new file mode 100644 index 000000000..8ca07c888 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_sharedfilesystem_share_v2.go @@ -0,0 +1,209 @@ +package openstack + +import ( + "fmt" + "log" + + "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares" + "github.com/hashicorp/terraform/helper/schema" +) + +const ( + // export_location_path filter appeared in 2.35 + minManilaShareListExportLocationPath = "2.35" +) + +func dataSourceSharedFilesystemShareV2() *schema.Resource { + return &schema.Resource{ + Read: dataSourceSharedFilesystemShareV2Read, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "project_id": { + Type: schema.TypeString, + Computed: true, + }, + + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "description": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "snapshot_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "share_network_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "status": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "is_public": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + + "metadata": { + Type: schema.TypeMap, + Optional: true, + Computed: true, + }, + + "export_location_path": { + Type: schema.TypeString, + Optional: true, + }, + + "share_proto": { + Type: schema.TypeString, + Computed: true, + }, + + "size": { + Type: schema.TypeInt, + Computed: true, + }, + + "export_locations": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "path": { + Type: schema.TypeString, + Computed: true, + }, + "preferred": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + + "availability_zone": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceSharedFilesystemShareV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + sfsClient, err := config.sharedfilesystemV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack sharedfilesystem sfsClient: %s", err) + } + + sfsClient.Microversion = minManilaShareMicroversion + + isPublic := d.Get("is_public").(bool) + metadataRaw := d.Get("metadata").(map[string]interface{}) + metadata := make(map[string]string, len(metadataRaw)) + for k, v := range metadataRaw { + if stringVal, ok := v.(string); ok { + metadata[k] = stringVal + } + } + + listOpts := shares.ListOpts{ + Name: d.Get("name").(string), + DisplayDescription: d.Get("description").(string), + ProjectID: d.Get("project_id").(string), + SnapshotID: d.Get("snapshot_id").(string), + ShareNetworkID: d.Get("share_network_id").(string), + Status: d.Get("status").(string), + Metadata: metadata, + IsPublic: &isPublic, + } + + if v, ok := d.GetOkExists("export_location_path"); ok { + listOpts.ExportLocationPath = v.(string) + sfsClient.Microversion = minManilaShareListExportLocationPath + } + + allPages, err := shares.ListDetail(sfsClient, listOpts).AllPages() + if err != nil { + return fmt.Errorf("Unable to query shares: %s", err) + } + + allShares, err := shares.ExtractShares(allPages) + if err != nil { + return fmt.Errorf("Unable to retrieve shares: %s", err) + } + + if len(allShares) < 1 { + return fmt.Errorf("Your query returned no results. " + + "Please change your search criteria and try again.") + } + + var share shares.Share + if len(allShares) > 1 { + log.Printf("[DEBUG] Multiple results found: %#v", allShares) + return fmt.Errorf("Your query returned more than one result. Please try a more " + + "specific search criteria.") + } else { + share = allShares[0] + } + + exportLocationsRaw, err := shares.GetExportLocations(sfsClient, share.ID).Extract() + if err != nil { + return fmt.Errorf("Failed to retrieve share's export_locations %s: %s", share.ID, err) + } + + log.Printf("[DEBUG] Retrieved share's export_locations %s: %#v", share.ID, exportLocationsRaw) + + var exportLocations []map[string]string + for _, v := range exportLocationsRaw { + exportLocations = append(exportLocations, map[string]string{ + "path": v.Path, + "preferred": fmt.Sprint(v.Preferred), + }) + } + + d.SetId(share.ID) + d.Set("name", share.Name) + d.Set("region", GetRegion(d, config)) + d.Set("project_id", share.ProjectID) + d.Set("snapshot_id", share.SnapshotID) + d.Set("share_network_id", share.ShareNetworkID) + d.Set("availability_zone", share.AvailabilityZone) + d.Set("description", share.Description) + d.Set("size", share.Size) + d.Set("status", share.Status) + d.Set("is_public", share.IsPublic) + d.Set("share_proto", share.ShareProto) + + if err := d.Set("metadata", share.Metadata); err != nil { + log.Printf("[DEBUG] Unable to set metadata for share %s: %s", share.ID, err) + } + + if err := d.Set("export_locations", exportLocations); err != nil { + log.Printf("[DEBUG] Unable to set export_locations for share %s: %s", share.ID, err) + } + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_sharedfilesystem_sharenetwork_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_sharedfilesystem_sharenetwork_v2.go new file mode 100644 index 000000000..49003d0a6 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_sharedfilesystem_sharenetwork_v2.go @@ -0,0 +1,188 @@ +package openstack + +import ( + "fmt" + "log" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharenetworks" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceSharedFilesystemShareNetworkV2() *schema.Resource { + return &schema.Resource{ + Read: dataSourceSharedFilesystemShareNetworkV2Read, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "project_id": { + Type: schema.TypeString, + Computed: true, + }, + + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "description": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "neutron_net_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "neutron_subnet_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "security_service_id": { + Type: schema.TypeString, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + + "security_service_ids": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + + "network_type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "segmentation_id": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + + "cidr": { + Type: schema.TypeString, + Computed: true, + }, + + "ip_version": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + }, + } +} + +func dataSourceSharedFilesystemShareNetworkV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + sfsClient, err := config.sharedfilesystemV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack sharedfilesystem sfsClient: %s", err) + } + + listOpts := sharenetworks.ListOpts{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + ProjectID: d.Get("project_id").(string), + NeutronNetID: d.Get("neutron_net_id").(string), + NeutronSubnetID: d.Get("neutron_subnet_id").(string), + NetworkType: d.Get("network_type").(string), + } + + if v, ok := d.GetOkExists("ip_version"); ok { + listOpts.IPVersion = gophercloud.IPVersion(v.(int)) + } + + if v, ok := d.GetOkExists("segmentation_id"); ok { + listOpts.SegmentationID = v.(int) + } + + allPages, err := sharenetworks.ListDetail(sfsClient, listOpts).AllPages() + if err != nil { + return fmt.Errorf("Unable to query share networks: %s", err) + } + + allShareNetworks, err := sharenetworks.ExtractShareNetworks(allPages) + if err != nil { + return fmt.Errorf("Unable to retrieve share networks: %s", err) + } + + if len(allShareNetworks) < 1 { + return fmt.Errorf("Your query returned no results. " + + "Please change your search criteria and try again.") + } + + var securityServiceID string + var securityServiceIDs []string + if v, ok := d.GetOkExists("security_service_id"); ok { + // filtering by "security_service_id" + securityServiceID = v.(string) + var filteredShareNetworks []sharenetworks.ShareNetwork + + log.Printf("[DEBUG] Filtering share networks by a %s security service ID", securityServiceID) + for _, shareNetwork := range allShareNetworks { + tmp, err := resourceSharedFilesystemShareNetworkV2GetSvcByShareNetID(sfsClient, shareNetwork.ID) + if err != nil { + return err + } + if strSliceContains(tmp, securityServiceID) { + filteredShareNetworks = append(filteredShareNetworks, shareNetwork) + securityServiceIDs = tmp + } + } + + if len(filteredShareNetworks) == 0 { + return fmt.Errorf("Your query returned no results after the security service ID filter. " + + "Please change your search criteria and try again.") + } + allShareNetworks = filteredShareNetworks + } + + var shareNetwork sharenetworks.ShareNetwork + if len(allShareNetworks) > 1 { + log.Printf("[DEBUG] Multiple results found: %#v", allShareNetworks) + return fmt.Errorf("Your query returned more than one result. Please try a more " + + "specific search criteria.") + } else { + shareNetwork = allShareNetworks[0] + } + + // skip extra calls if "security_service_id" filter was already used + if securityServiceID == "" { + securityServiceIDs, err = resourceSharedFilesystemShareNetworkV2GetSvcByShareNetID(sfsClient, shareNetwork.ID) + if err != nil { + return err + } + } + + d.SetId(shareNetwork.ID) + d.Set("name", shareNetwork.Name) + d.Set("description", shareNetwork.Description) + d.Set("project_id", shareNetwork.ProjectID) + d.Set("neutron_net_id", shareNetwork.NeutronNetID) + d.Set("neutron_subnet_id", shareNetwork.NeutronSubnetID) + d.Set("security_service_ids", securityServiceIDs) + d.Set("network_type", shareNetwork.NetworkType) + d.Set("ip_version", shareNetwork.IPVersion) + d.Set("segmentation_id", shareNetwork.SegmentationID) + d.Set("cidr", shareNetwork.CIDR) + d.Set("region", GetRegion(d, config)) + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_sharedfilesystem_snapshot_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_sharedfilesystem_snapshot_v2.go new file mode 100644 index 000000000..9a7106f5a --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/data_source_openstack_sharedfilesystem_snapshot_v2.go @@ -0,0 +1,125 @@ +package openstack + +import ( + "fmt" + "log" + + "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceSharedFilesystemSnapshotV2() *schema.Resource { + return &schema.Resource{ + Read: dataSourceSharedFilesystemSnapshotV2Read, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "project_id": { + Type: schema.TypeString, + Computed: true, + }, + + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "description": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "status": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "share_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "size": { + Type: schema.TypeInt, + Computed: true, + }, + + "share_proto": { + Type: schema.TypeString, + Computed: true, + }, + + "share_size": { + Type: schema.TypeInt, + Computed: true, + }, + }, + } +} + +func dataSourceSharedFilesystemSnapshotV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + sfsClient, err := config.sharedfilesystemV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack sharedfilesystem sfsClient: %s", err) + } + + sfsClient.Microversion = minManilaShareMicroversion + + listOpts := snapshots.ListOpts{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + ProjectID: d.Get("project_id").(string), + Status: d.Get("status").(string), + } + + allPages, err := snapshots.ListDetail(sfsClient, listOpts).AllPages() + if err != nil { + return fmt.Errorf("Unable to query snapshots: %s", err) + } + + allSnapshots, err := snapshots.ExtractSnapshots(allPages) + if err != nil { + return fmt.Errorf("Unable to retrieve snapshots: %s", err) + } + + if len(allSnapshots) < 1 { + return fmt.Errorf("Your query returned no results. " + + "Please change your search criteria and try again.") + } + + var share snapshots.Snapshot + if len(allSnapshots) > 1 { + log.Printf("[DEBUG] Multiple results found: %#v", allSnapshots) + return fmt.Errorf("Your query returned more than one result. Please try a more " + + "specific search criteria.") + } else { + share = allSnapshots[0] + } + + return dataSourceSharedFilesystemSnapshotV2Attributes(d, &share, GetRegion(d, config)) +} + +func dataSourceSharedFilesystemSnapshotV2Attributes(d *schema.ResourceData, snapshot *snapshots.Snapshot, region string) error { + d.SetId(snapshot.ID) + d.Set("name", snapshot.Name) + d.Set("region", region) + d.Set("project_id", snapshot.ProjectID) + d.Set("description", snapshot.Description) + d.Set("size", snapshot.Size) + d.Set("status", snapshot.Status) + d.Set("share_id", snapshot.ShareID) + d.Set("share_proto", snapshot.ShareProto) + d.Set("share_size", snapshot.ShareSize) + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/dns_recordset_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/dns_recordset_v2.go new file mode 100644 index 000000000..ebfe8d9e5 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/dns_recordset_v2.go @@ -0,0 +1,91 @@ +package openstack + +import ( + "fmt" + "log" + "regexp" + "strings" + + "github.com/hashicorp/terraform/helper/resource" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets" +) + +// RecordSetCreateOpts represents the attributes used when creating a new DNS record set. +type RecordSetCreateOpts struct { + recordsets.CreateOpts + ValueSpecs map[string]string `json:"value_specs,omitempty"` +} + +// ToRecordSetCreateMap casts a CreateOpts struct to a map. +// It overrides recordsets.ToRecordSetCreateMap to add the ValueSpecs field. +func (opts RecordSetCreateOpts) ToRecordSetCreateMap() (map[string]interface{}, error) { + b, err := BuildRequest(opts, "") + if err != nil { + return nil, err + } + + if m, ok := b[""].(map[string]interface{}); ok { + return m, nil + } + + return nil, fmt.Errorf("Expected map but got %T", b[""]) +} + +func dnsRecordSetV2RefreshFunc( + dnsClient *gophercloud.ServiceClient, zoneID, recordsetId string) resource.StateRefreshFunc { + + return func() (interface{}, string, error) { + recordset, err := recordsets.Get(dnsClient, zoneID, recordsetId).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + return recordset, "DELETED", nil + } + + return nil, "", err + } + + log.Printf("[DEBUG] openstack_dns_recordset_v2 %s current status: %s", recordset.ID, recordset.Status) + return recordset, recordset.Status, nil + } +} + +func dnsRecordSetV2ParseID(id string) (string, string, error) { + idParts := strings.Split(id, "/") + if len(idParts) != 2 { + return "", "", fmt.Errorf("Unable to determine openstack_dns_recordset_v2 ID from raw ID: %s", id) + } + + zoneID := idParts[0] + recordsetID := idParts[1] + + return zoneID, recordsetID, nil +} + +func expandDNSRecordSetV2Records(v []interface{}) []string { + records := make([]string, len(v)) + + // Strip out any [ ] characters in the address. + // This is to format IPv6 records in a way that DNSaaS / Designate wants. + re := regexp.MustCompile("[][]") + for i, rawRecord := range v { + record := rawRecord.(string) + record = re.ReplaceAllString(record, "") + records[i] = record + } + + return records +} + +// dnsRecordSetV2RecordsStateFunc will strip brackets from IPv6 addresses. +func dnsRecordSetV2RecordsStateFunc(v interface{}) string { + if addr, ok := v.(string); ok { + re := regexp.MustCompile("[][]") + addr = re.ReplaceAllString(addr, "") + + return addr + } + + return "" +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/dns_zone_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/dns_zone_v2.go new file mode 100644 index 000000000..d7f33167b --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/dns_zone_v2.go @@ -0,0 +1,52 @@ +package openstack + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform/helper/resource" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/dns/v2/zones" +) + +// ZoneCreateOpts represents the attributes used when creating a new DNS zone. +type ZoneCreateOpts struct { + zones.CreateOpts + ValueSpecs map[string]string `json:"value_specs,omitempty"` +} + +// ToZoneCreateMap casts a CreateOpts struct to a map. +// It overrides zones.ToZoneCreateMap to add the ValueSpecs field. +func (opts ZoneCreateOpts) ToZoneCreateMap() (map[string]interface{}, error) { + b, err := BuildRequest(opts, "") + if err != nil { + return nil, err + } + + if m, ok := b[""].(map[string]interface{}); ok { + if opts.TTL > 0 { + m["ttl"] = opts.TTL + } + + return m, nil + } + + return nil, fmt.Errorf("Expected map but got %T", b[""]) +} + +func dnsZoneV2RefreshFunc(dnsClient *gophercloud.ServiceClient, zoneId string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + zone, err := zones.Get(dnsClient, zoneId).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + return zone, "DELETED", nil + } + + return nil, "", err + } + + log.Printf("[DEBUG] openstack_dns_zone_v2 %s current status: %s", zone.ID, zone.Status) + return zone, zone.Status, nil + } +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/identity_auth_scope_v3.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/identity_auth_scope_v3.go new file mode 100644 index 000000000..90723a5f3 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/identity_auth_scope_v3.go @@ -0,0 +1,19 @@ +package openstack + +import ( + "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens" +) + +func flattenIdentityAuthScopeV3Roles(roles []tokens.Role) []map[string]string { + allRoles := make([]map[string]string, len(roles)) + + for i, r := range roles { + allRoles[i] = map[string]string{ + "role_name": r.Name, + "role_id": r.ID, + } + + } + + return allRoles +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/identity_role_assignment_v3.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/identity_role_assignment_v3.go new file mode 100644 index 000000000..dfc0a0336 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/identity_role_assignment_v3.go @@ -0,0 +1,63 @@ +package openstack + +import ( + "fmt" + "strings" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/identity/v3/roles" + "github.com/gophercloud/gophercloud/pagination" +) + +// Role assignments have no ID in OpenStack. +// Build an ID out of the IDs that make up the role assignment +func identityRoleAssignmentV3ID(domainID, projectID, groupID, userID, roleID string) string { + return fmt.Sprintf("%s/%s/%s/%s/%s", domainID, projectID, groupID, userID, roleID) +} + +func identityRoleAssignmentV3ParseID(roleAssignmentID string) (string, string, string, string, string, error) { + split := strings.Split(roleAssignmentID, "/") + + if len(split) != 5 { + return "", "", "", "", "", fmt.Errorf("Malformed ID: %s", roleAssignmentID) + } + + return split[0], split[1], split[2], split[3], split[4], nil +} + +func identityRoleAssignmentV3FindAssignment(identityClient *gophercloud.ServiceClient, id string) (roles.RoleAssignment, error) { + var assignment roles.RoleAssignment + + domainID, projectID, groupID, userID, roleID, err := identityRoleAssignmentV3ParseID(id) + if err != nil { + return assignment, err + } + + var opts roles.ListAssignmentsOpts + opts = roles.ListAssignmentsOpts{ + GroupID: groupID, + ScopeDomainID: domainID, + ScopeProjectID: projectID, + UserID: userID, + } + + pager := roles.ListAssignments(identityClient, opts) + + err = pager.EachPage(func(page pagination.Page) (bool, error) { + assignmentList, err := roles.ExtractRoleAssignments(page) + if err != nil { + return false, err + } + + for _, a := range assignmentList { + if a.Role.ID == roleID { + assignment = a + return false, nil + } + } + + return true, nil + }) + + return assignment, err +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/identity_user_v3.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/identity_user_v3.go new file mode 100644 index 000000000..9248f198b --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/identity_user_v3.go @@ -0,0 +1,73 @@ +package openstack + +import ( + "fmt" + "strings" + "time" + + "github.com/gophercloud/gophercloud/openstack/identity/v3/users" +) + +var userOptions = map[users.Option]string{ + users.IgnoreChangePasswordUponFirstUse: "ignore_change_password_upon_first_use", + users.IgnorePasswordExpiry: "ignore_password_expiry", + users.IgnoreLockoutFailureAttempts: "ignore_lockout_failure_attempts", + users.MultiFactorAuthEnabled: "multi_factor_auth_enabled", +} + +func expandIdentityUserV3MFARules(rules []interface{}) []interface{} { + var mfaRules []interface{} + + for _, rule := range rules { + ruleMap := rule.(map[string]interface{}) + ruleList := ruleMap["rule"].([]interface{}) + mfaRules = append(mfaRules, ruleList) + } + + return mfaRules +} + +func flattenIdentityUserV3MFARules(v []interface{}) []map[string]interface{} { + mfaRules := []map[string]interface{}{} + for _, rawRule := range v { + mfaRule := map[string]interface{}{ + "rule": rawRule, + } + mfaRules = append(mfaRules, mfaRule) + } + + return mfaRules +} + +// Ensure that password_expires_at query matches format explained in +// https://developer.openstack.org/api-ref/identity/v3/#list-users +func validatePasswordExpiresAtQuery(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + values := strings.SplitN(value, ":", 2) + if len(values) != 2 { + err := fmt.Errorf("%s '%s' does not match expected format: {operator}:{timestamp}", k, value) + errors = append(errors, err) + } + operator, timestamp := values[0], values[1] + + validOperators := map[string]bool{ + "lt": true, + "lte": true, + "gt": true, + "gte": true, + "eq": true, + "neq": true, + } + if !validOperators[operator] { + err := fmt.Errorf("'%s' is not a valid operator for %s. Choose one of 'lt', 'lte', 'gt', 'gte', 'eq', 'neq'", operator, k) + errors = append(errors, err) + } + + _, err := time.Parse(time.RFC3339, timestamp) + if err != nil { + err = fmt.Errorf("'%s' is not a valid timestamp for %s. It should be in the form 'YYYY-MM-DDTHH:mm:ssZ'", timestamp, k) + errors = append(errors, err) + } + + return +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/lb_v2_shared.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/lb_v2_shared.go new file mode 100644 index 000000000..d41e90e1c --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/lb_v2_shared.go @@ -0,0 +1,549 @@ +package openstack + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools" +) + +// lbPendingStatuses are the valid statuses a LoadBalancer will be in while +// it's updating. +var lbPendingStatuses = []string{"PENDING_CREATE", "PENDING_UPDATE"} + +// lbPendingDeleteStatuses are the valid statuses a LoadBalancer will be before delete +var lbPendingDeleteStatuses = []string{"ERROR", "PENDING_UPDATE", "PENDING_DELETE", "ACTIVE"} + +var lbSkipLBStatuses = []string{"ERROR", "ACTIVE"} + +// chooseLBV2Client will determine which load balacing client to use: +// Either the Octavia/LBaaS client or the Neutron/Networking v2 client. +func chooseLBV2Client(d *schema.ResourceData, config *Config) (*gophercloud.ServiceClient, error) { + if config.useOctavia { + return config.loadBalancerV2Client(GetRegion(d, config)) + } + return config.networkingV2Client(GetRegion(d, config)) +} + +// chooseLBV2AccTestClient will determine which load balacing client to use: +// Either the Octavia/LBaaS client or the Neutron/Networking v2 client. +// This is similar to the chooseLBV2Client function but specific for acceptance +// tests. +func chooseLBV2AccTestClient(config *Config, region string) (*gophercloud.ServiceClient, error) { + if config.useOctavia { + return config.loadBalancerV2Client(region) + } + return config.networkingV2Client(region) +} + +func waitForLBV2Listener(lbClient *gophercloud.ServiceClient, listener *listeners.Listener, target string, pending []string, timeout time.Duration) error { + log.Printf("[DEBUG] Waiting for listener %s to become %s.", listener.ID, target) + + if len(listener.Loadbalancers) == 0 { + return fmt.Errorf("Failed to detect a Load Balancer for the %s Listener", listener.ID) + } + + lbID := listener.Loadbalancers[0].ID + + stateConf := &resource.StateChangeConf{ + Target: []string{target}, + Pending: pending, + Refresh: resourceLBV2ListenerRefreshFunc(lbClient, lbID, listener), + Timeout: timeout, + Delay: 1 * time.Second, + MinTimeout: 1 * time.Second, + } + + _, err := stateConf.WaitForState() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + if target == "DELETED" { + return nil + } + } + + return fmt.Errorf("Error waiting for listener %s to become %s: %s", listener.ID, target, err) + } + + return nil +} + +func resourceLBV2ListenerRefreshFunc(lbClient *gophercloud.ServiceClient, lbID string, listener *listeners.Listener) resource.StateRefreshFunc { + if listener.ProvisioningStatus != "" { + return func() (interface{}, string, error) { + lb, status, err := resourceLBV2LoadBalancerRefreshFunc(lbClient, lbID)() + if err != nil { + return lb, status, err + } + if !strSliceContains(lbSkipLBStatuses, status) { + return lb, status, nil + } + + listener, err := listeners.Get(lbClient, listener.ID).Extract() + if err != nil { + return nil, "", err + } + + return listener, listener.ProvisioningStatus, nil + } + } + + return resourceLBV2LoadBalancerStatusRefreshFuncNeutron(lbClient, lbID, "listener", listener.ID) +} + +func waitForLBV2LoadBalancer(lbClient *gophercloud.ServiceClient, lbID string, target string, pending []string, timeout time.Duration) error { + log.Printf("[DEBUG] Waiting for loadbalancer %s to become %s.", lbID, target) + + stateConf := &resource.StateChangeConf{ + Target: []string{target}, + Pending: pending, + Refresh: resourceLBV2LoadBalancerRefreshFunc(lbClient, lbID), + Timeout: timeout, + Delay: 0, + MinTimeout: 1 * time.Second, + } + + _, err := stateConf.WaitForState() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + switch target { + case "DELETED": + return nil + default: + return fmt.Errorf("Error: loadbalancer %s not found: %s", lbID, err) + } + } + return fmt.Errorf("Error waiting for loadbalancer %s to become %s: %s", lbID, target, err) + } + + return nil +} + +func resourceLBV2LoadBalancerRefreshFunc(lbClient *gophercloud.ServiceClient, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + lb, err := loadbalancers.Get(lbClient, id).Extract() + if err != nil { + return nil, "", err + } + + return lb, lb.ProvisioningStatus, nil + } +} + +func waitForLBV2Member(lbClient *gophercloud.ServiceClient, parentPool *pools.Pool, member *pools.Member, target string, pending []string, timeout time.Duration) error { + log.Printf("[DEBUG] Waiting for member %s to become %s.", member.ID, target) + + lbID, err := lbV2FindLBIDviaPool(lbClient, parentPool) + if err != nil { + return err + } + + stateConf := &resource.StateChangeConf{ + Target: []string{target}, + Pending: pending, + Refresh: resourceLBV2MemberRefreshFunc(lbClient, lbID, parentPool.ID, member), + Timeout: timeout, + Delay: 1 * time.Second, + MinTimeout: 1 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + if target == "DELETED" { + return nil + } + } + + return fmt.Errorf("Error waiting for member %s to become %s: %s", member.ID, target, err) + } + + return nil +} + +func resourceLBV2MemberRefreshFunc(lbClient *gophercloud.ServiceClient, lbID string, poolID string, member *pools.Member) resource.StateRefreshFunc { + if member.ProvisioningStatus != "" { + return func() (interface{}, string, error) { + lb, status, err := resourceLBV2LoadBalancerRefreshFunc(lbClient, lbID)() + if err != nil { + return lb, status, err + } + if !strSliceContains(lbSkipLBStatuses, status) { + return lb, status, nil + } + + member, err := pools.GetMember(lbClient, poolID, member.ID).Extract() + if err != nil { + return nil, "", err + } + + return member, member.ProvisioningStatus, nil + } + } + + return resourceLBV2LoadBalancerStatusRefreshFuncNeutron(lbClient, lbID, "member", member.ID) +} + +func waitForLBV2Monitor(lbClient *gophercloud.ServiceClient, parentPool *pools.Pool, monitor *monitors.Monitor, target string, pending []string, timeout time.Duration) error { + log.Printf("[DEBUG] Waiting for monitor %s to become %s.", monitor.ID, target) + + lbID, err := lbV2FindLBIDviaPool(lbClient, parentPool) + if err != nil { + return err + } + + stateConf := &resource.StateChangeConf{ + Target: []string{target}, + Pending: pending, + Refresh: resourceLBV2MonitorRefreshFunc(lbClient, lbID, monitor), + Timeout: timeout, + Delay: 1 * time.Second, + MinTimeout: 1 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + if target == "DELETED" { + return nil + } + } + return fmt.Errorf("Error waiting for monitor %s to become %s: %s", monitor.ID, target, err) + } + + return nil +} + +func resourceLBV2MonitorRefreshFunc(lbClient *gophercloud.ServiceClient, lbID string, monitor *monitors.Monitor) resource.StateRefreshFunc { + if monitor.ProvisioningStatus != "" { + return func() (interface{}, string, error) { + lb, status, err := resourceLBV2LoadBalancerRefreshFunc(lbClient, lbID)() + if err != nil { + return lb, status, err + } + if !strSliceContains(lbSkipLBStatuses, status) { + return lb, status, nil + } + + monitor, err := monitors.Get(lbClient, monitor.ID).Extract() + if err != nil { + return nil, "", err + } + + return monitor, monitor.ProvisioningStatus, nil + } + } + + return resourceLBV2LoadBalancerStatusRefreshFuncNeutron(lbClient, lbID, "monitor", monitor.ID) +} + +func waitForLBV2Pool(lbClient *gophercloud.ServiceClient, pool *pools.Pool, target string, pending []string, timeout time.Duration) error { + log.Printf("[DEBUG] Waiting for pool %s to become %s.", pool.ID, target) + + lbID, err := lbV2FindLBIDviaPool(lbClient, pool) + if err != nil { + return err + } + + stateConf := &resource.StateChangeConf{ + Target: []string{target}, + Pending: pending, + Refresh: resourceLBV2PoolRefreshFunc(lbClient, lbID, pool), + Timeout: timeout, + Delay: 1 * time.Second, + MinTimeout: 1 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + if target == "DELETED" { + return nil + } + } + + return fmt.Errorf("Error waiting for pool %s to become %s: %s", pool.ID, target, err) + } + + return nil +} + +func resourceLBV2PoolRefreshFunc(lbClient *gophercloud.ServiceClient, lbID string, pool *pools.Pool) resource.StateRefreshFunc { + if pool.ProvisioningStatus != "" { + return func() (interface{}, string, error) { + lb, status, err := resourceLBV2LoadBalancerRefreshFunc(lbClient, lbID)() + if err != nil { + return lb, status, err + } + if !strSliceContains(lbSkipLBStatuses, status) { + return lb, status, nil + } + + pool, err := pools.Get(lbClient, pool.ID).Extract() + if err != nil { + return nil, "", err + } + + return pool, pool.ProvisioningStatus, nil + } + } + + return resourceLBV2LoadBalancerStatusRefreshFuncNeutron(lbClient, lbID, "pool", pool.ID) +} + +func lbV2FindLBIDviaPool(lbClient *gophercloud.ServiceClient, pool *pools.Pool) (string, error) { + if len(pool.Loadbalancers) > 0 { + return pool.Loadbalancers[0].ID, nil + } + + if len(pool.Listeners) > 0 { + listenerID := pool.Listeners[0].ID + listener, err := listeners.Get(lbClient, listenerID).Extract() + if err != nil { + return "", err + } + + if len(listener.Loadbalancers) > 0 { + return listener.Loadbalancers[0].ID, nil + } + } + + return "", fmt.Errorf("Unable to determine loadbalancer ID from pool %s", pool.ID) +} + +func resourceLBV2LoadBalancerStatusRefreshFuncNeutron(lbClient *gophercloud.ServiceClient, lbID, resourceType, resourceID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + statuses, err := loadbalancers.GetStatuses(lbClient, lbID).Extract() + if err != nil { + return nil, "", fmt.Errorf("Unable to get statuses from the Load Balancer %s statuses tree: %s", lbID, err) + } + + if !strSliceContains(lbSkipLBStatuses, statuses.Loadbalancer.ProvisioningStatus) { + return statuses.Loadbalancer, statuses.Loadbalancer.ProvisioningStatus, nil + } + + switch resourceType { + case "listener": + for _, listener := range statuses.Loadbalancer.Listeners { + if listener.ID == resourceID { + if listener.ProvisioningStatus != "" { + return listener, listener.ProvisioningStatus, nil + } + } + } + listener, err := listeners.Get(lbClient, resourceID).Extract() + return listener, "ACTIVE", err + + case "pool": + for _, pool := range statuses.Loadbalancer.Pools { + if pool.ID == resourceID { + if pool.ProvisioningStatus != "" { + return pool, pool.ProvisioningStatus, nil + } + } + } + pool, err := pools.Get(lbClient, resourceID).Extract() + return pool, "ACTIVE", err + + case "monitor": + for _, pool := range statuses.Loadbalancer.Pools { + if pool.Monitor.ID == resourceID { + if pool.Monitor.ProvisioningStatus != "" { + return pool.Monitor, pool.Monitor.ProvisioningStatus, nil + } + } + } + monitor, err := monitors.Get(lbClient, resourceID).Extract() + return monitor, "ACTIVE", err + + case "member": + for _, pool := range statuses.Loadbalancer.Pools { + for _, member := range pool.Members { + if member.ID == resourceID { + if member.ProvisioningStatus != "" { + return member, member.ProvisioningStatus, nil + } + } + } + } + return "", "DELETED", nil + + case "l7policy": + for _, listener := range statuses.Loadbalancer.Listeners { + for _, l7policy := range listener.L7Policies { + if l7policy.ID == resourceID { + if l7policy.ProvisioningStatus != "" { + return l7policy, l7policy.ProvisioningStatus, nil + } + } + } + } + l7policy, err := l7policies.Get(lbClient, resourceID).Extract() + return l7policy, "ACTIVE", err + + case "l7rule": + for _, listener := range statuses.Loadbalancer.Listeners { + for _, l7policy := range listener.L7Policies { + for _, l7rule := range l7policy.Rules { + if l7rule.ID == resourceID { + if l7rule.ProvisioningStatus != "" { + return l7rule, l7rule.ProvisioningStatus, nil + } + } + } + } + } + return "", "DELETED", nil + } + + return nil, "", fmt.Errorf("An unexpected error occurred querying the status of %s %s by loadbalancer %s", resourceType, resourceID, lbID) + } +} + +func resourceLBV2L7PolicyRefreshFunc(lbClient *gophercloud.ServiceClient, lbID string, l7policy *l7policies.L7Policy) resource.StateRefreshFunc { + if l7policy.ProvisioningStatus != "" { + return func() (interface{}, string, error) { + lb, status, err := resourceLBV2LoadBalancerRefreshFunc(lbClient, lbID)() + if err != nil { + return lb, status, err + } + if !strSliceContains(lbSkipLBStatuses, status) { + return lb, status, nil + } + + l7policy, err := l7policies.Get(lbClient, l7policy.ID).Extract() + if err != nil { + return nil, "", err + } + + return l7policy, l7policy.ProvisioningStatus, nil + } + } + + return resourceLBV2LoadBalancerStatusRefreshFuncNeutron(lbClient, lbID, "l7policy", l7policy.ID) +} + +func waitForLBV2L7Policy(lbClient *gophercloud.ServiceClient, parentListener *listeners.Listener, l7policy *l7policies.L7Policy, target string, pending []string, timeout time.Duration) error { + log.Printf("[DEBUG] Waiting for l7policy %s to become %s.", l7policy.ID, target) + + if len(parentListener.Loadbalancers) == 0 { + return fmt.Errorf("Unable to determine loadbalancer ID from listener %s", parentListener.ID) + } + + lbID := parentListener.Loadbalancers[0].ID + + stateConf := &resource.StateChangeConf{ + Target: []string{target}, + Pending: pending, + Refresh: resourceLBV2L7PolicyRefreshFunc(lbClient, lbID, l7policy), + Timeout: timeout, + Delay: 1 * time.Second, + MinTimeout: 1 * time.Second, + } + + _, err := stateConf.WaitForState() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + if target == "DELETED" { + return nil + } + } + + return fmt.Errorf("Error waiting for l7policy %s to become %s: %s", l7policy.ID, target, err) + } + + return nil +} + +func getListenerIDForL7Policy(lbClient *gophercloud.ServiceClient, id string) (string, error) { + log.Printf("[DEBUG] Trying to get Listener ID associated with the %s L7 Policy ID", id) + lbsPages, err := loadbalancers.List(lbClient, loadbalancers.ListOpts{}).AllPages() + if err != nil { + return "", fmt.Errorf("No Load Balancers were found: %s", err) + } + + lbs, err := loadbalancers.ExtractLoadBalancers(lbsPages) + if err != nil { + return "", fmt.Errorf("Unable to extract Load Balancers list: %s", err) + } + + for _, lb := range lbs { + statuses, err := loadbalancers.GetStatuses(lbClient, lb.ID).Extract() + if err != nil { + return "", fmt.Errorf("Failed to get Load Balancer statuses: %s", err) + } + for _, listener := range statuses.Loadbalancer.Listeners { + for _, l7policy := range listener.L7Policies { + if l7policy.ID == id { + return listener.ID, nil + } + } + } + } + + return "", fmt.Errorf("Unable to find Listener ID associated with the %s L7 Policy ID", id) +} + +func resourceLBV2L7RuleRefreshFunc(lbClient *gophercloud.ServiceClient, lbID string, l7policyID string, l7rule *l7policies.Rule) resource.StateRefreshFunc { + if l7rule.ProvisioningStatus != "" { + return func() (interface{}, string, error) { + lb, status, err := resourceLBV2LoadBalancerRefreshFunc(lbClient, lbID)() + if err != nil { + return lb, status, err + } + if !strSliceContains(lbSkipLBStatuses, status) { + return lb, status, nil + } + + l7rule, err := l7policies.GetRule(lbClient, l7policyID, l7rule.ID).Extract() + if err != nil { + return nil, "", err + } + + return l7rule, l7rule.ProvisioningStatus, nil + } + } + + return resourceLBV2LoadBalancerStatusRefreshFuncNeutron(lbClient, lbID, "l7rule", l7rule.ID) +} + +func waitForLBV2L7Rule(lbClient *gophercloud.ServiceClient, parentListener *listeners.Listener, parentL7policy *l7policies.L7Policy, l7rule *l7policies.Rule, target string, pending []string, timeout time.Duration) error { + log.Printf("[DEBUG] Waiting for l7rule %s to become %s.", l7rule.ID, target) + + if len(parentListener.Loadbalancers) == 0 { + return fmt.Errorf("Unable to determine loadbalancer ID from listener %s", parentListener.ID) + } + + lbID := parentListener.Loadbalancers[0].ID + + stateConf := &resource.StateChangeConf{ + Target: []string{target}, + Pending: pending, + Refresh: resourceLBV2L7RuleRefreshFunc(lbClient, lbID, parentL7policy.ID, l7rule), + Timeout: timeout, + Delay: 1 * time.Second, + MinTimeout: 1 * time.Second, + } + + _, err := stateConf.WaitForState() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + if target == "DELETED" { + return nil + } + } + + return fmt.Errorf("Error waiting for l7rule %s to become %s: %s", l7rule.ID, target, err) + } + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/networking_addressscope_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/networking_addressscope_v2.go new file mode 100644 index 000000000..1a18c2a1e --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/networking_addressscope_v2.go @@ -0,0 +1,22 @@ +package openstack + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes" + "github.com/hashicorp/terraform/helper/resource" +) + +func resourceNetworkingAddressScopeV2StateRefreshFunc(client *gophercloud.ServiceClient, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + a, err := addressscopes.Get(client, id).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + return a, "DELETED", nil + } + + return nil, "", err + } + + return a, "ACTIVE", nil + } +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/networking_floatingip_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/networking_floatingip_v2.go new file mode 100644 index 000000000..6b51ef9fc --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/networking_floatingip_v2.go @@ -0,0 +1,50 @@ +package openstack + +import ( + "fmt" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" + "github.com/hashicorp/terraform/helper/resource" +) + +// networkingFloatingIPV2ID retrieves floating IP ID by the provided IP address. +func networkingFloatingIPV2ID(client *gophercloud.ServiceClient, floatingIP string) (string, error) { + listOpts := floatingips.ListOpts{ + FloatingIP: floatingIP, + } + + allPages, err := floatingips.List(client, listOpts).AllPages() + if err != nil { + return "", err + } + + allFloatingIPs, err := floatingips.ExtractFloatingIPs(allPages) + if err != nil { + return "", err + } + + if len(allFloatingIPs) == 0 { + return "", fmt.Errorf("there are no openstack_networking_floatingip_v2 with %s IP", floatingIP) + } + if len(allFloatingIPs) > 1 { + return "", fmt.Errorf("there are more than one openstack_networking_floatingip_v2 with %s IP", floatingIP) + } + + return allFloatingIPs[0].ID, nil +} + +func networkingFloatingIPV2StateRefreshFunc(client *gophercloud.ServiceClient, fipID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + fip, err := floatingips.Get(client, fipID).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + return fip, "DELETED", nil + } + + return nil, "", err + } + + return fip, fip.Status, nil + } +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/networking_network_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/networking_network_v2.go new file mode 100644 index 000000000..d7f001757 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/networking_network_v2.go @@ -0,0 +1,111 @@ +package openstack + +import ( + "fmt" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/provider" + "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" + "github.com/gophercloud/gophercloud/pagination" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +// networkingNetworkV2ID retrieves network ID by the provided name. +func networkingNetworkV2ID(d *schema.ResourceData, meta interface{}, networkName string) (string, error) { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return "", fmt.Errorf("Error creating OpenStack network client: %s", err) + } + + opts := networks.ListOpts{Name: networkName} + pager := networks.List(networkingClient, opts) + networkID := "" + + err = pager.EachPage(func(page pagination.Page) (bool, error) { + networkList, err := networks.ExtractNetworks(page) + if err != nil { + return false, err + } + + for _, n := range networkList { + if n.Name == networkName { + networkID = n.ID + return false, nil + } + } + + return true, nil + }) + + return networkID, err +} + +// networkingNetworkV2Name retrieves network name by the provided ID. +func networkingNetworkV2Name(d *schema.ResourceData, meta interface{}, networkID string) (string, error) { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return "", fmt.Errorf("Error creating OpenStack network client: %s", err) + } + + opts := networks.ListOpts{ID: networkID} + pager := networks.List(networkingClient, opts) + networkName := "" + + err = pager.EachPage(func(page pagination.Page) (bool, error) { + networkList, err := networks.ExtractNetworks(page) + if err != nil { + return false, err + } + + for _, n := range networkList { + if n.ID == networkID { + networkName = n.Name + return false, nil + } + } + + return true, nil + }) + + return networkName, err +} + +func resourceNetworkingNetworkV2StateRefreshFunc(client *gophercloud.ServiceClient, networkID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + n, err := networks.Get(client, networkID).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + return n, "DELETED", nil + } + if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok { + if errCode.Actual == 409 { + return n, "ACTIVE", nil + } + } + + return n, "", err + } + + return n, n.Status, nil + } +} + +func expandNetworkingNetworkSegmentsV2(segments *schema.Set) []provider.Segment { + rawSegments := segments.List() + + providerSegments := make([]provider.Segment, len(rawSegments)) + for i, raw := range rawSegments { + rawMap := raw.(map[string]interface{}) + providerSegments[i] = provider.Segment{ + PhysicalNetwork: rawMap["physical_network"].(string), + NetworkType: rawMap["network_type"].(string), + SegmentationID: rawMap["segmentation_id"].(int), + } + } + + return providerSegments +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/networking_port_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/networking_port_v2.go new file mode 100644 index 000000000..96b35203c --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/networking_port_v2.go @@ -0,0 +1,180 @@ +package openstack + +import ( + "bytes" + "fmt" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/extradhcpopts" + "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceNetworkingPortV2StateRefreshFunc(client *gophercloud.ServiceClient, portID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + n, err := ports.Get(client, portID).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + return n, "DELETED", nil + } + + return n, "", err + } + + return n, n.Status, nil + } +} + +func expandNetworkingPortDHCPOptsV2Create(dhcpOpts *schema.Set) []extradhcpopts.CreateExtraDHCPOpt { + rawDHCPOpts := dhcpOpts.List() + + extraDHCPOpts := make([]extradhcpopts.CreateExtraDHCPOpt, len(rawDHCPOpts)) + for i, raw := range rawDHCPOpts { + rawMap := raw.(map[string]interface{}) + + ipVersion := rawMap["ip_version"].(int) + optName := rawMap["name"].(string) + optValue := rawMap["value"].(string) + + extraDHCPOpts[i] = extradhcpopts.CreateExtraDHCPOpt{ + OptName: optName, + OptValue: optValue, + IPVersion: gophercloud.IPVersion(ipVersion), + } + } + + return extraDHCPOpts +} + +func expandNetworkingPortDHCPOptsV2Update(dhcpOpts *schema.Set) []extradhcpopts.UpdateExtraDHCPOpt { + rawDHCPOpts := dhcpOpts.List() + + extraDHCPOpts := make([]extradhcpopts.UpdateExtraDHCPOpt, len(rawDHCPOpts)) + for i, raw := range rawDHCPOpts { + rawMap := raw.(map[string]interface{}) + + ipVersion := rawMap["ip_version"].(int) + optName := rawMap["name"].(string) + optValue := rawMap["value"].(string) + + extraDHCPOpts[i] = extradhcpopts.UpdateExtraDHCPOpt{ + OptName: optName, + OptValue: &optValue, + IPVersion: gophercloud.IPVersion(ipVersion), + } + } + + return extraDHCPOpts +} + +func expandNetworkingPortDHCPOptsV2Delete(dhcpOpts *schema.Set) []extradhcpopts.UpdateExtraDHCPOpt { + if dhcpOpts == nil { + return []extradhcpopts.UpdateExtraDHCPOpt{} + } + + rawDHCPOpts := dhcpOpts.List() + + extraDHCPOpts := make([]extradhcpopts.UpdateExtraDHCPOpt, len(rawDHCPOpts)) + for i, raw := range rawDHCPOpts { + rawMap := raw.(map[string]interface{}) + extraDHCPOpts[i] = extradhcpopts.UpdateExtraDHCPOpt{ + OptName: rawMap["name"].(string), + OptValue: nil, + } + } + + return extraDHCPOpts +} + +func flattenNetworkingPortDHCPOptsV2(dhcpOpts extradhcpopts.ExtraDHCPOptsExt) []map[string]interface{} { + dhcpOptsSet := make([]map[string]interface{}, len(dhcpOpts.ExtraDHCPOpts)) + + for i, dhcpOpt := range dhcpOpts.ExtraDHCPOpts { + dhcpOptsSet[i] = map[string]interface{}{ + "ip_version": dhcpOpt.IPVersion, + "name": dhcpOpt.OptName, + "value": dhcpOpt.OptValue, + } + } + + return dhcpOptsSet +} + +func expandNetworkingPortAllowedAddressPairsV2(allowedAddressPairs *schema.Set) []ports.AddressPair { + rawPairs := allowedAddressPairs.List() + + pairs := make([]ports.AddressPair, len(rawPairs)) + for i, raw := range rawPairs { + rawMap := raw.(map[string]interface{}) + pairs[i] = ports.AddressPair{ + IPAddress: rawMap["ip_address"].(string), + MACAddress: rawMap["mac_address"].(string), + } + } + + return pairs +} + +func flattenNetworkingPortAllowedAddressPairsV2(mac string, allowedAddressPairs []ports.AddressPair) []map[string]interface{} { + pairs := make([]map[string]interface{}, len(allowedAddressPairs)) + + for i, pair := range allowedAddressPairs { + pairs[i] = map[string]interface{}{ + "ip_address": pair.IPAddress, + } + // Only set the MAC address if it is different than the + // port's MAC. This means that a specific MAC was set. + if pair.MACAddress != mac { + pairs[i]["mac_address"] = pair.MACAddress + } + } + + return pairs +} + +func expandNetworkingPortFixedIPV2(d *schema.ResourceData) interface{} { + // If no_fixed_ip was specified, then just return an empty array. + // Since no_fixed_ip is mutually exclusive to fixed_ip, + // we can safely do this. + // + // Since we're only concerned about no_fixed_ip being set to "true", + // GetOk is used. + if _, ok := d.GetOk("no_fixed_ip"); ok { + return []interface{}{} + } + + rawIP := d.Get("fixed_ip").([]interface{}) + + if len(rawIP) == 0 { + return nil + } + + ip := make([]ports.IP, len(rawIP)) + for i, raw := range rawIP { + rawMap := raw.(map[string]interface{}) + ip[i] = ports.IP{ + SubnetID: rawMap["subnet_id"].(string), + IPAddress: rawMap["ip_address"].(string), + } + } + return ip +} + +func resourceNetworkingPortV2AllowedAddressPairsHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%s-%s", m["ip_address"].(string), m["mac_address"].(string))) + + return hashcode.String(buf.String()) +} + +func expandNetworkingPortFixedIPToStringSlice(fixedIPs []ports.IP) []string { + s := make([]string, len(fixedIPs)) + for i, fixedIP := range fixedIPs { + s[i] = fixedIP.IPAddress + } + + return s +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/networking_router_interface_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/networking_router_interface_v2.go new file mode 100644 index 000000000..b6d45e825 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/networking_router_interface_v2.go @@ -0,0 +1,68 @@ +package openstack + +import ( + "log" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers" + "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceNetworkingRouterInterfaceV2StateRefreshFunc(networkingClient *gophercloud.ServiceClient, portID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + r, err := ports.Get(networkingClient, portID).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + return r, "DELETED", nil + } + + return r, "", err + } + + return r, r.Status, nil + } +} + +func resourceNetworkingRouterInterfaceV2DeleteRefreshFunc(networkingClient *gophercloud.ServiceClient, d *schema.ResourceData) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + routerID := d.Get("router_id").(string) + routerInterfaceID := d.Id() + + log.Printf("[DEBUG] Attempting to delete openstack_networking_router_interface_v2 %s", routerInterfaceID) + + removeOpts := routers.RemoveInterfaceOpts{ + SubnetID: d.Get("subnet_id").(string), + PortID: d.Get("port_id").(string), + } + + r, err := ports.Get(networkingClient, routerInterfaceID).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + log.Printf("[DEBUG] Successfully deleted openstack_networking_router_interface_v2 %s", routerInterfaceID) + return r, "DELETED", nil + } + return r, "ACTIVE", err + } + + _, err = routers.RemoveInterface(networkingClient, routerID, removeOpts).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + log.Printf("[DEBUG] Successfully deleted openstack_networking_router_interface_v2 %s", routerInterfaceID) + return r, "DELETED", nil + } + if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok { + if errCode.Actual == 409 { + log.Printf("[DEBUG] openstack_networking_router_interface_v2 %s is still in use", routerInterfaceID) + return r, "ACTIVE", nil + } + } + + return r, "ACTIVE", err + } + + log.Printf("[DEBUG] openstack_networking_router_interface_v2 %s is still active", routerInterfaceID) + return r, "ACTIVE", nil + } +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/networking_subnetpool_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/networking_subnetpool_v2.go new file mode 100644 index 000000000..a9682e295 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/networking_subnetpool_v2.go @@ -0,0 +1,27 @@ +package openstack + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/subnetpools" + "github.com/hashicorp/terraform/helper/resource" +) + +func networkingSubnetpoolV2StateRefreshFunc(client *gophercloud.ServiceClient, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + subnetpool, err := subnetpools.Get(client, id).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + return subnetpool, "DELETED", nil + } + if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok { + if errCode.Actual == 409 { + return subnetpool, "ACTIVE", nil + } + } + + return nil, "", err + } + + return subnetpool, "ACTIVE", nil + } +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/networking_trunk_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/networking_trunk_v2.go new file mode 100644 index 000000000..c6e4a2c81 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/networking_trunk_v2.go @@ -0,0 +1,69 @@ +package openstack + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func networkingTrunkV2StateRefreshFunc(client *gophercloud.ServiceClient, trunkID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + trunk, err := trunks.Get(client, trunkID).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + return trunk, "DELETED", nil + } + + return nil, "", err + } + + return trunk, trunk.Status, nil + } +} + +func flattenNetworkingTrunkV2Subports(subports []trunks.Subport) []map[string]interface{} { + trunkSubports := make([]map[string]interface{}, len(subports)) + + for i, subport := range subports { + trunkSubports[i] = map[string]interface{}{ + "port_id": subport.PortID, + "segmentation_type": subport.SegmentationType, + "segmentation_id": subport.SegmentationID, + } + } + + return trunkSubports +} + +func expandNetworkingTrunkV2Subports(subports *schema.Set) []trunks.Subport { + rawSubports := subports.List() + + trunkSubports := make([]trunks.Subport, len(rawSubports)) + for i, raw := range rawSubports { + rawMap := raw.(map[string]interface{}) + + trunkSubports[i] = trunks.Subport{ + PortID: rawMap["port_id"].(string), + SegmentationType: rawMap["segmentation_type"].(string), + SegmentationID: rawMap["segmentation_id"].(int), + } + } + + return trunkSubports +} + +func expandNetworkingTrunkV2SubportsRemove(subports *schema.Set) []trunks.RemoveSubport { + rawSubports := subports.List() + + subportsToRemove := make([]trunks.RemoveSubport, len(rawSubports)) + for i, raw := range rawSubports { + rawMap := raw.(map[string]interface{}) + + subportsToRemove[i] = trunks.RemoveSubport{ + PortID: rawMap["port_id"].(string), + } + } + + return subportsToRemove +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/provider.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/provider.go index 4ec3ee55a..8b6b48677 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/provider.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/provider.go @@ -13,35 +13,56 @@ var osMutexKV = mutexkv.NewMutexKV() func Provider() terraform.ResourceProvider { return &schema.Provider{ Schema: map[string]*schema.Schema{ - "auth_url": &schema.Schema{ + "auth_url": { Type: schema.TypeString, - Required: true, - DefaultFunc: schema.EnvDefaultFunc("OS_AUTH_URL", nil), + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("OS_AUTH_URL", ""), Description: descriptions["auth_url"], }, - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Description: descriptions["region"], DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""), }, - "user_name": &schema.Schema{ + "user_name": { Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc("OS_USERNAME", ""), Description: descriptions["user_name"], }, - "user_id": &schema.Schema{ + "user_id": { Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc("OS_USER_ID", ""), Description: descriptions["user_name"], }, - "tenant_id": &schema.Schema{ + "application_credential_id": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("OS_APPLICATION_CREDENTIAL_ID", ""), + Description: descriptions["application_credential_id"], + }, + + "application_credential_name": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("OS_APPLICATION_CREDENTIAL_NAME", ""), + Description: descriptions["application_credential_name"], + }, + + "application_credential_secret": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("OS_APPLICATION_CREDENTIAL_SECRET", ""), + Description: descriptions["application_credential_secret"], + }, + + "tenant_id": { Type: schema.TypeString, Optional: true, DefaultFunc: schema.MultiEnvDefaultFunc([]string{ @@ -51,7 +72,7 @@ func Provider() terraform.ResourceProvider { Description: descriptions["tenant_id"], }, - "tenant_name": &schema.Schema{ + "tenant_name": { Type: schema.TypeString, Optional: true, DefaultFunc: schema.MultiEnvDefaultFunc([]string{ @@ -61,7 +82,7 @@ func Provider() terraform.ResourceProvider { Description: descriptions["tenant_name"], }, - "password": &schema.Schema{ + "password": { Type: schema.TypeString, Optional: true, Sensitive: true, @@ -69,119 +90,233 @@ func Provider() terraform.ResourceProvider { Description: descriptions["password"], }, - "token": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - DefaultFunc: schema.EnvDefaultFunc("OS_AUTH_TOKEN", ""), + "token": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.MultiEnvDefaultFunc([]string{ + "OS_TOKEN", + "OS_AUTH_TOKEN", + }, ""), Description: descriptions["token"], }, - "domain_id": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - DefaultFunc: schema.MultiEnvDefaultFunc([]string{ - "OS_USER_DOMAIN_ID", - "OS_PROJECT_DOMAIN_ID", - "OS_DOMAIN_ID", - }, ""), + "user_domain_name": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("OS_USER_DOMAIN_NAME", ""), + Description: descriptions["user_domain_name"], + }, + + "user_domain_id": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("OS_USER_DOMAIN_ID", ""), + Description: descriptions["user_domain_id"], + }, + + "project_domain_name": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("OS_PROJECT_DOMAIN_NAME", ""), + Description: descriptions["project_domain_name"], + }, + + "project_domain_id": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("OS_PROJECT_DOMAIN_ID", ""), + Description: descriptions["project_domain_id"], + }, + + "domain_id": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("OS_DOMAIN_ID", ""), Description: descriptions["domain_id"], }, - "domain_name": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - DefaultFunc: schema.MultiEnvDefaultFunc([]string{ - "OS_USER_DOMAIN_NAME", - "OS_PROJECT_DOMAIN_NAME", - "OS_DOMAIN_NAME", - "OS_DEFAULT_DOMAIN", - }, ""), + "domain_name": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("OS_DOMAIN_NAME", ""), Description: descriptions["domain_name"], }, - "insecure": &schema.Schema{ + "default_domain": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("OS_DEFAULT_DOMAIN", "default"), + Description: descriptions["default_domain"], + }, + + "insecure": { Type: schema.TypeBool, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("OS_INSECURE", ""), + DefaultFunc: schema.EnvDefaultFunc("OS_INSECURE", nil), Description: descriptions["insecure"], }, - "endpoint_type": &schema.Schema{ + "endpoint_type": { Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc("OS_ENDPOINT_TYPE", ""), }, - "cacert_file": &schema.Schema{ + "cacert_file": { Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc("OS_CACERT", ""), Description: descriptions["cacert_file"], }, - "cert": &schema.Schema{ + "cert": { Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc("OS_CERT", ""), Description: descriptions["cert"], }, - "key": &schema.Schema{ + "key": { Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc("OS_KEY", ""), Description: descriptions["key"], }, - "swauth": &schema.Schema{ + "swauth": { Type: schema.TypeBool, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("OS_SWAUTH", ""), + DefaultFunc: schema.EnvDefaultFunc("OS_SWAUTH", false), Description: descriptions["swauth"], }, + + "use_octavia": { + Type: schema.TypeBool, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("OS_USE_OCTAVIA", false), + Description: descriptions["use_octavia"], + }, + + "cloud": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("OS_CLOUD", ""), + Description: descriptions["cloud"], + }, + + "max_retries": { + Type: schema.TypeInt, + Optional: true, + Default: 0, + Description: descriptions["max_retries"], + }, + + "endpoint_overrides": { + Type: schema.TypeMap, + Optional: true, + Description: descriptions["endpoint_overrides"], + }, }, DataSourcesMap: map[string]*schema.Resource{ - "openstack_images_image_v2": dataSourceImagesImageV2(), - "openstack_networking_network_v2": dataSourceNetworkingNetworkV2(), + "openstack_blockstorage_snapshot_v2": dataSourceBlockStorageSnapshotV2(), + "openstack_blockstorage_snapshot_v3": dataSourceBlockStorageSnapshotV3(), + "openstack_compute_flavor_v2": dataSourceComputeFlavorV2(), + "openstack_compute_keypair_v2": dataSourceComputeKeypairV2(), + "openstack_containerinfra_clustertemplate_v1": dataSourceContainerInfraClusterTemplateV1(), + "openstack_containerinfra_cluster_v1": dataSourceContainerInfraCluster(), + "openstack_dns_zone_v2": dataSourceDNSZoneV2(), + "openstack_fw_policy_v1": dataSourceFWPolicyV1(), + "openstack_identity_role_v3": dataSourceIdentityRoleV3(), + "openstack_identity_project_v3": dataSourceIdentityProjectV3(), + "openstack_identity_user_v3": dataSourceIdentityUserV3(), + "openstack_identity_auth_scope_v3": dataSourceIdentityAuthScopeV3(), + "openstack_identity_endpoint_v3": dataSourceIdentityEndpointV3(), + "openstack_identity_group_v3": dataSourceIdentityGroupV3(), + "openstack_images_image_v2": dataSourceImagesImageV2(), + "openstack_networking_network_v2": dataSourceNetworkingNetworkV2(), + "openstack_networking_subnet_v2": dataSourceNetworkingSubnetV2(), + "openstack_networking_secgroup_v2": dataSourceNetworkingSecGroupV2(), + "openstack_networking_subnetpool_v2": dataSourceNetworkingSubnetPoolV2(), + "openstack_networking_floatingip_v2": dataSourceNetworkingFloatingIPV2(), + "openstack_networking_router_v2": dataSourceNetworkingRouterV2(), + "openstack_networking_port_v2": dataSourceNetworkingPortV2(), + "openstack_networking_port_ids_v2": dataSourceNetworkingPortIDsV2(), + "openstack_sharedfilesystem_sharenetwork_v2": dataSourceSharedFilesystemShareNetworkV2(), + "openstack_sharedfilesystem_share_v2": dataSourceSharedFilesystemShareV2(), + "openstack_sharedfilesystem_snapshot_v2": dataSourceSharedFilesystemSnapshotV2(), }, ResourcesMap: map[string]*schema.Resource{ - "openstack_blockstorage_volume_v1": resourceBlockStorageVolumeV1(), - "openstack_blockstorage_volume_v2": resourceBlockStorageVolumeV2(), - "openstack_blockstorage_volume_attach_v2": resourceBlockStorageVolumeAttachV2(), - "openstack_compute_instance_v2": resourceComputeInstanceV2(), - "openstack_compute_keypair_v2": resourceComputeKeypairV2(), - "openstack_compute_secgroup_v2": resourceComputeSecGroupV2(), - "openstack_compute_servergroup_v2": resourceComputeServerGroupV2(), - "openstack_compute_floatingip_v2": resourceComputeFloatingIPV2(), - "openstack_compute_floatingip_associate_v2": resourceComputeFloatingIPAssociateV2(), - "openstack_compute_volume_attach_v2": resourceComputeVolumeAttachV2(), - "openstack_dns_recordset_v2": resourceDNSRecordSetV2(), - "openstack_dns_zone_v2": resourceDNSZoneV2(), - "openstack_fw_firewall_v1": resourceFWFirewallV1(), - "openstack_fw_policy_v1": resourceFWPolicyV1(), - "openstack_fw_rule_v1": resourceFWRuleV1(), - "openstack_images_image_v2": resourceImagesImageV2(), - "openstack_lb_member_v1": resourceLBMemberV1(), - "openstack_lb_monitor_v1": resourceLBMonitorV1(), - "openstack_lb_pool_v1": resourceLBPoolV1(), - "openstack_lb_vip_v1": resourceLBVipV1(), - "openstack_lb_loadbalancer_v2": resourceLoadBalancerV2(), - "openstack_lb_listener_v2": resourceListenerV2(), - "openstack_lb_pool_v2": resourcePoolV2(), - "openstack_lb_member_v2": resourceMemberV2(), - "openstack_lb_monitor_v2": resourceMonitorV2(), - "openstack_networking_network_v2": resourceNetworkingNetworkV2(), - "openstack_networking_subnet_v2": resourceNetworkingSubnetV2(), - "openstack_networking_floatingip_v2": resourceNetworkingFloatingIPV2(), - "openstack_networking_port_v2": resourceNetworkingPortV2(), - "openstack_networking_router_v2": resourceNetworkingRouterV2(), - "openstack_networking_router_interface_v2": resourceNetworkingRouterInterfaceV2(), - "openstack_networking_router_route_v2": resourceNetworkingRouterRouteV2(), - "openstack_networking_secgroup_v2": resourceNetworkingSecGroupV2(), - "openstack_networking_secgroup_rule_v2": resourceNetworkingSecGroupRuleV2(), - "openstack_objectstorage_container_v1": resourceObjectStorageContainerV1(), + "openstack_blockstorage_volume_v1": resourceBlockStorageVolumeV1(), + "openstack_blockstorage_volume_v2": resourceBlockStorageVolumeV2(), + "openstack_blockstorage_volume_v3": resourceBlockStorageVolumeV3(), + "openstack_blockstorage_volume_attach_v2": resourceBlockStorageVolumeAttachV2(), + "openstack_blockstorage_volume_attach_v3": resourceBlockStorageVolumeAttachV3(), + "openstack_compute_flavor_v2": resourceComputeFlavorV2(), + "openstack_compute_flavor_access_v2": resourceComputeFlavorAccessV2(), + "openstack_compute_instance_v2": resourceComputeInstanceV2(), + "openstack_compute_interface_attach_v2": resourceComputeInterfaceAttachV2(), + "openstack_compute_keypair_v2": resourceComputeKeypairV2(), + "openstack_compute_secgroup_v2": resourceComputeSecGroupV2(), + "openstack_compute_servergroup_v2": resourceComputeServerGroupV2(), + "openstack_compute_floatingip_v2": resourceComputeFloatingIPV2(), + "openstack_compute_floatingip_associate_v2": resourceComputeFloatingIPAssociateV2(), + "openstack_compute_volume_attach_v2": resourceComputeVolumeAttachV2(), + "openstack_containerinfra_clustertemplate_v1": resourceContainerInfraClusterTemplateV1(), + "openstack_containerinfra_cluster_v1": resourceContainerInfraClusterV1(), + "openstack_db_instance_v1": resourceDatabaseInstanceV1(), + "openstack_db_user_v1": resourceDatabaseUserV1(), + "openstack_db_configuration_v1": resourceDatabaseConfigurationV1(), + "openstack_db_database_v1": resourceDatabaseDatabaseV1(), + "openstack_dns_recordset_v2": resourceDNSRecordSetV2(), + "openstack_dns_zone_v2": resourceDNSZoneV2(), + "openstack_fw_firewall_v1": resourceFWFirewallV1(), + "openstack_fw_policy_v1": resourceFWPolicyV1(), + "openstack_fw_rule_v1": resourceFWRuleV1(), + "openstack_identity_project_v3": resourceIdentityProjectV3(), + "openstack_identity_role_v3": resourceIdentityRoleV3(), + "openstack_identity_role_assignment_v3": resourceIdentityRoleAssignmentV3(), + "openstack_identity_user_v3": resourceIdentityUserV3(), + "openstack_images_image_v2": resourceImagesImageV2(), + "openstack_lb_member_v1": resourceLBMemberV1(), + "openstack_lb_monitor_v1": resourceLBMonitorV1(), + "openstack_lb_pool_v1": resourceLBPoolV1(), + "openstack_lb_vip_v1": resourceLBVipV1(), + "openstack_lb_loadbalancer_v2": resourceLoadBalancerV2(), + "openstack_lb_listener_v2": resourceListenerV2(), + "openstack_lb_pool_v2": resourcePoolV2(), + "openstack_lb_member_v2": resourceMemberV2(), + "openstack_lb_monitor_v2": resourceMonitorV2(), + "openstack_lb_l7policy_v2": resourceL7PolicyV2(), + "openstack_lb_l7rule_v2": resourceL7RuleV2(), + "openstack_networking_floatingip_v2": resourceNetworkingFloatingIPV2(), + "openstack_networking_floatingip_associate_v2": resourceNetworkingFloatingIPAssociateV2(), + "openstack_networking_network_v2": resourceNetworkingNetworkV2(), + "openstack_networking_port_v2": resourceNetworkingPortV2(), + "openstack_networking_port_secgroup_associate_v2": resourceNetworkingPortSecGroupAssociateV2(), + "openstack_networking_router_v2": resourceNetworkingRouterV2(), + "openstack_networking_router_interface_v2": resourceNetworkingRouterInterfaceV2(), + "openstack_networking_router_route_v2": resourceNetworkingRouterRouteV2(), + "openstack_networking_secgroup_v2": resourceNetworkingSecGroupV2(), + "openstack_networking_secgroup_rule_v2": resourceNetworkingSecGroupRuleV2(), + "openstack_networking_subnet_v2": resourceNetworkingSubnetV2(), + "openstack_networking_subnet_route_v2": resourceNetworkingSubnetRouteV2(), + "openstack_networking_subnetpool_v2": resourceNetworkingSubnetPoolV2(), + "openstack_networking_addressscope_v2": resourceNetworkingAddressScopeV2(), + "openstack_networking_trunk_v2": resourceNetworkingTrunkV2(), + "openstack_objectstorage_container_v1": resourceObjectStorageContainerV1(), + "openstack_objectstorage_object_v1": resourceObjectStorageObjectV1(), + "openstack_objectstorage_tempurl_v1": resourceObjectstorageTempurlV1(), + "openstack_vpnaas_ipsec_policy_v2": resourceIPSecPolicyV2(), + "openstack_vpnaas_service_v2": resourceServiceV2(), + "openstack_vpnaas_ike_policy_v2": resourceIKEPolicyV2(), + "openstack_vpnaas_endpoint_group_v2": resourceEndpointGroupV2(), + "openstack_vpnaas_site_connection_v2": resourceSiteConnectionV2(), + "openstack_sharedfilesystem_securityservice_v2": resourceSharedFilesystemSecurityServiceV2(), + "openstack_sharedfilesystem_sharenetwork_v2": resourceSharedFilesystemShareNetworkV2(), + "openstack_sharedfilesystem_share_v2": resourceSharedFilesystemShareV2(), + "openstack_sharedfilesystem_share_access_v2": resourceSharedFilesystemShareAccessV2(), }, ConfigureFunc: configureProvider, @@ -200,6 +335,12 @@ func init() { "user_id": "User ID to login with.", + "application_credential_id": "Application Credential ID to login with.", + + "application_credential_name": "Application Credential name to login with.", + + "application_credential_secret": "Application Credential secret to login with.", + "tenant_id": "The ID of the Tenant (Identity v2) or Project (Identity v3)\n" + "to login with.", @@ -210,10 +351,20 @@ func init() { "token": "Authentication token to use as an alternative to username/password.", + "user_domain_name": "The name of the domain where the user resides (Identity v3).", + + "user_domain_id": "The ID of the domain where the user resides (Identity v3).", + + "project_domain_name": "The name of the domain where the project resides (Identity v3).", + + "project_domain_id": "The ID of the domain where the proejct resides (Identity v3).", + "domain_id": "The ID of the Domain to scope to (Identity v3).", "domain_name": "The name of the Domain to scope to (Identity v3).", + "default_domain": "The name of the Domain ID to scope to if no other domain is specified. Defaults to `default` (Identity v3).", + "insecure": "Trust self-signed certificates.", "cacert_file": "A Custom CA certificate.", @@ -226,27 +377,54 @@ func init() { "swauth": "Use Swift's authentication system instead of Keystone. Only used for\n" + "interaction with Swift.", + + "use_octavia": "If set to `true`, API requests will go the Load Balancer\n" + + "service (Octavia) instead of the Networking service (Neutron).", + + "cloud": "An entry in a `clouds.yaml` file to use.", + + "max_retries": "How many times HTTP connection should be retried until giving up.", + + "endpoint_overrides": "A map of services with an endpoint to override what was\n" + + "from the Keystone catalog", } } func configureProvider(d *schema.ResourceData) (interface{}, error) { config := Config{ - CACertFile: d.Get("cacert_file").(string), - ClientCertFile: d.Get("cert").(string), - ClientKeyFile: d.Get("key").(string), - DomainID: d.Get("domain_id").(string), - DomainName: d.Get("domain_name").(string), - EndpointType: d.Get("endpoint_type").(string), - IdentityEndpoint: d.Get("auth_url").(string), - Insecure: d.Get("insecure").(bool), - Password: d.Get("password").(string), - Region: d.Get("region").(string), - Swauth: d.Get("swauth").(bool), - Token: d.Get("token").(string), - TenantID: d.Get("tenant_id").(string), - TenantName: d.Get("tenant_name").(string), - Username: d.Get("user_name").(string), - UserID: d.Get("user_id").(string), + CACertFile: d.Get("cacert_file").(string), + ClientCertFile: d.Get("cert").(string), + ClientKeyFile: d.Get("key").(string), + Cloud: d.Get("cloud").(string), + DefaultDomain: d.Get("default_domain").(string), + DomainID: d.Get("domain_id").(string), + DomainName: d.Get("domain_name").(string), + EndpointOverrides: d.Get("endpoint_overrides").(map[string]interface{}), + EndpointType: d.Get("endpoint_type").(string), + IdentityEndpoint: d.Get("auth_url").(string), + Password: d.Get("password").(string), + ProjectDomainID: d.Get("project_domain_id").(string), + ProjectDomainName: d.Get("project_domain_name").(string), + Region: d.Get("region").(string), + Swauth: d.Get("swauth").(bool), + Token: d.Get("token").(string), + TenantID: d.Get("tenant_id").(string), + TenantName: d.Get("tenant_name").(string), + UserDomainID: d.Get("user_domain_id").(string), + UserDomainName: d.Get("user_domain_name").(string), + Username: d.Get("user_name").(string), + UserID: d.Get("user_id").(string), + ApplicationCredentialID: d.Get("application_credential_id").(string), + ApplicationCredentialName: d.Get("application_credential_name").(string), + ApplicationCredentialSecret: d.Get("application_credential_secret").(string), + useOctavia: d.Get("use_octavia").(bool), + MaxRetries: d.Get("max_retries").(int), + } + + v, ok := d.GetOkExists("insecure") + if ok { + insecure := v.(bool) + config.Insecure = &insecure } if err := config.LoadAndValidate(); err != nil { diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_blockstorage_volume_attach_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_blockstorage_volume_attach_v2.go index cfc8f4f0a..be22e9ff1 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_blockstorage_volume_attach_v2.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_blockstorage_volume_attach_v2.go @@ -3,7 +3,6 @@ package openstack import ( "fmt" "log" - "strings" "time" "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions" @@ -11,6 +10,7 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" ) func resourceBlockStorageVolumeAttachV2() *schema.Resource { @@ -25,108 +25,103 @@ func resourceBlockStorageVolumeAttachV2() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "volume_id": &schema.Schema{ + "volume_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "instance_id": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Deprecated: "instance_id is no longer used in this resource", + "instance_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Removed: "instance_id is no longer used in this resource", }, - "host_name": &schema.Schema{ + "host_name": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "device": &schema.Schema{ + "device": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - "attach_mode": &schema.Schema{ + "attach_mode": { Type: schema.TypeString, Optional: true, ForceNew: true, - ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { - value := v.(string) - if value != "ro" && value != "rw" { - errors = append(errors, fmt.Errorf( - "Only 'ro' and 'rw' are supported values for 'attach_mode'")) - } - return - }, + ValidateFunc: validation.StringInSlice([]string{ + "ro", "rw", + }, false), }, - "initiator": &schema.Schema{ + "initiator": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - "ip_address": &schema.Schema{ + "ip_address": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - "multipath": &schema.Schema{ + "multipath": { Type: schema.TypeBool, Optional: true, ForceNew: true, }, - "os_type": &schema.Schema{ + "os_type": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - "platform": &schema.Schema{ + "platform": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - "wwpn": &schema.Schema{ + "wwpn": { Type: schema.TypeList, Optional: true, ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - "wwnn": &schema.Schema{ + "wwnn": { Type: schema.TypeString, Optional: true, ForceNew: true, }, // Volume attachment information - "data": &schema.Schema{ + "data": { Type: schema.TypeMap, Computed: true, Sensitive: true, }, - "driver_volume_type": &schema.Schema{ + "driver_volume_type": { Type: schema.TypeString, Computed: true, }, - "mount_point_base": &schema.Schema{ + "mount_point_base": { Type: schema.TypeString, Computed: true, }, @@ -184,7 +179,8 @@ func resourceBlockStorageVolumeAttachV2Create(d *schema.ResourceData, meta inter connInfo, err := volumeactions.InitializeConnection(client, volumeId, connOpts).Extract() if err != nil { - return fmt.Errorf("Unable to create connection: %s", err) + return fmt.Errorf( + "Unable to initialize connection for openstack_blockstorage_volume_attach_v2: %s", err) } // Only uncomment this when debugging since connInfo contains sensitive information. @@ -212,7 +208,7 @@ func resourceBlockStorageVolumeAttachV2Create(d *schema.ResourceData, meta inter } // Once the connection has been made, tell Cinder to mark the volume as attached. - attachMode, err := blockStorageVolumeAttachV2AttachMode(d.Get("attach_mode").(string)) + attachMode, err := expandBlockStorageV2AttachMode(d.Get("attach_mode").(string)) if err != nil { return nil } @@ -223,19 +219,21 @@ func resourceBlockStorageVolumeAttachV2Create(d *schema.ResourceData, meta inter Mode: attachMode, } - log.Printf("[DEBUG] Attachment Options: %#v", attachOpts) + log.Printf("[DEBUG] openstack_blockstorage_volume_attach_v2 attach options: %#v", attachOpts) if err := volumeactions.Attach(client, volumeId, attachOpts).ExtractErr(); err != nil { - return err + return fmt.Errorf( + "Error attaching openstack_blockstorage_volume_attach_v2 for volume %s: %s", volumeId, err) } // Wait for the volume to become available. - log.Printf("[DEBUG] Waiting for volume (%s) to become available", volumeId) + log.Printf( + "[DEBUG] Waiting for openstack_blockstorage_volume_attach_v2 volume %s to become available", volumeId) stateConf := &resource.StateChangeConf{ Pending: []string{"available", "attaching"}, Target: []string{"in-use"}, - Refresh: VolumeV2StateRefreshFunc(client, volumeId), + Refresh: blockStorageVolumeV2StateRefreshFunc(client, volumeId), Timeout: d.Timeout(schema.TimeoutCreate), Delay: 10 * time.Second, MinTimeout: 3 * time.Second, @@ -243,14 +241,16 @@ func resourceBlockStorageVolumeAttachV2Create(d *schema.ResourceData, meta inter _, err = stateConf.WaitForState() if err != nil { - return fmt.Errorf("Error waiting for volume (%s) to become ready: %s", volumeId, err) + return fmt.Errorf( + "Error waiting for openstack_blockstorage_volume_attach_v2 volume %s to become in-use: %s", volumeId, err) } // Once the volume has been marked as attached, // retrieve a fresh copy of it with all information now available. volume, err := volumes.Get(client, volumeId).Extract() if err != nil { - return err + return fmt.Errorf( + "Unable to retrieve openstack_blockstorage_volume_attach_v2 volume %s: %s", volumeId, err) } // Search for the attachmentId @@ -263,7 +263,8 @@ func resourceBlockStorageVolumeAttachV2Create(d *schema.ResourceData, meta inter } if attachmentId == "" { - return fmt.Errorf("Unable to determine attachment ID.") + return fmt.Errorf( + "Unable to determine attachment ID for openstack_blockstorage_volume_attach_v2 volume %s.", volumeId) } // The ID must be a combination of the volume and attachment ID @@ -281,17 +282,18 @@ func resourceBlockStorageVolumeAttachV2Read(d *schema.ResourceData, meta interfa return fmt.Errorf("Error creating OpenStack block storage client: %s", err) } - volumeId, attachmentId, err := blockStorageVolumeAttachV2ParseId(d.Id()) + volumeId, attachmentId, err := blockStorageVolumeAttachV2ParseID(d.Id()) if err != nil { return err } volume, err := volumes.Get(client, volumeId).Extract() if err != nil { - return err + return fmt.Errorf( + "Unable to retrieve openstack_blockstorage_volume_attach_v2 volume %s: %s", volumeId, err) } - log.Printf("[DEBUG] Retrieved volume %s: %#v", d.Id(), volume) + log.Printf("[DEBUG] Retrieved openstack_blockstorage_volume_attach_v2 volume %s: %#v", volumeId, volume) var attachment volumes.Attachment for _, v := range volume.Attachments { @@ -300,7 +302,8 @@ func resourceBlockStorageVolumeAttachV2Read(d *schema.ResourceData, meta interfa } } - log.Printf("[DEBUG] Retrieved volume attachment: %#v", attachment) + log.Printf( + "[DEBUG] Retrieved openstack_blockstorage_volume_attach_v2 attachment %s: %#v", d.Id(), attachment) return nil } @@ -312,7 +315,10 @@ func resourceBlockStorageVolumeAttachV2Delete(d *schema.ResourceData, meta inter return fmt.Errorf("Error creating OpenStack block storage client: %s", err) } - volumeId, attachmentId, err := blockStorageVolumeAttachV2ParseId(d.Id()) + volumeId, attachmentId, err := blockStorageVolumeAttachV2ParseID(d.Id()) + if err != nil { + return err + } // Terminate the connection termOpts := &volumeactions.TerminateConnectionOpts{} @@ -356,7 +362,8 @@ func resourceBlockStorageVolumeAttachV2Delete(d *schema.ResourceData, meta inter err = volumeactions.TerminateConnection(client, volumeId, termOpts).ExtractErr() if err != nil { - return fmt.Errorf("Error terminating volume connection %s: %s", volumeId, err) + return fmt.Errorf( + "Error terminating openstack_blockstorage_volume_attach_v2 connection %s: %s", d.Id(), err) } // Detach the volume @@ -364,7 +371,8 @@ func resourceBlockStorageVolumeAttachV2Delete(d *schema.ResourceData, meta inter AttachmentID: attachmentId, } - log.Printf("[DEBUG] Detachment Options: %#v", detachOpts) + log.Printf( + "[DEBUG] openstack_blockstorage_volume_attach_v2 detachment options %s: %#v", d.Id(), detachOpts) if err := volumeactions.Detach(client, volumeId, detachOpts).ExtractErr(); err != nil { return err @@ -373,7 +381,7 @@ func resourceBlockStorageVolumeAttachV2Delete(d *schema.ResourceData, meta inter stateConf := &resource.StateChangeConf{ Pending: []string{"in-use", "attaching", "detaching"}, Target: []string{"available"}, - Refresh: VolumeV2StateRefreshFunc(client, volumeId), + Refresh: blockStorageVolumeV2StateRefreshFunc(client, volumeId), Timeout: d.Timeout(schema.TimeoutDelete), Delay: 10 * time.Second, MinTimeout: 3 * time.Second, @@ -381,34 +389,9 @@ func resourceBlockStorageVolumeAttachV2Delete(d *schema.ResourceData, meta inter _, err = stateConf.WaitForState() if err != nil { - return fmt.Errorf("Error waiting for volume (%s) to become available: %s", volumeId, err) + return fmt.Errorf( + "Error waiting for openstack_blockstorage_volume_attach_v2 volume %s to become available: %s", volumeId, err) } return nil } - -func blockStorageVolumeAttachV2AttachMode(v string) (volumeactions.AttachMode, error) { - var attachMode volumeactions.AttachMode - var attachError error - switch v { - case "": - attachMode = "" - case "ro": - attachMode = volumeactions.ReadOnly - case "rw": - attachMode = volumeactions.ReadWrite - default: - attachError = fmt.Errorf("Invalid attach_mode specified") - } - - return attachMode, attachError -} - -func blockStorageVolumeAttachV2ParseId(id string) (string, string, error) { - parts := strings.Split(id, "/") - if len(parts) < 2 { - return "", "", fmt.Errorf("Unable to determine attachment ID") - } - - return parts[0], parts[1], nil -} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_blockstorage_volume_attach_v3.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_blockstorage_volume_attach_v3.go new file mode 100644 index 000000000..398e0ebd4 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_blockstorage_volume_attach_v3.go @@ -0,0 +1,387 @@ +package openstack + +import ( + "fmt" + "log" + "time" + + "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions" + "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" +) + +func resourceBlockStorageVolumeAttachV3() *schema.Resource { + return &schema.Resource{ + Create: resourceBlockStorageVolumeAttachV3Create, + Read: resourceBlockStorageVolumeAttachV3Read, + Delete: resourceBlockStorageVolumeAttachV3Delete, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "volume_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "host_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "device": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "attach_mode": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + "ro", "rw", + }, false), + }, + + "initiator": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "ip_address": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "multipath": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + + "os_type": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "platform": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "wwpn": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "wwnn": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + // Volume attachment information + "data": { + Type: schema.TypeMap, + Computed: true, + Sensitive: true, + }, + + "driver_volume_type": { + Type: schema.TypeString, + Computed: true, + }, + + "mount_point_base": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceBlockStorageVolumeAttachV3Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + client, err := config.blockStorageV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack block storage client: %s", err) + } + + // initialize the connection + volumeId := d.Get("volume_id").(string) + connOpts := &volumeactions.InitializeConnectionOpts{} + if v, ok := d.GetOk("host_name"); ok { + connOpts.Host = v.(string) + } + + if v, ok := d.GetOk("multipath"); ok { + multipath := v.(bool) + connOpts.Multipath = &multipath + } + + if v, ok := d.GetOk("ip_address"); ok { + connOpts.IP = v.(string) + } + + if v, ok := d.GetOk("initiator"); ok { + connOpts.Initiator = v.(string) + } + + if v, ok := d.GetOk("os_type"); ok { + connOpts.OSType = v.(string) + } + + if v, ok := d.GetOk("platform"); ok { + connOpts.Platform = v.(string) + } + + if v, ok := d.GetOk("wwnns"); ok { + connOpts.Wwnns = v.(string) + } + + if v, ok := d.GetOk("wwpns"); ok { + var wwpns []string + for _, i := range v.([]string) { + wwpns = append(wwpns, i) + } + + connOpts.Wwpns = wwpns + } + + connInfo, err := volumeactions.InitializeConnection(client, volumeId, connOpts).Extract() + if err != nil { + return fmt.Errorf( + "Unable to initialize connection for openstack_blockstorage_volume_attach_v3: %s", err) + } + + // Only uncomment this when debugging since connInfo contains sensitive information. + // log.Printf("[DEBUG] Volume Connection for %s: %#v", volumeId, connInfo) + + // Because this information is only returned upon creation, + // it must be set in Create. + if v, ok := connInfo["data"]; ok { + data := make(map[string]string) + for key, value := range v.(map[string]interface{}) { + if v, ok := value.(string); ok { + data[key] = v + } + } + + d.Set("data", data) + } + + if v, ok := connInfo["driver_volume_type"]; ok { + d.Set("driver_volume_type", v) + } + + if v, ok := connInfo["mount_point_base"]; ok { + d.Set("mount_point_base", v) + } + + // Once the connection has been made, tell Cinder to mark the volume as attached. + attachMode, err := expandBlockStorageV3AttachMode(d.Get("attach_mode").(string)) + if err != nil { + return nil + } + + attachOpts := &volumeactions.AttachOpts{ + HostName: d.Get("host_name").(string), + MountPoint: d.Get("device").(string), + Mode: attachMode, + } + + log.Printf("[DEBUG] openstack_blockstorage_volume_attach_v3 attach options: %#v", attachOpts) + + if err := volumeactions.Attach(client, volumeId, attachOpts).ExtractErr(); err != nil { + return fmt.Errorf( + "Error attaching openstack_blockstorage_volume_attach_v3 for volume %s: %s", volumeId, err) + } + + // Wait for the volume to become available. + log.Printf( + "[DEBUG] Waiting for openstack_blockstorage_volume_attach_v3 volume %s to become available", volumeId) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"available", "attaching"}, + Target: []string{"in-use"}, + Refresh: blockStorageVolumeV3StateRefreshFunc(client, volumeId), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for openstack_blockstorage_volume_attach_v3 volume %s to become in-use: %s", volumeId, err) + } + + // Once the volume has been marked as attached, + // retrieve a fresh copy of it with all information now available. + volume, err := volumes.Get(client, volumeId).Extract() + if err != nil { + return fmt.Errorf( + "Unable to retrieve openstack_blockstorage_volume_attach_v3 volume %s: %s", volumeId, err) + } + + // Search for the attachmentId + var attachmentId string + hostName := d.Get("host_name").(string) + for _, attachment := range volume.Attachments { + if hostName != "" && hostName == attachment.HostName { + attachmentId = attachment.AttachmentID + } + } + + if attachmentId == "" { + return fmt.Errorf( + "Unable to determine attachment ID for openstack_blockstorage_volume_attach_v3 volume %s.", volumeId) + } + + // The ID must be a combination of the volume and attachment ID + // since a volume ID is required to retrieve an attachment ID. + id := fmt.Sprintf("%s/%s", volumeId, attachmentId) + d.SetId(id) + + return resourceBlockStorageVolumeAttachV3Read(d, meta) +} + +func resourceBlockStorageVolumeAttachV3Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + client, err := config.blockStorageV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack block storage client: %s", err) + } + + volumeId, attachmentId, err := blockStorageVolumeAttachV3ParseID(d.Id()) + if err != nil { + return err + } + + volume, err := volumes.Get(client, volumeId).Extract() + if err != nil { + return fmt.Errorf( + "Unable to retrieve openstack_blockstorage_volume_attach_v3 volume %s: %s", volumeId, err) + } + + log.Printf("[DEBUG] Retrieved openstack_blockstorage_volume_attach_v3 volume %s: %#v", volumeId, volume) + + var attachment volumes.Attachment + for _, v := range volume.Attachments { + if attachmentId == v.AttachmentID { + attachment = v + } + } + + log.Printf( + "[DEBUG] Retrieved openstack_blockstorage_volume_attach_v3 attachment %s: %#v", d.Id(), attachment) + + return nil +} + +func resourceBlockStorageVolumeAttachV3Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + client, err := config.blockStorageV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack block storage client: %s", err) + } + + volumeId, attachmentId, err := blockStorageVolumeAttachV3ParseID(d.Id()) + + // Terminate the connection + termOpts := &volumeactions.TerminateConnectionOpts{} + if v, ok := d.GetOk("host_name"); ok { + termOpts.Host = v.(string) + } + + if v, ok := d.GetOk("multipath"); ok { + multipath := v.(bool) + termOpts.Multipath = &multipath + } + + if v, ok := d.GetOk("ip_address"); ok { + termOpts.IP = v.(string) + } + + if v, ok := d.GetOk("initiator"); ok { + termOpts.Initiator = v.(string) + } + + if v, ok := d.GetOk("os_type"); ok { + termOpts.OSType = v.(string) + } + + if v, ok := d.GetOk("platform"); ok { + termOpts.Platform = v.(string) + } + + if v, ok := d.GetOk("wwnns"); ok { + termOpts.Wwnns = v.(string) + } + + if v, ok := d.GetOk("wwpns"); ok { + var wwpns []string + for _, i := range v.([]string) { + wwpns = append(wwpns, i) + } + + termOpts.Wwpns = wwpns + } + + err = volumeactions.TerminateConnection(client, volumeId, termOpts).ExtractErr() + if err != nil { + return fmt.Errorf( + "Error terminating openstack_blockstorage_volume_attach_v3 connection %s: %s", d.Id(), err) + } + + // Detach the volume + detachOpts := volumeactions.DetachOpts{ + AttachmentID: attachmentId, + } + + log.Printf( + "[DEBUG] openstack_blockstorage_volume_attach_v3 detachment options %s: %#v", d.Id(), detachOpts) + + if err := volumeactions.Detach(client, volumeId, detachOpts).ExtractErr(); err != nil { + return err + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"in-use", "attaching", "detaching"}, + Target: []string{"available"}, + Refresh: blockStorageVolumeV3StateRefreshFunc(client, volumeId), + Timeout: d.Timeout(schema.TimeoutDelete), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for openstack_blockstorage_volume_attach_v3 volume %s to become available: %s", volumeId, err) + } + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_blockstorage_volume_v1.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_blockstorage_volume_v1.go index 7529f6341..e82e0f549 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_blockstorage_volume_v1.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_blockstorage_volume_v1.go @@ -1,7 +1,6 @@ package openstack import ( - "bytes" "fmt" "log" "time" @@ -9,7 +8,7 @@ import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach" - "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" ) @@ -30,81 +29,90 @@ func resourceBlockStorageVolumeV1() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "size": &schema.Schema{ + "size": { Type: schema.TypeInt, Required: true, ForceNew: true, }, - "name": &schema.Schema{ + + "name": { Type: schema.TypeString, Optional: true, ForceNew: false, }, - "description": &schema.Schema{ + + "description": { Type: schema.TypeString, Optional: true, ForceNew: false, }, - "availability_zone": &schema.Schema{ + + "availability_zone": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, - "metadata": &schema.Schema{ + + "metadata": { Type: schema.TypeMap, Optional: true, ForceNew: false, Computed: true, }, - "snapshot_id": &schema.Schema{ + + "snapshot_id": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - "source_vol_id": &schema.Schema{ + + "source_vol_id": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - "image_id": &schema.Schema{ + + "image_id": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - "volume_type": &schema.Schema{ + + "volume_type": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, - "attachment": &schema.Schema{ + + "attachment": { Type: schema.TypeSet, Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "id": &schema.Schema{ + "id": { Type: schema.TypeString, Computed: true, }, - "instance_id": &schema.Schema{ + "instance_id": { Type: schema.TypeString, Computed: true, }, - "device": &schema.Schema{ + "device": { Type: schema.TypeString, Computed: true, }, }, }, - Set: resourceVolumeAttachmentHash, + Set: blockStorageVolumeV1AttachmentHash, }, }, } @@ -117,6 +125,7 @@ func resourceBlockStorageVolumeV1Create(d *schema.ResourceData, meta interface{} return fmt.Errorf("Error creating OpenStack block storage client: %s", err) } + metadata := d.Get("metadata").(map[string]interface{}) createOpts := &volumes.CreateOpts{ Description: d.Get("description").(string), AvailabilityZone: d.Get("availability_zone").(string), @@ -126,25 +135,20 @@ func resourceBlockStorageVolumeV1Create(d *schema.ResourceData, meta interface{} SourceVolID: d.Get("source_vol_id").(string), ImageID: d.Get("image_id").(string), VolumeType: d.Get("volume_type").(string), - Metadata: resourceContainerMetadataV2(d), + Metadata: expandToMapStringString(metadata), } - log.Printf("[DEBUG] Create Options: %#v", createOpts) + log.Printf("[DEBUG] openstack_blockstorage_volume_v1 create options: %#v", createOpts) + v, err := volumes.Create(blockStorageClient, createOpts).Extract() if err != nil { - return fmt.Errorf("Error creating OpenStack volume: %s", err) + return fmt.Errorf("Error creating openstack_blockstorage_volume_v1: %s", err) } - log.Printf("[INFO] Volume ID: %s", v.ID) - - // Wait for the volume to become available. - log.Printf( - "[DEBUG] Waiting for volume (%s) to become available", - v.ID) stateConf := &resource.StateChangeConf{ Pending: []string{"downloading", "creating"}, Target: []string{"available"}, - Refresh: VolumeV1StateRefreshFunc(blockStorageClient, v.ID), + Refresh: blockStorageVolumeV1StateRefreshFunc(blockStorageClient, v.ID), Timeout: d.Timeout(schema.TimeoutCreate), Delay: 10 * time.Second, MinTimeout: 3 * time.Second, @@ -153,8 +157,7 @@ func resourceBlockStorageVolumeV1Create(d *schema.ResourceData, meta interface{} _, err = stateConf.WaitForState() if err != nil { return fmt.Errorf( - "Error waiting for volume (%s) to become ready: %s", - v.ID, err) + "Error waiting for openstack_blockstorage_volume_v1 %s to become ready: %s", v.ID, err) } // Store the ID now @@ -173,10 +176,10 @@ func resourceBlockStorageVolumeV1Read(d *schema.ResourceData, meta interface{}) v, err := volumes.Get(blockStorageClient, d.Id()).Extract() if err != nil { - return CheckDeleted(d, err, "volume") + return CheckDeleted(d, err, "Error retrieving openstack_blockstorage_volume_v1") } - log.Printf("[DEBUG] Retrieved volume %s: %+v", d.Id(), v) + log.Printf("[DEBUG] Retrieved openstack_blockstorage_volume_v1 %s: %#v", d.Id(), v) d.Set("size", v.Size) d.Set("description", v.Description) @@ -188,15 +191,12 @@ func resourceBlockStorageVolumeV1Read(d *schema.ResourceData, meta interface{}) d.Set("metadata", v.Metadata) d.Set("region", GetRegion(d, config)) - attachments := make([]map[string]interface{}, len(v.Attachments)) - for i, attachment := range v.Attachments { - attachments[i] = make(map[string]interface{}) - attachments[i]["id"] = attachment["id"] - attachments[i]["instance_id"] = attachment["server_id"] - attachments[i]["device"] = attachment["device"] - log.Printf("[DEBUG] attachment: %v", attachment) + attachments := flattenBlockStorageVolumeV1Attachments(v.Attachments) + log.Printf("[DEBUG] openstack_blockstorage_volume_v1 %s attachments: %#v", d.Id(), attachments) + if err := d.Set("attachment", attachments); err != nil { + log.Printf( + "[DEBUG] unable to set openstack_blockstorage_volume_v1 %s attachments: %s", d.Id(), err) } - d.Set("attachment", attachments) return nil } @@ -208,18 +208,21 @@ func resourceBlockStorageVolumeV1Update(d *schema.ResourceData, meta interface{} return fmt.Errorf("Error creating OpenStack block storage client: %s", err) } + name := d.Get("name").(string) + description := d.Get("description").(string) updateOpts := volumes.UpdateOpts{ - Name: d.Get("name").(string), - Description: d.Get("description").(string), + Name: &name, + Description: &description, } if d.HasChange("metadata") { - updateOpts.Metadata = resourceVolumeMetadataV1(d) + metadata := d.Get("metadata").(map[string]interface{}) + updateOpts.Metadata = expandToMapStringString(metadata) } _, err = volumes.Update(blockStorageClient, d.Id(), updateOpts).Extract() if err != nil { - return fmt.Errorf("Error updating OpenStack volume: %s", err) + return fmt.Errorf("Error updating openstack_blockstorage_volume_v1 %s: %s", d.Id(), err) } return resourceBlockStorageVolumeV1Read(d, meta) @@ -234,38 +237,56 @@ func resourceBlockStorageVolumeV1Delete(d *schema.ResourceData, meta interface{} v, err := volumes.Get(blockStorageClient, d.Id()).Extract() if err != nil { - return CheckDeleted(d, err, "volume") + return CheckDeleted(d, err, "Error retrieving openstack_blockstorage_volume_v1") } - // make sure this volume is detached from all instances before deleting + // Make sure this volume is detached from all instances before deleting. if len(v.Attachments) > 0 { - log.Printf("[DEBUG] detaching volumes") - if computeClient, err := config.computeV2Client(GetRegion(d, config)); err != nil { - return err - } else { - for _, volumeAttachment := range v.Attachments { - log.Printf("[DEBUG] Attachment: %v", volumeAttachment) - if err := volumeattach.Delete(computeClient, volumeAttachment["server_id"].(string), volumeAttachment["id"].(string)).ExtractErr(); err != nil { - return err + computeClient, err := config.computeV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } + + for _, volumeAttachment := range v.Attachments { + log.Printf("[DEBUG] openstack_blockstorage_volume_v1 %s attachment: %#v", d.Id(), volumeAttachment) + + serverID := volumeAttachment["server_id"].(string) + attachmentID := volumeAttachment["id"].(string) + if err := volumeattach.Delete(computeClient, serverID, attachmentID).ExtractErr(); err != nil { + // It's possible the volume was already detached by + // openstack_compute_volume_attach_v2, so consider + // a 404 acceptable and continue. + if _, ok := err.(gophercloud.ErrDefault404); ok { + continue } - } - stateConf := &resource.StateChangeConf{ - Pending: []string{"in-use", "attaching", "detaching"}, - Target: []string{"available"}, - Refresh: VolumeV1StateRefreshFunc(blockStorageClient, d.Id()), - Timeout: 10 * time.Minute, - Delay: 10 * time.Second, - MinTimeout: 3 * time.Second, - } + // A 409 is also acceptable because there's another + // concurrent action happening. + if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok { + if errCode.Actual == 409 { + continue + } + } - _, err = stateConf.WaitForState() - if err != nil { return fmt.Errorf( - "Error waiting for volume (%s) to become available: %s", - d.Id(), err) + "Error detaching openstack_blockstorage_volume_v1 %s from %s: %s", d.Id(), serverID, err) } } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"in-use", "attaching", "detaching"}, + Target: []string{"available", "deleted"}, + Refresh: blockStorageVolumeV1StateRefreshFunc(blockStorageClient, d.Id()), + Timeout: 10 * time.Minute, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for openstack_blockstorage_volume_v1 %s to become available: %s", d.Id(), err) + } } // It's possible that this volume was used as a boot device and is currently @@ -273,17 +294,14 @@ func resourceBlockStorageVolumeV1Delete(d *schema.ResourceData, meta interface{} // If this is true, just move on. It'll eventually delete. if v.Status != "deleting" { if err := volumes.Delete(blockStorageClient, d.Id()).ExtractErr(); err != nil { - return CheckDeleted(d, err, "volume") + return CheckDeleted(d, err, "Error deleting openstack_blockstorage_volume_v1") } } - // Wait for the volume to delete before moving on. - log.Printf("[DEBUG] Waiting for volume (%s) to delete", d.Id()) - stateConf := &resource.StateChangeConf{ Pending: []string{"deleting", "downloading", "available"}, Target: []string{"deleted"}, - Refresh: VolumeV1StateRefreshFunc(blockStorageClient, d.Id()), + Refresh: blockStorageVolumeV1StateRefreshFunc(blockStorageClient, d.Id()), Timeout: d.Timeout(schema.TimeoutDelete), Delay: 10 * time.Second, MinTimeout: 3 * time.Second, @@ -291,50 +309,8 @@ func resourceBlockStorageVolumeV1Delete(d *schema.ResourceData, meta interface{} _, err = stateConf.WaitForState() if err != nil { - return fmt.Errorf( - "Error waiting for volume (%s) to delete: %s", - d.Id(), err) + return fmt.Errorf("Error waiting for openstack_blockstorage_volume_v1 %s to delete: %s", d.Id(), err) } - d.SetId("") return nil } - -func resourceVolumeMetadataV1(d *schema.ResourceData) map[string]string { - m := make(map[string]string) - for key, val := range d.Get("metadata").(map[string]interface{}) { - m[key] = val.(string) - } - return m -} - -// VolumeV1StateRefreshFunc returns a resource.StateRefreshFunc that is used to watch -// an OpenStack volume. -func VolumeV1StateRefreshFunc(client *gophercloud.ServiceClient, volumeID string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - v, err := volumes.Get(client, volumeID).Extract() - if err != nil { - if _, ok := err.(gophercloud.ErrDefault404); ok { - return v, "deleted", nil - } - return nil, "", err - } - - if v.Status == "error" { - return v, v.Status, fmt.Errorf("There was an error creating the volume. " + - "Please check with your cloud admin or check the Block Storage " + - "API logs to see why this error occurred.") - } - - return v, v.Status, nil - } -} - -func resourceVolumeAttachmentHash(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - if m["instance_id"] != nil { - buf.WriteString(fmt.Sprintf("%s-", m["instance_id"].(string))) - } - return hashcode.String(buf.String()) -} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_blockstorage_volume_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_blockstorage_volume_v2.go index ea035cf6f..97a8b565b 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_blockstorage_volume_v2.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_blockstorage_volume_v2.go @@ -1,7 +1,6 @@ package openstack import ( - "bytes" "fmt" "log" "time" @@ -9,7 +8,7 @@ import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach" - "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" ) @@ -30,91 +29,102 @@ func resourceBlockStorageVolumeV2() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "size": &schema.Schema{ + "size": { Type: schema.TypeInt, Required: true, ForceNew: true, }, - "name": &schema.Schema{ + + "name": { Type: schema.TypeString, Optional: true, ForceNew: false, }, - "description": &schema.Schema{ + + "description": { Type: schema.TypeString, Optional: true, ForceNew: false, }, - "availability_zone": &schema.Schema{ + + "availability_zone": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, - "metadata": &schema.Schema{ + + "metadata": { Type: schema.TypeMap, Optional: true, ForceNew: false, Computed: true, }, - "snapshot_id": &schema.Schema{ + + "snapshot_id": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - "source_vol_id": &schema.Schema{ + + "source_vol_id": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - "image_id": &schema.Schema{ + + "image_id": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - "volume_type": &schema.Schema{ + + "volume_type": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, - "consistency_group_id": &schema.Schema{ + + "consistency_group_id": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - "source_replica": &schema.Schema{ + + "source_replica": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - "attachment": &schema.Schema{ + + "attachment": { Type: schema.TypeSet, Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "id": &schema.Schema{ + "id": { Type: schema.TypeString, Computed: true, }, - "instance_id": &schema.Schema{ + "instance_id": { Type: schema.TypeString, Computed: true, }, - "device": &schema.Schema{ + "device": { Type: schema.TypeString, Computed: true, }, }, }, - Set: resourceVolumeV2AttachmentHash, + Set: blockStorageVolumeV2AttachmentHash, }, }, } @@ -127,12 +137,13 @@ func resourceBlockStorageVolumeV2Create(d *schema.ResourceData, meta interface{} return fmt.Errorf("Error creating OpenStack block storage client: %s", err) } + metadata := d.Get("metadata").(map[string]interface{}) createOpts := &volumes.CreateOpts{ AvailabilityZone: d.Get("availability_zone").(string), ConsistencyGroupID: d.Get("consistency_group_id").(string), Description: d.Get("description").(string), ImageID: d.Get("image_id").(string), - Metadata: resourceContainerMetadataV2(d), + Metadata: expandToMapStringString(metadata), Name: d.Get("name").(string), Size: d.Get("size").(int), SnapshotID: d.Get("snapshot_id").(string), @@ -141,22 +152,17 @@ func resourceBlockStorageVolumeV2Create(d *schema.ResourceData, meta interface{} VolumeType: d.Get("volume_type").(string), } - log.Printf("[DEBUG] Create Options: %#v", createOpts) + log.Printf("[DEBUG] openstack_blockstorage_volume_v2 create options: %#v", createOpts) + v, err := volumes.Create(blockStorageClient, createOpts).Extract() if err != nil { - return fmt.Errorf("Error creating OpenStack volume: %s", err) + return fmt.Errorf("Error creating openstack_blockstorage_volume_v2: %s", err) } - log.Printf("[INFO] Volume ID: %s", v.ID) - - // Wait for the volume to become available. - log.Printf( - "[DEBUG] Waiting for volume (%s) to become available", - v.ID) stateConf := &resource.StateChangeConf{ Pending: []string{"downloading", "creating"}, Target: []string{"available"}, - Refresh: VolumeV2StateRefreshFunc(blockStorageClient, v.ID), + Refresh: blockStorageVolumeV2StateRefreshFunc(blockStorageClient, v.ID), Timeout: d.Timeout(schema.TimeoutCreate), Delay: 10 * time.Second, MinTimeout: 3 * time.Second, @@ -165,8 +171,7 @@ func resourceBlockStorageVolumeV2Create(d *schema.ResourceData, meta interface{} _, err = stateConf.WaitForState() if err != nil { return fmt.Errorf( - "Error waiting for volume (%s) to become ready: %s", - v.ID, err) + "Error waiting for openstack_blockstorage_volume_v2 %s to become ready: %s", v.ID, err) } // Store the ID now @@ -184,10 +189,10 @@ func resourceBlockStorageVolumeV2Read(d *schema.ResourceData, meta interface{}) v, err := volumes.Get(blockStorageClient, d.Id()).Extract() if err != nil { - return CheckDeleted(d, err, "volume") + return CheckDeleted(d, err, "Error retrieving openstack_blockstorage_volume_v2") } - log.Printf("[DEBUG] Retrieved volume %s: %+v", d.Id(), v) + log.Printf("[DEBUG] Retrieved openstack_blockstorage_volume_v2 %s: %#v", d.Id(), v) d.Set("size", v.Size) d.Set("description", v.Description) @@ -199,15 +204,12 @@ func resourceBlockStorageVolumeV2Read(d *schema.ResourceData, meta interface{}) d.Set("metadata", v.Metadata) d.Set("region", GetRegion(d, config)) - attachments := make([]map[string]interface{}, len(v.Attachments)) - for i, attachment := range v.Attachments { - attachments[i] = make(map[string]interface{}) - attachments[i]["id"] = attachment.ID - attachments[i]["instance_id"] = attachment.ServerID - attachments[i]["device"] = attachment.Device - log.Printf("[DEBUG] attachment: %v", attachment) + attachments := flattenBlockStorageVolumeV2Attachments(v.Attachments) + log.Printf("[DEBUG] openstack_blockstorage_volume_v2 %s attachments: %#v", d.Id(), attachments) + if err := d.Set("attachment", attachments); err != nil { + log.Printf( + "[DEBUG] unable to set openstack_blockstorage_volume_v2 %s attachments: %s", d.Id(), err) } - d.Set("attachment", attachments) return nil } @@ -219,18 +221,21 @@ func resourceBlockStorageVolumeV2Update(d *schema.ResourceData, meta interface{} return fmt.Errorf("Error creating OpenStack block storage client: %s", err) } + name := d.Get("name").(string) + description := d.Get("description").(string) updateOpts := volumes.UpdateOpts{ - Name: d.Get("name").(string), - Description: d.Get("description").(string), + Name: &name, + Description: &description, } if d.HasChange("metadata") { - updateOpts.Metadata = resourceVolumeMetadataV2(d) + metadata := d.Get("metadata").(map[string]interface{}) + updateOpts.Metadata = expandToMapStringString(metadata) } _, err = volumes.Update(blockStorageClient, d.Id(), updateOpts).Extract() if err != nil { - return fmt.Errorf("Error updating OpenStack volume: %s", err) + return fmt.Errorf("Error updating openstack_blockstorage_volume_v2 %s: %s", d.Id(), err) } return resourceBlockStorageVolumeV2Read(d, meta) @@ -245,56 +250,71 @@ func resourceBlockStorageVolumeV2Delete(d *schema.ResourceData, meta interface{} v, err := volumes.Get(blockStorageClient, d.Id()).Extract() if err != nil { - return CheckDeleted(d, err, "volume") + return CheckDeleted(d, err, "Error retrieving openstack_blockstorage_volume_v2") } - // make sure this volume is detached from all instances before deleting + // Make sure this volume is detached from all instances before deleting. if len(v.Attachments) > 0 { - log.Printf("[DEBUG] detaching volumes") - if computeClient, err := config.computeV2Client(GetRegion(d, config)); err != nil { - return err - } else { - for _, volumeAttachment := range v.Attachments { - log.Printf("[DEBUG] Attachment: %v", volumeAttachment) - if err := volumeattach.Delete(computeClient, volumeAttachment.ServerID, volumeAttachment.ID).ExtractErr(); err != nil { - return err + computeClient, err := config.computeV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } + + for _, volumeAttachment := range v.Attachments { + log.Printf("[DEBUG] openstack_blockstorage_volume_v2 %s attachment: %#v", d.Id(), volumeAttachment) + + serverID := volumeAttachment.ServerID + attachmentID := volumeAttachment.ID + if err := volumeattach.Delete(computeClient, serverID, attachmentID).ExtractErr(); err != nil { + // It's possible the volume was already detached by + // openstack_compute_volume_attach_v2, so consider + // a 404 acceptable and continue. + if _, ok := err.(gophercloud.ErrDefault404); ok { + continue } - } - stateConf := &resource.StateChangeConf{ - Pending: []string{"in-use", "attaching", "detaching"}, - Target: []string{"available"}, - Refresh: VolumeV2StateRefreshFunc(blockStorageClient, d.Id()), - Timeout: 10 * time.Minute, - Delay: 10 * time.Second, - MinTimeout: 3 * time.Second, - } + // A 409 is also acceptable because there's another + // concurrent action happening. + if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok { + if errCode.Actual == 409 { + continue + } + } - _, err = stateConf.WaitForState() - if err != nil { return fmt.Errorf( - "Error waiting for volume (%s) to become available: %s", - d.Id(), err) + "Error detaching openstack_blockstorage_volume_v2 %s from %s: %s", d.Id(), serverID, err) } } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"in-use", "attaching", "detaching"}, + Target: []string{"available", "deleted"}, + Refresh: blockStorageVolumeV2StateRefreshFunc(blockStorageClient, d.Id()), + Timeout: 10 * time.Minute, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for openstack_blockstorage_volume_v2 %s to become available: %s", d.Id(), err) + } } // It's possible that this volume was used as a boot device and is currently // in a "deleting" state from when the instance was terminated. // If this is true, just move on. It'll eventually delete. if v.Status != "deleting" { - if err := volumes.Delete(blockStorageClient, d.Id()).ExtractErr(); err != nil { - return CheckDeleted(d, err, "volume") + if err := volumes.Delete(blockStorageClient, d.Id(), nil).ExtractErr(); err != nil { + return CheckDeleted(d, err, "Error deleting openstack_blockstorage_volume_v2") } } - // Wait for the volume to delete before moving on. - log.Printf("[DEBUG] Waiting for volume (%s) to delete", d.Id()) - stateConf := &resource.StateChangeConf{ Pending: []string{"deleting", "downloading", "available"}, Target: []string{"deleted"}, - Refresh: VolumeV2StateRefreshFunc(blockStorageClient, d.Id()), + Refresh: blockStorageVolumeV2StateRefreshFunc(blockStorageClient, d.Id()), Timeout: d.Timeout(schema.TimeoutDelete), Delay: 10 * time.Second, MinTimeout: 3 * time.Second, @@ -302,50 +322,8 @@ func resourceBlockStorageVolumeV2Delete(d *schema.ResourceData, meta interface{} _, err = stateConf.WaitForState() if err != nil { - return fmt.Errorf( - "Error waiting for volume (%s) to delete: %s", - d.Id(), err) + return fmt.Errorf("Error waiting for openstack_blockstorage_volume_v2 %s to delete: %s", d.Id(), err) } - d.SetId("") return nil } - -func resourceVolumeMetadataV2(d *schema.ResourceData) map[string]string { - m := make(map[string]string) - for key, val := range d.Get("metadata").(map[string]interface{}) { - m[key] = val.(string) - } - return m -} - -// VolumeV2StateRefreshFunc returns a resource.StateRefreshFunc that is used to watch -// an OpenStack volume. -func VolumeV2StateRefreshFunc(client *gophercloud.ServiceClient, volumeID string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - v, err := volumes.Get(client, volumeID).Extract() - if err != nil { - if _, ok := err.(gophercloud.ErrDefault404); ok { - return v, "deleted", nil - } - return nil, "", err - } - - if v.Status == "error" { - return v, v.Status, fmt.Errorf("There was an error creating the volume. " + - "Please check with your cloud admin or check the Block Storage " + - "API logs to see why this error occurred.") - } - - return v, v.Status, nil - } -} - -func resourceVolumeV2AttachmentHash(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - if m["instance_id"] != nil { - buf.WriteString(fmt.Sprintf("%s-", m["instance_id"].(string))) - } - return hashcode.String(buf.String()) -} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_blockstorage_volume_v3.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_blockstorage_volume_v3.go new file mode 100644 index 000000000..ee74c36a8 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_blockstorage_volume_v3.go @@ -0,0 +1,384 @@ +package openstack + +import ( + "fmt" + "log" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions" + "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes" + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceBlockStorageVolumeV3() *schema.Resource { + return &schema.Resource{ + Create: resourceBlockStorageVolumeV3Create, + Read: resourceBlockStorageVolumeV3Read, + Update: resourceBlockStorageVolumeV3Update, + Delete: resourceBlockStorageVolumeV3Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "size": { + Type: schema.TypeInt, + Required: true, + }, + + "enable_online_resize": { + Type: schema.TypeBool, + Optional: true, + }, + + "name": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + + "description": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + + "availability_zone": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + + "metadata": { + Type: schema.TypeMap, + Optional: true, + ForceNew: false, + Computed: true, + }, + + "snapshot_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "source_vol_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "image_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "volume_type": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + + "consistency_group_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "source_replica": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "multiattach": { + Type: schema.TypeBool, + Optional: true, + }, + + "attachment": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + }, + "instance_id": { + Type: schema.TypeString, + Computed: true, + }, + "device": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + Set: blockStorageVolumeV3AttachmentHash, + }, + }, + } +} + +func resourceBlockStorageVolumeV3Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + blockStorageClient, err := config.blockStorageV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack block storage client: %s", err) + } + + metadata := d.Get("metadata").(map[string]interface{}) + createOpts := &volumes.CreateOpts{ + AvailabilityZone: d.Get("availability_zone").(string), + ConsistencyGroupID: d.Get("consistency_group_id").(string), + Description: d.Get("description").(string), + ImageID: d.Get("image_id").(string), + Metadata: expandToMapStringString(metadata), + Name: d.Get("name").(string), + Size: d.Get("size").(int), + SnapshotID: d.Get("snapshot_id").(string), + SourceReplica: d.Get("source_replica").(string), + SourceVolID: d.Get("source_vol_id").(string), + VolumeType: d.Get("volume_type").(string), + Multiattach: d.Get("multiattach").(bool), + } + + log.Printf("[DEBUG] openstack_blockstorage_volume_v3 create options: %#v", createOpts) + + v, err := volumes.Create(blockStorageClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating openstack_blockstorage_volume_v3: %s", err) + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"downloading", "creating"}, + Target: []string{"available"}, + Refresh: blockStorageVolumeV3StateRefreshFunc(blockStorageClient, v.ID), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for openstack_blockstorage_volume_v3 %s to become ready: %s", v.ID, err) + } + + // Store the ID now + d.SetId(v.ID) + + return resourceBlockStorageVolumeV3Read(d, meta) +} + +func resourceBlockStorageVolumeV3Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + blockStorageClient, err := config.blockStorageV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack block storage client: %s", err) + } + + v, err := volumes.Get(blockStorageClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "Error retrieving openstack_blockstorage_volume_v3") + } + + log.Printf("[DEBUG] Retrieved openstack_blockstorage_volume_v3 %s: %#v", d.Id(), v) + + d.Set("size", v.Size) + d.Set("description", v.Description) + d.Set("availability_zone", v.AvailabilityZone) + d.Set("name", v.Name) + d.Set("snapshot_id", v.SnapshotID) + d.Set("source_vol_id", v.SourceVolID) + d.Set("volume_type", v.VolumeType) + d.Set("metadata", v.Metadata) + d.Set("region", GetRegion(d, config)) + + attachments := flattenBlockStorageVolumeV3Attachments(v.Attachments) + log.Printf("[DEBUG] openstack_blockstorage_volume_v3 %s attachments: %#v", d.Id(), attachments) + if err := d.Set("attachment", attachments); err != nil { + log.Printf( + "[DEBUG] unable to set openstack_blockstorage_volume_v3 %s attachments: %s", d.Id(), err) + } + + return nil +} + +func resourceBlockStorageVolumeV3Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + blockStorageClient, err := config.blockStorageV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack block storage client: %s", err) + } + + name := d.Get("name").(string) + description := d.Get("description").(string) + updateOpts := volumes.UpdateOpts{ + Name: &name, + Description: &description, + } + + if d.HasChange("metadata") { + metadata := d.Get("metadata").(map[string]interface{}) + updateOpts.Metadata = expandToMapStringString(metadata) + } + + var v *volumes.Volume + if d.HasChange("size") { + v, err = volumes.Get(blockStorageClient, d.Id()).Extract() + if err != nil { + return fmt.Errorf("Error extending openstack_blockstorage_volume_v3 %s: %s", d.Id(), err) + } + + if v.Status == "in-use" { + if v, ok := d.Get("enable_online_resize").(bool); ok && !v { + return fmt.Errorf( + `Error extending openstack_blockstorage_volume_v3 %s, + volume is attached to the instance and + resizing online is disabled, + see enable_online_resize option`, d.Id()) + } + + blockStorageClient.Microversion = "3.42" + } + + extendOpts := volumeactions.ExtendSizeOpts{ + NewSize: d.Get("size").(int), + } + + err = volumeactions.ExtendSize(blockStorageClient, d.Id(), extendOpts).ExtractErr() + if err != nil { + return fmt.Errorf("Error extending openstack_blockstorage_volume_v3 %s size: %s", d.Id(), err) + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"extending"}, + Target: []string{"available", "in-use"}, + Refresh: blockStorageVolumeV3StateRefreshFunc(blockStorageClient, d.Id()), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err := stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for openstack_blockstorage_volume_v3 %s to become ready: %s", d.Id(), err) + } + } + + _, err = volumes.Update(blockStorageClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating openstack_blockstorage_volume_v3 %s: %s", d.Id(), err) + } + + return resourceBlockStorageVolumeV3Read(d, meta) +} + +func resourceBlockStorageVolumeV3Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + blockStorageClient, err := config.blockStorageV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack block storage client: %s", err) + } + + v, err := volumes.Get(blockStorageClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "Error retrieving openstack_blockstorage_volume_v3") + } + + // make sure this volume is detached from all instances before deleting + if len(v.Attachments) > 0 { + computeClient, err := config.computeV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } + + for _, volumeAttachment := range v.Attachments { + log.Printf("[DEBUG] openstack_blockstorage_volume_v3 %s attachment: %#v", d.Id(), volumeAttachment) + + serverID := volumeAttachment.ServerID + attachmentID := volumeAttachment.ID + if err := volumeattach.Delete(computeClient, serverID, attachmentID).ExtractErr(); err != nil { + // It's possible the volume was already detached by + // openstack_compute_volume_attach_v2, so consider + // a 404 acceptable and continue. + if _, ok := err.(gophercloud.ErrDefault404); ok { + continue + } + + // A 409 is also acceptable because there's another + // concurrent action happening. + if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok { + if errCode.Actual == 409 { + continue + } + } + + return fmt.Errorf( + "Error detaching openstack_blockstorage_volume_v3 %s from %s: %s", d.Id(), serverID, err) + } + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"in-use", "attaching", "detaching"}, + Target: []string{"available", "deleted"}, + Refresh: blockStorageVolumeV3StateRefreshFunc(blockStorageClient, d.Id()), + Timeout: 10 * time.Minute, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for openstack_blockstorage_volume_v3 %s to become available: %s", d.Id(), err) + } + } + + // It's possible that this volume was used as a boot device and is currently + // in a "deleting" state from when the instance was terminated. + // If this is true, just move on. It'll eventually delete. + if v.Status != "deleting" { + if err := volumes.Delete(blockStorageClient, d.Id(), nil).ExtractErr(); err != nil { + return CheckDeleted(d, err, "Error deleting openstack_blockstorage_volume_v3") + } + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"deleting", "downloading", "available"}, + Target: []string{"deleted"}, + Refresh: blockStorageVolumeV3StateRefreshFunc(blockStorageClient, d.Id()), + Timeout: d.Timeout(schema.TimeoutDelete), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for openstack_blockstorage_volume_v3 %s to delete: %s", d.Id(), err) + } + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_flavor_access_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_flavor_access_v2.go new file mode 100644 index 000000000..5966c34e8 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_flavor_access_v2.go @@ -0,0 +1,148 @@ +package openstack + +import ( + "fmt" + "log" + "strings" + + "github.com/hashicorp/terraform/helper/schema" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors" + "github.com/gophercloud/gophercloud/pagination" +) + +func resourceComputeFlavorAccessV2() *schema.Resource { + return &schema.Resource{ + Create: resourceComputeFlavorAccessV2Create, + Read: resourceComputeFlavorAccessV2Read, + Delete: resourceComputeFlavorAccessV2Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "flavor_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "tenant_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceComputeFlavorAccessV2Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + computeClient, err := config.computeV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } + + flavorID := d.Get("flavor_id").(string) + tenantID := d.Get("tenant_id").(string) + + accessOpts := flavors.AddAccessOpts{ + Tenant: tenantID, + } + log.Printf("[DEBUG] Flavor Access Options: %#v", accessOpts) + + if _, err := flavors.AddAccess(computeClient, flavorID, accessOpts).Extract(); err != nil { + return fmt.Errorf("Error adding access to tenant %s for flavor %s: %s", tenantID, flavorID, err) + } + + id := fmt.Sprintf("%s/%s", flavorID, tenantID) + d.SetId(id) + + return resourceComputeFlavorAccessV2Read(d, meta) +} + +func resourceComputeFlavorAccessV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + computeClient, err := config.computeV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } + + flavorAccess, err := getFlavorAccess(computeClient, d) + if err != nil { + return CheckDeleted(d, err, "Error getting flavor access") + } + + d.Set("region", GetRegion(d, config)) + d.Set("flavor_id", flavorAccess.FlavorID) + d.Set("tenant_id", flavorAccess.TenantID) + + return nil +} + +func resourceComputeFlavorAccessV2Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + computeClient, err := config.computeV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } + + flavorAccess, err := getFlavorAccess(computeClient, d) + if err != nil { + return fmt.Errorf("Error getting flavor access: %s", err) + } + + removeAccessOpts := flavors.RemoveAccessOpts{Tenant: flavorAccess.TenantID} + log.Printf("[DEBUG] RemoveAccess Options: %#v", removeAccessOpts) + + if _, err := flavors.RemoveAccess(computeClient, flavorAccess.FlavorID, removeAccessOpts).Extract(); err != nil { + return fmt.Errorf("Error removing tenant %s access from flavor %s: %s", flavorAccess.TenantID, flavorAccess.FlavorID, err) + } + + return nil +} + +func parseComputeFlavorAccessId(id string) (string, string, error) { + idParts := strings.Split(id, "/") + if len(idParts) < 2 { + return "", "", fmt.Errorf("Unable to determine flavor access ID") + } + + flavorID := idParts[0] + tenantID := idParts[1] + + return flavorID, tenantID, nil +} + +func getFlavorAccess(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) (flavors.FlavorAccess, error) { + var access flavors.FlavorAccess + flavorID, tenantID, err := parseComputeFlavorAccessId(d.Id()) + if err != nil { + return access, err + } + + pager := flavors.ListAccesses(computeClient, flavorID) + err = pager.EachPage(func(page pagination.Page) (bool, error) { + accessList, err := flavors.ExtractAccesses(page) + if err != nil { + return false, err + } + + for _, a := range accessList { + if a.TenantID == tenantID && a.FlavorID == flavorID { + access = a + return false, nil + } + } + + return true, nil + }) + + return access, err +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_flavor_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_flavor_v2.go new file mode 100644 index 000000000..118976ed8 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_flavor_v2.go @@ -0,0 +1,212 @@ +package openstack + +import ( + "fmt" + "log" + + "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceComputeFlavorV2() *schema.Resource { + return &schema.Resource{ + Create: resourceComputeFlavorV2Create, + Read: resourceComputeFlavorV2Read, + Update: resourceComputeFlavorV2Update, + Delete: resourceComputeFlavorV2Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "ram": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + + "vcpus": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + + "disk": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + + "swap": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + + "rx_tx_factor": { + Type: schema.TypeFloat, + Optional: true, + ForceNew: true, + Default: 1, + }, + + "is_public": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + + "ephemeral": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + + "extra_specs": { + Type: schema.TypeMap, + Optional: true, + Computed: true, + }, + }, + } +} + +func resourceComputeFlavorV2Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + computeClient, err := config.computeV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } + + name := d.Get("name").(string) + disk := d.Get("disk").(int) + swap := d.Get("swap").(int) + isPublic := d.Get("is_public").(bool) + ephemeral := d.Get("ephemeral").(int) + createOpts := flavors.CreateOpts{ + Name: name, + RAM: d.Get("ram").(int), + VCPUs: d.Get("vcpus").(int), + Disk: &disk, + Swap: &swap, + RxTxFactor: d.Get("rx_tx_factor").(float64), + IsPublic: &isPublic, + Ephemeral: &ephemeral, + } + + log.Printf("[DEBUG] openstack_compute_flavor_v2 create options: %#v", createOpts) + fl, err := flavors.Create(computeClient, &createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating openstack_compute_flavor_v2 %s: %s", name, err) + } + + d.SetId(fl.ID) + + extraSpecsRaw := d.Get("extra_specs").(map[string]interface{}) + if len(extraSpecsRaw) > 0 { + extraSpecs := expandComputeFlavorV2ExtraSpecs(extraSpecsRaw) + + _, err := flavors.CreateExtraSpecs(computeClient, fl.ID, extraSpecs).Extract() + if err != nil { + return fmt.Errorf("Error creating extra_specs for openstack_compute_flavor_v2 %s: %s", fl.ID, err) + } + } + + return resourceComputeFlavorV2Read(d, meta) +} + +func resourceComputeFlavorV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + computeClient, err := config.computeV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } + + fl, err := flavors.Get(computeClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "Error retrieving openstack_compute_flavor_v2") + } + + log.Printf("[DEBUG] Retrieved openstack_compute_flavor_v2 %s: %#v", d.Id(), fl) + + d.Set("name", fl.Name) + d.Set("ram", fl.RAM) + d.Set("vcpus", fl.VCPUs) + d.Set("disk", fl.Disk) + d.Set("swap", fl.Swap) + d.Set("rx_tx_factor", fl.RxTxFactor) + d.Set("is_public", fl.IsPublic) + // d.Set("ephemeral", fl.Ephemeral) TODO: Implement this in gophercloud + d.Set("region", GetRegion(d, config)) + + es, err := flavors.ListExtraSpecs(computeClient, d.Id()).Extract() + if err != nil { + return fmt.Errorf("Error reading extra_specs for openstack_compute_flavor_v2 %s: %s", d.Id(), err) + } + + if err := d.Set("extra_specs", es); err != nil { + log.Printf("[WARN] Unable to set extra_specs for openstack_compute_flavor_v2 %s: %s", d.Id(), err) + } + + return nil +} + +func resourceComputeFlavorV2Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + computeClient, err := config.computeV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } + + if d.HasChange("extra_specs") { + oldES, newES := d.GetChange("extra_specs") + + // Delete all old extra specs. + for oldKey := range oldES.(map[string]interface{}) { + if err := flavors.DeleteExtraSpec(computeClient, d.Id(), oldKey).ExtractErr(); err != nil { + return fmt.Errorf("Error deleting extra_spec %s from openstack_compute_flavor_v2 %s: %s", oldKey, d.Id(), err) + } + } + + // Add new extra specs. + newESRaw := newES.(map[string]interface{}) + if len(newESRaw) > 0 { + extraSpecs := expandComputeFlavorV2ExtraSpecs(newESRaw) + + _, err := flavors.CreateExtraSpecs(computeClient, d.Id(), extraSpecs).Extract() + if err != nil { + return fmt.Errorf("Error creating extra_specs for openstack_compute_flavor_v2 %s: %s", d.Id(), err) + } + } + } + + return resourceComputeFlavorV2Read(d, meta) +} + +func resourceComputeFlavorV2Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + computeClient, err := config.computeV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } + + err = flavors.Delete(computeClient, d.Id()).ExtractErr() + if err != nil { + return CheckDeleted(d, err, "Error deleting openstack_compute_flavor_v2") + } + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_floatingip_associate_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_floatingip_associate_v2.go index 4f0d70345..c187993d3 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_floatingip_associate_v2.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_floatingip_associate_v2.go @@ -4,12 +4,15 @@ import ( "fmt" "log" "strings" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips" "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" nfloatingips "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" - "github.com/hashicorp/terraform/helper/schema" ) func resourceComputeFloatingIPAssociateV2() *schema.Resource { @@ -21,29 +24,41 @@ func resourceComputeFloatingIPAssociateV2() *schema.Resource { State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + }, + Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "floating_ip": &schema.Schema{ + "floating_ip": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "instance_id": &schema.Schema{ + + "instance_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "fixed_ip": &schema.Schema{ + + "fixed_ip": { Type: schema.TypeString, Optional: true, ForceNew: true, }, + + "wait_until_associated": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, }, } } @@ -70,15 +85,39 @@ func resourceComputeFloatingIPAssociateV2Create(d *schema.ResourceData, meta int return fmt.Errorf("Error associating Floating IP: %s", err) } + // This API call should be synchronous, but we've had reports where it isn't. + // If the user opted in to wait for association, then poll here. + var waitUntilAssociated bool + if v, ok := d.GetOkExists("wait_until_associated"); ok { + if wua, ok := v.(bool); ok { + waitUntilAssociated = wua + } + } + + if waitUntilAssociated { + log.Printf("[DEBUG] Waiting until %s is associated with %s", instanceId, floatingIP) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"NOT_ASSOCIATED"}, + Target: []string{"ASSOCIATED"}, + Refresh: resourceComputeFloatingIPAssociateV2CheckAssociation(computeClient, instanceId, floatingIP), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 0, + MinTimeout: 3 * time.Second, + } + + _, err := stateConf.WaitForState() + if err != nil { + return err + } + } + // There's an API call to get this information, but it has been // deprecated. The Neutron API could be used, but I'm trying not // to mix service APIs. Therefore, a faux ID will be used. id := fmt.Sprintf("%s/%s/%s", floatingIP, instanceId, fixedIP) d.SetId(id) - // This API call is synchronous, so Create won't return until the IP - // is attached. No need to wait for a state. - return resourceComputeFloatingIPAssociateV2Read(d, meta) } @@ -233,3 +272,30 @@ func resourceComputeFloatingIPAssociateV2ComputeExists(computeClient *gopherclou return false, nil } + +func resourceComputeFloatingIPAssociateV2CheckAssociation( + computeClient *gophercloud.ServiceClient, instanceId, floatingIP string) resource.StateRefreshFunc { + + return func() (interface{}, string, error) { + instance, err := servers.Get(computeClient, instanceId).Extract() + if err != nil { + return instance, "", err + } + + var associated bool + for _, networkAddresses := range instance.Addresses { + for _, element := range networkAddresses.([]interface{}) { + address := element.(map[string]interface{}) + if address["OS-EXT-IPS:type"] == "floating" && address["addr"] == floatingIP { + associated = true + } + } + } + + if associated { + return instance, "ASSOCIATED", nil + } + + return instance, "NOT_ASSOCIATED", nil + } +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_floatingip_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_floatingip_v2.go index 9f85850bc..16578a54f 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_floatingip_v2.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_floatingip_v2.go @@ -12,38 +12,38 @@ func resourceComputeFloatingIPV2() *schema.Resource { return &schema.Resource{ Create: resourceComputeFloatingIPV2Create, Read: resourceComputeFloatingIPV2Read, - Update: nil, Delete: resourceComputeFloatingIPV2Delete, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "pool": &schema.Schema{ + "pool": { Type: schema.TypeString, Required: true, ForceNew: true, DefaultFunc: schema.EnvDefaultFunc("OS_POOL_NAME", nil), }, - "address": &schema.Schema{ + // computed-only + "address": { Type: schema.TypeString, Computed: true, }, - "fixed_ip": &schema.Schema{ + "fixed_ip": { Type: schema.TypeString, Computed: true, }, - "instance_id": &schema.Schema{ + "instance_id": { Type: schema.TypeString, Computed: true, }, @@ -61,10 +61,12 @@ func resourceComputeFloatingIPV2Create(d *schema.ResourceData, meta interface{}) createOpts := &floatingips.CreateOpts{ Pool: d.Get("pool").(string), } - log.Printf("[DEBUG] Create Options: %#v", createOpts) + + log.Printf("[DEBUG] openstack_compute_floatingip_v2 Create Options: %#v", createOpts) + newFip, err := floatingips.Create(computeClient, createOpts).Extract() if err != nil { - return fmt.Errorf("Error creating Floating IP: %s", err) + return fmt.Errorf("Error creating openstack_compute_floatingip_v2: %s", err) } d.SetId(newFip.ID) @@ -81,10 +83,10 @@ func resourceComputeFloatingIPV2Read(d *schema.ResourceData, meta interface{}) e fip, err := floatingips.Get(computeClient, d.Id()).Extract() if err != nil { - return CheckDeleted(d, err, "floating ip") + return CheckDeleted(d, err, "Error retrieving openstack_compute_floatingip_v2") } - log.Printf("[DEBUG] Retrieved Floating IP %s: %+v", d.Id(), fip) + log.Printf("[DEBUG] Retrieved openstack_compute_floatingip_v2 %s: %#v", d.Id(), fip) d.Set("pool", fip.Pool) d.Set("instance_id", fip.InstanceID) @@ -102,9 +104,8 @@ func resourceComputeFloatingIPV2Delete(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error creating OpenStack compute client: %s", err) } - log.Printf("[DEBUG] Deleting Floating IP %s", d.Id()) if err := floatingips.Delete(computeClient, d.Id()).ExtractErr(); err != nil { - return fmt.Errorf("Error deleting Floating IP: %s", err) + return CheckDeleted(d, err, "Error deleting openstack_compute_floatingip_v2") } return nil diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_instance_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_instance_v2.go index 81726ed24..a0e0c0bec 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_instance_v2.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_instance_v2.go @@ -7,24 +7,23 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume" - "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop" - "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/tenantnetworks" - "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach" "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors" "github.com/gophercloud/gophercloud/openstack/compute/v2/images" "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" ) func resourceComputeInstanceV2() *schema.Resource { @@ -41,51 +40,49 @@ func resourceComputeInstanceV2() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "name": &schema.Schema{ + "name": { Type: schema.TypeString, Required: true, ForceNew: false, }, - "image_id": &schema.Schema{ + "image_id": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, - "image_name": &schema.Schema{ + "image_name": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, - "flavor_id": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - ForceNew: false, - Computed: true, - DefaultFunc: schema.EnvDefaultFunc("OS_FLAVOR_ID", nil), + "flavor_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + Computed: true, }, - "flavor_name": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - ForceNew: false, - Computed: true, - DefaultFunc: schema.EnvDefaultFunc("OS_FLAVOR_NAME", nil), + "flavor_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + Computed: true, }, - "floating_ip": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - ForceNew: false, - Deprecated: "Use the openstack_compute_floatingip_associate_v2 resource instead", + "floating_ip": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + Removed: "Use the openstack_compute_floatingip_associate_v2 resource instead", }, - "user_data": &schema.Schema{ + "user_data": { Type: schema.TypeString, Optional: true, ForceNew: true, @@ -100,7 +97,7 @@ func resourceComputeInstanceV2() *schema.Resource { } }, }, - "security_groups": &schema.Schema{ + "security_groups": { Type: schema.TypeSet, Optional: true, ForceNew: false, @@ -108,60 +105,61 @@ func resourceComputeInstanceV2() *schema.Resource { Elem: &schema.Schema{Type: schema.TypeString}, Set: schema.HashString, }, - "availability_zone": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Computed: true, + "availability_zone": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + DiffSuppressFunc: suppressAvailabilityZoneDetailDiffs, }, - "network": &schema.Schema{ + "network": { Type: schema.TypeList, Optional: true, ForceNew: true, Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "uuid": &schema.Schema{ + "uuid": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, - "name": &schema.Schema{ + "name": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, - "port": &schema.Schema{ + "port": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, - "fixed_ip_v4": &schema.Schema{ + "fixed_ip_v4": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, - "fixed_ip_v6": &schema.Schema{ + "fixed_ip_v6": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, - "floating_ip": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Computed: true, - Deprecated: "Use the openstack_compute_floatingip_associate_v2 resource instead", + "floating_ip": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Removed: "Use the openstack_compute_floatingip_associate_v2 resource instead", }, - "mac": &schema.Schema{ + "mac": { Type: schema.TypeString, Computed: true, }, - "access_network": &schema.Schema{ + "access_network": { Type: schema.TypeBool, Optional: true, Default: false, @@ -169,75 +167,86 @@ func resourceComputeInstanceV2() *schema.Resource { }, }, }, - "metadata": &schema.Schema{ + "metadata": { Type: schema.TypeMap, Optional: true, ForceNew: false, }, - "config_drive": &schema.Schema{ + "config_drive": { Type: schema.TypeBool, Optional: true, ForceNew: true, }, - "admin_pass": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - ForceNew: false, + "admin_pass": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + ForceNew: false, }, - "access_ip_v4": &schema.Schema{ + "access_ip_v4": { Type: schema.TypeString, Computed: true, Optional: true, ForceNew: false, }, - "access_ip_v6": &schema.Schema{ + "access_ip_v6": { Type: schema.TypeString, Computed: true, Optional: true, ForceNew: false, }, - "key_pair": &schema.Schema{ + "key_pair": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - "block_device": &schema.Schema{ + "block_device": { Type: schema.TypeList, Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "source_type": &schema.Schema{ + "source_type": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "uuid": &schema.Schema{ + "uuid": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - "volume_size": &schema.Schema{ + "volume_size": { Type: schema.TypeInt, Optional: true, ForceNew: true, }, - "destination_type": &schema.Schema{ + "destination_type": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - "boot_index": &schema.Schema{ + "boot_index": { Type: schema.TypeInt, Optional: true, ForceNew: true, }, - "delete_on_termination": &schema.Schema{ + "delete_on_termination": { Type: schema.TypeBool, Optional: true, Default: false, ForceNew: true, }, - "guest_format": &schema.Schema{ + "guest_format": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "device_type": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "disk_bus": { Type: schema.TypeString, Optional: true, ForceNew: true, @@ -245,83 +254,87 @@ func resourceComputeInstanceV2() *schema.Resource { }, }, }, - "volume": &schema.Schema{ - Type: schema.TypeSet, - Optional: true, - Deprecated: "Use block_device or openstack_compute_volume_attach_v2 instead", + "volume": { + Type: schema.TypeSet, + Optional: true, + Removed: "Use block_device or openstack_compute_volume_attach_v2 instead", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "id": &schema.Schema{ + "id": { Type: schema.TypeString, Optional: true, Computed: true, }, - "volume_id": &schema.Schema{ + "volume_id": { Type: schema.TypeString, Required: true, }, - "device": &schema.Schema{ + "device": { Type: schema.TypeString, Optional: true, Computed: true, }, }, }, - Set: resourceComputeVolumeAttachmentHash, }, - "scheduler_hints": &schema.Schema{ + "scheduler_hints": { Type: schema.TypeSet, Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "group": &schema.Schema{ + "group": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - "different_host": &schema.Schema{ + "different_host": { Type: schema.TypeList, Optional: true, ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - "same_host": &schema.Schema{ + "same_host": { Type: schema.TypeList, Optional: true, ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - "query": &schema.Schema{ + "query": { Type: schema.TypeList, Optional: true, ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - "target_cell": &schema.Schema{ + "target_cell": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - "build_near_host_ip": &schema.Schema{ + "build_near_host_ip": { Type: schema.TypeString, Optional: true, ForceNew: true, }, + "additional_properties": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + }, }, }, Set: resourceComputeSchedulerHintsHash, }, - "personality": &schema.Schema{ + "personality": { Type: schema.TypeSet, Optional: true, ForceNew: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "file": &schema.Schema{ + "file": { Type: schema.TypeString, Required: true, }, - "content": &schema.Schema{ + "content": { Type: schema.TypeString, Required: true, }, @@ -329,20 +342,45 @@ func resourceComputeInstanceV2() *schema.Resource { }, Set: resourceComputeInstancePersonalityHash, }, - "stop_before_destroy": &schema.Schema{ + "stop_before_destroy": { Type: schema.TypeBool, Optional: true, Default: false, }, - "force_delete": &schema.Schema{ + "force_delete": { Type: schema.TypeBool, Optional: true, Default: false, }, - "all_metadata": &schema.Schema{ + "all_metadata": { Type: schema.TypeMap, Computed: true, }, + "power_state": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + Default: "active", + ValidateFunc: validation.StringInSlice([]string{ + "active", "shutoff", + }, true), + DiffSuppressFunc: suppressPowerStateDiffs, + }, + "vendor_options": { + Type: schema.TypeSet, + Optional: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ignore_resize_confirmation": { + Type: schema.TypeBool, + Default: false, + Optional: true, + }, + }, + }, + }, }, } } @@ -365,6 +403,9 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e return err } + // Determines the Flavor ID using the following rules: + // If a flavor_id was specified, use it. + // If a flavor_name was specified, lookup the flavor ID, report if error. flavorId, err := getFlavorID(computeClient, d) if err != nil { return err @@ -376,26 +417,15 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e return err } - // check if floating IP configuration is correct - if err := checkInstanceFloatingIPs(d); err != nil { - return err - } - // Build a list of networks with the information given upon creation. // Error out if an invalid network configuration was used. - networkDetails, err := getInstanceNetworks(computeClient, d) + allInstanceNetworks, err := getAllInstanceNetworks(d, meta) if err != nil { return err } - networks := make([]servers.Network, len(networkDetails)) - for i, net := range networkDetails { - networks[i] = servers.Network{ - UUID: net["uuid"].(string), - Port: net["port"].(string), - FixedIP: net["fixed_ip_v4"].(string), - } - } + // Build a []servers.Network to pass into the create options. + networks := expandInstanceNetworks(allInstanceNetworks) configDrive := d.Get("config_drive").(bool) @@ -483,25 +513,25 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e server.ID, err) } - // Now that the instance has been created, we need to do an early read on the - // networks in order to associate floating IPs - _, err = getInstanceNetworksAndAddresses(computeClient, d) + vmState := d.Get("power_state").(string) + if strings.ToLower(vmState) == "shutoff" { + err = startstop.Stop(computeClient, d.Id()).ExtractErr() + if err != nil { + return fmt.Errorf("Error stopping OpenStack instance: %s", err) + } + stopStateConf := &resource.StateChangeConf{ + //Pending: []string{"ACTIVE"}, + Target: []string{"SHUTOFF"}, + Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } - // If floating IPs were specified, associate them after the instance has launched. - err = associateFloatingIPsToInstance(computeClient, d) - if err != nil { - return err - } - - // if volumes were specified, attach them after the instance has launched. - if v, ok := d.GetOk("volume"); ok { - vols := v.(*schema.Set).List() - if blockClient, err := config.blockStorageV1Client(GetRegion(d, config)); err != nil { - return fmt.Errorf("Error creating OpenStack block storage client: %s", err) - } else { - if err := attachVolumesToInstance(computeClient, blockClient, d.Id(), vols); err != nil { - return err - } + log.Printf("[DEBUG] Waiting for instance (%s) to stop", d.Id()) + _, err = stopStateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for instance (%s) to become inactive(shutoff): %s", d.Id(), err) } } @@ -525,7 +555,7 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err d.Set("name", server.Name) // Get the instance network and address information - networks, err := getInstanceNetworksAndAddresses(computeClient, d) + networks, err := flattenInstanceNetworks(d, meta) if err != nil { return err } @@ -533,6 +563,8 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err // Determine the best IPv4 and IPv6 addresses to access the instance with hostv4, hostv6 := getInstanceAccessAddresses(d, networks) + // AccessIPv4/v6 isn't standard in OpenStack, but there have been reports + // of them being used in some environments. if server.AccessIPv4 != "" && hostv4 == "" { hostv4 = server.AccessIPv4 } @@ -547,7 +579,7 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err // Determine the best IP address to use for SSH connectivity. // Prefer IPv4 over IPv6. - preferredSSHAddress := "" + var preferredSSHAddress string if hostv4 != "" { preferredSSHAddress = hostv4 } else if hostv6 != "" { @@ -587,15 +619,10 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err return err } - // volume attachments - if err := getVolumeAttachments(computeClient, d); err != nil { - return err - } - // Build a custom struct for the availability zone extension var serverWithAZ struct { servers.Server - availabilityzones.ServerExt + availabilityzones.ServerAvailabilityZoneExt } // Do another Get so the above work is not disturbed. @@ -610,6 +637,15 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err // Set the region d.Set("region", GetRegion(d, config)) + // Set the current power_state + currentStatus := strings.ToLower(server.Status) + switch currentStatus { + case "active", "shutoff", "error", "migrating": + d.Set("power_state", currentStatus) + default: + return fmt.Errorf("Invalid power_state for instance %s: %s", d.Id(), server.Status) + } + return nil } @@ -632,15 +668,59 @@ func resourceComputeInstanceV2Update(d *schema.ResourceData, meta interface{}) e } } + if d.HasChange("power_state") { + vmState := d.Get("power_state").(string) + if strings.ToLower(vmState) == "shutoff" { + err = startstop.Stop(computeClient, d.Id()).ExtractErr() + if err != nil { + return fmt.Errorf("Error stopping OpenStack instance: %s", err) + } + stopStateConf := &resource.StateChangeConf{ + //Pending: []string{"ACTIVE"}, + Target: []string{"SHUTOFF"}, + Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), + Timeout: d.Timeout(schema.TimeoutUpdate), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + log.Printf("[DEBUG] Waiting for instance (%s) to stop", d.Id()) + _, err = stopStateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for instance (%s) to become inactive(shutoff): %s", d.Id(), err) + } + } + if strings.ToLower(vmState) == "active" { + err = startstop.Start(computeClient, d.Id()).ExtractErr() + if err != nil { + return fmt.Errorf("Error starting OpenStack instance: %s", err) + } + startStateConf := &resource.StateChangeConf{ + //Pending: []string{"SHUTOFF"}, + Target: []string{"ACTIVE"}, + Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), + Timeout: d.Timeout(schema.TimeoutUpdate), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + log.Printf("[DEBUG] Waiting for instance (%s) to start", d.Id()) + _, err = startStateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for instance (%s) to become active: %s", d.Id(), err) + } + } + } + if d.HasChange("metadata") { oldMetadata, newMetadata := d.GetChange("metadata") var metadataToDelete []string // Determine if any metadata keys were removed from the configuration. // Then request those keys to be deleted. - for oldKey, _ := range oldMetadata.(map[string]interface{}) { + for oldKey := range oldMetadata.(map[string]interface{}) { var found bool - for newKey, _ := range newMetadata.(map[string]interface{}) { + for newKey := range newMetadata.(map[string]interface{}) { if oldKey == newKey { found = true } @@ -712,91 +792,15 @@ func resourceComputeInstanceV2Update(d *schema.ResourceData, meta interface{}) e } } - if d.HasChange("floating_ip") { - oldFIP, newFIP := d.GetChange("floating_ip") - log.Printf("[DEBUG] Old Floating IP: %v", oldFIP) - log.Printf("[DEBUG] New Floating IP: %v", newFIP) - if oldFIP.(string) != "" { - log.Printf("[DEBUG] Attempting to disassociate %s from %s", oldFIP, d.Id()) - if err := disassociateFloatingIPFromInstance(computeClient, oldFIP.(string), d.Id(), ""); err != nil { - return fmt.Errorf("Error disassociating Floating IP during update: %s", err) - } - } - - if newFIP.(string) != "" { - log.Printf("[DEBUG] Attempting to associate %s to %s", newFIP, d.Id()) - if err := associateFloatingIPToInstance(computeClient, newFIP.(string), d.Id(), ""); err != nil { - return fmt.Errorf("Error associating Floating IP during update: %s", err) - } - } - } - - if d.HasChange("network") { - oldNetworks, newNetworks := d.GetChange("network") - oldNetworkList := oldNetworks.([]interface{}) - newNetworkList := newNetworks.([]interface{}) - for i, oldNet := range oldNetworkList { - var oldFIP, newFIP string - var oldFixedIP, newFixedIP string - - if oldNetRaw, ok := oldNet.(map[string]interface{}); ok { - oldFIP = oldNetRaw["floating_ip"].(string) - oldFixedIP = oldNetRaw["fixed_ip_v4"].(string) - } - - if len(newNetworkList) > i { - if newNetRaw, ok := newNetworkList[i].(map[string]interface{}); ok { - newFIP = newNetRaw["floating_ip"].(string) - newFixedIP = newNetRaw["fixed_ip_v4"].(string) - } - } - - // Only changes to the floating IP are supported - if oldFIP != "" && oldFIP != newFIP { - log.Printf("[DEBUG] Attempting to disassociate %s from %s", oldFIP, d.Id()) - if err := disassociateFloatingIPFromInstance(computeClient, oldFIP, d.Id(), oldFixedIP); err != nil { - return fmt.Errorf("Error disassociating Floating IP during update: %s", err) - } - } - - if newFIP != "" && oldFIP != newFIP { - log.Printf("[DEBUG] Attempting to associate %s to %s", newFIP, d.Id()) - if err := associateFloatingIPToInstance(computeClient, newFIP, d.Id(), newFixedIP); err != nil { - return fmt.Errorf("Error associating Floating IP during update: %s", err) - } - } - } - } - - if d.HasChange("volume") { - // old attachments and new attachments - oldAttachments, newAttachments := d.GetChange("volume") - // for each old attachment, detach the volume - oldAttachmentSet := oldAttachments.(*schema.Set).List() - - log.Printf("[DEBUG] Attempting to detach the following volumes: %#v", oldAttachmentSet) - if blockClient, err := config.blockStorageV1Client(GetRegion(d, config)); err != nil { - return err - } else { - if err := detachVolumesFromInstance(computeClient, blockClient, d.Id(), oldAttachmentSet); err != nil { - return err - } - } - - // for each new attachment, attach the volume - newAttachmentSet := newAttachments.(*schema.Set).List() - if blockClient, err := config.blockStorageV1Client(GetRegion(d, config)); err != nil { - return err - } else { - if err := attachVolumesToInstance(computeClient, blockClient, d.Id(), newAttachmentSet); err != nil { - return err - } - } - - d.SetPartial("volume") - } - if d.HasChange("flavor_id") || d.HasChange("flavor_name") { + // Get vendor_options + vendorOptionsRaw := d.Get("vendor_options").(*schema.Set) + var ignoreResizeConfirmation bool + if vendorOptionsRaw.Len() > 0 { + vendorOptions := expandVendorOptions(vendorOptionsRaw.List()) + ignoreResizeConfirmation = vendorOptions["ignore_resize_confirmation"].(bool) + } + var newFlavorId string var err error if d.HasChange("flavor_id") { @@ -821,39 +825,56 @@ func resourceComputeInstanceV2Update(d *schema.ResourceData, meta interface{}) e // Wait for the instance to finish resizing. log.Printf("[DEBUG] Waiting for instance (%s) to finish resizing", d.Id()) - stateConf := &resource.StateChangeConf{ - Pending: []string{"RESIZE"}, - Target: []string{"VERIFY_RESIZE"}, - Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), - Timeout: d.Timeout(schema.TimeoutUpdate), - Delay: 10 * time.Second, - MinTimeout: 3 * time.Second, - } + // Resize instance without confirmation if specified by user. + if ignoreResizeConfirmation { + stateConf := &resource.StateChangeConf{ + Pending: []string{"RESIZE", "VERIFY_RESIZE"}, + Target: []string{"ACTIVE", "SHUTOFF"}, + Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), + Timeout: d.Timeout(schema.TimeoutUpdate), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } - _, err = stateConf.WaitForState() - if err != nil { - return fmt.Errorf("Error waiting for instance (%s) to resize: %s", d.Id(), err) - } + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for instance (%s) to resize: %s", d.Id(), err) + } + } else { + stateConf := &resource.StateChangeConf{ + Pending: []string{"RESIZE"}, + Target: []string{"VERIFY_RESIZE"}, + Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), + Timeout: d.Timeout(schema.TimeoutUpdate), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } - // Confirm resize. - log.Printf("[DEBUG] Confirming resize") - err = servers.ConfirmResize(computeClient, d.Id()).ExtractErr() - if err != nil { - return fmt.Errorf("Error confirming resize of OpenStack server: %s", err) - } + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for instance (%s) to resize: %s", d.Id(), err) + } - stateConf = &resource.StateChangeConf{ - Pending: []string{"VERIFY_RESIZE"}, - Target: []string{"ACTIVE"}, - Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), - Timeout: d.Timeout(schema.TimeoutUpdate), - Delay: 10 * time.Second, - MinTimeout: 3 * time.Second, - } + // Confirm resize. + log.Printf("[DEBUG] Confirming resize") + err = servers.ConfirmResize(computeClient, d.Id()).ExtractErr() + if err != nil { + return fmt.Errorf("Error confirming resize of OpenStack server: %s", err) + } - _, err = stateConf.WaitForState() - if err != nil { - return fmt.Errorf("Error waiting for instance (%s) to confirm resize: %s", d.Id(), err) + stateConf = &resource.StateChangeConf{ + Pending: []string{"VERIFY_RESIZE"}, + Target: []string{"ACTIVE", "SHUTOFF"}, + Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), + Timeout: d.Timeout(schema.TimeoutUpdate), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for instance (%s) to confirm resize: %s", d.Id(), err) + } } } @@ -867,26 +888,10 @@ func resourceComputeInstanceV2Delete(d *schema.ResourceData, meta interface{}) e return fmt.Errorf("Error creating OpenStack compute client: %s", err) } - // Make sure all volumes are detached before deleting - volumes := d.Get("volume") - if volumeSet, ok := volumes.(*schema.Set); ok { - volumeList := volumeSet.List() - if len(volumeList) > 0 { - log.Printf("[DEBUG] Attempting to detach the following volumes: %#v", volumeList) - if blockClient, err := config.blockStorageV1Client(GetRegion(d, config)); err != nil { - return err - } else { - if err := detachVolumesFromInstance(computeClient, blockClient, d.Id(), volumeList); err != nil { - return err - } - } - } - } - if d.Get("stop_before_destroy").(bool) { err = startstop.Stop(computeClient, d.Id()).ExtractErr() if err != nil { - log.Printf("[WARN] Error stopping OpenStack instance: %s", err) + log.Printf("[WARN] Error stopping openstack_compute_instance_v2: %s", err) } else { stopStateConf := &resource.StateChangeConf{ Pending: []string{"ACTIVE"}, @@ -908,13 +913,13 @@ func resourceComputeInstanceV2Delete(d *schema.ResourceData, meta interface{}) e log.Printf("[DEBUG] Force deleting OpenStack Instance %s", d.Id()) err = servers.ForceDelete(computeClient, d.Id()).ExtractErr() if err != nil { - return fmt.Errorf("Error deleting OpenStack server: %s", err) + return CheckDeleted(d, err, "Error force deleting openstack_compute_instance_v2") } } else { log.Printf("[DEBUG] Deleting OpenStack Instance %s", d.Id()) err = servers.Delete(computeClient, d.Id()).ExtractErr() if err != nil { - return fmt.Errorf("Error deleting OpenStack server: %s", err) + return CheckDeleted(d, err, "Error deleting openstack_compute_instance_v2") } } @@ -937,7 +942,6 @@ func resourceComputeInstanceV2Delete(d *schema.ResourceData, meta interface{}) e d.Id(), err) } - d.SetId("") return nil } @@ -966,291 +970,6 @@ func resourceInstanceSecGroupsV2(d *schema.ResourceData) []string { return secgroups } -// getInstanceNetworks collects instance network information from different sources -// and aggregates it all together. -func getInstanceNetworksAndAddresses(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) ([]map[string]interface{}, error) { - server, err := servers.Get(computeClient, d.Id()).Extract() - - if err != nil { - return nil, CheckDeleted(d, err, "server") - } - - networkDetails, err := getInstanceNetworks(computeClient, d) - addresses := getInstanceAddresses(server.Addresses) - if err != nil { - return nil, err - } - - // if there are no networkDetails, make networks at least a length of 1 - networkLength := 1 - if len(networkDetails) > 0 { - networkLength = len(networkDetails) - } - networks := make([]map[string]interface{}, networkLength) - - // Loop through all networks and addresses, - // merge relevant address details. - if len(networkDetails) == 0 { - for netName, n := range addresses { - networks[0] = map[string]interface{}{ - "name": netName, - "fixed_ip_v4": n["fixed_ip_v4"], - "fixed_ip_v6": n["fixed_ip_v6"], - "floating_ip": n["floating_ip"], - "mac": n["mac"], - } - } - } else { - for i, net := range networkDetails { - n := addresses[net["name"].(string)] - - networks[i] = map[string]interface{}{ - "uuid": networkDetails[i]["uuid"], - "name": networkDetails[i]["name"], - "port": networkDetails[i]["port"], - "fixed_ip_v4": n["fixed_ip_v4"], - "fixed_ip_v6": n["fixed_ip_v6"], - "floating_ip": n["floating_ip"], - "mac": n["mac"], - "access_network": networkDetails[i]["access_network"], - } - } - } - - log.Printf("[DEBUG] networks: %+v", networks) - - return networks, nil -} - -func getInstanceNetworks(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) ([]map[string]interface{}, error) { - rawNetworks := d.Get("network").([]interface{}) - newNetworks := make([]map[string]interface{}, 0, len(rawNetworks)) - var tenantnet tenantnetworks.Network - - tenantNetworkExt := true - for _, raw := range rawNetworks { - // Not sure what causes this, but it is a possibility (see GH-2323). - // Since we call this function to reconcile what we'll save in the - // state anyways, we just ignore it. - if raw == nil { - continue - } - - rawMap := raw.(map[string]interface{}) - - // Both a floating IP and a port cannot be specified - if fip, ok := rawMap["floating_ip"].(string); ok { - if port, ok := rawMap["port"].(string); ok { - if fip != "" && port != "" { - return nil, fmt.Errorf("Only one of a floating IP or port may be specified per network.") - } - } - } - - allPages, err := tenantnetworks.List(computeClient).AllPages() - if err != nil { - if _, ok := err.(gophercloud.ErrDefault404); ok { - log.Printf("[DEBUG] os-tenant-networks disabled") - tenantNetworkExt = false - } - - log.Printf("[DEBUG] Err looks like: %+v", err) - if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok { - if errCode.Actual == 403 { - log.Printf("[DEBUG] os-tenant-networks disabled.") - tenantNetworkExt = false - } else { - log.Printf("[DEBUG] unexpected os-tenant-networks error: %s", err) - tenantNetworkExt = false - } - } - } - - // In some cases, a call to os-tenant-networks might work, - // but the response is invalid. Catch this during extraction. - networkList := []tenantnetworks.Network{} - if tenantNetworkExt { - networkList, err = tenantnetworks.ExtractNetworks(allPages) - if err != nil { - log.Printf("[DEBUG] error extracting os-tenant-networks results: %s", err) - tenantNetworkExt = false - } - } - - networkID := "" - networkName := "" - if tenantNetworkExt { - for _, network := range networkList { - if network.Name == rawMap["name"] { - tenantnet = network - } - if network.ID == rawMap["uuid"] { - tenantnet = network - } - } - - networkID = tenantnet.ID - networkName = tenantnet.Name - } else { - networkID = rawMap["uuid"].(string) - networkName = rawMap["name"].(string) - } - - newNetworks = append(newNetworks, map[string]interface{}{ - "uuid": networkID, - "name": networkName, - "port": rawMap["port"].(string), - "fixed_ip_v4": rawMap["fixed_ip_v4"].(string), - "access_network": rawMap["access_network"].(bool), - }) - } - - log.Printf("[DEBUG] networks: %+v", newNetworks) - return newNetworks, nil -} - -func getInstanceAddresses(addresses map[string]interface{}) map[string]map[string]interface{} { - addrs := make(map[string]map[string]interface{}) - for n, networkAddresses := range addresses { - addrs[n] = make(map[string]interface{}) - for _, element := range networkAddresses.([]interface{}) { - address := element.(map[string]interface{}) - if address["OS-EXT-IPS:type"] == "floating" { - addrs[n]["floating_ip"] = address["addr"] - } else { - if address["version"].(float64) == 4 { - addrs[n]["fixed_ip_v4"] = address["addr"].(string) - } else { - addrs[n]["fixed_ip_v6"] = fmt.Sprintf("[%s]", address["addr"].(string)) - } - } - if mac, ok := address["OS-EXT-IPS-MAC:mac_addr"]; ok { - addrs[n]["mac"] = mac.(string) - } - } - } - - log.Printf("[DEBUG] Addresses: %+v", addresses) - - return addrs -} - -func getInstanceAccessAddresses(d *schema.ResourceData, networks []map[string]interface{}) (string, string) { - var hostv4, hostv6 string - - // Start with a global floating IP - floatingIP := d.Get("floating_ip").(string) - if floatingIP != "" { - hostv4 = floatingIP - } - - // Loop through all networks - // If the network has a valid floating, fixed v4, or fixed v6 address - // and hostv4 or hostv6 is not set, set hostv4/hostv6. - // If the network is an "access_network" overwrite hostv4/hostv6. - for _, n := range networks { - var accessNetwork bool - - if an, ok := n["access_network"].(bool); ok && an { - accessNetwork = true - } - - if fixedIPv4, ok := n["fixed_ip_v4"].(string); ok && fixedIPv4 != "" { - if hostv4 == "" || accessNetwork { - hostv4 = fixedIPv4 - } - } - - if floatingIP, ok := n["floating_ip"].(string); ok && floatingIP != "" { - if hostv4 == "" || accessNetwork { - hostv4 = floatingIP - } - } - - if fixedIPv6, ok := n["fixed_ip_v6"].(string); ok && fixedIPv6 != "" { - if hostv6 == "" || accessNetwork { - hostv6 = fixedIPv6 - } - } - } - - log.Printf("[DEBUG] OpenStack Instance Network Access Addresses: %s, %s", hostv4, hostv6) - - return hostv4, hostv6 -} - -func checkInstanceFloatingIPs(d *schema.ResourceData) error { - rawNetworks := d.Get("network").([]interface{}) - floatingIP := d.Get("floating_ip").(string) - - for _, raw := range rawNetworks { - if raw == nil { - continue - } - - rawMap := raw.(map[string]interface{}) - - // Error if a floating IP was specified both globally and in the network block. - if floatingIP != "" && rawMap["floating_ip"] != "" { - return fmt.Errorf("Cannot specify a floating IP both globally and in a network block.") - } - } - return nil -} - -func associateFloatingIPsToInstance(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) error { - floatingIP := d.Get("floating_ip").(string) - rawNetworks := d.Get("network").([]interface{}) - instanceID := d.Id() - - if floatingIP != "" { - if err := associateFloatingIPToInstance(computeClient, floatingIP, instanceID, ""); err != nil { - return err - } - } else { - for _, raw := range rawNetworks { - if raw == nil { - continue - } - - rawMap := raw.(map[string]interface{}) - if rawMap["floating_ip"].(string) != "" { - floatingIP := rawMap["floating_ip"].(string) - fixedIP := rawMap["fixed_ip_v4"].(string) - if err := associateFloatingIPToInstance(computeClient, floatingIP, instanceID, fixedIP); err != nil { - return err - } - } - } - } - return nil -} - -func associateFloatingIPToInstance(computeClient *gophercloud.ServiceClient, floatingIP string, instanceID string, fixedIP string) error { - associateOpts := floatingips.AssociateOpts{ - FloatingIP: floatingIP, - FixedIP: fixedIP, - } - - if err := floatingips.AssociateInstance(computeClient, instanceID, associateOpts).ExtractErr(); err != nil { - return fmt.Errorf("Error associating floating IP: %s", err) - } - - return nil -} - -func disassociateFloatingIPFromInstance(computeClient *gophercloud.ServiceClient, floatingIP string, instanceID string, fixedIP string) error { - disassociateOpts := floatingips.DisassociateOpts{ - FloatingIP: floatingIP, - } - - if err := floatingips.DisassociateInstance(computeClient, instanceID, disassociateOpts).ExtractErr(); err != nil { - return fmt.Errorf("Error disassociating floating IP: %s", err) - } - - return nil -} - func resourceInstanceMetadataV2(d *schema.ResourceData) map[string]string { m := make(map[string]string) for key, val := range d.Get("metadata").(map[string]interface{}) { @@ -1269,6 +988,8 @@ func resourceInstanceBlockDevicesV2(d *schema.ResourceData, bds []interface{}) ( BootIndex: bdM["boot_index"].(int), DeleteOnTermination: bdM["delete_on_termination"].(bool), GuestFormat: bdM["guest_format"].(string), + DeviceType: bdM["device_type"].(string), + DiskBus: bdM["disk_bus"].(string), } sourceType := bdM["source_type"].(string) @@ -1323,12 +1044,13 @@ func resourceInstanceSchedulerHintsV2(d *schema.ResourceData, schedulerHintsRaw } schedulerHints := schedulerhints.SchedulerHints{ - Group: schedulerHintsRaw["group"].(string), - DifferentHost: differentHost, - SameHost: sameHost, - Query: query, - TargetCell: schedulerHintsRaw["target_cell"].(string), - BuildNearHostIP: schedulerHintsRaw["build_near_host_ip"].(string), + Group: schedulerHintsRaw["group"].(string), + DifferentHost: differentHost, + SameHost: sameHost, + Query: query, + TargetCell: schedulerHintsRaw["target_cell"].(string), + BuildNearHostIP: schedulerHintsRaw["build_near_host_ip"].(string), + AdditionalProperties: schedulerHintsRaw["additional_properties"].(map[string]interface{}), } return schedulerHints @@ -1415,23 +1137,33 @@ func setImageInformation(computeClient *gophercloud.ServiceClient, server *serve return nil } -func getFlavorID(client *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) { - flavorId := d.Get("flavor_id").(string) - - if flavorId != "" { +func getFlavorID(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) { + if flavorId := d.Get("flavor_id").(string); flavorId != "" { return flavorId, nil + } else { + // Try the OS_FLAVOR_ID environment variable + if v := os.Getenv("OS_FLAVOR_ID"); v != "" { + return v, nil + } } flavorName := d.Get("flavor_name").(string) - return flavors.IDFromName(client, flavorName) -} + if flavorName == "" { + // Try the OS_FLAVOR_NAME environment variable + if v := os.Getenv("OS_FLAVOR_NAME"); v != "" { + flavorName = v + } + } -func resourceComputeVolumeAttachmentHash(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%s-", m["volume_id"].(string))) + if flavorName != "" { + flavorId, err := flavors.IDFromName(computeClient, flavorName) + if err != nil { + return "", err + } + return flavorId, nil + } - return hashcode.String(buf.String()) + return "", fmt.Errorf("Neither a flavor_id or flavor_name could be determined.") } func resourceComputeSchedulerHintsHash(v interface{}) int { @@ -1450,6 +1182,12 @@ func resourceComputeSchedulerHintsHash(v interface{}) int { buf.WriteString(fmt.Sprintf("%s-", m["build_host_near_ip"].(string))) } + if m["additional_properties"] != nil { + for _, v := range m["additional_properties"].(map[string]interface{}) { + buf.WriteString(fmt.Sprintf("%s-", v)) + } + } + buf.WriteString(fmt.Sprintf("%s-", m["different_host"].([]interface{}))) buf.WriteString(fmt.Sprintf("%s-", m["same_host"].([]interface{}))) buf.WriteString(fmt.Sprintf("%s-", m["query"].([]interface{}))) @@ -1457,121 +1195,6 @@ func resourceComputeSchedulerHintsHash(v interface{}) int { return hashcode.String(buf.String()) } -func attachVolumesToInstance(computeClient *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, serverId string, vols []interface{}) error { - for _, v := range vols { - va := v.(map[string]interface{}) - volumeId := va["volume_id"].(string) - device := va["device"].(string) - - s := "" - if serverId != "" { - s = serverId - } else if va["server_id"] != "" { - s = va["server_id"].(string) - } else { - return fmt.Errorf("Unable to determine server ID to attach volume.") - } - - vaOpts := &volumeattach.CreateOpts{ - Device: device, - VolumeID: volumeId, - } - - if _, err := volumeattach.Create(computeClient, s, vaOpts).Extract(); err != nil { - return err - } - - stateConf := &resource.StateChangeConf{ - Pending: []string{"attaching", "available"}, - Target: []string{"in-use"}, - Refresh: VolumeV1StateRefreshFunc(blockClient, va["volume_id"].(string)), - Timeout: 30 * time.Minute, - Delay: 5 * time.Second, - MinTimeout: 2 * time.Second, - } - - if _, err := stateConf.WaitForState(); err != nil { - return err - } - - log.Printf("[INFO] Attached volume %s to instance %s", volumeId, serverId) - } - return nil -} - -func detachVolumesFromInstance(computeClient *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, serverId string, vols []interface{}) error { - for _, v := range vols { - va := v.(map[string]interface{}) - aId := va["id"].(string) - - log.Printf("[INFO] Attempting to detach volume %s", va["volume_id"]) - if err := volumeattach.Delete(computeClient, serverId, aId).ExtractErr(); err != nil { - return err - } - - stateConf := &resource.StateChangeConf{ - Pending: []string{"detaching", "in-use"}, - Target: []string{"available"}, - Refresh: VolumeV1StateRefreshFunc(blockClient, va["volume_id"].(string)), - Timeout: 30 * time.Minute, - Delay: 5 * time.Second, - MinTimeout: 2 * time.Second, - } - - if _, err := stateConf.WaitForState(); err != nil { - return err - } - log.Printf("[INFO] Detached volume %s from instance %s", va["volume_id"], serverId) - } - - return nil -} - -func getVolumeAttachments(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) error { - var vols []map[string]interface{} - - allPages, err := volumeattach.List(computeClient, d.Id()).AllPages() - if err != nil { - if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok { - if errCode.Actual == 403 { - log.Printf("[DEBUG] os-volume_attachments disabled.") - return nil - } else { - return err - } - } - } - - allVolumeAttachments, err := volumeattach.ExtractVolumeAttachments(allPages) - if err != nil { - return err - } - - if v, ok := d.GetOk("volume"); ok { - volumes := v.(*schema.Set).List() - for _, volume := range volumes { - if volumeMap, ok := volume.(map[string]interface{}); ok { - if v, ok := volumeMap["volume_id"].(string); ok { - for _, volumeAttachment := range allVolumeAttachments { - if v == volumeAttachment.ID { - vol := make(map[string]interface{}) - vol["id"] = volumeAttachment.ID - vol["volume_id"] = volumeAttachment.VolumeID - vol["device"] = volumeAttachment.Device - vols = append(vols, vol) - } - } - } - } - } - } - - log.Printf("[INFO] Volume attachments: %v", vols) - d.Set("volume", vols) - - return nil -} - func checkBlockDeviceConfig(d *schema.ResourceData) error { if vL, ok := d.GetOk("block_device"); ok { for _, v := range vL.([]interface{}) { @@ -1628,3 +1251,29 @@ func resourceInstancePersonalityV2(d *schema.ResourceData) servers.Personality { return personalities } + +// suppressAvailabilityZoneDetailDiffs will suppress diffs when a user specifies an +// availability zone in the format of `az:host:node` and Nova/Compute responds with +// only `az`. +func suppressAvailabilityZoneDetailDiffs(k, old, new string, d *schema.ResourceData) bool { + if strings.Contains(new, ":") { + parts := strings.Split(new, ":") + az := parts[0] + + if az == old { + return true + } + } + + return false +} + +// suppressPowerStateDiffs will allow a state of "error" or "migrating" even though we don't +// allow them as a user input. +func suppressPowerStateDiffs(k, old, new string, d *schema.ResourceData) bool { + if old == "error" || old == "migrating" { + return true + } + + return false +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_interface_attach_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_interface_attach_v2.go new file mode 100644 index 000000000..8eeb39947 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_interface_attach_v2.go @@ -0,0 +1,185 @@ +package openstack + +import ( + "fmt" + "log" + "time" + + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceComputeInterfaceAttachV2() *schema.Resource { + return &schema.Resource{ + Create: resourceComputeInterfaceAttachV2Create, + Read: resourceComputeInterfaceAttachV2Read, + Delete: resourceComputeInterfaceAttachV2Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "port_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: []string{"network_id"}, + }, + + "network_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: []string{"port_id"}, + }, + + "instance_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "fixed_ip": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func resourceComputeInterfaceAttachV2Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + computeClient, err := config.computeV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } + + instanceId := d.Get("instance_id").(string) + + var portId string + if v, ok := d.GetOk("port_id"); ok { + portId = v.(string) + } + + var networkId string + if v, ok := d.GetOk("network_id"); ok { + networkId = v.(string) + } + + if networkId == "" && portId == "" { + return fmt.Errorf("Must set one of network_id and port_id") + } + + // For some odd reason the API takes an array of IPs, but you can only have one element in the array. + var fixedIPs []attachinterfaces.FixedIP + if v, ok := d.GetOk("fixed_ip"); ok { + fixedIPs = append(fixedIPs, attachinterfaces.FixedIP{IPAddress: v.(string)}) + } + + attachOpts := attachinterfaces.CreateOpts{ + PortID: portId, + NetworkID: networkId, + FixedIPs: fixedIPs, + } + + log.Printf("[DEBUG] openstack_compute_interface_attach_v2 attach options: %#v", attachOpts) + + attachment, err := attachinterfaces.Create(computeClient, instanceId, attachOpts).Extract() + if err != nil { + return err + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"ATTACHING"}, + Target: []string{"ATTACHED"}, + Refresh: computeInterfaceAttachV2AttachFunc(computeClient, instanceId, attachment.PortID), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 5 * time.Second, + MinTimeout: 5 * time.Second, + } + + if _, err = stateConf.WaitForState(); err != nil { + return fmt.Errorf("Error creating openstack_compute_interface_attach_v2 %s: %s", instanceId, err) + } + + // Use the instance ID and attachment ID as the resource ID. + id := fmt.Sprintf("%s/%s", instanceId, attachment.PortID) + + log.Printf("[DEBUG] Created openstack_compute_interface_attach_v2 %s: %#v", id, attachment) + + d.SetId(id) + + return resourceComputeInterfaceAttachV2Read(d, meta) +} + +func resourceComputeInterfaceAttachV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + computeClient, err := config.computeV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } + + instanceId, attachmentId, err := computeInterfaceAttachV2ParseID(d.Id()) + if err != nil { + return err + } + + attachment, err := attachinterfaces.Get(computeClient, instanceId, attachmentId).Extract() + if err != nil { + return CheckDeleted(d, err, "Error retrieving openstack_compute_interface_attach_v2") + } + + log.Printf("[DEBUG] Retrieved openstack_compute_interface_attach_v2 %s: %#v", d.Id(), attachment) + + d.Set("instance_id", instanceId) + d.Set("port_id", attachment.PortID) + d.Set("network_id", attachment.NetID) + d.Set("region", GetRegion(d, config)) + + return nil +} + +func resourceComputeInterfaceAttachV2Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + computeClient, err := config.computeV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } + + instanceId, attachmentId, err := computeInterfaceAttachV2ParseID(d.Id()) + if err != nil { + return err + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{""}, + Target: []string{"DETACHED"}, + Refresh: computeInterfaceAttachV2DetachFunc(computeClient, instanceId, attachmentId), + Timeout: d.Timeout(schema.TimeoutDelete), + Delay: 5 * time.Second, + MinTimeout: 5 * time.Second, + } + + if _, err = stateConf.WaitForState(); err != nil { + return fmt.Errorf("Error detaching openstack_compute_interface_attach_v2 %s: %s", d.Id(), err) + } + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_keypair_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_keypair_v2.go index 5d2da47b3..82b7cc2a1 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_keypair_v2.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_keypair_v2.go @@ -18,28 +18,42 @@ func resourceComputeKeypairV2() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "name": &schema.Schema{ + "name": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "public_key": &schema.Schema{ + + "public_key": { Type: schema.TypeString, Optional: true, + Computed: true, ForceNew: true, }, - "value_specs": &schema.Schema{ + + "value_specs": { Type: schema.TypeMap, Optional: true, ForceNew: true, }, + + // computed-only + "private_key": { + Type: schema.TypeString, + Computed: true, + }, + + "fingerprint": { + Type: schema.TypeString, + Computed: true, + }, }, } } @@ -51,22 +65,27 @@ func resourceComputeKeypairV2Create(d *schema.ResourceData, meta interface{}) er return fmt.Errorf("Error creating OpenStack compute client: %s", err) } - createOpts := KeyPairCreateOpts{ + name := d.Get("name").(string) + createOpts := ComputeKeyPairV2CreateOpts{ keypairs.CreateOpts{ - Name: d.Get("name").(string), + Name: name, PublicKey: d.Get("public_key").(string), }, MapValueSpecs(d), } - log.Printf("[DEBUG] Create Options: %#v", createOpts) + log.Printf("[DEBUG] openstack_compute_keypair_v2 create options: %#v", createOpts) + kp, err := keypairs.Create(computeClient, createOpts).Extract() if err != nil { - return fmt.Errorf("Error creating OpenStack keypair: %s", err) + return fmt.Errorf("Unable to create openstack_compute_keypair_v2 %s: %s", name, err) } d.SetId(kp.Name) + // Private Key is only available in the response to a create. + d.Set("private_key", kp.PrivateKey) + return resourceComputeKeypairV2Read(d, meta) } @@ -79,11 +98,14 @@ func resourceComputeKeypairV2Read(d *schema.ResourceData, meta interface{}) erro kp, err := keypairs.Get(computeClient, d.Id()).Extract() if err != nil { - return CheckDeleted(d, err, "keypair") + return CheckDeleted(d, err, "Error retrieving openstack_compute_keypair_v2") } + log.Printf("[DEBUG] Retrieved openstack_compute_keypair_v2 %s: %#v", d.Id(), kp) + d.Set("name", kp.Name) d.Set("public_key", kp.PublicKey) + d.Set("fingerprint", kp.Fingerprint) d.Set("region", GetRegion(d, config)) return nil @@ -98,8 +120,8 @@ func resourceComputeKeypairV2Delete(d *schema.ResourceData, meta interface{}) er err = keypairs.Delete(computeClient, d.Id()).ExtractErr() if err != nil { - return fmt.Errorf("Error deleting OpenStack keypair: %s", err) + return CheckDeleted(d, err, "Error deleting openstack_compute_keypair_v2") } - d.SetId("") + return nil } diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_secgroup_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_secgroup_v2.go index e3cb3f6dc..270a3b480 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_secgroup_v2.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_secgroup_v2.go @@ -1,7 +1,6 @@ package openstack import ( - "bytes" "fmt" "log" "strings" @@ -9,7 +8,6 @@ import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups" - "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" ) @@ -29,49 +27,56 @@ func resourceComputeSecGroupV2() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "name": &schema.Schema{ + "name": { Type: schema.TypeString, Required: true, ForceNew: false, }, - "description": &schema.Schema{ + + "description": { Type: schema.TypeString, Required: true, ForceNew: false, }, - "rule": &schema.Schema{ + + "rule": { Type: schema.TypeSet, Optional: true, Computed: true, + Set: computeSecGroupV2RuleHash, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "id": &schema.Schema{ + "id": { Type: schema.TypeString, Computed: true, }, - "from_port": &schema.Schema{ + + "from_port": { Type: schema.TypeInt, Required: true, ForceNew: false, }, - "to_port": &schema.Schema{ + + "to_port": { Type: schema.TypeInt, Required: true, ForceNew: false, }, - "ip_protocol": &schema.Schema{ + + "ip_protocol": { Type: schema.TypeString, Required: true, ForceNew: false, }, - "cidr": &schema.Schema{ + + "cidr": { Type: schema.TypeString, Optional: true, ForceNew: false, @@ -79,12 +84,14 @@ func resourceComputeSecGroupV2() *schema.Resource { return strings.ToLower(v.(string)) }, }, - "from_group_id": &schema.Schema{ + + "from_group_id": { Type: schema.TypeString, Optional: true, ForceNew: false, }, - "self": &schema.Schema{ + + "self": { Type: schema.TypeBool, Optional: true, Default: false, @@ -92,7 +99,6 @@ func resourceComputeSecGroupV2() *schema.Resource { }, }, }, - Set: secgroupRuleV2Hash, }, }, } @@ -106,30 +112,32 @@ func resourceComputeSecGroupV2Create(d *schema.ResourceData, meta interface{}) e } // Before creating the security group, make sure all rules are valid. - if err := checkSecGroupV2RulesForErrors(d); err != nil { + if err := computeSecGroupV2RulesCheckForErrors(d); err != nil { return err } // If all rules are valid, proceed with creating the security gruop. + name := d.Get("name").(string) createOpts := secgroups.CreateOpts{ - Name: d.Get("name").(string), + Name: name, Description: d.Get("description").(string), } - log.Printf("[DEBUG] Create Options: %#v", createOpts) + log.Printf("[DEBUG] openstack_compute_secgroup_v2 Create Options: %#v", createOpts) sg, err := secgroups.Create(computeClient, createOpts).Extract() if err != nil { - return fmt.Errorf("Error creating OpenStack security group: %s", err) + return fmt.Errorf("Error creating openstack_compute_secgroup_v2 %s: %s", name, err) } d.SetId(sg.ID) // Now that the security group has been created, iterate through each rule and create it - createRuleOptsList := resourceSecGroupRulesV2(d) + createRuleOptsList := expandComputeSecGroupV2CreateRules(d) + for _, createRuleOpts := range createRuleOptsList { _, err := secgroups.CreateRule(computeClient, createRuleOpts).Extract() if err != nil { - return fmt.Errorf("Error creating OpenStack security group rule: %s", err) + return fmt.Errorf("Error creating openstack_compute_secgroup_v2 %s rule: %s", name, err) } } @@ -145,18 +153,22 @@ func resourceComputeSecGroupV2Read(d *schema.ResourceData, meta interface{}) err sg, err := secgroups.Get(computeClient, d.Id()).Extract() if err != nil { - return CheckDeleted(d, err, "security group") + return CheckDeleted(d, err, "Error retrieving openstack_compute_secgroup_v2") } d.Set("name", sg.Name) d.Set("description", sg.Description) - rtm, err := rulesToMap(computeClient, d, sg.Rules) + rules, err := flattenComputeSecGroupV2Rules(computeClient, d, sg.Rules) if err != nil { return err } - log.Printf("[DEBUG] rulesToMap(sg.Rules): %+v", rtm) - d.Set("rule", rtm) + + log.Printf("[DEBUG] Retrieved openstack_compute_secgroup_v2 %s rules: %#v", d.Id(), rules) + + if err := d.Set("rule", rules); err != nil { + return fmt.Errorf("Unable to set openstack_compute_secgroup_v2 %s rules: %s", d.Id(), err) + } d.Set("region", GetRegion(d, config)) @@ -170,16 +182,17 @@ func resourceComputeSecGroupV2Update(d *schema.ResourceData, meta interface{}) e return fmt.Errorf("Error creating OpenStack compute client: %s", err) } + description := d.Get("description").(string) updateOpts := secgroups.UpdateOpts{ Name: d.Get("name").(string), - Description: d.Get("description").(string), + Description: &description, } - log.Printf("[DEBUG] Updating Security Group (%s) with options: %+v", d.Id(), updateOpts) + log.Printf("[DEBUG] openstack_compute_secgroup_v2 %s Update Options: %#v", d.Id(), updateOpts) _, err = secgroups.Update(computeClient, d.Id(), updateOpts).Extract() if err != nil { - return fmt.Errorf("Error updating OpenStack security group (%s): %s", d.Id(), err) + return fmt.Errorf("Error updating openstack_compute_secgroup_v2 %s: %s", d.Id(), err) } if d.HasChange("rule") { @@ -188,29 +201,28 @@ func resourceComputeSecGroupV2Update(d *schema.ResourceData, meta interface{}) e secgrouprulesToAdd := newSGRSet.Difference(oldSGRSet) secgrouprulesToRemove := oldSGRSet.Difference(newSGRSet) - log.Printf("[DEBUG] Security group rules to add: %v", secgrouprulesToAdd) - log.Printf("[DEBUG] Security groups rules to remove: %v", secgrouprulesToRemove) + log.Printf("[DEBUG] openstack_compute_secgroup_v2 %s rules to add: %v", d.Id(), secgrouprulesToAdd) + log.Printf("[DEBUG] openstack_compute_secgroup_v2 %s rules to remove: %v", d.Id(), secgrouprulesToRemove) for _, rawRule := range secgrouprulesToAdd.List() { - createRuleOpts := resourceSecGroupRuleCreateOptsV2(d, rawRule) - rule, err := secgroups.CreateRule(computeClient, createRuleOpts).Extract() + createRuleOpts := expandComputeSecGroupV2CreateRule(d, rawRule) + + _, err := secgroups.CreateRule(computeClient, createRuleOpts).Extract() if err != nil { - return fmt.Errorf("Error adding rule to OpenStack security group (%s): %s", d.Id(), err) + return fmt.Errorf("Error adding rule to openstack_compute_secgroup_v2 %s: %s", d.Id(), err) } - log.Printf("[DEBUG] Added rule (%s) to OpenStack security group (%s) ", rule.ID, d.Id()) } for _, r := range secgrouprulesToRemove.List() { - rule := resourceSecGroupRuleV2(d, r) + rule := expandComputeSecGroupV2Rule(d, r) + err := secgroups.DeleteRule(computeClient, rule.ID).ExtractErr() if err != nil { if _, ok := err.(gophercloud.ErrDefault404); ok { continue } - return fmt.Errorf("Error removing rule (%s) from OpenStack security group (%s)", rule.ID, d.Id()) - } else { - log.Printf("[DEBUG] Removed rule (%s) from OpenStack security group (%s): %s", rule.ID, d.Id(), err) + return fmt.Errorf("Error removing rule %s from openstack_compute_secgroup_v2 %s: %s", rule.ID, d.Id(), err) } } } @@ -228,171 +240,16 @@ func resourceComputeSecGroupV2Delete(d *schema.ResourceData, meta interface{}) e stateConf := &resource.StateChangeConf{ Pending: []string{"ACTIVE"}, Target: []string{"DELETED"}, - Refresh: SecGroupV2StateRefreshFunc(computeClient, d), + Refresh: computeSecGroupV2StateRefreshFunc(computeClient, d), Timeout: d.Timeout(schema.TimeoutDelete), - Delay: 10 * time.Second, + Delay: 1 * time.Second, MinTimeout: 3 * time.Second, } _, err = stateConf.WaitForState() if err != nil { - return fmt.Errorf("Error deleting OpenStack security group: %s", err) - } - - d.SetId("") - return nil -} - -func resourceSecGroupRulesV2(d *schema.ResourceData) []secgroups.CreateRuleOpts { - rawRules := d.Get("rule").(*schema.Set).List() - createRuleOptsList := make([]secgroups.CreateRuleOpts, len(rawRules)) - for i, rawRule := range rawRules { - createRuleOptsList[i] = resourceSecGroupRuleCreateOptsV2(d, rawRule) - } - return createRuleOptsList -} - -func resourceSecGroupRuleCreateOptsV2(d *schema.ResourceData, rawRule interface{}) secgroups.CreateRuleOpts { - rawRuleMap := rawRule.(map[string]interface{}) - groupId := rawRuleMap["from_group_id"].(string) - if rawRuleMap["self"].(bool) { - groupId = d.Id() - } - return secgroups.CreateRuleOpts{ - ParentGroupID: d.Id(), - FromPort: rawRuleMap["from_port"].(int), - ToPort: rawRuleMap["to_port"].(int), - IPProtocol: rawRuleMap["ip_protocol"].(string), - CIDR: rawRuleMap["cidr"].(string), - FromGroupID: groupId, - } -} - -func checkSecGroupV2RulesForErrors(d *schema.ResourceData) error { - rawRules := d.Get("rule").(*schema.Set).List() - for _, rawRule := range rawRules { - rawRuleMap := rawRule.(map[string]interface{}) - - // only one of cidr, from_group_id, or self can be set - cidr := rawRuleMap["cidr"].(string) - groupId := rawRuleMap["from_group_id"].(string) - self := rawRuleMap["self"].(bool) - errorMessage := fmt.Errorf("Only one of cidr, from_group_id, or self can be set.") - - // if cidr is set, from_group_id and self cannot be set - if cidr != "" { - if groupId != "" || self { - return errorMessage - } - } - - // if from_group_id is set, cidr and self cannot be set - if groupId != "" { - if cidr != "" || self { - return errorMessage - } - } - - // if self is set, cidr and from_group_id cannot be set - if self { - if cidr != "" || groupId != "" { - return errorMessage - } - } + return CheckDeleted(d, err, "Error deleting openstack_compute_secgroup_v2") } return nil } - -func resourceSecGroupRuleV2(d *schema.ResourceData, rawRule interface{}) secgroups.Rule { - rawRuleMap := rawRule.(map[string]interface{}) - return secgroups.Rule{ - ID: rawRuleMap["id"].(string), - ParentGroupID: d.Id(), - FromPort: rawRuleMap["from_port"].(int), - ToPort: rawRuleMap["to_port"].(int), - IPProtocol: rawRuleMap["ip_protocol"].(string), - IPRange: secgroups.IPRange{CIDR: rawRuleMap["cidr"].(string)}, - } -} - -func rulesToMap(computeClient *gophercloud.ServiceClient, d *schema.ResourceData, sgrs []secgroups.Rule) ([]map[string]interface{}, error) { - sgrMap := make([]map[string]interface{}, len(sgrs)) - for i, sgr := range sgrs { - groupId := "" - self := false - if sgr.Group.Name != "" { - if sgr.Group.Name == d.Get("name").(string) { - self = true - } else { - // Since Nova only returns the secgroup Name (and not the ID) for the group attribute, - // we need to look up all security groups and match the name. - // Nevermind that Nova wants the ID when setting the Group *and* that multiple groups - // with the same name can exist... - allPages, err := secgroups.List(computeClient).AllPages() - if err != nil { - return nil, err - } - securityGroups, err := secgroups.ExtractSecurityGroups(allPages) - if err != nil { - return nil, err - } - - for _, sg := range securityGroups { - if sg.Name == sgr.Group.Name { - groupId = sg.ID - } - } - } - } - - sgrMap[i] = map[string]interface{}{ - "id": sgr.ID, - "from_port": sgr.FromPort, - "to_port": sgr.ToPort, - "ip_protocol": sgr.IPProtocol, - "cidr": sgr.IPRange.CIDR, - "self": self, - "from_group_id": groupId, - } - } - return sgrMap, nil -} - -func secgroupRuleV2Hash(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int))) - buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int))) - buf.WriteString(fmt.Sprintf("%s-", m["ip_protocol"].(string))) - buf.WriteString(fmt.Sprintf("%s-", strings.ToLower(m["cidr"].(string)))) - buf.WriteString(fmt.Sprintf("%s-", m["from_group_id"].(string))) - buf.WriteString(fmt.Sprintf("%t-", m["self"].(bool))) - - return hashcode.String(buf.String()) -} - -func SecGroupV2StateRefreshFunc(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - log.Printf("[DEBUG] Attempting to delete Security Group %s.\n", d.Id()) - - err := secgroups.Delete(computeClient, d.Id()).ExtractErr() - if err != nil { - return nil, "", err - } - - s, err := secgroups.Get(computeClient, d.Id()).Extract() - if err != nil { - err = CheckDeleted(d, err, "Security Group") - if err != nil { - return s, "", err - } else { - log.Printf("[DEBUG] Successfully deleted Security Group %s", d.Id()) - return s, "DELETED", nil - } - } - - log.Printf("[DEBUG] Security Group %s still active.\n", d.Id()) - return s, "ACTIVE", nil - } -} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_servergroup_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_servergroup_v2.go index 45e8993ac..138e543d5 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_servergroup_v2.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_servergroup_v2.go @@ -19,30 +19,33 @@ func resourceComputeServerGroupV2() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "name": &schema.Schema{ + "name": { Type: schema.TypeString, ForceNew: true, Required: true, }, - "policies": &schema.Schema{ + + "policies": { Type: schema.TypeList, Optional: true, ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - "members": &schema.Schema{ + + "members": { Type: schema.TypeList, Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - "value_specs": &schema.Schema{ + + "value_specs": { Type: schema.TypeMap, Optional: true, ForceNew: true, @@ -58,18 +61,23 @@ func resourceComputeServerGroupV2Create(d *schema.ResourceData, meta interface{} return fmt.Errorf("Error creating OpenStack compute client: %s", err) } - createOpts := ServerGroupCreateOpts{ + name := d.Get("name").(string) + + rawPolicies := d.Get("policies").([]interface{}) + policies := expandComputeServerGroupV2Policies(computeClient, rawPolicies) + + createOpts := ComputeServerGroupV2CreateOpts{ servergroups.CreateOpts{ - Name: d.Get("name").(string), - Policies: resourceServerGroupPoliciesV2(d), + Name: name, + Policies: policies, }, MapValueSpecs(d), } - log.Printf("[DEBUG] Create Options: %#v", createOpts) + log.Printf("[DEBUG] openstack_compute_servergroup_v2 create options: %#v", createOpts) newSG, err := servergroups.Create(computeClient, createOpts).Extract() if err != nil { - return fmt.Errorf("Error creating ServerGroup: %s", err) + return fmt.Errorf("Error creating openstack_compute_servergroup_v2 %s: %s", name, err) } d.SetId(newSG.ID) @@ -86,27 +94,14 @@ func resourceComputeServerGroupV2Read(d *schema.ResourceData, meta interface{}) sg, err := servergroups.Get(computeClient, d.Id()).Extract() if err != nil { - return CheckDeleted(d, err, "server group") + return CheckDeleted(d, err, "Error retrieving openstack_compute_servergroup_v2") } - log.Printf("[DEBUG] Retrieved ServerGroup %s: %+v", d.Id(), sg) + log.Printf("[DEBUG] Retrieved openstack_compute_servergroup_v2 %s: %#v", d.Id(), sg) - // Set the name d.Set("name", sg.Name) - - // Set the policies - policies := []string{} - for _, p := range sg.Policies { - policies = append(policies, p) - } - d.Set("policies", policies) - - // Set the members - members := []string{} - for _, m := range sg.Members { - members = append(members, m) - } - d.Set("members", members) + d.Set("policies", sg.Policies) + d.Set("members", sg.Members) d.Set("region", GetRegion(d, config)) @@ -120,19 +115,9 @@ func resourceComputeServerGroupV2Delete(d *schema.ResourceData, meta interface{} return fmt.Errorf("Error creating OpenStack compute client: %s", err) } - log.Printf("[DEBUG] Deleting ServerGroup %s", d.Id()) if err := servergroups.Delete(computeClient, d.Id()).ExtractErr(); err != nil { - return fmt.Errorf("Error deleting ServerGroup: %s", err) + return CheckDeleted(d, err, "Error deleting openstack_compute_servergroup_v2") } return nil } - -func resourceServerGroupPoliciesV2(d *schema.ResourceData) []string { - rawPolicies := d.Get("policies").([]interface{}) - policies := make([]string, len(rawPolicies)) - for i, raw := range rawPolicies { - policies[i] = raw.(string) - } - return policies -} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_volume_attach_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_volume_attach_v2.go index fa517414b..f35d1cd74 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_volume_attach_v2.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_compute_volume_attach_v2.go @@ -3,10 +3,8 @@ package openstack import ( "fmt" "log" - "strings" "time" - "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach" "github.com/hashicorp/terraform/helper/resource" @@ -28,30 +26,36 @@ func resourceComputeVolumeAttachV2() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "instance_id": &schema.Schema{ + "instance_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "volume_id": &schema.Schema{ + "volume_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "device": &schema.Schema{ + "device": { Type: schema.TypeString, Computed: true, Optional: true, }, + + "multiattach": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, }, } } @@ -76,28 +80,30 @@ func resourceComputeVolumeAttachV2Create(d *schema.ResourceData, meta interface{ VolumeID: volumeId, } - log.Printf("[DEBUG] Creating volume attachment: %#v", attachOpts) + log.Printf("[DEBUG] openstack_compute_volume_attach_v2 attach options %s: %#v", instanceId, attachOpts) + + if v := d.Get("multiattach").(bool); v { + computeClient.Microversion = "2.60" + } attachment, err := volumeattach.Create(computeClient, instanceId, attachOpts).Extract() if err != nil { - return err + return fmt.Errorf("Error creating openstack_compute_volume_attach_v2 %s: %s", instanceId, err) } stateConf := &resource.StateChangeConf{ Pending: []string{"ATTACHING"}, Target: []string{"ATTACHED"}, - Refresh: resourceComputeVolumeAttachV2AttachFunc(computeClient, instanceId, attachment.ID), + Refresh: computeVolumeAttachV2AttachFunc(computeClient, instanceId, attachment.ID), Timeout: d.Timeout(schema.TimeoutCreate), - Delay: 30 * time.Second, - MinTimeout: 15 * time.Second, + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, } if _, err = stateConf.WaitForState(); err != nil { - return fmt.Errorf("Error attaching OpenStack volume: %s", err) + return fmt.Errorf("Error attaching openstack_compute_volume_attach_v2 %s: %s", instanceId, err) } - log.Printf("[DEBUG] Created volume attachment: %#v", attachment) - // Use the instance ID and attachment ID as the resource ID. // This is because an attachment cannot be retrieved just by its ID alone. id := fmt.Sprintf("%s/%s", instanceId, attachment.ID) @@ -114,17 +120,17 @@ func resourceComputeVolumeAttachV2Read(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error creating OpenStack compute client: %s", err) } - instanceId, attachmentId, err := parseComputeVolumeAttachmentId(d.Id()) + instanceId, attachmentId, err := computeVolumeAttachV2ParseID(d.Id()) if err != nil { return err } attachment, err := volumeattach.Get(computeClient, instanceId, attachmentId).Extract() if err != nil { - return CheckDeleted(d, err, "compute_volume_attach") + return CheckDeleted(d, err, "Error retrieving openstack_compute_volume_attach_v2") } - log.Printf("[DEBUG] Retrieved volume attachment: %#v", attachment) + log.Printf("[DEBUG] Retrieved openstack_compute_volume_attach_v2 %s: %#v", d.Id(), attachment) d.Set("instance_id", attachment.ServerID) d.Set("volume_id", attachment.VolumeID) @@ -141,7 +147,7 @@ func resourceComputeVolumeAttachV2Delete(d *schema.ResourceData, meta interface{ return fmt.Errorf("Error creating OpenStack compute client: %s", err) } - instanceId, attachmentId, err := parseComputeVolumeAttachmentId(d.Id()) + instanceId, attachmentId, err := computeVolumeAttachV2ParseID(d.Id()) if err != nil { return err } @@ -149,74 +155,15 @@ func resourceComputeVolumeAttachV2Delete(d *schema.ResourceData, meta interface{ stateConf := &resource.StateChangeConf{ Pending: []string{""}, Target: []string{"DETACHED"}, - Refresh: resourceComputeVolumeAttachV2DetachFunc(computeClient, instanceId, attachmentId), + Refresh: computeVolumeAttachV2DetachFunc(computeClient, instanceId, attachmentId), Timeout: d.Timeout(schema.TimeoutDelete), - Delay: 15 * time.Second, - MinTimeout: 15 * time.Second, + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, } if _, err = stateConf.WaitForState(); err != nil { - return fmt.Errorf("Error detaching OpenStack volume: %s", err) + return CheckDeleted(d, err, "Error detaching openstack_compute_volume_attach_v2") } return nil } - -func resourceComputeVolumeAttachV2AttachFunc( - computeClient *gophercloud.ServiceClient, instanceId, attachmentId string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - va, err := volumeattach.Get(computeClient, instanceId, attachmentId).Extract() - if err != nil { - if _, ok := err.(gophercloud.ErrDefault404); ok { - return va, "ATTACHING", nil - } - return va, "", err - } - - return va, "ATTACHED", nil - } -} - -func resourceComputeVolumeAttachV2DetachFunc( - computeClient *gophercloud.ServiceClient, instanceId, attachmentId string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - log.Printf("[DEBUG] Attempting to detach OpenStack volume %s from instance %s", - attachmentId, instanceId) - - va, err := volumeattach.Get(computeClient, instanceId, attachmentId).Extract() - if err != nil { - if _, ok := err.(gophercloud.ErrDefault404); ok { - return va, "DETACHED", nil - } - return va, "", err - } - - err = volumeattach.Delete(computeClient, instanceId, attachmentId).ExtractErr() - if err != nil { - if _, ok := err.(gophercloud.ErrDefault404); ok { - return va, "DETACHED", nil - } - - if _, ok := err.(gophercloud.ErrDefault400); ok { - return nil, "", nil - } - - return nil, "", err - } - - log.Printf("[DEBUG] OpenStack Volume Attachment (%s) is still active.", attachmentId) - return nil, "", nil - } -} - -func parseComputeVolumeAttachmentId(id string) (string, string, error) { - idParts := strings.Split(id, "/") - if len(idParts) < 2 { - return "", "", fmt.Errorf("Unable to determine volume attachment ID") - } - - instanceId := idParts[0] - attachmentId := idParts[1] - - return instanceId, attachmentId, nil -} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_containerinfra_cluster_v1.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_containerinfra_cluster_v1.go new file mode 100644 index 000000000..bf1ddfff7 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_containerinfra_cluster_v1.go @@ -0,0 +1,378 @@ +package openstack + +import ( + "fmt" + "log" + "strconv" + "strings" + "time" + + "github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceContainerInfraClusterV1() *schema.Resource { + return &schema.Resource{ + Create: resourceContainerInfraClusterV1Create, + Read: resourceContainerInfraClusterV1Read, + Update: resourceContainerInfraClusterV1Update, + Delete: resourceContainerInfraClusterV1Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(60 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + + "name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + + "project_id": { + Type: schema.TypeString, + ForceNew: true, + Computed: true, + }, + + "user_id": { + Type: schema.TypeString, + ForceNew: true, + Computed: true, + }, + + "created_at": { + Type: schema.TypeString, + ForceNew: false, + Computed: true, + }, + + "updated_at": { + Type: schema.TypeString, + ForceNew: false, + Computed: true, + }, + + "api_address": { + Type: schema.TypeString, + ForceNew: false, + Computed: true, + }, + + "coe_version": { + Type: schema.TypeString, + ForceNew: false, + Computed: true, + }, + + "cluster_template_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: schema.EnvDefaultFunc("OS_MAGNUM_CLUSTER_TEMPLATE", nil), + }, + + "container_version": { + Type: schema.TypeString, + ForceNew: false, + Computed: true, + }, + + "create_timeout": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + Computed: true, + }, + + "discovery_url": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + + "docker_volume_size": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + Computed: true, + }, + + "flavor": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + + "master_flavor": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + + "keypair": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + + "labels": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + Computed: true, + }, + + "master_count": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + Computed: true, + }, + + "node_count": { + Type: schema.TypeInt, + Optional: true, + ForceNew: false, + Computed: true, + }, + + "master_addresses": { + Type: schema.TypeString, + ForceNew: false, + Computed: true, + }, + + "node_addresses": { + Type: schema.TypeString, + ForceNew: false, + Computed: true, + }, + + "stack_id": { + Type: schema.TypeString, + ForceNew: false, + Computed: true, + }, + }, + } +} + +func resourceContainerInfraClusterV1Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + containerInfraClient, err := config.containerInfraV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack container infra client: %s", err) + } + + // Get and check labels map. + rawLabels := d.Get("labels").(map[string]interface{}) + labels, err := expandContainerInfraV1LabelsMap(rawLabels) + if err != nil { + return err + } + + // Determine the flavors to use. + // First check if it was set in the config. + // If not, try using the appropriate environment variable. + flavor, err := containerInfraClusterV1Flavor(d) + if err != nil { + return fmt.Errorf("Unable to determine openstack_containerinfra_cluster_v1 flavor") + } + + masterFlavor, err := containerInfraClusterV1Flavor(d) + if err != nil { + return fmt.Errorf("Unable to determine openstack_containerinfra_cluster_v1 master_flavor") + } + + createOpts := clusters.CreateOpts{ + ClusterTemplateID: d.Get("cluster_template_id").(string), + DiscoveryURL: d.Get("discovery_url").(string), + FlavorID: flavor, + Keypair: d.Get("keypair").(string), + Labels: labels, + MasterFlavorID: masterFlavor, + Name: d.Get("name").(string), + } + + // Set int parameters that will be passed by reference. + createTimeout := d.Get("create_timeout").(int) + if createTimeout > 0 { + createOpts.CreateTimeout = &createTimeout + } + + dockerVolumeSize := d.Get("docker_volume_size").(int) + if dockerVolumeSize > 0 { + createOpts.DockerVolumeSize = &dockerVolumeSize + } + + masterCount := d.Get("master_count").(int) + if masterCount > 0 { + createOpts.MasterCount = &masterCount + } + + nodeCount := d.Get("node_count").(int) + if nodeCount > 0 { + createOpts.NodeCount = &nodeCount + } + + s, err := clusters.Create(containerInfraClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating openstack_containerinfra_cluster_v1: %s", err) + } + + // Store the Cluster ID. + d.SetId(s) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"CREATE_IN_PROGRESS"}, + Target: []string{"CREATE_COMPLETE"}, + Refresh: containerInfraClusterV1StateRefreshFunc(containerInfraClient, s), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 1 * time.Minute, + PollInterval: 20 * time.Second, + } + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for openstack_containerinfra_cluster_v1 %s to become ready: %s", s, err) + } + + log.Printf("[DEBUG] Created openstack_containerinfra_cluster_v1 %s", s) + return resourceContainerInfraClusterV1Read(d, meta) +} + +func resourceContainerInfraClusterV1Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + containerInfraClient, err := config.containerInfraV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack container infra client: %s", err) + } + + s, err := clusters.Get(containerInfraClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "Error retrieving openstack_containerinfra_cluster_v1") + } + + log.Printf("[DEBUG] Retrieved openstack_containerinfra_cluster_v1 %s: %#v", d.Id(), s) + + if err := d.Set("labels", s.Labels); err != nil { + return fmt.Errorf("Unable to set openstack_containerinfra_cluster_v1 labels: %s", err) + } + + d.Set("name", s.Name) + d.Set("api_address", s.APIAddress) + d.Set("coe_version", s.COEVersion) + d.Set("cluster_template_id", s.ClusterTemplateID) + d.Set("container_version", s.ContainerVersion) + d.Set("create_timeout", s.CreateTimeout) + d.Set("discovery_url", s.DiscoveryURL) + d.Set("docker_volume_size", s.DockerVolumeSize) + d.Set("flavor", s.FlavorID) + d.Set("master_flavor", s.MasterFlavorID) + d.Set("keypair", s.KeyPair) + d.Set("master_count", s.MasterCount) + d.Set("node_count", s.NodeCount) + d.Set("master_addresses", s.MasterAddresses) + d.Set("node_addresses", s.NodeAddresses) + d.Set("stack_id", s.StackID) + + if err := d.Set("created_at", s.CreatedAt.Format(time.RFC3339)); err != nil { + log.Printf("[DEBUG] Unable to set openstack_containerinfra_cluster_v1 created_at: %s", err) + } + if err := d.Set("updated_at", s.UpdatedAt.Format(time.RFC3339)); err != nil { + log.Printf("[DEBUG] Unable to set openstack_containerinfra_cluster_v1 updated_at: %s", err) + } + + return nil +} + +func resourceContainerInfraClusterV1Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + containerInfraClient, err := config.containerInfraV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack container infra client: %s", err) + } + + updateOpts := []clusters.UpdateOptsBuilder{} + + if d.HasChange("node_count") { + v := d.Get("node_count").(int) + nodeCount := strconv.Itoa(v) + updateOpts = append(updateOpts, clusters.UpdateOpts{ + Op: clusters.ReplaceOp, + Path: strings.Join([]string{"/", "node_count"}, ""), + Value: nodeCount, + }) + } + + log.Printf( + "[DEBUG] Updating openstack_containerinfra_cluster_v1 %s with options: %#v", d.Id(), updateOpts) + + _, err = clusters.Update(containerInfraClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating openstack_containerinfra_cluster_v1 %s: %s", d.Id(), err) + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"UPDATE_IN_PROGRESS"}, + Target: []string{"UPDATE_COMPLETE"}, + Refresh: containerInfraClusterV1StateRefreshFunc(containerInfraClient, d.Id()), + Timeout: d.Timeout(schema.TimeoutUpdate), + Delay: 1 * time.Minute, + PollInterval: 20 * time.Second, + } + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for openstack_containerinfra_cluster_v1 %s to become updated: %s", d.Id(), err) + } + + return resourceContainerInfraClusterV1Read(d, meta) +} + +func resourceContainerInfraClusterV1Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + containerInfraClient, err := config.containerInfraV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack container infra client: %s", err) + } + + if err := clusters.Delete(containerInfraClient, d.Id()).ExtractErr(); err != nil { + return CheckDeleted(d, err, "Error deleting openstack_containerinfra_cluster_v1") + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"DELETE_IN_PROGRESS"}, + Target: []string{"DELETE_COMPLETE"}, + Refresh: containerInfraClusterV1StateRefreshFunc(containerInfraClient, d.Id()), + Timeout: d.Timeout(schema.TimeoutDelete), + Delay: 30 * time.Second, + PollInterval: 10 * time.Second, + } + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for openstack_containerinfra_cluster_v1 %s to become deleted: %s", d.Id(), err) + } + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_containerinfra_clustertemplate_v1.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_containerinfra_clustertemplate_v1.go new file mode 100644 index 000000000..bf5f17a3b --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_containerinfra_clustertemplate_v1.go @@ -0,0 +1,545 @@ +package openstack + +import ( + "fmt" + "log" + "strconv" + "time" + + "github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" +) + +func resourceContainerInfraClusterTemplateV1() *schema.Resource { + return &schema.Resource{ + Create: resourceContainerInfraClusterTemplateV1Create, + Read: resourceContainerInfraClusterTemplateV1Read, + Update: resourceContainerInfraClusterTemplateV1Update, + Delete: resourceContainerInfraClusterTemplateV1Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: false, + }, + + "project_id": { + Type: schema.TypeString, + ForceNew: true, + Computed: true, + }, + + "user_id": { + Type: schema.TypeString, + ForceNew: true, + Computed: true, + }, + + "created_at": { + Type: schema.TypeString, + ForceNew: false, + Computed: true, + }, + + "updated_at": { + Type: schema.TypeString, + ForceNew: false, + Computed: true, + }, + + "apiserver_port": { + Type: schema.TypeInt, + Optional: true, + ForceNew: false, + ValidateFunc: validation.IntBetween(1024, 65535), + }, + + "coe": { + Type: schema.TypeString, + Required: true, + ForceNew: false, + }, + + "cluster_distro": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + Computed: true, + }, + + "dns_nameserver": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + + "docker_storage_driver": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + + "docker_volume_size": { + Type: schema.TypeInt, + Optional: true, + ForceNew: false, + }, + + "external_network_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + + "fixed_network": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + + "fixed_subnet": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + + "flavor": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + DefaultFunc: schema.EnvDefaultFunc("OS_MAGNUM_FLAVOR", nil), + }, + + "master_flavor": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + DefaultFunc: schema.EnvDefaultFunc("OS_MAGNUM_MASTER_FLAVOR", nil), + }, + + "floating_ip_enabled": { + Type: schema.TypeBool, + Optional: true, + ForceNew: false, + }, + + "http_proxy": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + + "https_proxy": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + + "image": { + Type: schema.TypeString, + Required: true, + ForceNew: false, + DefaultFunc: schema.EnvDefaultFunc("OS_MAGNUM_IMAGE", nil), + }, + + "insecure_registry": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + + "keypair_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + + "labels": { + Type: schema.TypeMap, + Optional: true, + ForceNew: false, + }, + + "master_lb_enabled": { + Type: schema.TypeBool, + Optional: true, + ForceNew: false, + }, + + "network_driver": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + Computed: true, + }, + + "no_proxy": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + + "public": { + Type: schema.TypeBool, + Optional: true, + ForceNew: false, + }, + + "registry_enabled": { + Type: schema.TypeBool, + Optional: true, + ForceNew: false, + }, + + "server_type": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + Computed: true, + }, + + "tls_disabled": { + Type: schema.TypeBool, + Optional: true, + ForceNew: false, + }, + + "volume_driver": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + }, + } +} + +func resourceContainerInfraClusterTemplateV1Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + containerInfraClient, err := config.containerInfraV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack container infra client: %s", err) + } + + // Get boolean parameters that will be passed by reference. + floatingIPEnabled := d.Get("floating_ip_enabled").(bool) + masterLBEnabled := d.Get("master_lb_enabled").(bool) + public := d.Get("public").(bool) + registryEnabled := d.Get("registry_enabled").(bool) + tlsDisabled := d.Get("tls_disabled").(bool) + + // Get and check labels map. + rawLabels := d.Get("labels").(map[string]interface{}) + labels, err := expandContainerInfraV1LabelsMap(rawLabels) + if err != nil { + return err + } + + createOpts := clustertemplates.CreateOpts{ + COE: d.Get("coe").(string), + DNSNameServer: d.Get("dns_nameserver").(string), + DockerStorageDriver: d.Get("docker_storage_driver").(string), + ExternalNetworkID: d.Get("external_network_id").(string), + FixedNetwork: d.Get("fixed_network").(string), + FixedSubnet: d.Get("fixed_subnet").(string), + FlavorID: d.Get("flavor").(string), + MasterFlavorID: d.Get("master_flavor").(string), + FloatingIPEnabled: &floatingIPEnabled, + HTTPProxy: d.Get("http_proxy").(string), + HTTPSProxy: d.Get("https_proxy").(string), + ImageID: d.Get("image").(string), + InsecureRegistry: d.Get("insecure_registry").(string), + KeyPairID: d.Get("keypair_id").(string), + Labels: labels, + MasterLBEnabled: &masterLBEnabled, + Name: d.Get("name").(string), + NetworkDriver: d.Get("network_driver").(string), + NoProxy: d.Get("no_proxy").(string), + Public: &public, + RegistryEnabled: ®istryEnabled, + ServerType: d.Get("server_type").(string), + TLSDisabled: &tlsDisabled, + VolumeDriver: d.Get("volume_driver").(string), + } + + // Set int parameters that will be passed by reference. + apiServerPort := d.Get("apiserver_port").(int) + if apiServerPort > 0 { + createOpts.APIServerPort = &apiServerPort + } + dockerVolumeSize := d.Get("docker_volume_size").(int) + if dockerVolumeSize > 0 { + createOpts.DockerVolumeSize = &dockerVolumeSize + } + + log.Printf("[DEBUG] openstack_containerinfra_clustertemplate_v1 create options: %#v", createOpts) + + s, err := clustertemplates.Create(containerInfraClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating openstack_containerinfra_clustertemplate_v1: %s", err) + } + + d.SetId(s.UUID) + + log.Printf("[DEBUG] Created openstack_containerinfra_clustertemplate_v1 %s: %#v", s.UUID, s) + return resourceContainerInfraClusterTemplateV1Read(d, meta) +} + +func resourceContainerInfraClusterTemplateV1Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + containerInfraClient, err := config.containerInfraV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack container infra client: %s", err) + } + + s, err := clustertemplates.Get(containerInfraClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "Error retrieving openstack_containerinfra_clustertemplate_v1") + } + + log.Printf("[DEBUG] Retrieved openstack_containerinfra_clustertemplate_v1 %s: %#v", d.Id(), s) + + if err := d.Set("labels", s.Labels); err != nil { + return fmt.Errorf("Unable to set labels: %s", err) + } + + d.Set("apiserver_port", s.APIServerPort) + d.Set("coe", s.COE) + d.Set("cluster_distro", s.ClusterDistro) + d.Set("dns_nameserver", s.DNSNameServer) + d.Set("docker_storage_driver", s.DockerStorageDriver) + d.Set("docker_volume_size", s.DockerVolumeSize) + d.Set("external_network_id", s.ExternalNetworkID) + d.Set("fixed_network", s.FixedNetwork) + d.Set("fixed_subnet", s.FixedSubnet) + d.Set("flavor_id", s.FlavorID) + d.Set("master_flavor_id", s.MasterFlavorID) + d.Set("floating_ip_enabled", s.FloatingIPEnabled) + d.Set("http_proxy", s.HTTPProxy) + d.Set("https_proxy", s.HTTPSProxy) + d.Set("image_id", s.ImageID) + d.Set("insecure_registry", s.InsecureRegistry) + d.Set("keypair_id", s.KeyPairID) + d.Set("master_lb_enabled", s.MasterLBEnabled) + d.Set("network_driver", s.NetworkDriver) + d.Set("no_proxy", s.NoProxy) + d.Set("public", s.Public) + d.Set("registry_enabled", s.RegistryEnabled) + d.Set("server_type", s.ServerType) + d.Set("tls_disabled", s.TLSDisabled) + d.Set("volume_driver", s.VolumeDriver) + d.Set("region", GetRegion(d, config)) + d.Set("name", s.Name) + d.Set("project_id", s.ProjectID) + d.Set("user_id", s.UserID) + + if err := d.Set("created_at", s.CreatedAt.Format(time.RFC3339)); err != nil { + log.Printf("[DEBUG] Unable to set openstack_containerinfra_clustertemplate_v1 created_at: %s", err) + } + if err := d.Set("updated_at", s.UpdatedAt.Format(time.RFC3339)); err != nil { + log.Printf("[DEBUG] Unable to set openstack_containerinfra_clustertemplate_v1 updated_at: %s", err) + } + + return nil +} + +func resourceContainerInfraClusterTemplateV1Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + containerInfraClient, err := config.containerInfraV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack container infra client: %s", err) + } + + updateOpts := []clustertemplates.UpdateOptsBuilder{} + + if d.HasChange("name") { + v := d.Get("name").(string) + updateOpts = containerInfraClusterTemplateV1AppendUpdateOpts(updateOpts, "name", v) + } + + if d.HasChange("apiserver_port") { + v := d.Get("apiserver_port").(int) + apiServerPort := strconv.Itoa(v) + updateOpts = containerInfraClusterTemplateV1AppendUpdateOpts( + updateOpts, "apiserver_port", apiServerPort) + } + + if d.HasChange("coe") { + v := d.Get("coe").(string) + updateOpts = containerInfraClusterTemplateV1AppendUpdateOpts(updateOpts, "coe", v) + } + + if d.HasChange("cluster_distro") { + v := d.Get("cluster_distro").(string) + updateOpts = containerInfraClusterTemplateV1AppendUpdateOpts(updateOpts, "cluster_distro", v) + } + + if d.HasChange("dns_nameserver") { + v := d.Get("dns_nameserver").(string) + updateOpts = containerInfraClusterTemplateV1AppendUpdateOpts(updateOpts, "dns_nameserver", v) + } + + if d.HasChange("docker_storage_driver") { + v := d.Get("docker_storage_driver").(string) + updateOpts = containerInfraClusterTemplateV1AppendUpdateOpts(updateOpts, "docker_storage_driver", v) + } + + if d.HasChange("docker_volume_size") { + v := d.Get("docker_volume_size").(int) + dockerVolumeSize := strconv.Itoa(v) + updateOpts = containerInfraClusterTemplateV1AppendUpdateOpts( + updateOpts, "docker_volume_size", dockerVolumeSize) + } + + if d.HasChange("external_network_id") { + v := d.Get("external_network_id").(string) + updateOpts = containerInfraClusterTemplateV1AppendUpdateOpts(updateOpts, "external_network_id", v) + } + + if d.HasChange("fixed_network") { + v := d.Get("fixed_network").(string) + updateOpts = containerInfraClusterTemplateV1AppendUpdateOpts(updateOpts, "fixed_network", v) + } + + if d.HasChange("fixed_subnet") { + v := d.Get("fixed_subnet").(string) + updateOpts = containerInfraClusterTemplateV1AppendUpdateOpts(updateOpts, "fixed_subnet", v) + } + + if d.HasChange("flavor") { + v := d.Get("flavor").(string) + updateOpts = containerInfraClusterTemplateV1AppendUpdateOpts(updateOpts, "flavor_id", v) + } + + if d.HasChange("master_flavor") { + v := d.Get("master_flavor").(string) + updateOpts = containerInfraClusterTemplateV1AppendUpdateOpts(updateOpts, "master_flavor_id", v) + } + + if d.HasChange("floating_ip_enabled") { + v := d.Get("floating_ip_enabled").(bool) + floatingIPEnabled := strconv.FormatBool(v) + updateOpts = containerInfraClusterTemplateV1AppendUpdateOpts( + updateOpts, "floating_ip_enabled", floatingIPEnabled) + } + + if d.HasChange("http_proxy") { + v := d.Get("http_proxy").(string) + updateOpts = containerInfraClusterTemplateV1AppendUpdateOpts(updateOpts, "http_proxy", v) + } + + if d.HasChange("https_proxy") { + v := d.Get("https_proxy").(string) + updateOpts = containerInfraClusterTemplateV1AppendUpdateOpts(updateOpts, "https_proxy", v) + } + + if d.HasChange("image") { + v := d.Get("image").(string) + updateOpts = containerInfraClusterTemplateV1AppendUpdateOpts(updateOpts, "image_id", v) + } + + if d.HasChange("insecure_registry") { + v := d.Get("insecure_registry").(string) + updateOpts = containerInfraClusterTemplateV1AppendUpdateOpts(updateOpts, "insecure_registry", v) + } + + if d.HasChange("keypair_id") { + v := d.Get("keypair_id").(string) + updateOpts = containerInfraClusterTemplateV1AppendUpdateOpts(updateOpts, "keypair_id", v) + } + + if d.HasChange("labels") { + rawLabels := d.Get("labels").(map[string]interface{}) + v, err := expandContainerInfraV1LabelsString(rawLabels) + if err != nil { + return err + } + updateOpts = containerInfraClusterTemplateV1AppendUpdateOpts(updateOpts, "labels", v) + } + + if d.HasChange("master_lb_enabled") { + v := d.Get("master_lb_enabled").(bool) + masterLBEnabled := strconv.FormatBool(v) + updateOpts = containerInfraClusterTemplateV1AppendUpdateOpts( + updateOpts, "master_lb_enabled", masterLBEnabled) + } + + if d.HasChange("network_driver") { + v := d.Get("network_driver").(string) + updateOpts = containerInfraClusterTemplateV1AppendUpdateOpts(updateOpts, "network_driver", v) + } + + if d.HasChange("no_proxy") { + v := d.Get("no_proxy").(string) + updateOpts = containerInfraClusterTemplateV1AppendUpdateOpts(updateOpts, "no_proxy", v) + } + + if d.HasChange("public") { + v := d.Get("public").(bool) + public := strconv.FormatBool(v) + updateOpts = containerInfraClusterTemplateV1AppendUpdateOpts(updateOpts, "public", public) + } + + if d.HasChange("registry_enabled") { + v := d.Get("registry_enabled").(bool) + registryEnabled := strconv.FormatBool(v) + updateOpts = containerInfraClusterTemplateV1AppendUpdateOpts( + updateOpts, "registry_enabled", registryEnabled) + } + + if d.HasChange("server_type") { + v := d.Get("server_type").(string) + updateOpts = containerInfraClusterTemplateV1AppendUpdateOpts(updateOpts, "server_type", v) + } + + if d.HasChange("tls_disabled") { + v := d.Get("tls_disabled").(bool) + tlsDisabled := strconv.FormatBool(v) + updateOpts = containerInfraClusterTemplateV1AppendUpdateOpts(updateOpts, "tls_disabled", tlsDisabled) + } + + if d.HasChange("volume_driver") { + v := d.Get("volume_driver").(string) + updateOpts = containerInfraClusterTemplateV1AppendUpdateOpts(updateOpts, "volume_driver", v) + } + + log.Printf( + "[DEBUG] Updating openstack_containerinfra_clustertemplate_v1 %s with options: %#v", d.Id(), updateOpts) + + _, err = clustertemplates.Update(containerInfraClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating openstack_containerinfra_clustertemplate_v1 %s: %s", d.Id(), err) + } + + return resourceContainerInfraClusterTemplateV1Read(d, meta) +} + +func resourceContainerInfraClusterTemplateV1Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + containerInfraClient, err := config.containerInfraV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack container infra client: %s", err) + } + + if err := clustertemplates.Delete(containerInfraClient, d.Id()).ExtractErr(); err != nil { + return CheckDeleted(d, err, "Error deleting openstack_containerinfra_clustertemplate_v1") + } + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_db_configuration_v1.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_db_configuration_v1.go new file mode 100644 index 000000000..70f4834d8 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_db_configuration_v1.go @@ -0,0 +1,229 @@ +package openstack + +import ( + "fmt" + "log" + "strconv" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/db/v1/configurations" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceDatabaseConfigurationV1() *schema.Resource { + return &schema.Resource{ + Create: resourceDatabaseConfigurationV1Create, + Read: resourceDatabaseConfigurationV1Read, + Delete: resourceDatabaseConfigurationV1Delete, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""), + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "description": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "datastore": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "version": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + }, + MaxItems: 1, + }, + "configuration": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "value": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + }, + }, + }, + } +} + +func resourceDatabaseConfigurationV1Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + databaseV1Client, err := config.databaseV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating cloud database client: %s", err) + } + + var datastore configurations.DatastoreOpts + if p, ok := d.GetOk("datastore"); ok { + pV := (p.([]interface{}))[0].(map[string]interface{}) + + datastore = configurations.DatastoreOpts{ + Version: pV["version"].(string), + Type: pV["type"].(string), + } + } + + createOpts := &configurations.CreateOpts{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + } + + createOpts.Datastore = &datastore + + values := make(map[string]interface{}) + if p, ok := d.GetOk("configuration"); ok { + + listSlice, _ := p.([]interface{}) + for _, d := range listSlice { + if z, ok := d.(map[string]interface{}); ok { + name := z["name"].(string) + value := z["value"].(interface{}) + + // check if value can be converted into int + if valueInt, err := strconv.Atoi(value.(string)); err == nil { + value = valueInt + } + + values[name] = value + } + } + } + + createOpts.Values = values + + log.Printf("[DEBUG] Create Options: %#v", createOpts) + cgroup, err := configurations.Create(databaseV1Client, createOpts).Extract() + + if err != nil { + return fmt.Errorf("Error creating cloud database configuration: %s", err) + } + log.Printf("[INFO] configuration ID: %s", cgroup.ID) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"BUILD"}, + Target: []string{"ACTIVE"}, + Refresh: DatabaseConfigurationV1StateRefreshFunc(databaseV1Client, cgroup.ID), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for configuration (%s) to become ready: %s", + cgroup.ID, err) + } + + // Store the ID now + d.SetId(cgroup.ID) + + return resourceDatabaseConfigurationV1Read(d, meta) +} + +func resourceDatabaseConfigurationV1Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + databaseV1Client, err := config.databaseV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack cloud database client: %s", err) + } + + cgroup, err := configurations.Get(databaseV1Client, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "configuration") + } + + log.Printf("[DEBUG] Retrieved configuration %s: %+v", d.Id(), cgroup) + + d.Set("name", cgroup.Name) + d.Set("description", cgroup.Description) + d.Set("region", GetRegion(d, config)) + + return nil +} + +func resourceDatabaseConfigurationV1Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + databaseV1Client, err := config.databaseV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating RS cloud instance client: %s", err) + } + + log.Printf("[DEBUG] Deleting cloud database configuration %s", d.Id()) + err = configurations.Delete(databaseV1Client, d.Id()).ExtractErr() + if err != nil { + return fmt.Errorf("Error deleting cloud configuration: %s", err) + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"ACTIVE", "SHUTOFF"}, + Target: []string{"DELETED"}, + Refresh: DatabaseConfigurationV1StateRefreshFunc(databaseV1Client, d.Id()), + Timeout: d.Timeout(schema.TimeoutDelete), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for configuration (%s) to delete: %s", + d.Id(), err) + } + + d.SetId("") + return nil +} + +// DatabaseConfigurationV1StateRefreshFunc returns a resource.StateRefreshFunc that is used to watch +// an cloud database instance. +func DatabaseConfigurationV1StateRefreshFunc(client *gophercloud.ServiceClient, cgroupID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + i, err := configurations.Get(client, cgroupID).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + return i, "DELETED", nil + } + return nil, "", err + } + + return i, "ACTIVE", nil + } +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_db_database_v1.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_db_database_v1.go new file mode 100644 index 000000000..dc95ce93e --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_db_database_v1.go @@ -0,0 +1,210 @@ +package openstack + +import ( + "fmt" + "log" + "strings" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/db/v1/databases" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceDatabaseDatabaseV1() *schema.Resource { + return &schema.Resource{ + Create: resourceDatabaseDatabaseV1Create, + Read: resourceDatabaseDatabaseV1Read, + Delete: resourceDatabaseDatabaseV1Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""), + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "instance_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceDatabaseDatabaseV1Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + databaseV1Client, err := config.databaseV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating cloud database client: %s", err) + } + + dbName := d.Get("name").(string) + instanceID := d.Get("instance_id").(string) + + var dbs databases.BatchCreateOpts + dbs = append(dbs, databases.CreateOpts{ + Name: dbName, + }) + + exists, err := DatabaseDatabaseV1State(databaseV1Client, instanceID, dbName) + if err != nil { + return fmt.Errorf("Error checking database status: %s", err) + } + + if exists { + return fmt.Errorf("Database %s exists on instance %s", dbName, instanceID) + } + + err = databases.Create(databaseV1Client, instanceID, dbs).ExtractErr() + if err != nil { + return err + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"BUILD"}, + Target: []string{"ACTIVE"}, + Refresh: DatabaseDatabaseV1StateRefreshFunc(databaseV1Client, instanceID, dbName), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for database to become ready: %s", err) + } + + // Store the ID now + d.SetId(fmt.Sprintf("%s/%s", instanceID, dbName)) + + return resourceDatabaseDatabaseV1Read(d, meta) +} + +func resourceDatabaseDatabaseV1Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + databaseV1Client, err := config.databaseV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating database client: %s", err) + } + + dbID := strings.SplitN(d.Id(), "/", 2) + if len(dbID) != 2 { + return fmt.Errorf("Invalid openstack_db_database_v1 ID format") + } + + instanceID := dbID[0] + dbName := dbID[1] + + exists, err := DatabaseDatabaseV1State(databaseV1Client, instanceID, dbName) + if err != nil { + return fmt.Errorf("Error checking database status: %s", err) + } + + if !exists { + return fmt.Errorf("database %s was not found", err) + } + + log.Printf("[DEBUG] Retrieved database %s", dbName) + + d.Set("name", dbName) + d.Set("instance_id", instanceID) + + return nil +} + +func resourceDatabaseDatabaseV1Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + databaseV1Client, err := config.databaseV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating cloud database client: %s", err) + } + + dbID := strings.SplitN(d.Id(), "/", 2) + if len(dbID) != 2 { + return fmt.Errorf("Invalid openstack_db_database_v1 ID: %s", d.Id()) + } + + instanceID := dbID[0] + dbName := dbID[1] + + exists, err := DatabaseDatabaseV1State(databaseV1Client, instanceID, dbName) + if err != nil { + return fmt.Errorf("Error checking database status: %s", err) + } + + if !exists { + return nil + } + + err = databases.Delete(databaseV1Client, instanceID, dbName).ExtractErr() + if err != nil { + return fmt.Errorf("Error deleting database %s: %s", dbName, err) + } + + return nil +} + +// DatabaseDatabaseV1StateRefreshFunc returns a resource.StateRefreshFunc +// that is used to watch a database. +func DatabaseDatabaseV1StateRefreshFunc(client *gophercloud.ServiceClient, instanceID string, dbName string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + pages, err := databases.List(client, instanceID).AllPages() + if err != nil { + return nil, "", fmt.Errorf("Unable to retrieve databases: %s", err) + } + + allDatabases, err := databases.ExtractDBs(pages) + if err != nil { + return nil, "", fmt.Errorf("Unable to extract databases: %s", err) + } + + for _, v := range allDatabases { + if v.Name == dbName { + return v, "ACTIVE", nil + } + } + + return nil, "BUILD", nil + } +} + +func DatabaseDatabaseV1State(client *gophercloud.ServiceClient, instanceID string, dbName string) (exists bool, err error) { + exists = false + err = nil + + pages, err := databases.List(client, instanceID).AllPages() + if err != nil { + return + } + + allDatabases, err := databases.ExtractDBs(pages) + if err != nil { + return + } + + for _, v := range allDatabases { + if v.Name == dbName { + exists = true + return + } + } + + return false, err +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_db_instance_v1.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_db_instance_v1.go new file mode 100644 index 000000000..f72e26597 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_db_instance_v1.go @@ -0,0 +1,406 @@ +package openstack + +import ( + "fmt" + "log" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/db/v1/databases" + "github.com/gophercloud/gophercloud/openstack/db/v1/instances" + "github.com/gophercloud/gophercloud/openstack/db/v1/users" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceDatabaseInstanceV1() *schema.Resource { + return &schema.Resource{ + Create: resourceDatabaseInstanceV1Create, + Read: resourceDatabaseInstanceV1Read, + Delete: resourceDatabaseInstanceV1Delete, + Update: resourceDatabaseInstanceUpdate, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""), + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "flavor_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + DefaultFunc: schema.EnvDefaultFunc("OS_FLAVOR_ID", nil), + }, + "size": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + "datastore": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "version": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + }, + }, + "network": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "uuid": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "port": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "fixed_ip_v4": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "fixed_ip_v6": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + }, + }, + "database": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "charset": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "collate": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + }, + }, + "user": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "password": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Sensitive: true, + }, + "host": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "databases": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + "configuration_id": { + Type: schema.TypeString, + Optional: true, + Computed: false, + ForceNew: false, + }, + }, + } +} + +func resourceDatabaseInstanceV1Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + databaseV1Client, err := config.databaseV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating database client: %s", err) + } + + createOpts := &instances.CreateOpts{ + FlavorRef: d.Get("flavor_id").(string), + Name: d.Get("name").(string), + Size: d.Get("size").(int), + } + + var datastore instances.DatastoreOpts + if v, ok := d.GetOk("datastore"); ok { + if v, ok := v.([]interface{}); ok && len(v) > 0 { + ds := v[0].(map[string]interface{}) + datastore = instances.DatastoreOpts{ + Version: ds["version"].(string), + Type: ds["type"].(string), + } + createOpts.Datastore = &datastore + } + } + + // networks + var networks []instances.NetworkOpts + + if v, ok := d.GetOk("network"); ok { + if networkList, ok := v.([]interface{}); ok { + for _, v := range networkList { + network := v.(map[string]interface{}) + networks = append(networks, instances.NetworkOpts{ + UUID: network["uuid"].(string), + Port: network["port"].(string), + V4FixedIP: network["fixed_ip_v4"].(string), + V6FixedIP: network["fixed_ip_v6"].(string), + }) + } + } + } + + createOpts.Networks = networks + + // databases + var dbs databases.BatchCreateOpts + + if v, ok := d.GetOk("database"); ok { + if databaseList, ok := v.([]interface{}); ok { + for _, v := range databaseList { + db := v.(map[string]interface{}) + dbs = append(dbs, databases.CreateOpts{ + Name: db["name"].(string), + CharSet: db["charset"].(string), + Collate: db["collate"].(string), + }) + } + } + } + + createOpts.Databases = dbs + + // users + var UserList users.BatchCreateOpts + + if v, ok := d.GetOk("user"); ok { + if userList, ok := v.([]interface{}); ok { + for _, v := range userList { + user := v.(map[string]interface{}) + UserList = append(UserList, users.CreateOpts{ + Name: user["name"].(string), + Password: user["password"].(string), + Databases: resourceDBv1GetDatabases(user["databases"]), + Host: user["host"].(string), + }) + } + } + } + + createOpts.Users = UserList + + log.Printf("[DEBUG] Create Options: %#v", createOpts) + instance, err := instances.Create(databaseV1Client, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating database instance: %s", err) + } + log.Printf("[INFO] database instance ID: %s", instance.ID) + + // Wait for the instance to become available. + log.Printf( + "[DEBUG] Waiting for database instance (%s) to become available", + instance.ID) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"BUILD"}, + Target: []string{"ACTIVE"}, + Refresh: DatabaseInstanceV1StateRefreshFunc(databaseV1Client, instance.ID), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for database instance (%s) to become ready: %s", + instance.ID, err) + } + + if configuration, ok := d.GetOk("configuration_id"); ok { + err := instances.AttachConfigurationGroup(databaseV1Client, instance.ID, configuration.(string)).ExtractErr() + if err != nil { + return err + } + log.Printf("Attaching configuration %v to the instance %v", configuration, instance.ID) + } + + // Store the ID now + d.SetId(instance.ID) + + return resourceDatabaseInstanceV1Read(d, meta) +} + +func resourceDatabaseInstanceV1Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + databaseV1Client, err := config.databaseV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating database client: %s", err) + } + + instance, err := instances.Get(databaseV1Client, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "instance") + } + + log.Printf("[DEBUG] Retrieved database instance %s: %+v", d.Id(), instance) + + d.Set("name", instance.Name) + d.Set("flavor_id", instance.Flavor) + d.Set("datastore", instance.Datastore) + d.Set("region", GetRegion(d, config)) + + return nil +} + +func resourceDatabaseInstanceUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + databaseV1Client, err := config.databaseV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating database client: %s", err) + } + + if d.HasChange("configuration_id") { + old, new := d.GetChange("configuration_id") + + err := instances.DetachConfigurationGroup(databaseV1Client, d.Id()).ExtractErr() + if err != nil { + return err + } + log.Printf("Detaching configuration %v from the instance %v", old, d.Id()) + + if new != "" { + err := instances.AttachConfigurationGroup(databaseV1Client, d.Id(), new.(string)).ExtractErr() + if err != nil { + return err + } + log.Printf("Attaching configuration %v to the instance %v", new, d.Id()) + } + } + + return resourceDatabaseInstanceV1Read(d, meta) +} + +func resourceDatabaseInstanceV1Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + databaseV1Client, err := config.databaseV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating database client: %s", err) + } + + log.Printf("[DEBUG] Deleting database instance %s", d.Id()) + err = instances.Delete(databaseV1Client, d.Id()).ExtractErr() + if err != nil { + return fmt.Errorf("Error deleting database instance: %s", err) + } + + // Wait for the database to delete before moving on. + log.Printf("[DEBUG] Waiting for database instance (%s) to delete", d.Id()) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"ACTIVE", "SHUTDOWN"}, + Target: []string{"DELETED"}, + Refresh: DatabaseInstanceV1StateRefreshFunc(databaseV1Client, d.Id()), + Timeout: d.Timeout(schema.TimeoutDelete), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for database instance (%s) to delete: %s", + d.Id(), err) + } + + return nil +} + +// DatabaseInstanceV1StateRefreshFunc returns a resource.StateRefreshFunc +// that is used to watch a database instance. +func DatabaseInstanceV1StateRefreshFunc(client *gophercloud.ServiceClient, instanceID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + i, err := instances.Get(client, instanceID).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + return i, "DELETED", nil + } + return nil, "", err + } + + if i.Status == "error" { + return i, i.Status, fmt.Errorf("There was an error creating the database instance.") + } + + return i, i.Status, nil + } +} + +func resourceDBv1GetDatabases(v interface{}) databases.BatchCreateOpts { + var dbs databases.BatchCreateOpts + + if v, ok := v.(*schema.Set); ok { + for _, db := range v.List() { + dbs = append(dbs, databases.CreateOpts{ + Name: db.(string), + }) + } + } + + return dbs +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_db_user_v1.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_db_user_v1.go new file mode 100644 index 000000000..4936c11cb --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_db_user_v1.go @@ -0,0 +1,236 @@ +package openstack + +import ( + "fmt" + "log" + "strings" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/db/v1/databases" + "github.com/gophercloud/gophercloud/openstack/db/v1/users" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceDatabaseUserV1() *schema.Resource { + return &schema.Resource{ + Create: resourceDatabaseUserV1Create, + Read: resourceDatabaseUserV1Read, + Delete: resourceDatabaseUserV1Delete, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""), + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "instance_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "password": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Sensitive: true, + }, + "host": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "databases": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + }, + } +} + +func resourceDatabaseUserV1Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + databaseV1Client, err := config.databaseV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating cloud database client: %s", err) + } + + userName := d.Get("name").(string) + rawDatabases := d.Get("databases").(*schema.Set).List() + instanceID := d.Get("instance_id").(string) + + var dbs databases.BatchCreateOpts + for _, db := range rawDatabases { + dbs = append(dbs, databases.CreateOpts{ + Name: db.(string), + }) + } + + var usersList users.BatchCreateOpts + usersList = append(usersList, users.CreateOpts{ + Name: userName, + Password: d.Get("password").(string), + Host: d.Get("host").(string), + Databases: dbs, + }) + + users.Create(databaseV1Client, instanceID, usersList) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"BUILD"}, + Target: []string{"ACTIVE"}, + Refresh: DatabaseUserV1StateRefreshFunc(databaseV1Client, instanceID, userName), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for user (%s) to be created", err) + } + + // Store the ID now + d.SetId(fmt.Sprintf("%s/%s", instanceID, userName)) + + return resourceDatabaseUserV1Read(d, meta) +} + +func resourceDatabaseUserV1Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + databaseV1Client, err := config.databaseV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating cloud database client: %s", err) + } + + userID := strings.SplitN(d.Id(), "/", 2) + if len(userID) != 2 { + return fmt.Errorf("Invalid openstack_db_user_v1 ID format") + } + + instanceID := userID[0] + userName := userID[1] + + exists, userObj, err := DatabaseUserV1State(databaseV1Client, instanceID, userName) + if err != nil { + return fmt.Errorf("Error checking user status: %s", err) + } + + if !exists { + return fmt.Errorf("User %s was not found: %s", userName, err) + } + + log.Printf("[DEBUG] Retrieved user %s", userName) + + d.Set("name", userName) + + var databases []string + for _, dbName := range userObj.Databases { + databases = append(databases, dbName.Name) + } + + if err := d.Set("databases", databases); err != nil { + return fmt.Errorf("Unable to set databases: %s", err) + } + + return nil +} + +func resourceDatabaseUserV1Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + databaseV1Client, err := config.databaseV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating cloud database client: %s", err) + } + + userID := strings.SplitN(d.Id(), "/", 2) + if len(userID) != 2 { + return fmt.Errorf("Invalid openstack_db_user_v1 ID format") + } + + instanceID := userID[0] + userName := userID[1] + + exists, _, err := DatabaseUserV1State(databaseV1Client, instanceID, userName) + if err != nil { + return fmt.Errorf("Error checking user status: %s", err) + } + + if !exists { + log.Printf("User %s was not found on instance %s", userName, instanceID) + return nil + } + + log.Printf("[DEBUG] Retrieved user %s", userName) + + users.Delete(databaseV1Client, instanceID, userName) + + d.SetId("") + return nil +} + +// DatabaseUserV1StateRefreshFunc returns a resource.StateRefreshFunc that is used to watch db user. +func DatabaseUserV1StateRefreshFunc(client *gophercloud.ServiceClient, instanceID string, userName string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + + pages, err := users.List(client, instanceID).AllPages() + if err != nil { + return nil, "", fmt.Errorf("Unable to retrieve users, pages: %s", err) + } + + allUsers, err := users.ExtractUsers(pages) + if err != nil { + return nil, "", fmt.Errorf("Unable to retrieve users, extract: %s", err) + } + + for _, v := range allUsers { + if v.Name == userName { + return v, "ACTIVE", nil + } + } + + return nil, "BUILD", nil + } +} + +// DatabaseUserV1State is used to check whether user exists on particular database instance +func DatabaseUserV1State(client *gophercloud.ServiceClient, instanceID string, userName string) (exists bool, userObj users.User, err error) { + exists = false + err = nil + + pages, err := users.List(client, instanceID).AllPages() + if err != nil { + return + } + + allUsers, err := users.ExtractUsers(pages) + if err != nil { + return + } + + for _, v := range allUsers { + if v.Name == userName { + exists = true + userObj = v + return + } + } + + return false, userObj, err +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_dns_recordset_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_dns_recordset_v2.go index 1a7173b1d..a283f777f 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_dns_recordset_v2.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_dns_recordset_v2.go @@ -3,10 +3,8 @@ package openstack import ( "fmt" "log" - "strings" "time" - "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets" "github.com/hashicorp/terraform/helper/resource" @@ -20,7 +18,7 @@ func resourceDNSRecordSetV2() *schema.Resource { Update: resourceDNSRecordSetV2Update, Delete: resourceDNSRecordSetV2Delete, Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, + State: resourceDNSRecordSetV2Import, }, Timeouts: &schema.ResourceTimeout{ @@ -30,46 +28,56 @@ func resourceDNSRecordSetV2() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, - "zone_id": &schema.Schema{ + + "zone_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "name": &schema.Schema{ + + "name": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "description": &schema.Schema{ + + "description": { Type: schema.TypeString, Optional: true, ForceNew: false, }, - "records": &schema.Schema{ + + "records": { Type: schema.TypeList, Optional: true, ForceNew: false, - Elem: &schema.Schema{Type: schema.TypeString}, + Elem: &schema.Schema{ + Type: schema.TypeString, + StateFunc: dnsRecordSetV2RecordsStateFunc, + }, }, - "ttl": &schema.Schema{ + + "ttl": { Type: schema.TypeInt, Optional: true, Computed: true, ForceNew: false, }, - "type": &schema.Schema{ + + "type": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "value_specs": &schema.Schema{ + + "value_specs": { Type: schema.TypeMap, Optional: true, ForceNew: true, @@ -85,11 +93,7 @@ func resourceDNSRecordSetV2Create(d *schema.ResourceData, meta interface{}) erro return fmt.Errorf("Error creating OpenStack DNS client: %s", err) } - recordsraw := d.Get("records").([]interface{}) - records := make([]string, len(recordsraw)) - for i, recordraw := range recordsraw { - records[i] = recordraw.(string) - } + records := expandDNSRecordSetV2Records(d.Get("records").([]interface{})) createOpts := RecordSetCreateOpts{ recordsets.CreateOpts{ @@ -102,19 +106,18 @@ func resourceDNSRecordSetV2Create(d *schema.ResourceData, meta interface{}) erro MapValueSpecs(d), } - zoneID := d.Get("zone_id").(string) + log.Printf("[DEBUG] openstack_dns_recordset_v2 create options: %#v", createOpts) - log.Printf("[DEBUG] Create Options: %#v", createOpts) + zoneID := d.Get("zone_id").(string) n, err := recordsets.Create(dnsClient, zoneID, createOpts).Extract() if err != nil { - return fmt.Errorf("Error creating OpenStack DNS record set: %s", err) + return fmt.Errorf("Error creating openstack_dns_recordset_v2: %s", err) } - log.Printf("[DEBUG] Waiting for DNS record set (%s) to become available", n.ID) stateConf := &resource.StateChangeConf{ Target: []string{"ACTIVE"}, Pending: []string{"PENDING"}, - Refresh: waitForDNSRecordSet(dnsClient, zoneID, n.ID), + Refresh: dnsRecordSetV2RefreshFunc(dnsClient, zoneID, n.ID), Timeout: d.Timeout(schema.TimeoutCreate), Delay: 5 * time.Second, MinTimeout: 3 * time.Second, @@ -125,7 +128,13 @@ func resourceDNSRecordSetV2Create(d *schema.ResourceData, meta interface{}) erro id := fmt.Sprintf("%s/%s", zoneID, n.ID) d.SetId(id) - log.Printf("[DEBUG] Created OpenStack DNS record set %s: %#v", n.ID, n) + // This is a workaround to store the modified IP addresses in the state + // because we don't want to make records computed or change it to TypeSet + // in order to retain backwards compatibility. + // Because of the StateFunc, this will not cause issues. + d.Set("records", records) + + log.Printf("[DEBUG] Created openstack_dns_recordset_v2 %s: %#v", n.ID, n) return resourceDNSRecordSetV2Read(d, meta) } @@ -137,25 +146,24 @@ func resourceDNSRecordSetV2Read(d *schema.ResourceData, meta interface{}) error } // Obtain relevant info from parsing the ID - zoneID, recordsetID, err := parseDNSV2RecordSetID(d.Id()) + zoneID, recordsetID, err := dnsRecordSetV2ParseID(d.Id()) if err != nil { return err } n, err := recordsets.Get(dnsClient, zoneID, recordsetID).Extract() if err != nil { - return CheckDeleted(d, err, "record_set") + return CheckDeleted(d, err, "Error retrieving openstack_dns_recordset_v2") } - log.Printf("[DEBUG] Retrieved record set %s: %#v", recordsetID, n) + log.Printf("[DEBUG] Retrieved openstack_dns_recordset_v2 %s: %#v", recordsetID, n) d.Set("name", n.Name) d.Set("description", n.Description) d.Set("ttl", n.TTL) d.Set("type", n.Type) - d.Set("records", n.Records) - d.Set("region", GetRegion(d, config)) d.Set("zone_id", zoneID) + d.Set("region", GetRegion(d, config)) return nil } @@ -173,36 +181,32 @@ func resourceDNSRecordSetV2Update(d *schema.ResourceData, meta interface{}) erro } if d.HasChange("records") { - recordsraw := d.Get("records").([]interface{}) - records := make([]string, len(recordsraw)) - for i, recordraw := range recordsraw { - records[i] = recordraw.(string) - } + records := expandDNSRecordSetV2Records(d.Get("records").([]interface{})) updateOpts.Records = records } if d.HasChange("description") { - updateOpts.Description = d.Get("description").(string) + description := d.Get("description").(string) + updateOpts.Description = &description } // Obtain relevant info from parsing the ID - zoneID, recordsetID, err := parseDNSV2RecordSetID(d.Id()) + zoneID, recordsetID, err := dnsRecordSetV2ParseID(d.Id()) if err != nil { return err } - log.Printf("[DEBUG] Updating record set %s with options: %#v", recordsetID, updateOpts) + log.Printf("[DEBUG] Updating openstack_dns_recordset_v2 %s with options: %#v", recordsetID, updateOpts) _, err = recordsets.Update(dnsClient, zoneID, recordsetID, updateOpts).Extract() if err != nil { - return fmt.Errorf("Error updating OpenStack DNS record set: %s", err) + return fmt.Errorf("Error updating openstack_dns_recordset_v2 %s: %s", d.Id(), err) } - log.Printf("[DEBUG] Waiting for DNS record set (%s) to update", recordsetID) stateConf := &resource.StateChangeConf{ Target: []string{"ACTIVE"}, Pending: []string{"PENDING"}, - Refresh: waitForDNSRecordSet(dnsClient, zoneID, recordsetID), + Refresh: dnsRecordSetV2RefreshFunc(dnsClient, zoneID, recordsetID), Timeout: d.Timeout(schema.TimeoutUpdate), Delay: 5 * time.Second, MinTimeout: 3 * time.Second, @@ -221,21 +225,20 @@ func resourceDNSRecordSetV2Delete(d *schema.ResourceData, meta interface{}) erro } // Obtain relevant info from parsing the ID - zoneID, recordsetID, err := parseDNSV2RecordSetID(d.Id()) + zoneID, recordsetID, err := dnsRecordSetV2ParseID(d.Id()) if err != nil { return err } err = recordsets.Delete(dnsClient, zoneID, recordsetID).ExtractErr() if err != nil { - return fmt.Errorf("Error deleting OpenStack DNS record set: %s", err) + return CheckDeleted(d, err, "Error deleting openstack_dns_recordset_v2") } - log.Printf("[DEBUG] Waiting for DNS record set (%s) to be deleted", recordsetID) stateConf := &resource.StateChangeConf{ Target: []string{"DELETED"}, Pending: []string{"ACTIVE", "PENDING"}, - Refresh: waitForDNSRecordSet(dnsClient, zoneID, recordsetID), + Refresh: dnsRecordSetV2RefreshFunc(dnsClient, zoneID, recordsetID), Timeout: d.Timeout(schema.TimeoutDelete), Delay: 5 * time.Second, MinTimeout: 3 * time.Second, @@ -243,34 +246,26 @@ func resourceDNSRecordSetV2Delete(d *schema.ResourceData, meta interface{}) erro _, err = stateConf.WaitForState() - d.SetId("") return nil } -func waitForDNSRecordSet(dnsClient *gophercloud.ServiceClient, zoneID, recordsetId string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - recordset, err := recordsets.Get(dnsClient, zoneID, recordsetId).Extract() - if err != nil { - if _, ok := err.(gophercloud.ErrDefault404); ok { - return recordset, "DELETED", nil - } - - return nil, "", err - } - - log.Printf("[DEBUG] OpenStack DNS record set (%s) current status: %s", recordset.ID, recordset.Status) - return recordset, recordset.Status, nil - } -} - -func parseDNSV2RecordSetID(id string) (string, string, error) { - idParts := strings.Split(id, "/") - if len(idParts) != 2 { - return "", "", fmt.Errorf("Unable to determine DNS record set ID from raw ID: %s", id) +func resourceDNSRecordSetV2Import(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + dnsClient, err := config.dnsV2Client(GetRegion(d, config)) + if err != nil { + return nil, fmt.Errorf("Error creating OpenStack DNS client: %s", err) } - zoneID := idParts[0] - recordsetID := idParts[1] + zoneID, recordsetID, err := dnsRecordSetV2ParseID(d.Id()) + if err != nil { + return nil, err + } - return zoneID, recordsetID, nil + n, err := recordsets.Get(dnsClient, zoneID, recordsetID).Extract() + if err != nil { + return nil, fmt.Errorf("Error retrieving openstack_dns_recordset_v2 %s: %s", d.Id(), err) + } + + d.Set("records", n.Records) + return []*schema.ResourceData{d}, nil } diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_dns_zone_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_dns_zone_v2.go index a3028e194..3faad35e9 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_dns_zone_v2.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_dns_zone_v2.go @@ -5,11 +5,11 @@ import ( "log" "time" - "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/dns/v2/zones" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" ) func resourceDNSZoneV2() *schema.Resource { @@ -29,52 +29,62 @@ func resourceDNSZoneV2() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "name": &schema.Schema{ + + "name": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "email": &schema.Schema{ + + "email": { Type: schema.TypeString, Optional: true, ForceNew: false, }, - "type": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - ValidateFunc: resourceDNSZoneV2ValidType, + + "type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + "PRIMARY", "SECONDARY", + }, false), }, - "attributes": &schema.Schema{ + + "attributes": { Type: schema.TypeMap, Optional: true, ForceNew: true, }, - "ttl": &schema.Schema{ + + "ttl": { Type: schema.TypeInt, Optional: true, Computed: true, ForceNew: false, }, - "description": &schema.Schema{ + + "description": { Type: schema.TypeString, Optional: true, ForceNew: false, }, - "masters": &schema.Schema{ + + "masters": { Type: schema.TypeSet, Optional: true, ForceNew: false, Elem: &schema.Schema{Type: schema.TypeString}, }, - "value_specs": &schema.Schema{ + + "value_specs": { Type: schema.TypeMap, Optional: true, ForceNew: true, @@ -90,42 +100,30 @@ func resourceDNSZoneV2Create(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error creating OpenStack DNS client: %s", err) } - mastersraw := d.Get("masters").(*schema.Set).List() - masters := make([]string, len(mastersraw)) - for i, masterraw := range mastersraw { - masters[i] = masterraw.(string) - } - - attrsraw := d.Get("attributes").(map[string]interface{}) - attrs := make(map[string]string, len(attrsraw)) - for k, v := range attrsraw { - attrs[k] = v.(string) - } - createOpts := ZoneCreateOpts{ zones.CreateOpts{ Name: d.Get("name").(string), Type: d.Get("type").(string), - Attributes: attrs, TTL: d.Get("ttl").(int), Email: d.Get("email").(string), Description: d.Get("description").(string), - Masters: masters, + Attributes: expandToMapStringString(d.Get("attributes").(map[string]interface{})), + Masters: expandToStringSlice(d.Get("masters").(*schema.Set).List()), }, MapValueSpecs(d), } - log.Printf("[DEBUG] Create Options: %#v", createOpts) + log.Printf("[DEBUG] openstack_dns_zone_v2 create options: %#v", createOpts) n, err := zones.Create(dnsClient, createOpts).Extract() if err != nil { - return fmt.Errorf("Error creating OpenStack DNS zone: %s", err) + return fmt.Errorf("Error creating openstack_dns_zone_v2: %s", err) } - log.Printf("[DEBUG] Waiting for DNS Zone (%s) to become available", n.ID) + log.Printf("[DEBUG] Waiting for openstack_dns_zone_v2 %s to become available", n.ID) stateConf := &resource.StateChangeConf{ Target: []string{"ACTIVE"}, Pending: []string{"PENDING"}, - Refresh: waitForDNSZone(dnsClient, n.ID), + Refresh: dnsZoneV2RefreshFunc(dnsClient, n.ID), Timeout: d.Timeout(schema.TimeoutCreate), Delay: 5 * time.Second, MinTimeout: 3 * time.Second, @@ -148,10 +146,10 @@ func resourceDNSZoneV2Read(d *schema.ResourceData, meta interface{}) error { n, err := zones.Get(dnsClient, d.Id()).Extract() if err != nil { - return CheckDeleted(d, err, "zone") + return CheckDeleted(d, err, "Error retrieving openstack_dns_zone_v2") } - log.Printf("[DEBUG] Retrieved Zone %s: %#v", d.Id(), n) + log.Printf("[DEBUG] Retrieved openstack_dns_zone_v2 %s: %#v", d.Id(), n) d.Set("name", n.Name) d.Set("email", n.Email) @@ -176,33 +174,31 @@ func resourceDNSZoneV2Update(d *schema.ResourceData, meta interface{}) error { if d.HasChange("email") { updateOpts.Email = d.Get("email").(string) } + if d.HasChange("ttl") { updateOpts.TTL = d.Get("ttl").(int) } + if d.HasChange("masters") { - mastersraw := d.Get("masters").(*schema.Set).List() - masters := make([]string, len(mastersraw)) - for i, masterraw := range mastersraw { - masters[i] = masterraw.(string) - } - updateOpts.Masters = masters - } - if d.HasChange("description") { - updateOpts.Description = d.Get("description").(string) + updateOpts.Masters = expandToStringSlice(d.Get("masters").(*schema.Set).List()) } - log.Printf("[DEBUG] Updating Zone %s with options: %#v", d.Id(), updateOpts) + if d.HasChange("description") { + description := d.Get("description").(string) + updateOpts.Description = &description + } + + log.Printf("[DEBUG] Updating openstack_dns_zone_v2 %s with options: %#v", d.Id(), updateOpts) _, err = zones.Update(dnsClient, d.Id(), updateOpts).Extract() if err != nil { - return fmt.Errorf("Error updating OpenStack DNS Zone: %s", err) + return fmt.Errorf("Error updating openstack_dns_zone_v2 %s: %s", d.Id(), err) } - log.Printf("[DEBUG] Waiting for DNS Zone (%s) to update", d.Id()) stateConf := &resource.StateChangeConf{ Target: []string{"ACTIVE"}, Pending: []string{"PENDING"}, - Refresh: waitForDNSZone(dnsClient, d.Id()), + Refresh: dnsZoneV2RefreshFunc(dnsClient, d.Id()), Timeout: d.Timeout(schema.TimeoutUpdate), Delay: 5 * time.Second, MinTimeout: 3 * time.Second, @@ -222,14 +218,13 @@ func resourceDNSZoneV2Delete(d *schema.ResourceData, meta interface{}) error { _, err = zones.Delete(dnsClient, d.Id()).Extract() if err != nil { - return fmt.Errorf("Error deleting OpenStack DNS Zone: %s", err) + return CheckDeleted(d, err, "Error deleting openstack_dns_zone_v2") } - log.Printf("[DEBUG] Waiting for DNS Zone (%s) to become available", d.Id()) stateConf := &resource.StateChangeConf{ Target: []string{"DELETED"}, Pending: []string{"ACTIVE", "PENDING"}, - Refresh: waitForDNSZone(dnsClient, d.Id()), + Refresh: dnsZoneV2RefreshFunc(dnsClient, d.Id()), Timeout: d.Timeout(schema.TimeoutDelete), Delay: 5 * time.Second, MinTimeout: 3 * time.Second, @@ -237,40 +232,5 @@ func resourceDNSZoneV2Delete(d *schema.ResourceData, meta interface{}) error { _, err = stateConf.WaitForState() - d.SetId("") return nil } - -func resourceDNSZoneV2ValidType(v interface{}, k string) (ws []string, errors []error) { - value := v.(string) - validTypes := []string{ - "PRIMARY", - "SECONDARY", - } - - for _, v := range validTypes { - if value == v { - return - } - } - - err := fmt.Errorf("%s must be one of %s", k, validTypes) - errors = append(errors, err) - return -} - -func waitForDNSZone(dnsClient *gophercloud.ServiceClient, zoneId string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - zone, err := zones.Get(dnsClient, zoneId).Extract() - if err != nil { - if _, ok := err.(gophercloud.ErrDefault404); ok { - return zone, "DELETED", nil - } - - return nil, "", err - } - - log.Printf("[DEBUG] OpenStack DNS Zone (%s) current status: %s", zone.ID, zone.Status) - return zone, zone.Status, nil - } -} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_fw_firewall_v1.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_fw_firewall_v1.go index 957b0f752..85ab563ec 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_fw_firewall_v1.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_fw_firewall_v1.go @@ -29,48 +29,49 @@ func resourceFWFirewallV1() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "name": &schema.Schema{ + "name": { Type: schema.TypeString, Optional: true, }, - "description": &schema.Schema{ + "description": { Type: schema.TypeString, Optional: true, }, - "policy_id": &schema.Schema{ + "policy_id": { Type: schema.TypeString, Required: true, }, - "admin_state_up": &schema.Schema{ + "admin_state_up": { Type: schema.TypeBool, Optional: true, Default: true, }, - "tenant_id": &schema.Schema{ + "tenant_id": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, - "associated_routers": &schema.Schema{ + "associated_routers": { Type: schema.TypeSet, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, Set: schema.HashString, ConflictsWith: []string{"no_routers"}, + Computed: true, }, - "no_routers": &schema.Schema{ + "no_routers": { Type: schema.TypeBool, Optional: true, ConflictsWith: []string{"associated_routers"}, }, - "value_specs": &schema.Schema{ + "value_specs": { Type: schema.TypeMap, Optional: true, ForceNew: true, @@ -90,12 +91,15 @@ func resourceFWFirewallV1Create(d *schema.ResourceData, meta interface{}) error var createOpts firewalls.CreateOptsBuilder adminStateUp := d.Get("admin_state_up").(bool) - createOpts = &firewalls.CreateOpts{ - Name: d.Get("name").(string), - Description: d.Get("description").(string), - PolicyID: d.Get("policy_id").(string), - AdminStateUp: &adminStateUp, - TenantID: d.Get("tenant_id").(string), + createOpts = FirewallCreateOpts{ + firewalls.CreateOpts{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + PolicyID: d.Get("policy_id").(string), + AdminStateUp: &adminStateUp, + TenantID: d.Get("tenant_id").(string), + }, + MapValueSpecs(d), } associatedRoutersRaw := d.Get("associated_routers").(*schema.Set).List() @@ -122,11 +126,6 @@ func resourceFWFirewallV1Create(d *schema.ResourceData, meta interface{}) error } } - createOpts = &FirewallCreateOpts{ - createOpts, - MapValueSpecs(d), - } - log.Printf("[DEBUG] Create firewall: %#v", createOpts) firewall, err := firewalls.Create(networkingClient, createOpts).Extract() @@ -195,11 +194,13 @@ func resourceFWFirewallV1Update(d *schema.ResourceData, meta interface{}) error } if d.HasChange("name") { - opts.Name = d.Get("name").(string) + name := d.Get("name").(string) + opts.Name = &name } if d.HasChange("description") { - opts.Description = d.Get("description").(string) + description := d.Get("description").(string) + opts.Description = &description } if d.HasChange("admin_state_up") { diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_fw_policy_v1.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_fw_policy_v1.go index 9012854aa..4e4ebb830 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_fw_policy_v1.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_fw_policy_v1.go @@ -26,41 +26,41 @@ func resourceFWPolicyV1() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "name": &schema.Schema{ + "name": { Type: schema.TypeString, Optional: true, }, - "description": &schema.Schema{ + "description": { Type: schema.TypeString, Optional: true, }, - "audited": &schema.Schema{ + "audited": { Type: schema.TypeBool, Optional: true, Default: false, }, - "shared": &schema.Schema{ + "shared": { Type: schema.TypeBool, Optional: true, }, - "tenant_id": &schema.Schema{ + "tenant_id": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, - "rules": &schema.Schema{ + "rules": { Type: schema.TypeList, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - "value_specs": &schema.Schema{ + "value_specs": { Type: schema.TypeMap, Optional: true, ForceNew: true, @@ -155,11 +155,13 @@ func resourceFWPolicyV1Update(d *schema.ResourceData, meta interface{}) error { opts := policies.UpdateOpts{} if d.HasChange("name") { - opts.Name = d.Get("name").(string) + name := d.Get("name").(string) + opts.Name = &name } if d.HasChange("description") { - opts.Description = d.Get("description").(string) + description := d.Get("description").(string) + opts.Description = &description } if d.HasChange("rules") { diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_fw_rule_v1.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_fw_rule_v1.go index c8e86e4ec..e080e36a5 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_fw_rule_v1.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_fw_rule_v1.go @@ -21,60 +21,60 @@ func resourceFWRuleV1() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "name": &schema.Schema{ + "name": { Type: schema.TypeString, Optional: true, }, - "description": &schema.Schema{ + "description": { Type: schema.TypeString, Optional: true, }, - "protocol": &schema.Schema{ + "protocol": { Type: schema.TypeString, Required: true, }, - "action": &schema.Schema{ + "action": { Type: schema.TypeString, Required: true, }, - "ip_version": &schema.Schema{ + "ip_version": { Type: schema.TypeInt, Optional: true, Default: 4, }, - "source_ip_address": &schema.Schema{ + "source_ip_address": { Type: schema.TypeString, Optional: true, }, - "destination_ip_address": &schema.Schema{ + "destination_ip_address": { Type: schema.TypeString, Optional: true, }, - "source_port": &schema.Schema{ + "source_port": { Type: schema.TypeString, Optional: true, }, - "destination_port": &schema.Schema{ + "destination_port": { Type: schema.TypeString, Optional: true, }, - "enabled": &schema.Schema{ + "enabled": { Type: schema.TypeBool, Optional: true, Default: true, }, - "tenant_id": &schema.Schema{ + "tenant_id": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - "value_specs": &schema.Schema{ + "value_specs": { Type: schema.TypeMap, Optional: true, ForceNew: true, @@ -171,62 +171,82 @@ func resourceFWRuleV1Update(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } - opts := rules.UpdateOpts{} - + var updateOpts rules.UpdateOpts if d.HasChange("name") { - v := d.Get("name").(string) - opts.Name = &v + name := d.Get("name").(string) + updateOpts.Name = &name } if d.HasChange("description") { - v := d.Get("description").(string) - opts.Description = &v + description := d.Get("description").(string) + updateOpts.Description = &description } if d.HasChange("protocol") { - v := d.Get("protocol").(string) - opts.Protocol = &v + protocol := d.Get("protocol").(string) + updateOpts.Protocol = &protocol } if d.HasChange("action") { - v := d.Get("action").(string) - opts.Action = &v + action := d.Get("action").(string) + updateOpts.Action = &action } if d.HasChange("ip_version") { - v := d.Get("ip_version").(int) - ipVersion := resourceFWRuleV1DetermineIPVersion(v) - opts.IPVersion = &ipVersion + ipVersion := resourceFWRuleV1DetermineIPVersion(d.Get("ip_version").(int)) + updateOpts.IPVersion = &ipVersion } if d.HasChange("source_ip_address") { - v := d.Get("source_ip_address").(string) - opts.SourceIPAddress = &v - } + sourceIPAddress := d.Get("source_ip_address").(string) + updateOpts.SourceIPAddress = &sourceIPAddress - if d.HasChange("destination_ip_address") { - v := d.Get("destination_ip_address").(string) - opts.DestinationIPAddress = &v + // Also include the ip_version. + ipVersion := resourceFWRuleV1DetermineIPVersion(d.Get("ip_version").(int)) + updateOpts.IPVersion = &ipVersion } if d.HasChange("source_port") { - v := d.Get("source_port").(string) - opts.SourcePort = &v + sourcePort := d.Get("source_port").(string) + if sourcePort == "" { + sourcePort = "0" + } + updateOpts.SourcePort = &sourcePort + + // Also include the protocol. + protocol := d.Get("protocol").(string) + updateOpts.Protocol = &protocol + } + + if d.HasChange("destination_ip_address") { + destinationIPAddress := d.Get("destination_ip_address").(string) + updateOpts.DestinationIPAddress = &destinationIPAddress + + // Also include the ip_version. + ipVersion := resourceFWRuleV1DetermineIPVersion(d.Get("ip_version").(int)) + updateOpts.IPVersion = &ipVersion } if d.HasChange("destination_port") { - v := d.Get("destination_port").(string) - opts.DestinationPort = &v + destinationPort := d.Get("destination_port").(string) + if destinationPort == "" { + destinationPort = "0" + } + + updateOpts.DestinationPort = &destinationPort + + // Also include the protocol. + protocol := d.Get("protocol").(string) + updateOpts.Protocol = &protocol } if d.HasChange("enabled") { - v := d.Get("enabled").(bool) - opts.Enabled = &v + enabled := d.Get("enabled").(bool) + updateOpts.Enabled = &enabled } - log.Printf("[DEBUG] Updating firewall rules: %#v", opts) - - err = rules.Update(networkingClient, d.Id(), opts).Err + log.Printf("[DEBUG] Updating firewall rules: %#v", updateOpts) + err = rules.Update(networkingClient, d.Id(), updateOpts).Err if err != nil { return err } diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_identity_project_v3.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_identity_project_v3.go new file mode 100644 index 000000000..684426b39 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_identity_project_v3.go @@ -0,0 +1,185 @@ +package openstack + +import ( + "fmt" + "log" + + "github.com/gophercloud/gophercloud/openstack/identity/v3/projects" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceIdentityProjectV3() *schema.Resource { + return &schema.Resource{ + Create: resourceIdentityProjectV3Create, + Read: resourceIdentityProjectV3Read, + Update: resourceIdentityProjectV3Update, + Delete: resourceIdentityProjectV3Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "description": { + Type: schema.TypeString, + Optional: true, + }, + + "domain_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + + "is_domain": { + Type: schema.TypeBool, + Optional: true, + }, + + "name": { + Type: schema.TypeString, + Optional: true, + }, + + "parent_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + } +} + +func resourceIdentityProjectV3Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + identityClient, err := config.identityV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack identity client: %s", err) + } + + enabled := d.Get("enabled").(bool) + isDomain := d.Get("is_domain").(bool) + createOpts := projects.CreateOpts{ + Description: d.Get("description").(string), + DomainID: d.Get("domain_id").(string), + Enabled: &enabled, + IsDomain: &isDomain, + Name: d.Get("name").(string), + ParentID: d.Get("parent_id").(string), + } + + log.Printf("[DEBUG] openstack_identity_project_v3 create options: %#v", createOpts) + project, err := projects.Create(identityClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating openstack_identity_project_v3: %s", err) + } + + d.SetId(project.ID) + + return resourceIdentityProjectV3Read(d, meta) +} + +func resourceIdentityProjectV3Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + identityClient, err := config.identityV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack identity client: %s", err) + } + + project, err := projects.Get(identityClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "Error retrieving openstack_identity_project_v3") + } + + log.Printf("[DEBUG] Retrieved openstack_identity_project_v3 %s: %#v", d.Id(), project) + + d.Set("description", project.Description) + d.Set("domain_id", project.DomainID) + d.Set("enabled", project.Enabled) + d.Set("is_domain", project.IsDomain) + d.Set("name", project.Name) + d.Set("parent_id", project.ParentID) + d.Set("region", GetRegion(d, config)) + + return nil +} + +func resourceIdentityProjectV3Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + identityClient, err := config.identityV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack identity client: %s", err) + } + + var hasChange bool + var updateOpts projects.UpdateOpts + + if d.HasChange("domain_id") { + hasChange = true + updateOpts.DomainID = d.Get("domain_id").(string) + } + + if d.HasChange("enabled") { + hasChange = true + enabled := d.Get("enabled").(bool) + updateOpts.Enabled = &enabled + } + + if d.HasChange("is_domain") { + hasChange = true + isDomain := d.Get("is_domain").(bool) + updateOpts.IsDomain = &isDomain + } + + if d.HasChange("name") { + hasChange = true + updateOpts.Name = d.Get("name").(string) + } + + if d.HasChange("parent_id") { + hasChange = true + updateOpts.ParentID = d.Get("parent_id").(string) + } + + if d.HasChange("description") { + hasChange = true + description := d.Get("description").(string) + updateOpts.Description = &description + } + + if hasChange { + _, err := projects.Update(identityClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating openstack_identity_project_v3 %s: %s", d.Id(), err) + } + } + + return resourceIdentityProjectV3Read(d, meta) +} + +func resourceIdentityProjectV3Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + identityClient, err := config.identityV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack identity client: %s", err) + } + + err = projects.Delete(identityClient, d.Id()).ExtractErr() + if err != nil { + return CheckDeleted(d, err, "Error deleting openstack_identity_project_v3") + } + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_identity_role_assignment_v3.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_identity_role_assignment_v3.go new file mode 100644 index 000000000..63408bfa6 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_identity_role_assignment_v3.go @@ -0,0 +1,144 @@ +package openstack + +import ( + "fmt" + "log" + + "github.com/gophercloud/gophercloud/openstack/identity/v3/roles" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceIdentityRoleAssignmentV3() *schema.Resource { + return &schema.Resource{ + Create: resourceIdentityRoleAssignmentV3Create, + Read: resourceIdentityRoleAssignmentV3Read, + Delete: resourceIdentityRoleAssignmentV3Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "domain_id": { + Type: schema.TypeString, + ConflictsWith: []string{"project_id"}, + Optional: true, + ForceNew: true, + }, + + "group_id": { + Type: schema.TypeString, + ConflictsWith: []string{"user_id"}, + Optional: true, + ForceNew: true, + }, + + "project_id": { + Type: schema.TypeString, + ConflictsWith: []string{"domain_id"}, + Optional: true, + ForceNew: true, + }, + + "role_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "user_id": { + Type: schema.TypeString, + ConflictsWith: []string{"group_id"}, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func resourceIdentityRoleAssignmentV3Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + identityClient, err := config.identityV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack identity client: %s", err) + } + + roleID := d.Get("role_id").(string) + domainID := d.Get("domain_id").(string) + groupID := d.Get("group_id").(string) + projectID := d.Get("project_id").(string) + userID := d.Get("user_id").(string) + + opts := roles.AssignOpts{ + DomainID: domainID, + GroupID: groupID, + ProjectID: projectID, + UserID: userID, + } + + err = roles.Assign(identityClient, roleID, opts).ExtractErr() + if err != nil { + return fmt.Errorf("Error creating openstack_identity_role_assignment_v3: %s", err) + } + + id := identityRoleAssignmentV3ID(domainID, projectID, groupID, userID, roleID) + d.SetId(id) + + return resourceIdentityRoleAssignmentV3Read(d, meta) +} + +func resourceIdentityRoleAssignmentV3Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + identityClient, err := config.identityV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack identity client: %s", err) + } + + roleAssignment, err := identityRoleAssignmentV3FindAssignment(identityClient, d.Id()) + if err != nil { + return CheckDeleted(d, err, "Error retrieving openstack_identity_role_assignment_v3") + } + + log.Printf("[DEBUG] Retrieved openstack_identity_role_assignment_v3 %s: %#v", d.Id(), roleAssignment) + d.Set("domain_id", roleAssignment.Scope.Domain.ID) + d.Set("project_id", roleAssignment.Scope.Project.ID) + d.Set("group_id", roleAssignment.Group.ID) + d.Set("user_id", roleAssignment.User.ID) + d.Set("role_id", roleAssignment.Role.ID) + d.Set("region", GetRegion(d, config)) + + return nil +} + +func resourceIdentityRoleAssignmentV3Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + identityClient, err := config.identityV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack identity client: %s", err) + } + + domainID, projectID, groupID, userID, roleID, err := identityRoleAssignmentV3ParseID(d.Id()) + if err != nil { + return fmt.Errorf("Error determining openstack_identity_role_assignment_v3 ID: %s", err) + } + + opts := roles.UnassignOpts{ + DomainID: domainID, + GroupID: groupID, + ProjectID: projectID, + UserID: userID, + } + + roles.Unassign(identityClient, roleID, opts).ExtractErr() + if err != nil { + return CheckDeleted(d, err, "Error unassigning openstack_identity_role_assignment_v3") + } + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_identity_role_v3.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_identity_role_v3.go new file mode 100644 index 000000000..2eee2f89e --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_identity_role_v3.go @@ -0,0 +1,125 @@ +package openstack + +import ( + "fmt" + "log" + + "github.com/gophercloud/gophercloud/openstack/identity/v3/roles" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceIdentityRoleV3() *schema.Resource { + return &schema.Resource{ + Create: resourceIdentityRoleV3Create, + Read: resourceIdentityRoleV3Read, + Update: resourceIdentityRoleV3Update, + Delete: resourceIdentityRoleV3Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "domain_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "name": { + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func resourceIdentityRoleV3Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + identityClient, err := config.identityV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack identity client: %s", err) + } + + createOpts := roles.CreateOpts{ + DomainID: d.Get("domain_id").(string), + Name: d.Get("name").(string), + } + + log.Printf("[DEBUG] openstack_identity_role_v3 create options: %#v", createOpts) + role, err := roles.Create(identityClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating openstack_identity_role_v3: %s", err) + } + + d.SetId(role.ID) + + return resourceIdentityRoleV3Read(d, meta) +} + +func resourceIdentityRoleV3Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + identityClient, err := config.identityV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack identity client: %s", err) + } + + role, err := roles.Get(identityClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "Error retrieving openstack_identity_role_v3") + } + + log.Printf("[DEBUG] Retrieved openstack_identity_role_v3: %#v", role) + + d.Set("domain_id", role.DomainID) + d.Set("name", role.Name) + d.Set("region", GetRegion(d, config)) + + return nil +} + +func resourceIdentityRoleV3Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + identityClient, err := config.identityV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack identity client: %s", err) + } + + var hasChange bool + var updateOpts roles.UpdateOpts + + if d.HasChange("name") { + hasChange = true + updateOpts.Name = d.Get("name").(string) + } + + if hasChange { + _, err := roles.Update(identityClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating openstack_identity_role_v3 %s: %s", d.Id(), err) + } + } + + return resourceIdentityRoleV3Read(d, meta) +} + +func resourceIdentityRoleV3Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + identityClient, err := config.identityV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack identity client: %s", err) + } + + err = roles.Delete(identityClient, d.Id()).ExtractErr() + if err != nil { + return CheckDeleted(d, err, "Error deleting openstack_identity_role_v3") + } + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_identity_user_v3.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_identity_user_v3.go new file mode 100644 index 000000000..1efba0e76 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_identity_user_v3.go @@ -0,0 +1,287 @@ +package openstack + +import ( + "fmt" + "log" + + "github.com/gophercloud/gophercloud/openstack/identity/v3/users" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceIdentityUserV3() *schema.Resource { + return &schema.Resource{ + Create: resourceIdentityUserV3Create, + Read: resourceIdentityUserV3Read, + Update: resourceIdentityUserV3Update, + Delete: resourceIdentityUserV3Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "default_project_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "description": { + Type: schema.TypeString, + Optional: true, + }, + + "domain_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + + "extra": { + Type: schema.TypeMap, + Optional: true, + }, + + "name": { + Type: schema.TypeString, + Optional: true, + }, + + "password": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + }, + + // The following are all specific options that must + // be bundled into user.Options + "ignore_change_password_upon_first_use": { + Type: schema.TypeBool, + Optional: true, + }, + + "ignore_password_expiry": { + Type: schema.TypeBool, + Optional: true, + }, + + "ignore_lockout_failure_attempts": { + Type: schema.TypeBool, + Optional: true, + }, + + "multi_factor_auth_enabled": { + Type: schema.TypeBool, + Optional: true, + }, + + "multi_factor_auth_rule": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "rule": { + Type: schema.TypeList, + MinItems: 1, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + }, + } +} + +func resourceIdentityUserV3Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + identityClient, err := config.identityV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack identity client: %s", err) + } + + enabled := d.Get("enabled").(bool) + createOpts := users.CreateOpts{ + DefaultProjectID: d.Get("default_project_id").(string), + Description: d.Get("description").(string), + DomainID: d.Get("domain_id").(string), + Enabled: &enabled, + Extra: d.Get("extra").(map[string]interface{}), + Name: d.Get("name").(string), + } + + // Build the user options + options := map[users.Option]interface{}{} + for optionType, option := range userOptions { + if v, ok := d.GetOk(option); ok { + options[optionType] = v.(bool) + } + } + + // Build the MFA rules + mfaRules := expandIdentityUserV3MFARules(d.Get("multi_factor_auth_rule").([]interface{})) + if len(mfaRules) > 0 { + options[users.MultiFactorAuthRules] = mfaRules + } + + createOpts.Options = options + + log.Printf("[DEBUG] openstack_identity_user_v3 create options: %#v", createOpts) + + // Add password here so it wouldn't go in the above log entry + createOpts.Password = d.Get("password").(string) + + user, err := users.Create(identityClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating openstack_identity_user_v3: %s", err) + } + + d.SetId(user.ID) + + return resourceIdentityUserV3Read(d, meta) +} + +func resourceIdentityUserV3Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + identityClient, err := config.identityV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack identity client: %s", err) + } + + user, err := users.Get(identityClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "Error retrieving openstack_identity_user_v3") + } + + log.Printf("[DEBUG] Retrieved openstack_identity_user_v3 %s: %#v", d.Id(), user) + + d.Set("default_project_id", user.DefaultProjectID) + d.Set("description", user.Description) + d.Set("domain_id", user.DomainID) + d.Set("enabled", user.Enabled) + d.Set("extra", user.Extra) + d.Set("name", user.Name) + d.Set("region", GetRegion(d, config)) + + // Check and see if any options match those defined in the schema. + options := user.Options + for _, option := range userOptions { + if v, ok := options[option]; ok { + d.Set(option, v.(bool)) + } + } + + if v, ok := options["multi_factor_auth_rules"].([]interface{}); ok { + mfaRules := flattenIdentityUserV3MFARules(v) + d.Set("multi_factor_auth_rule", mfaRules) + } + + return nil +} + +func resourceIdentityUserV3Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + identityClient, err := config.identityV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack identity client: %s", err) + } + + var hasChange bool + var updateOpts users.UpdateOpts + + if d.HasChange("default_project_id") { + hasChange = true + updateOpts.DefaultProjectID = d.Get("default_project_id").(string) + } + + if d.HasChange("description") { + hasChange = true + description := d.Get("description").(string) + updateOpts.Description = &description + } + + if d.HasChange("domain_id") { + hasChange = true + updateOpts.DomainID = d.Get("domain_id").(string) + } + + if d.HasChange("enabled") { + hasChange = true + enabled := d.Get("enabled").(bool) + updateOpts.Enabled = &enabled + } + + if d.HasChange("extra") { + hasChange = true + updateOpts.Extra = d.Get("extra").(map[string]interface{}) + } + + if d.HasChange("name") { + hasChange = true + updateOpts.Name = d.Get("name").(string) + } + + // Determine if the options have changed + options := map[users.Option]interface{}{} + for optionType, option := range userOptions { + if d.HasChange(option) { + hasChange = true + options[optionType] = d.Get(option).(bool) + } + } + + // Build the MFA rules + if d.HasChange("multi_factor_auth_rule") { + mfaRules := expandIdentityUserV3MFARules(d.Get("multi_factor_auth_rule").([]interface{})) + if len(mfaRules) > 0 { + options[users.MultiFactorAuthRules] = mfaRules + } + } + + updateOpts.Options = options + + if hasChange { + log.Printf("[DEBUG] openstack_identity_user_v3 %s update options: %#v", d.Id(), updateOpts) + } + + if d.HasChange("password") { + hasChange = true + updateOpts.Password = d.Get("password").(string) + } + + if hasChange { + _, err := users.Update(identityClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating openstack_identity_user_v3 %s: %s", d.Id(), err) + } + } + + return resourceIdentityUserV3Read(d, meta) +} + +func resourceIdentityUserV3Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + identityClient, err := config.identityV3Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack identity client: %s", err) + } + + err = users.Delete(identityClient, d.Id()).ExtractErr() + if err != nil { + return CheckDeleted(d, err, "Error deleting openstack_identity_user_v3") + } + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_images_image_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_images_image_v2.go index 0b3fc803a..0d514cbcf 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_images_image_v2.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_images_image_v2.go @@ -9,6 +9,7 @@ import ( "net/http" "os" "path/filepath" + "strings" "time" "github.com/gophercloud/gophercloud" @@ -29,73 +30,60 @@ func resourceImagesImageV2() *schema.Resource { State: schema.ImportStatePassthrough, }, + CustomizeDiff: resourceImagesImageV2UpdateComputedAttributes, + Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(30 * time.Minute), }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "checksum": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - }, - - "container_format": &schema.Schema{ + "container_format": { Type: schema.TypeString, Required: true, ForceNew: true, ValidateFunc: resourceImagesImageV2ValidateContainerFormat, }, - "created_at": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - }, - - "disk_format": &schema.Schema{ + "disk_format": { Type: schema.TypeString, Required: true, ForceNew: true, ValidateFunc: resourceImagesImageV2ValidateDiskFormat, }, - "file": &schema.Schema{ + "file": { Type: schema.TypeString, Computed: true, }, - "image_cache_path": &schema.Schema{ + "image_cache_path": { Type: schema.TypeString, Optional: true, Default: fmt.Sprintf("%s/.terraform/image_cache", os.Getenv("HOME")), }, - "image_source_url": &schema.Schema{ + "image_source_url": { Type: schema.TypeString, Optional: true, ForceNew: true, ConflictsWith: []string{"local_file_path"}, }, - "local_file_path": &schema.Schema{ + "local_file_path": { Type: schema.TypeString, Optional: true, ForceNew: true, ConflictsWith: []string{"image_source_url"}, }, - "metadata": &schema.Schema{ - Type: schema.TypeMap, - Computed: true, - }, - - "min_disk_gb": &schema.Schema{ + "min_disk_gb": { Type: schema.TypeInt, Optional: true, ForceNew: true, @@ -103,7 +91,7 @@ func resourceImagesImageV2() *schema.Resource { Default: 0, }, - "min_ram_mb": &schema.Schema{ + "min_ram_mb": { Type: schema.TypeInt, Optional: true, ForceNew: true, @@ -111,58 +99,93 @@ func resourceImagesImageV2() *schema.Resource { Default: 0, }, - "name": &schema.Schema{ + "name": { Type: schema.TypeString, Required: true, ForceNew: false, }, - "owner": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - }, - - "protected": &schema.Schema{ + "protected": { Type: schema.TypeBool, Optional: true, ForceNew: true, Default: false, }, - "schema": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - }, - - "size_bytes": &schema.Schema{ - Type: schema.TypeInt, - Computed: true, - }, - - "status": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - }, - - "tags": &schema.Schema{ + "tags": { Type: schema.TypeSet, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, Set: schema.HashString, }, - "update_at": &schema.Schema{ - Type: schema.TypeString, - Computed: true, + "verify_checksum": { + Type: schema.TypeBool, + Optional: true, + ForceNew: false, + Default: true, }, - "visibility": &schema.Schema{ + "visibility": { Type: schema.TypeString, Optional: true, ForceNew: false, ValidateFunc: resourceImagesImageV2ValidateVisibility, Default: "private", }, + + "properties": { + Type: schema.TypeMap, + Optional: true, + Computed: true, + }, + + // Computed-only + "checksum": { + Type: schema.TypeString, + Computed: true, + }, + + "created_at": { + Type: schema.TypeString, + Computed: true, + }, + + "metadata": { + Type: schema.TypeMap, + Computed: true, + }, + + "owner": { + Type: schema.TypeString, + Computed: true, + }, + + "schema": { + Type: schema.TypeString, + Computed: true, + }, + + "size_bytes": { + Type: schema.TypeInt, + Computed: true, + }, + + "status": { + Type: schema.TypeString, + Computed: true, + }, + + "update_at": { + Type: schema.TypeString, + Computed: true, + Deprecated: "Use updated_at instead", + }, + + "updated_at": { + Type: schema.TypeString, + Computed: true, + }, }, } } @@ -176,6 +199,10 @@ func resourceImagesImageV2Create(d *schema.ResourceData, meta interface{}) error protected := d.Get("protected").(bool) visibility := resourceImagesImageV2VisibilityFromString(d.Get("visibility").(string)) + + properties := d.Get("properties").(map[string]interface{}) + imageProperties := resourceImagesImageV2ExpandProperties(properties) + createOpts := &images.CreateOpts{ Name: d.Get("name").(string), ContainerFormat: d.Get("container_format").(string), @@ -184,6 +211,7 @@ func resourceImagesImageV2Create(d *schema.ResourceData, meta interface{}) error MinRAM: d.Get("min_ram_mb").(int), Protected: &protected, Visibility: &visibility, + Properties: imageProperties, } if v, ok := d.GetOk("tags"); ok { @@ -229,7 +257,7 @@ func resourceImagesImageV2Create(d *schema.ResourceData, meta interface{}) error stateConf := &resource.StateChangeConf{ Pending: []string{string(images.ImageStatusQueued), string(images.ImageStatusSaving)}, Target: []string{string(images.ImageStatusActive)}, - Refresh: resourceImagesImageV2RefreshFunc(imageClient, d.Id(), fileSize, fileChecksum), + Refresh: resourceImagesImageV2RefreshFunc(imageClient, d.Id()), Timeout: d.Timeout(schema.TimeoutCreate), Delay: 10 * time.Second, MinTimeout: 3 * time.Second, @@ -239,6 +267,16 @@ func resourceImagesImageV2Create(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("Error waiting for Image: %s", err) } + img, err := images.Get(imageClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "image") + } + + verifyChecksum := d.Get("verify_checksum").(bool) + if img.Checksum != fileChecksum && verifyChecksum { + return fmt.Errorf("Error wrong checksum: got %q, expected %q", img.Checksum, fileChecksum) + } + d.Partial(false) return resourceImagesImageV2Read(d, meta) @@ -265,8 +303,10 @@ func resourceImagesImageV2Read(d *schema.ResourceData, meta interface{}) error { d.Set("checksum", img.Checksum) d.Set("size_bytes", img.SizeBytes) d.Set("metadata", img.Metadata) - d.Set("created_at", img.CreatedAt) - d.Set("update_at", img.UpdatedAt) + d.Set("created_at", img.CreatedAt.Format(time.RFC3339)) + d.Set("updated_at", img.UpdatedAt.Format(time.RFC3339)) + // Deprecated + d.Set("update_at", img.UpdatedAt.Format(time.RFC3339)) d.Set("container_format", img.ContainerFormat) d.Set("disk_format", img.DiskFormat) d.Set("min_disk_gb", img.MinDiskGigabytes) @@ -279,6 +319,11 @@ func resourceImagesImageV2Read(d *schema.ResourceData, meta interface{}) error { d.Set("visibility", img.Visibility) d.Set("region", GetRegion(d, config)) + properties := resourceImagesImageV2ExpandProperties(img.Properties) + if err := d.Set("properties", properties); err != nil { + log.Printf("[WARN] unable to set properties for image %s: %s", img.ID, err) + } + return nil } @@ -310,6 +355,72 @@ func resourceImagesImageV2Update(d *schema.ResourceData, meta interface{}) error updateOpts = append(updateOpts, v) } + if d.HasChange("properties") { + o, n := d.GetChange("properties") + oldProperties := resourceImagesImageV2ExpandProperties(o.(map[string]interface{})) + newProperties := resourceImagesImageV2ExpandProperties(n.(map[string]interface{})) + + // Check for new and changed properties + for newKey, newValue := range newProperties { + var changed bool + + oldValue, found := oldProperties[newKey] + if found && (newValue != oldValue) { + changed = true + } + + // os_ keys are provided by the OpenStack Image service. + // These are read-only properties that cannot be modified. + // Ignore them here and let CustomizeDiff handle them. + if strings.HasPrefix(newKey, "os_") { + found = true + changed = false + } + + // direct_url is provided by some storage drivers. + // This is a read-only property that cannot be modified. + // Ignore it here and let CustomizeDiff handle it. + if newKey == "direct_url" { + found = true + changed = false + } + + if !found { + v := images.UpdateImageProperty{ + Op: images.AddOp, + Name: newKey, + Value: newValue, + } + + updateOpts = append(updateOpts, v) + } + + if found && changed { + v := images.UpdateImageProperty{ + Op: images.ReplaceOp, + Name: newKey, + Value: newValue, + } + + updateOpts = append(updateOpts, v) + } + } + + // Check for removed properties + for oldKey := range oldProperties { + _, found := newProperties[oldKey] + + if !found { + v := images.UpdateImageProperty{ + Op: images.RemoveOp, + Name: oldKey, + } + + updateOpts = append(updateOpts, v) + } + } + } + log.Printf("[DEBUG] Update Options: %#v", updateOpts) _, err = images.Update(imageClient, d.Id(), updateOpts).Extract() @@ -358,7 +469,7 @@ func resourceImagesImageV2ValidateVisibility(v interface{}, k string) (ws []stri func validatePositiveInt(v interface{}, k string) (ws []string, errors []error) { value := v.(int) - if value > 0 { + if value >= 0 { return } errors = append(errors, fmt.Errorf("%q must be a positive integer", k)) @@ -378,7 +489,7 @@ func resourceImagesImageV2ValidateDiskFormat(v interface{}, k string) (ws []stri return } -var ContainerFormats = [9]string{"ami", "ari", "aki", "bare", "ovf"} +var ContainerFormats = [9]string{"ami", "ari", "aki", "bare", "ovf", "ova"} func resourceImagesImageV2ValidateContainerFormat(v interface{}, k string) (ws []string, errors []error) { value := v.(string) @@ -391,6 +502,21 @@ func resourceImagesImageV2ValidateContainerFormat(v interface{}, k string) (ws [ return } +func resourceImagesImageV2MemberStatusFromString(v string) images.ImageMemberStatus { + switch v { + case "accepted": + return images.ImageMemberStatusAccepted + case "pending": + return images.ImageMemberStatusPending + case "rejected": + return images.ImageMemberStatusRejected + case "all": + return images.ImageMemberStatusAll + } + + return "" +} + func resourceImagesImageV2VisibilityFromString(v string) images.ImageVisibility { switch v { case "public": @@ -477,7 +603,7 @@ func resourceImagesImageV2File(d *schema.ResourceData) (string, error) { } } -func resourceImagesImageV2RefreshFunc(client *gophercloud.ServiceClient, id string, fileSize int64, checksum string) resource.StateRefreshFunc { +func resourceImagesImageV2RefreshFunc(client *gophercloud.ServiceClient, id string) resource.StateRefreshFunc { return func() (interface{}, string, error) { img, err := images.Get(client, id).Extract() if err != nil { @@ -485,10 +611,6 @@ func resourceImagesImageV2RefreshFunc(client *gophercloud.ServiceClient, id stri } log.Printf("[DEBUG] OpenStack image status is: %s", img.Status) - if img.Checksum != checksum || int64(img.SizeBytes) != fileSize { - return img, fmt.Sprintf("%s", img.Status), fmt.Errorf("Error wrong size %v or checksum %q", img.SizeBytes, img.Checksum) - } - return img, fmt.Sprintf("%s", img.Status), nil } } @@ -501,3 +623,55 @@ func resourceImagesImageV2BuildTags(v []interface{}) []string { return tags } + +func resourceImagesImageV2ExpandProperties(v map[string]interface{}) map[string]string { + properties := map[string]string{} + for key, value := range v { + if v, ok := value.(string); ok { + properties[key] = v + } + } + + return properties +} + +func resourceImagesImageV2UpdateComputedAttributes(diff *schema.ResourceDiff, meta interface{}) error { + if diff.HasChange("properties") { + // Only check if the image has been created. + if diff.Id() != "" { + // Try to reconcile the properties set by the server + // with the properties set by the user. + // + // old = user properties + server properties + // new = user properties only + o, n := diff.GetChange("properties") + + newProperties := resourceImagesImageV2ExpandProperties(n.(map[string]interface{})) + + for oldKey, oldValue := range o.(map[string]interface{}) { + // os_ keys are provided by the OpenStack Image service. + if strings.HasPrefix(oldKey, "os_") { + if v, ok := oldValue.(string); ok { + newProperties[oldKey] = v + } + } + + // direct_url is provided by some storage drivers. + if oldKey == "direct_url" { + if v, ok := oldValue.(string); ok { + newProperties[oldKey] = v + } + } + } + + // Set the diff to the newProperties, which includes the server-side + // os_ properties. + // + // If the user has changed properties, they will be caught at this + // point, too. + diff.SetNew("properties", newProperties) + } + } + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_l7policy_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_l7policy_v2.go new file mode 100644 index 000000000..0fc34d903 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_l7policy_v2.go @@ -0,0 +1,427 @@ +package openstack + +import ( + "fmt" + "log" + "net/url" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools" +) + +func resourceL7PolicyV2() *schema.Resource { + return &schema.Resource{ + Create: resourceL7PolicyV2Create, + Read: resourceL7PolicyV2Read, + Update: resourceL7PolicyV2Update, + Delete: resourceL7PolicyV2Delete, + Importer: &schema.ResourceImporter{ + State: resourceL7PolicyV2Import, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "tenant_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "name": { + Type: schema.TypeString, + Optional: true, + }, + + "description": { + Type: schema.TypeString, + Optional: true, + }, + + "action": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "REDIRECT_TO_POOL", "REDIRECT_TO_URL", "REJECT", + }, true), + }, + + "listener_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "position": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + + "redirect_pool_id": { + Type: schema.TypeString, + ConflictsWith: []string{"redirect_url"}, + Optional: true, + }, + + "redirect_url": { + Type: schema.TypeString, + ConflictsWith: []string{"redirect_pool_id"}, + Optional: true, + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + _, err := url.ParseRequestURI(value) + if err != nil { + errors = append(errors, fmt.Errorf("URL is not valid: %s", err)) + } + return + }, + }, + + "admin_state_up": { + Type: schema.TypeBool, + Default: true, + Optional: true, + }, + }, + } +} + +func resourceL7PolicyV2Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + lbClient, err := chooseLBV2Client(d, config) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + // Assign some required variables for use in creation. + listenerID := d.Get("listener_id").(string) + action := d.Get("action").(string) + redirectPoolID := d.Get("redirect_pool_id").(string) + redirectURL := d.Get("redirect_url").(string) + + // Ensure the right combination of options have been specified. + err = checkL7PolicyAction(action, redirectURL, redirectPoolID) + if err != nil { + return fmt.Errorf("Unable to create L7 Policy: %s", err) + } + + adminStateUp := d.Get("admin_state_up").(bool) + createOpts := l7policies.CreateOpts{ + TenantID: d.Get("tenant_id").(string), + Name: d.Get("name").(string), + Description: d.Get("description").(string), + Action: l7policies.Action(action), + ListenerID: listenerID, + RedirectPoolID: redirectPoolID, + RedirectURL: redirectURL, + AdminStateUp: &adminStateUp, + } + + if v, ok := d.GetOk("position"); ok { + createOpts.Position = int32(v.(int)) + } + + log.Printf("[DEBUG] Create Options: %#v", createOpts) + + timeout := d.Timeout(schema.TimeoutCreate) + + // Make sure the associated pool is active before proceeding. + if redirectPoolID != "" { + pool, err := pools.Get(lbClient, redirectPoolID).Extract() + if err != nil { + return fmt.Errorf("Unable to retrieve %s: %s", redirectPoolID, err) + } + + err = waitForLBV2Pool(lbClient, pool, "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err + } + } + + // Get a clean copy of the parent listener. + parentListener, err := listeners.Get(lbClient, listenerID).Extract() + if err != nil { + return fmt.Errorf("Unable to retrieve listener %s: %s", listenerID, err) + } + + // Wait for parent Listener to become active before continuing. + err = waitForLBV2Listener(lbClient, parentListener, "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err + } + + log.Printf("[DEBUG] Attempting to create L7 Policy") + var l7Policy *l7policies.L7Policy + err = resource.Retry(timeout, func() *resource.RetryError { + l7Policy, err = l7policies.Create(lbClient, createOpts).Extract() + if err != nil { + return checkForRetryableError(err) + } + return nil + }) + + if err != nil { + return fmt.Errorf("Error creating L7 Policy: %s", err) + } + + // Wait for L7 Policy to become active before continuing + err = waitForLBV2L7Policy(lbClient, parentListener, l7Policy, "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err + } + + d.SetId(l7Policy.ID) + + return resourceL7PolicyV2Read(d, meta) +} + +func resourceL7PolicyV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + lbClient, err := chooseLBV2Client(d, config) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + l7Policy, err := l7policies.Get(lbClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "L7 Policy") + } + + log.Printf("[DEBUG] Retrieved L7 Policy %s: %#v", d.Id(), l7Policy) + + d.Set("action", l7Policy.Action) + d.Set("description", l7Policy.Description) + d.Set("tenant_id", l7Policy.TenantID) + d.Set("name", l7Policy.Name) + d.Set("position", int(l7Policy.Position)) + d.Set("redirect_url", l7Policy.RedirectURL) + d.Set("redirect_pool_id", l7Policy.RedirectPoolID) + d.Set("region", GetRegion(d, config)) + d.Set("admin_state_up", l7Policy.AdminStateUp) + + return nil +} + +func resourceL7PolicyV2Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + lbClient, err := chooseLBV2Client(d, config) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + // Assign some required variables for use in updating. + listenerID := d.Get("listener_id").(string) + action := d.Get("action").(string) + redirectPoolID := d.Get("redirect_pool_id").(string) + redirectURL := d.Get("redirect_url").(string) + + var updateOpts l7policies.UpdateOpts + + if d.HasChange("action") { + updateOpts.Action = l7policies.Action(action) + } + if d.HasChange("name") { + name := d.Get("name").(string) + updateOpts.Name = &name + } + if d.HasChange("description") { + description := d.Get("description").(string) + updateOpts.Description = &description + } + if d.HasChange("redirect_pool_id") { + redirectPoolID = d.Get("redirect_pool_id").(string) + + updateOpts.RedirectPoolID = &redirectPoolID + } + if d.HasChange("redirect_url") { + redirectURL = d.Get("redirect_url").(string) + updateOpts.RedirectURL = &redirectURL + } + if d.HasChange("position") { + updateOpts.Position = int32(d.Get("position").(int)) + } + if d.HasChange("admin_state_up") { + adminStateUp := d.Get("admin_state_up").(bool) + updateOpts.AdminStateUp = &adminStateUp + } + + // Ensure the right combination of options have been specified. + err = checkL7PolicyAction(action, redirectURL, redirectPoolID) + if err != nil { + return err + } + + // Make sure the pool is active before continuing. + timeout := d.Timeout(schema.TimeoutUpdate) + if redirectPoolID != "" { + pool, err := pools.Get(lbClient, redirectPoolID).Extract() + if err != nil { + return fmt.Errorf("Unable to retrieve %s: %s", redirectPoolID, err) + } + + err = waitForLBV2Pool(lbClient, pool, "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err + } + } + + // Get a clean copy of the parent listener. + parentListener, err := listeners.Get(lbClient, listenerID).Extract() + if err != nil { + return fmt.Errorf("Unable to retrieve parent listener %s: %s", listenerID, err) + } + + // Get a clean copy of the L7 Policy. + l7Policy, err := l7policies.Get(lbClient, d.Id()).Extract() + if err != nil { + return fmt.Errorf("Unable to retrieve L7 Policy: %s: %s", d.Id(), err) + } + + // Wait for parent Listener to become active before continuing. + err = waitForLBV2Listener(lbClient, parentListener, "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err + } + + // Wait for L7 Policy to become active before continuing + err = waitForLBV2L7Policy(lbClient, parentListener, l7Policy, "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating L7 Policy %s with options: %#v", d.Id(), updateOpts) + err = resource.Retry(timeout, func() *resource.RetryError { + _, err = l7policies.Update(lbClient, d.Id(), updateOpts).Extract() + if err != nil { + return checkForRetryableError(err) + } + return nil + }) + + if err != nil { + return fmt.Errorf("Unable to update L7 Policy %s: %s", d.Id(), err) + } + + // Wait for L7 Policy to become active before continuing + err = waitForLBV2L7Policy(lbClient, parentListener, l7Policy, "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err + } + + return resourceL7PolicyV2Read(d, meta) +} + +func resourceL7PolicyV2Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + lbClient, err := chooseLBV2Client(d, config) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + timeout := d.Timeout(schema.TimeoutDelete) + listenerID := d.Get("listener_id").(string) + + // Get a clean copy of the listener. + listener, err := listeners.Get(lbClient, listenerID).Extract() + if err != nil { + return fmt.Errorf("Unable to retrieve parent listener (%s) for the L7 Policy: %s", listenerID, err) + } + + // Get a clean copy of the L7 Policy. + l7Policy, err := l7policies.Get(lbClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "Unable to retrieve L7 Policy") + } + + // Wait for Listener to become active before continuing. + err = waitForLBV2Listener(lbClient, listener, "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err + } + + log.Printf("[DEBUG] Attempting to delete L7 Policy %s", d.Id()) + err = resource.Retry(timeout, func() *resource.RetryError { + err = l7policies.Delete(lbClient, d.Id()).ExtractErr() + if err != nil { + return checkForRetryableError(err) + } + return nil + }) + + if err != nil { + return CheckDeleted(d, err, "Error deleting L7 Policy") + } + + err = waitForLBV2L7Policy(lbClient, listener, l7Policy, "DELETED", lbPendingDeleteStatuses, timeout) + if err != nil { + return err + } + + return nil +} + +func resourceL7PolicyV2Import(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + lbClient, err := chooseLBV2Client(d, config) + if err != nil { + return nil, fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + l7Policy, err := l7policies.Get(lbClient, d.Id()).Extract() + if err != nil { + return nil, CheckDeleted(d, err, "L7 Policy") + } + + log.Printf("[DEBUG] Retrieved L7 Policy %s during the import: %#v", d.Id(), l7Policy) + + if l7Policy.ListenerID != "" { + d.Set("listener_id", l7Policy.ListenerID) + } else { + // Fallback for the Neutron LBaaSv2 extension + listenerID, err := getListenerIDForL7Policy(lbClient, d.Id()) + if err != nil { + return nil, err + } + d.Set("listener_id", listenerID) + } + + return []*schema.ResourceData{d}, nil +} + +func checkL7PolicyAction(action, redirectURL, redirectPoolID string) error { + if action == "REJECT" { + if redirectURL != "" || redirectPoolID != "" { + return fmt.Errorf( + "redirect_url and redirect_pool_id must be empty when action is set to %s", action) + } + } + + if action == "REDIRECT_TO_POOL" && redirectURL != "" { + return fmt.Errorf("redirect_url must be empty when action is set to %s", action) + } + + if action == "REDIRECT_TO_URL" && redirectPoolID != "" { + return fmt.Errorf("redirect_pool_id must be empty when action is set to %s", action) + } + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_l7rule_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_l7rule_v2.go new file mode 100644 index 000000000..1b0056520 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_l7rule_v2.go @@ -0,0 +1,420 @@ +package openstack + +import ( + "fmt" + "log" + "strings" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners" +) + +func resourceL7RuleV2() *schema.Resource { + return &schema.Resource{ + Create: resourceL7RuleV2Create, + Read: resourceL7RuleV2Read, + Update: resourceL7RuleV2Update, + Delete: resourceL7RuleV2Delete, + Importer: &schema.ResourceImporter{ + State: resourceL7RuleV2Import, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "tenant_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "COOKIE", "FILE_TYPE", "HEADER", "HOST_NAME", "PATH", + }, true), + }, + + "compare_type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "CONTAINS", "STARTS_WITH", "ENDS_WITH", "EQUAL_TO", "REGEX", + }, true), + }, + + "l7policy_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "listener_id": { + Type: schema.TypeString, + Computed: true, + }, + + "value": { + Type: schema.TypeString, + Required: true, + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + if len(v.(string)) == 0 { + errors = append(errors, fmt.Errorf("'value' field should not be empty")) + } + return + }, + }, + + "key": { + Type: schema.TypeString, + Optional: true, + }, + + "invert": { + Type: schema.TypeBool, + Default: false, + Optional: true, + }, + + "admin_state_up": { + Type: schema.TypeBool, + Default: true, + Optional: true, + }, + }, + } +} + +func resourceL7RuleV2Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + lbClient, err := chooseLBV2Client(d, config) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + // Assign some required variables for use in creation. + l7policyID := d.Get("l7policy_id").(string) + listenerID := "" + ruleType := d.Get("type").(string) + key := d.Get("key").(string) + compareType := d.Get("compare_type").(string) + adminStateUp := d.Get("admin_state_up").(bool) + + // Ensure the right combination of options have been specified. + err = checkL7RuleType(ruleType, key) + if err != nil { + return fmt.Errorf("Unable to create L7 Rule: %s", err) + } + + createOpts := l7policies.CreateRuleOpts{ + TenantID: d.Get("tenant_id").(string), + RuleType: l7policies.RuleType(ruleType), + CompareType: l7policies.CompareType(compareType), + Value: d.Get("value").(string), + Key: key, + Invert: d.Get("invert").(bool), + AdminStateUp: &adminStateUp, + } + + log.Printf("[DEBUG] Create Options: %#v", createOpts) + + timeout := d.Timeout(schema.TimeoutCreate) + + // Get a clean copy of the parent L7 Policy. + parentL7Policy, err := l7policies.Get(lbClient, l7policyID).Extract() + if err != nil { + return fmt.Errorf("Unable to get parent L7 Policy: %s", err) + } + + if parentL7Policy.ListenerID != "" { + listenerID = parentL7Policy.ListenerID + } else { + // Fallback for the Neutron LBaaSv2 extension + listenerID, err = getListenerIDForL7Policy(lbClient, l7policyID) + if err != nil { + return err + } + } + + // Get a clean copy of the parent listener. + parentListener, err := listeners.Get(lbClient, listenerID).Extract() + if err != nil { + return fmt.Errorf("Unable to retrieve listener %s: %s", listenerID, err) + } + + // Wait for parent L7 Policy to become active before continuing + err = waitForLBV2L7Policy(lbClient, parentListener, parentL7Policy, "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err + } + + log.Printf("[DEBUG] Attempting to create L7 Rule") + var l7Rule *l7policies.Rule + err = resource.Retry(timeout, func() *resource.RetryError { + l7Rule, err = l7policies.CreateRule(lbClient, l7policyID, createOpts).Extract() + if err != nil { + return checkForRetryableError(err) + } + return nil + }) + + if err != nil { + return fmt.Errorf("Error creating L7 Rule: %s", err) + } + + // Wait for L7 Rule to become active before continuing + err = waitForLBV2L7Rule(lbClient, parentListener, parentL7Policy, l7Rule, "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err + } + + d.SetId(l7Rule.ID) + d.Set("listener_id", listenerID) + + return resourceL7RuleV2Read(d, meta) +} + +func resourceL7RuleV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + lbClient, err := chooseLBV2Client(d, config) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + l7policyID := d.Get("l7policy_id").(string) + + l7Rule, err := l7policies.GetRule(lbClient, l7policyID, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "L7 Rule") + } + + log.Printf("[DEBUG] Retrieved L7 Rule %s: %#v", d.Id(), l7Rule) + + d.Set("l7policy_id", l7policyID) + d.Set("type", l7Rule.RuleType) + d.Set("compare_type", l7Rule.CompareType) + d.Set("tenant_id", l7Rule.TenantID) + d.Set("value", l7Rule.Value) + d.Set("key", l7Rule.Key) + d.Set("invert", l7Rule.Invert) + d.Set("admin_state_up", l7Rule.AdminStateUp) + + return nil +} + +func resourceL7RuleV2Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + lbClient, err := chooseLBV2Client(d, config) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + // Assign some required variables for use in updating. + l7policyID := d.Get("l7policy_id").(string) + listenerID := d.Get("listener_id").(string) + ruleType := d.Get("type").(string) + key := d.Get("key").(string) + + // Key should always be set + updateOpts := l7policies.UpdateRuleOpts{ + Key: &key, + } + + if d.HasChange("type") { + updateOpts.RuleType = l7policies.RuleType(ruleType) + } + if d.HasChange("compare_type") { + updateOpts.CompareType = l7policies.CompareType(d.Get("compare_type").(string)) + } + if d.HasChange("value") { + updateOpts.Value = d.Get("value").(string) + } + if d.HasChange("invert") { + invert := d.Get("invert").(bool) + updateOpts.Invert = &invert + } + + // Ensure the right combination of options have been specified. + err = checkL7RuleType(ruleType, key) + if err != nil { + return err + } + + timeout := d.Timeout(schema.TimeoutUpdate) + + // Get a clean copy of the parent listener. + parentListener, err := listeners.Get(lbClient, listenerID).Extract() + if err != nil { + return fmt.Errorf("Unable to retrieve listener %s: %s", listenerID, err) + } + + // Get a clean copy of the parent L7 Policy. + parentL7Policy, err := l7policies.Get(lbClient, l7policyID).Extract() + if err != nil { + return fmt.Errorf("Unable to get parent L7 Policy: %s", err) + } + + // Get a clean copy of the L7 Rule. + l7Rule, err := l7policies.GetRule(lbClient, l7policyID, d.Id()).Extract() + if err != nil { + return fmt.Errorf("Unable to get L7 Rule: %s", err) + } + + // Wait for parent L7 Policy to become active before continuing + err = waitForLBV2L7Policy(lbClient, parentListener, parentL7Policy, "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err + } + + // Wait for L7 Rule to become active before continuing + err = waitForLBV2L7Rule(lbClient, parentListener, parentL7Policy, l7Rule, "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating L7 Rule %s with options: %#v", d.Id(), updateOpts) + err = resource.Retry(timeout, func() *resource.RetryError { + _, err := l7policies.UpdateRule(lbClient, l7policyID, d.Id(), updateOpts).Extract() + if err != nil { + return checkForRetryableError(err) + } + return nil + }) + + if err != nil { + return fmt.Errorf("Unable to update L7 Rule %s: %s", d.Id(), err) + } + + // Wait for L7 Rule to become active before continuing + err = waitForLBV2L7Rule(lbClient, parentListener, parentL7Policy, l7Rule, "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err + } + + return resourceL7RuleV2Read(d, meta) +} + +func resourceL7RuleV2Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + lbClient, err := chooseLBV2Client(d, config) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + timeout := d.Timeout(schema.TimeoutDelete) + + l7policyID := d.Get("l7policy_id").(string) + listenerID := d.Get("listener_id").(string) + + // Get a clean copy of the parent listener. + parentListener, err := listeners.Get(lbClient, listenerID).Extract() + if err != nil { + return fmt.Errorf("Unable to retrieve parent listener (%s) for the L7 Rule: %s", listenerID, err) + } + + // Get a clean copy of the parent L7 Policy. + parentL7Policy, err := l7policies.Get(lbClient, l7policyID).Extract() + if err != nil { + return fmt.Errorf("Unable to retrieve parent L7 Policy (%s) for the L7 Rule: %s", l7policyID, err) + } + + // Get a clean copy of the L7 Rule. + l7Rule, err := l7policies.GetRule(lbClient, l7policyID, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "Unable to retrieve L7 Rule") + } + + // Wait for parent L7 Policy to become active before continuing + err = waitForLBV2L7Policy(lbClient, parentListener, parentL7Policy, "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err + } + + log.Printf("[DEBUG] Attempting to delete L7 Rule %s", d.Id()) + err = resource.Retry(timeout, func() *resource.RetryError { + err = l7policies.DeleteRule(lbClient, l7policyID, d.Id()).ExtractErr() + if err != nil { + return checkForRetryableError(err) + } + return nil + }) + + if err != nil { + return CheckDeleted(d, err, "Error deleting L7 Rule") + } + + err = waitForLBV2L7Rule(lbClient, parentListener, parentL7Policy, l7Rule, "DELETED", lbPendingDeleteStatuses, timeout) + if err != nil { + return err + } + + return nil +} + +func resourceL7RuleV2Import(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + parts := strings.SplitN(d.Id(), "/", 2) + if len(parts) != 2 { + err := fmt.Errorf("Invalid format specified for L7 Rule. Format must be /") + return nil, err + } + + config := meta.(*Config) + lbClient, err := chooseLBV2Client(d, config) + if err != nil { + return nil, fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + listenerID := "" + l7policyID := parts[0] + l7ruleID := parts[1] + + // Get a clean copy of the parent L7 Policy. + parentL7Policy, err := l7policies.Get(lbClient, l7policyID).Extract() + if err != nil { + return nil, fmt.Errorf("Unable to get parent L7 Policy: %s", err) + } + + if parentL7Policy.ListenerID != "" { + listenerID = parentL7Policy.ListenerID + } else { + // Fallback for the Neutron LBaaSv2 extension + listenerID, err = getListenerIDForL7Policy(lbClient, l7policyID) + if err != nil { + return nil, err + } + } + + d.SetId(l7ruleID) + d.Set("l7policy_id", l7policyID) + d.Set("listener_id", listenerID) + + return []*schema.ResourceData{d}, nil +} + +func checkL7RuleType(ruleType, key string) error { + keyRequired := []string{"COOKIE", "HEADER"} + if strSliceContains(keyRequired, ruleType) && key == "" { + return fmt.Errorf("key attribute is required, when the L7 Rule type is %s", strings.Join(keyRequired, " or ")) + } else if !strSliceContains(keyRequired, ruleType) && key != "" { + return fmt.Errorf("key attribute must not be used, when the L7 Rule type is not %s", strings.Join(keyRequired, " or ")) + } + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_listener_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_listener_v2.go index 6951de1fc..7580d9ec5 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_listener_v2.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_listener_v2.go @@ -8,7 +8,6 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" - "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners" ) @@ -18,106 +17,108 @@ func resourceListenerV2() *schema.Resource { Read: resourceListenerV2Read, Update: resourceListenerV2Update, Delete: resourceListenerV2Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "protocol": &schema.Schema{ + "protocol": { Type: schema.TypeString, Required: true, ForceNew: true, ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { value := v.(string) - if value != "TCP" && value != "HTTP" && value != "HTTPS" { + if value != "TCP" && value != "HTTP" && value != "HTTPS" && value != "TERMINATED_HTTPS" { errors = append(errors, fmt.Errorf( - "Only 'TCP', 'HTTP', and 'HTTPS' are supported values for 'protocol'")) + "Only 'TCP', 'HTTP', 'HTTPS' and 'TERMINATED_HTTPS' are supported values for 'protocol'")) } return }, }, - "protocol_port": &schema.Schema{ + "protocol_port": { Type: schema.TypeInt, Required: true, ForceNew: true, }, - "tenant_id": &schema.Schema{ + "tenant_id": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "loadbalancer_id": &schema.Schema{ + "loadbalancer_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "name": &schema.Schema{ + "name": { Type: schema.TypeString, Optional: true, Computed: true, }, - "default_pool_id": &schema.Schema{ + "default_pool_id": { Type: schema.TypeString, Optional: true, Computed: true, - ForceNew: true, }, - "description": &schema.Schema{ + "description": { Type: schema.TypeString, Optional: true, }, - "connection_limit": &schema.Schema{ + "connection_limit": { Type: schema.TypeInt, Optional: true, + Computed: true, }, - "default_tls_container_ref": &schema.Schema{ + "default_tls_container_ref": { Type: schema.TypeString, Optional: true, }, - "sni_container_refs": &schema.Schema{ + "sni_container_refs": { Type: schema.TypeList, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - "admin_state_up": &schema.Schema{ + "admin_state_up": { Type: schema.TypeBool, Default: true, Optional: true, }, - - "id": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Computed: true, - }, }, } } func resourceListenerV2Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + lbClient, err := chooseLBV2Client(d, config) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } adminStateUp := d.Get("admin_state_up").(bool) - connLimit := d.Get("connection_limit").(int) var sniContainerRefs []string if raw, ok := d.GetOk("sni_container_refs"); ok { for _, v := range raw.([]interface{}) { @@ -132,31 +133,43 @@ func resourceListenerV2Create(d *schema.ResourceData, meta interface{}) error { Name: d.Get("name").(string), DefaultPoolID: d.Get("default_pool_id").(string), Description: d.Get("description").(string), - ConnLimit: &connLimit, DefaultTlsContainerRef: d.Get("default_tls_container_ref").(string), SniContainerRefs: sniContainerRefs, AdminStateUp: &adminStateUp, } + if v, ok := d.GetOk("connection_limit"); ok { + connectionLimit := v.(int) + createOpts.ConnLimit = &connectionLimit + } + log.Printf("[DEBUG] Create Options: %#v", createOpts) - listener, err := listeners.Create(networkingClient, createOpts).Extract() + + lbID := createOpts.LoadbalancerID + timeout := d.Timeout(schema.TimeoutCreate) + + // Wait for LoadBalancer to become active before continuing + err = waitForLBV2LoadBalancer(lbClient, lbID, "ACTIVE", lbPendingStatuses, timeout) if err != nil { - return fmt.Errorf("Error creating OpenStack LBaaSV2 listener: %s", err) - } - log.Printf("[INFO] Listener ID: %s", listener.ID) - - log.Printf("[DEBUG] Waiting for Openstack LBaaSV2 listener (%s) to become available.", listener.ID) - - stateConf := &resource.StateChangeConf{ - Pending: []string{"PENDING_CREATE"}, - Target: []string{"ACTIVE"}, - Refresh: waitForListenerActive(networkingClient, listener.ID), - Timeout: 2 * time.Minute, - Delay: 5 * time.Second, - MinTimeout: 3 * time.Second, + return err } - _, err = stateConf.WaitForState() + log.Printf("[DEBUG] Attempting to create listener") + var listener *listeners.Listener + err = resource.Retry(timeout, func() *resource.RetryError { + listener, err = listeners.Create(lbClient, createOpts).Extract() + if err != nil { + return checkForRetryableError(err) + } + return nil + }) + + if err != nil { + return fmt.Errorf("Error creating listener: %s", err) + } + + // Wait for the listener to become ACTIVE. + err = waitForLBV2Listener(lbClient, listener, "ACTIVE", lbPendingStatuses, timeout) if err != nil { return err } @@ -168,19 +181,23 @@ func resourceListenerV2Create(d *schema.ResourceData, meta interface{}) error { func resourceListenerV2Read(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + lbClient, err := chooseLBV2Client(d, config) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } - listener, err := listeners.Get(networkingClient, d.Id()).Extract() + listener, err := listeners.Get(lbClient, d.Id()).Extract() if err != nil { - return CheckDeleted(d, err, "LBV2 listener") + return CheckDeleted(d, err, "listener") } - log.Printf("[DEBUG] Retrieved OpenStack LBaaSV2 listener %s: %+v", d.Id(), listener) + log.Printf("[DEBUG] Retrieved listener %s: %#v", d.Id(), listener) + + // Required by import + if len(listener.Loadbalancers) > 0 { + d.Set("loadbalancer_id", listener.Loadbalancers[0].ID) + } - d.Set("id", listener.ID) d.Set("name", listener.Name) d.Set("protocol", listener.Protocol) d.Set("tenant_id", listener.TenantID) @@ -198,22 +215,34 @@ func resourceListenerV2Read(d *schema.ResourceData, meta interface{}) error { func resourceListenerV2Update(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + lbClient, err := chooseLBV2Client(d, config) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } + // Get a clean copy of the listener. + listener, err := listeners.Get(lbClient, d.Id()).Extract() + if err != nil { + return fmt.Errorf("Unable to retrieve listener %s: %s", d.Id(), err) + } + var updateOpts listeners.UpdateOpts if d.HasChange("name") { - updateOpts.Name = d.Get("name").(string) + name := d.Get("name").(string) + updateOpts.Name = &name } if d.HasChange("description") { - updateOpts.Description = d.Get("description").(string) + description := d.Get("description").(string) + updateOpts.Description = &description } if d.HasChange("connection_limit") { connLimit := d.Get("connection_limit").(int) updateOpts.ConnLimit = &connLimit } + if d.HasChange("default_pool_id") { + defaultPoolID := d.Get("default_pool_id").(string) + updateOpts.DefaultPoolID = &defaultPoolID + } if d.HasChange("default_tls_container_ref") { updateOpts.DefaultTlsContainerRef = d.Get("default_tls_container_ref").(string) } @@ -231,11 +260,30 @@ func resourceListenerV2Update(d *schema.ResourceData, meta interface{}) error { updateOpts.AdminStateUp = &asu } - log.Printf("[DEBUG] Updating OpenStack LBaaSV2 Listener %s with options: %+v", d.Id(), updateOpts) - - _, err = listeners.Update(networkingClient, d.Id(), updateOpts).Extract() + // Wait for the listener to become ACTIVE. + timeout := d.Timeout(schema.TimeoutUpdate) + err = waitForLBV2Listener(lbClient, listener, "ACTIVE", lbPendingStatuses, timeout) if err != nil { - return fmt.Errorf("Error updating OpenStack LBaaSV2 Listener: %s", err) + return err + } + + log.Printf("[DEBUG] Updating listener %s with options: %#v", d.Id(), updateOpts) + err = resource.Retry(timeout, func() *resource.RetryError { + _, err = listeners.Update(lbClient, d.Id(), updateOpts).Extract() + if err != nil { + return checkForRetryableError(err) + } + return nil + }) + + if err != nil { + return fmt.Errorf("Error updating listener %s: %s", d.Id(), err) + } + + // Wait for the listener to become ACTIVE. + err = waitForLBV2Listener(lbClient, listener, "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err } return resourceListenerV2Read(d, meta) @@ -244,74 +292,37 @@ func resourceListenerV2Update(d *schema.ResourceData, meta interface{}) error { func resourceListenerV2Delete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + lbClient, err := chooseLBV2Client(d, config) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } - stateConf := &resource.StateChangeConf{ - Pending: []string{"ACTIVE", "PENDING_DELETE"}, - Target: []string{"DELETED"}, - Refresh: waitForListenerDelete(networkingClient, d.Id()), - Timeout: 2 * time.Minute, - Delay: 5 * time.Second, - MinTimeout: 3 * time.Second, - } - - _, err = stateConf.WaitForState() + // Get a clean copy of the listener. + listener, err := listeners.Get(lbClient, d.Id()).Extract() if err != nil { - return fmt.Errorf("Error deleting OpenStack LBaaSV2 listener: %s", err) + return CheckDeleted(d, err, "Unable to retrieve listener") + } + + timeout := d.Timeout(schema.TimeoutDelete) + + log.Printf("[DEBUG] Deleting listener %s", d.Id()) + err = resource.Retry(timeout, func() *resource.RetryError { + err = listeners.Delete(lbClient, d.Id()).ExtractErr() + if err != nil { + return checkForRetryableError(err) + } + return nil + }) + + if err != nil { + return CheckDeleted(d, err, "Error deleting listener") + } + + // Wait for the listener to become DELETED. + err = waitForLBV2Listener(lbClient, listener, "DELETED", lbPendingDeleteStatuses, timeout) + if err != nil { + return err } - d.SetId("") return nil } - -func waitForListenerActive(networkingClient *gophercloud.ServiceClient, listenerID string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - listener, err := listeners.Get(networkingClient, listenerID).Extract() - if err != nil { - return nil, "", err - } - - // The listener resource has no Status attribute, so a successful Get is the best we can do - log.Printf("[DEBUG] OpenStack LBaaSV2 listener: %+v", listener) - return listener, "ACTIVE", nil - } -} - -func waitForListenerDelete(networkingClient *gophercloud.ServiceClient, listenerID string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - log.Printf("[DEBUG] Attempting to delete OpenStack LBaaSV2 listener %s", listenerID) - - listener, err := listeners.Get(networkingClient, listenerID).Extract() - if err != nil { - if _, ok := err.(gophercloud.ErrDefault404); ok { - log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 listener %s", listenerID) - return listener, "DELETED", nil - } - return listener, "ACTIVE", err - } - - log.Printf("[DEBUG] Openstack LBaaSV2 listener: %+v", listener) - err = listeners.Delete(networkingClient, listenerID).ExtractErr() - if err != nil { - if _, ok := err.(gophercloud.ErrDefault404); ok { - log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 listener %s", listenerID) - return listener, "DELETED", nil - } - - if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok { - if errCode.Actual == 409 { - log.Printf("[DEBUG] OpenStack LBaaSV2 listener (%s) is still in use.", listenerID) - return listener, "ACTIVE", nil - } - } - - return listener, "ACTIVE", err - } - - log.Printf("[DEBUG] OpenStack LBaaSV2 listener %s still active.", listenerID) - return listener, "ACTIVE", nil - } -} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_loadbalancer_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_loadbalancer_v2.go index d2cc9ed8d..5e234a399 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_loadbalancer_v2.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_loadbalancer_v2.go @@ -19,83 +19,79 @@ func resourceLoadBalancerV2() *schema.Resource { Read: resourceLoadBalancerV2Read, Update: resourceLoadBalancerV2Update, Delete: resourceLoadBalancerV2Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, Timeouts: &schema.ResourceTimeout{ - Create: schema.DefaultTimeout(20 * time.Minute), - Delete: schema.DefaultTimeout(10 * time.Minute), + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(5 * time.Minute), }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "name": &schema.Schema{ + "name": { Type: schema.TypeString, Optional: true, }, - "description": &schema.Schema{ + "description": { Type: schema.TypeString, Optional: true, }, - "vip_subnet_id": &schema.Schema{ + "vip_subnet_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "tenant_id": &schema.Schema{ + "tenant_id": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "vip_address": &schema.Schema{ + "vip_address": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "vip_port_id": &schema.Schema{ + "vip_port_id": { Type: schema.TypeString, Computed: true, }, - "admin_state_up": &schema.Schema{ + "admin_state_up": { Type: schema.TypeBool, Default: true, Optional: true, }, - "flavor": &schema.Schema{ + "flavor": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - "provider": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - Deprecated: "Please use loadbalancer_provider", - }, - - "loadbalancer_provider": &schema.Schema{ + "loadbalancer_provider": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "security_group_ids": &schema.Schema{ + "security_group_ids": { Type: schema.TypeSet, Optional: true, Computed: true, @@ -108,7 +104,7 @@ func resourceLoadBalancerV2() *schema.Resource { func resourceLoadBalancerV2Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + lbClient, err := chooseLBV2Client(d, config) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } @@ -131,28 +127,22 @@ func resourceLoadBalancerV2Create(d *schema.ResourceData, meta interface{}) erro } log.Printf("[DEBUG] Create Options: %#v", createOpts) - lb, err := loadbalancers.Create(networkingClient, createOpts).Extract() + lb, err := loadbalancers.Create(lbClient, createOpts).Extract() if err != nil { - return fmt.Errorf("Error creating OpenStack LoadBalancer: %s", err) - } - log.Printf("[INFO] LoadBalancer ID: %s", lb.ID) - - log.Printf("[DEBUG] Waiting for Openstack LoadBalancer (%s) to become available.", lb.ID) - - stateConf := &resource.StateChangeConf{ - Pending: []string{"PENDING_CREATE"}, - Target: []string{"ACTIVE"}, - Refresh: waitForLoadBalancerActive(networkingClient, lb.ID), - Timeout: d.Timeout(schema.TimeoutCreate), - Delay: 5 * time.Second, - MinTimeout: 3 * time.Second, + return fmt.Errorf("Error creating LoadBalancer: %s", err) } - _, err = stateConf.WaitForState() + // Wait for LoadBalancer to become active before continuing + timeout := d.Timeout(schema.TimeoutCreate) + err = waitForLBV2LoadBalancer(lbClient, lb.ID, "ACTIVE", lbPendingStatuses, timeout) if err != nil { return err } + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } // Once the loadbalancer has been created, apply any requested security groups // to the port that was created behind the scenes. if err := resourceLoadBalancerV2SecurityGroups(networkingClient, lb.VipPortID, d); err != nil { @@ -167,17 +157,17 @@ func resourceLoadBalancerV2Create(d *schema.ResourceData, meta interface{}) erro func resourceLoadBalancerV2Read(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + lbClient, err := chooseLBV2Client(d, config) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } - lb, err := loadbalancers.Get(networkingClient, d.Id()).Extract() + lb, err := loadbalancers.Get(lbClient, d.Id()).Extract() if err != nil { - return CheckDeleted(d, err, "LoadBalancerV2") + return CheckDeleted(d, err, "loadbalancer") } - log.Printf("[DEBUG] Retrieved OpenStack LBaaSV2 LoadBalancer %s: %+v", d.Id(), lb) + log.Printf("[DEBUG] Retrieved loadbalancer %s: %#v", d.Id(), lb) d.Set("name", lb.Name) d.Set("description", lb.Description) @@ -192,6 +182,10 @@ func resourceLoadBalancerV2Read(d *schema.ResourceData, meta interface{}) error // Get any security groups on the VIP Port if lb.VipPortID != "" { + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } port, err := ports.Get(networkingClient, lb.VipPortID).Extract() if err != nil { return err @@ -205,32 +199,59 @@ func resourceLoadBalancerV2Read(d *schema.ResourceData, meta interface{}) error func resourceLoadBalancerV2Update(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + lbClient, err := chooseLBV2Client(d, config) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } var updateOpts loadbalancers.UpdateOpts if d.HasChange("name") { - updateOpts.Name = d.Get("name").(string) + name := d.Get("name").(string) + updateOpts.Name = &name } if d.HasChange("description") { - updateOpts.Description = d.Get("description").(string) + description := d.Get("description").(string) + updateOpts.Description = &description } if d.HasChange("admin_state_up") { asu := d.Get("admin_state_up").(bool) updateOpts.AdminStateUp = &asu } - log.Printf("[DEBUG] Updating OpenStack LBaaSV2 LoadBalancer %s with options: %+v", d.Id(), updateOpts) + if updateOpts != (loadbalancers.UpdateOpts{}) { + // Wait for LoadBalancer to become active before continuing + timeout := d.Timeout(schema.TimeoutUpdate) + err = waitForLBV2LoadBalancer(lbClient, d.Id(), "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err + } - _, err = loadbalancers.Update(networkingClient, d.Id(), updateOpts).Extract() - if err != nil { - return fmt.Errorf("Error updating OpenStack LBaaSV2 LoadBalancer: %s", err) + log.Printf("[DEBUG] Updating loadbalancer %s with options: %#v", d.Id(), updateOpts) + err = resource.Retry(timeout, func() *resource.RetryError { + _, err = loadbalancers.Update(lbClient, d.Id(), updateOpts).Extract() + if err != nil { + return checkForRetryableError(err) + } + return nil + }) + + if err != nil { + return fmt.Errorf("Error updating loadbalancer %s: %s", d.Id(), err) + } + + // Wait for LoadBalancer to become active before continuing + err = waitForLBV2LoadBalancer(lbClient, d.Id(), "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err + } } // Security Groups get updated separately if d.HasChange("security_group_ids") { + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } vipPortID := d.Get("vip_port_id").(string) if err := resourceLoadBalancerV2SecurityGroups(networkingClient, vipPortID, d); err != nil { return err @@ -242,38 +263,44 @@ func resourceLoadBalancerV2Update(d *schema.ResourceData, meta interface{}) erro func resourceLoadBalancerV2Delete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + lbClient, err := chooseLBV2Client(d, config) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } - stateConf := &resource.StateChangeConf{ - Pending: []string{"ACTIVE", "PENDING_DELETE"}, - Target: []string{"DELETED"}, - Refresh: waitForLoadBalancerDelete(networkingClient, d.Id()), - Timeout: d.Timeout(schema.TimeoutDelete), - Delay: 5 * time.Second, - MinTimeout: 3 * time.Second, - } + log.Printf("[DEBUG] Deleting loadbalancer %s", d.Id()) + timeout := d.Timeout(schema.TimeoutDelete) + err = resource.Retry(timeout, func() *resource.RetryError { + err = loadbalancers.Delete(lbClient, d.Id()).ExtractErr() + if err != nil { + return checkForRetryableError(err) + } + return nil + }) - _, err = stateConf.WaitForState() if err != nil { - return fmt.Errorf("Error deleting OpenStack LBaaSV2 LoadBalancer: %s", err) + return CheckDeleted(d, err, "Error deleting loadbalancer") + } + + // Wait for LoadBalancer to become delete + err = waitForLBV2LoadBalancer(lbClient, d.Id(), "DELETED", lbPendingDeleteStatuses, timeout) + if err != nil { + return err } - d.SetId("") return nil } func resourceLoadBalancerV2SecurityGroups(networkingClient *gophercloud.ServiceClient, vipPortID string, d *schema.ResourceData) error { if vipPortID != "" { - if _, ok := d.GetOk("security_group_ids"); ok { + if v, ok := d.GetOk("security_group_ids"); ok { + securityGroups := expandToStringSlice(v.(*schema.Set).List()) updateOpts := ports.UpdateOpts{ - SecurityGroups: resourcePortSecurityGroupsV2(d), + SecurityGroups: &securityGroups, } - log.Printf("[DEBUG] Adding security groups to OpenStack LoadBalancer "+ - "VIP Port (%s): %#v", vipPortID, updateOpts) + log.Printf("[DEBUG] Adding security groups to loadbalancer "+ + "VIP Port %s: %#v", vipPortID, updateOpts) _, err := ports.Update(networkingClient, vipPortID, updateOpts).Extract() if err != nil { @@ -284,55 +311,3 @@ func resourceLoadBalancerV2SecurityGroups(networkingClient *gophercloud.ServiceC return nil } - -func waitForLoadBalancerActive(networkingClient *gophercloud.ServiceClient, lbID string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - lb, err := loadbalancers.Get(networkingClient, lbID).Extract() - if err != nil { - return nil, "", err - } - - log.Printf("[DEBUG] OpenStack LBaaSV2 LoadBalancer: %+v", lb) - if lb.ProvisioningStatus == "ACTIVE" { - return lb, "ACTIVE", nil - } - - return lb, lb.ProvisioningStatus, nil - } -} - -func waitForLoadBalancerDelete(networkingClient *gophercloud.ServiceClient, lbID string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - log.Printf("[DEBUG] Attempting to delete OpenStack LBaaSV2 LoadBalancer %s", lbID) - - lb, err := loadbalancers.Get(networkingClient, lbID).Extract() - if err != nil { - if _, ok := err.(gophercloud.ErrDefault404); ok { - log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 LoadBalancer %s", lbID) - return lb, "DELETED", nil - } - return lb, "ACTIVE", err - } - - log.Printf("[DEBUG] Openstack LoadBalancerV2: %+v", lb) - err = loadbalancers.Delete(networkingClient, lbID).ExtractErr() - if err != nil { - if _, ok := err.(gophercloud.ErrDefault404); ok { - log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 LoadBalancer %s", lbID) - return lb, "DELETED", nil - } - - if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok { - if errCode.Actual == 409 { - log.Printf("[DEBUG] OpenStack LBaaSV2 LoadBalancer (%s) is still in use.", lbID) - return lb, "ACTIVE", nil - } - } - - return lb, "ACTIVE", err - } - - log.Printf("[DEBUG] OpenStack LBaaSV2 LoadBalancer (%s) still active.", lbID) - return lb, "ACTIVE", nil - } -} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_member_v1.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_member_v1.go index 5b203371f..c4df180d0 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_member_v1.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_member_v1.go @@ -28,38 +28,38 @@ func resourceLBMemberV1() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "tenant_id": &schema.Schema{ + "tenant_id": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - "pool_id": &schema.Schema{ + "pool_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "address": &schema.Schema{ + "address": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "port": &schema.Schema{ + "port": { Type: schema.TypeInt, Required: true, ForceNew: true, }, - "weight": &schema.Schema{ + "weight": { Type: schema.TypeInt, Optional: true, Computed: true, }, - "admin_state_up": &schema.Schema{ + "admin_state_up": { Type: schema.TypeBool, Optional: true, ForceNew: false, diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_member_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_member_v2.go index 62d3dd9dc..6dcef080e 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_member_v2.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_member_v2.go @@ -3,12 +3,13 @@ package openstack import ( "fmt" "log" + "strings" "time" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" - "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools" ) @@ -18,88 +19,79 @@ func resourceMemberV2() *schema.Resource { Read: resourceMemberV2Read, Update: resourceMemberV2Update, Delete: resourceMemberV2Delete, + Importer: &schema.ResourceImporter{ + State: resourceMemberV2Import, + }, Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), Delete: schema.DefaultTimeout(10 * time.Minute), }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "name": &schema.Schema{ + "name": { Type: schema.TypeString, Optional: true, }, - "tenant_id": &schema.Schema{ + "tenant_id": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "address": &schema.Schema{ + "address": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "protocol_port": &schema.Schema{ + "protocol_port": { Type: schema.TypeInt, Required: true, ForceNew: true, }, - "weight": &schema.Schema{ - Type: schema.TypeInt, - Optional: true, - Computed: true, - ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { - value := v.(int) - if value < 1 { - errors = append(errors, fmt.Errorf( - "Only numbers greater than 0 are supported values for 'weight'")) - } - return - }, + "weight": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ValidateFunc: validation.IntAtLeast(0), }, - "subnet_id": &schema.Schema{ + "subnet_id": { Type: schema.TypeString, - Required: true, + Optional: true, ForceNew: true, }, - "admin_state_up": &schema.Schema{ + "admin_state_up": { Type: schema.TypeBool, Default: true, Optional: true, }, - "pool_id": &schema.Schema{ + "pool_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - - "id": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Computed: true, - }, }, } } func resourceMemberV2Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + lbClient, err := chooseLBV2Client(d, config) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } @@ -110,7 +102,6 @@ func resourceMemberV2Create(d *schema.ResourceData, meta interface{}) error { TenantID: d.Get("tenant_id").(string), Address: d.Get("address").(string), ProtocolPort: d.Get("protocol_port").(int), - Weight: d.Get("weight").(int), AdminStateUp: &adminStateUp, } @@ -119,50 +110,45 @@ func resourceMemberV2Create(d *schema.ResourceData, meta interface{}) error { createOpts.SubnetID = v.(string) } - poolID := d.Get("pool_id").(string) + // Set the weight only if it's defined in the configuration. + // This prevents all members from being created with a default weight of 0. + if v, ok := d.GetOkExists("weight"); ok { + weight := v.(int) + createOpts.Weight = &weight + } log.Printf("[DEBUG] Create Options: %#v", createOpts) - var member *pools.Member - err = resource.Retry(10*time.Minute, func() *resource.RetryError { - var err error - log.Printf("[DEBUG] Attempting to create LBaaSV2 member") - member, err = pools.CreateMember(networkingClient, poolID, createOpts).Extract() - if err != nil { - switch errCode := err.(type) { - case gophercloud.ErrDefault500: - log.Printf("[DEBUG] OpenStack LBaaSV2 member is still creating.") - return resource.RetryableError(err) - case gophercloud.ErrUnexpectedResponseCode: - if errCode.Actual == 409 { - log.Printf("[DEBUG] OpenStack LBaaSV2 member is still creating.") - return resource.RetryableError(err) - } + // Get a clean copy of the parent pool. + poolID := d.Get("pool_id").(string) + parentPool, err := pools.Get(lbClient, poolID).Extract() + if err != nil { + return fmt.Errorf("Unable to retrieve parent pool %s: %s", poolID, err) + } - default: - return resource.NonRetryableError(err) - } + // Wait for parent pool to become active before continuing + timeout := d.Timeout(schema.TimeoutCreate) + err = waitForLBV2Pool(lbClient, parentPool, "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err + } + + log.Printf("[DEBUG] Attempting to create member") + var member *pools.Member + err = resource.Retry(timeout, func() *resource.RetryError { + member, err = pools.CreateMember(lbClient, poolID, createOpts).Extract() + if err != nil { + return checkForRetryableError(err) } return nil }) if err != nil { - return fmt.Errorf("Error creating OpenStack LBaaSV2 member: %s", err) - } - log.Printf("[INFO] member ID: %s", member.ID) - - log.Printf("[DEBUG] Waiting for Openstack LBaaSV2 member (%s) to become available.", member.ID) - - stateConf := &resource.StateChangeConf{ - Pending: []string{"PENDING_CREATE"}, - Target: []string{"ACTIVE"}, - Refresh: waitForMemberActive(networkingClient, poolID, member.ID), - Timeout: d.Timeout(schema.TimeoutCreate), - Delay: 5 * time.Second, - MinTimeout: 3 * time.Second, + return fmt.Errorf("Error creating member: %s", err) } - _, err = stateConf.WaitForState() + // Wait for member to become active before continuing + err = waitForLBV2Member(lbClient, parentPool, member, "ACTIVE", lbPendingStatuses, timeout) if err != nil { return err } @@ -174,17 +160,19 @@ func resourceMemberV2Create(d *schema.ResourceData, meta interface{}) error { func resourceMemberV2Read(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + lbClient, err := chooseLBV2Client(d, config) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } - member, err := pools.GetMember(networkingClient, d.Get("pool_id").(string), d.Id()).Extract() + poolID := d.Get("pool_id").(string) + + member, err := pools.GetMember(lbClient, poolID, d.Id()).Extract() if err != nil { - return CheckDeleted(d, err, "LBV2 Member") + return CheckDeleted(d, err, "member") } - log.Printf("[DEBUG] Retrieved OpenStack LBaaSV2 Member %s: %+v", d.Id(), member) + log.Printf("[DEBUG] Retrieved member %s: %#v", d.Id(), member) d.Set("name", member.Name) d.Set("weight", member.Weight) @@ -193,7 +181,6 @@ func resourceMemberV2Read(d *schema.ResourceData, meta interface{}) error { d.Set("subnet_id", member.SubnetID) d.Set("address", member.Address) d.Set("protocol_port", member.ProtocolPort) - d.Set("id", member.ID) d.Set("region", GetRegion(d, config)) return nil @@ -201,28 +188,68 @@ func resourceMemberV2Read(d *schema.ResourceData, meta interface{}) error { func resourceMemberV2Update(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + lbClient, err := chooseLBV2Client(d, config) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } var updateOpts pools.UpdateMemberOpts if d.HasChange("name") { - updateOpts.Name = d.Get("name").(string) + name := d.Get("name").(string) + updateOpts.Name = &name } if d.HasChange("weight") { - updateOpts.Weight = d.Get("weight").(int) + weight := d.Get("weight").(int) + updateOpts.Weight = &weight } if d.HasChange("admin_state_up") { asu := d.Get("admin_state_up").(bool) updateOpts.AdminStateUp = &asu } - log.Printf("[DEBUG] Updating OpenStack LBaaSV2 Member %s with options: %+v", d.Id(), updateOpts) - - _, err = pools.UpdateMember(networkingClient, d.Get("pool_id").(string), d.Id(), updateOpts).Extract() + // Get a clean copy of the parent pool. + poolID := d.Get("pool_id").(string) + parentPool, err := pools.Get(lbClient, poolID).Extract() if err != nil { - return fmt.Errorf("Error updating OpenStack LBaaSV2 Member: %s", err) + return fmt.Errorf("Unable to retrieve parent pool %s: %s", poolID, err) + } + + // Get a clean copy of the member. + member, err := pools.GetMember(lbClient, poolID, d.Id()).Extract() + if err != nil { + return fmt.Errorf("Unable to retrieve member: %s: %s", d.Id(), err) + } + + // Wait for parent pool to become active before continuing. + timeout := d.Timeout(schema.TimeoutUpdate) + err = waitForLBV2Pool(lbClient, parentPool, "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err + } + + // Wait for the member to become active before continuing. + err = waitForLBV2Member(lbClient, parentPool, member, "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating member %s with options: %#v", d.Id(), updateOpts) + err = resource.Retry(timeout, func() *resource.RetryError { + _, err = pools.UpdateMember(lbClient, poolID, d.Id(), updateOpts).Extract() + if err != nil { + return checkForRetryableError(err) + } + return nil + }) + + if err != nil { + return fmt.Errorf("Unable to update member %s: %s", d.Id(), err) + } + + // Wait for the member to become active before continuing. + err = waitForLBV2Member(lbClient, parentPool, member, "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err } return resourceMemberV2Read(d, meta) @@ -230,77 +257,65 @@ func resourceMemberV2Update(d *schema.ResourceData, meta interface{}) error { func resourceMemberV2Delete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + lbClient, err := chooseLBV2Client(d, config) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } - stateConf := &resource.StateChangeConf{ - Pending: []string{"ACTIVE", "PENDING_DELETE"}, - Target: []string{"DELETED"}, - Refresh: waitForMemberDelete(networkingClient, d.Get("pool_id").(string), d.Id()), - Timeout: d.Timeout(schema.TimeoutDelete), - Delay: 5 * time.Second, - MinTimeout: 3 * time.Second, - } - - _, err = stateConf.WaitForState() + // Get a clean copy of the parent pool. + poolID := d.Get("pool_id").(string) + parentPool, err := pools.Get(lbClient, poolID).Extract() if err != nil { - return fmt.Errorf("Error deleting OpenStack LBaaSV2 Member: %s", err) + return fmt.Errorf("Unable to retrieve parent pool (%s) for the member: %s", poolID, err) + } + + // Get a clean copy of the member. + member, err := pools.GetMember(lbClient, poolID, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "Unable to retrieve member") + } + + // Wait for parent pool to become active before continuing. + timeout := d.Timeout(schema.TimeoutDelete) + err = waitForLBV2Pool(lbClient, parentPool, "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err + } + + log.Printf("[DEBUG] Attempting to delete member %s", d.Id()) + err = resource.Retry(timeout, func() *resource.RetryError { + err = pools.DeleteMember(lbClient, poolID, d.Id()).ExtractErr() + if err != nil { + return checkForRetryableError(err) + } + return nil + }) + + if err != nil { + return CheckDeleted(d, err, "Error deleting member") + } + + // Wait for the member to become DELETED. + err = waitForLBV2Member(lbClient, parentPool, member, "DELETED", lbPendingDeleteStatuses, timeout) + if err != nil { + return err } - d.SetId("") return nil } -func waitForMemberActive(networkingClient *gophercloud.ServiceClient, poolID string, memberID string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - member, err := pools.GetMember(networkingClient, poolID, memberID).Extract() - if err != nil { - return nil, "", err - } - - // The member resource has no Status attribute, so a successful Get is the best we can do - log.Printf("[DEBUG] OpenStack LBaaSV2 Member: %+v", member) - return member, "ACTIVE", nil - } -} - -func waitForMemberDelete(networkingClient *gophercloud.ServiceClient, poolID string, memberID string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - log.Printf("[DEBUG] Attempting to delete OpenStack LBaaSV2 Member %s", memberID) - - member, err := pools.GetMember(networkingClient, poolID, memberID).Extract() - if err != nil { - if _, ok := err.(gophercloud.ErrDefault404); ok { - log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Member %s", memberID) - return member, "DELETED", nil - } - return member, "ACTIVE", err - } - - log.Printf("[DEBUG] Openstack LBaaSV2 Member: %+v", member) - err = pools.DeleteMember(networkingClient, poolID, memberID).ExtractErr() - if err != nil { - switch errCode := err.(type) { - case gophercloud.ErrDefault404: - log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Member %s", memberID) - return member, "DELETED", nil - case gophercloud.ErrDefault500: - log.Printf("[DEBUG] OpenStack LBaaSV2 Member (%s) is still in use.", memberID) - return member, "PENDING_DELETE", nil - case gophercloud.ErrUnexpectedResponseCode: - if errCode.Actual == 409 { - log.Printf("[DEBUG] OpenStack LBaaSV2 Member (%s) is still in use.", memberID) - return member, "PENDING_DELETE", nil - } - - default: - return member, "ACTIVE", err - } - } - - log.Printf("[DEBUG] OpenStack LBaaSV2 Member %s still active.", memberID) - return member, "ACTIVE", nil +func resourceMemberV2Import(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + parts := strings.SplitN(d.Id(), "/", 2) + if len(parts) != 2 { + err := fmt.Errorf("Invalid format specified for Member. Format must be /") + return nil, err } + + poolID := parts[0] + memberID := parts[1] + + d.SetId(memberID) + d.Set("pool_id", poolID) + + return []*schema.ResourceData{d}, nil } diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_monitor_v1.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_monitor_v1.go index db6c5b261..8fe6f00ab 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_monitor_v1.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_monitor_v1.go @@ -29,54 +29,54 @@ func resourceLBMonitorV1() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "tenant_id": &schema.Schema{ + "tenant_id": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, - "type": &schema.Schema{ + "type": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "delay": &schema.Schema{ + "delay": { Type: schema.TypeInt, Required: true, ForceNew: false, }, - "timeout": &schema.Schema{ + "timeout": { Type: schema.TypeInt, Required: true, ForceNew: false, }, - "max_retries": &schema.Schema{ + "max_retries": { Type: schema.TypeInt, Required: true, ForceNew: false, }, - "url_path": &schema.Schema{ + "url_path": { Type: schema.TypeString, Optional: true, ForceNew: false, }, - "http_method": &schema.Schema{ + "http_method": { Type: schema.TypeString, Optional: true, ForceNew: false, }, - "expected_codes": &schema.Schema{ + "expected_codes": { Type: schema.TypeString, Optional: true, ForceNew: false, }, - "admin_state_up": &schema.Schema{ + "admin_state_up": { Type: schema.TypeString, Optional: true, ForceNew: false, diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_monitor_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_monitor_v2.go index fb27e256a..8419875f8 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_monitor_v2.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_monitor_v2.go @@ -8,8 +8,8 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" - "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools" ) func resourceMonitorV2() *schema.Resource { @@ -18,88 +18,93 @@ func resourceMonitorV2() *schema.Resource { Read: resourceMonitorV2Read, Update: resourceMonitorV2Update, Delete: resourceMonitorV2Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), Delete: schema.DefaultTimeout(10 * time.Minute), }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "pool_id": &schema.Schema{ + "pool_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "name": &schema.Schema{ + "name": { Type: schema.TypeString, Optional: true, }, - "tenant_id": &schema.Schema{ + "tenant_id": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "type": &schema.Schema{ + "type": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "delay": &schema.Schema{ + + "delay": { Type: schema.TypeInt, Required: true, }, - "timeout": &schema.Schema{ + + "timeout": { Type: schema.TypeInt, Required: true, }, - "max_retries": &schema.Schema{ + + "max_retries": { Type: schema.TypeInt, Required: true, }, - "url_path": &schema.Schema{ + + "url_path": { Type: schema.TypeString, Optional: true, Computed: true, }, - "http_method": &schema.Schema{ + + "http_method": { Type: schema.TypeString, Optional: true, Computed: true, }, - "expected_codes": &schema.Schema{ + + "expected_codes": { Type: schema.TypeString, Optional: true, Computed: true, }, - "admin_state_up": &schema.Schema{ + + "admin_state_up": { Type: schema.TypeBool, Default: true, Optional: true, }, - - "id": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Computed: true, - }, }, } } func resourceMonitorV2Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + lbClient, err := chooseLBV2Client(d, config) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } @@ -119,25 +124,37 @@ func resourceMonitorV2Create(d *schema.ResourceData, meta interface{}) error { AdminStateUp: &adminStateUp, } - log.Printf("[DEBUG] Create Options: %#v", createOpts) - monitor, err := monitors.Create(networkingClient, createOpts).Extract() + // Get a clean copy of the parent pool. + poolID := createOpts.PoolID + parentPool, err := pools.Get(lbClient, poolID).Extract() if err != nil { - return fmt.Errorf("Error creating OpenStack LBaaSV2 monitor: %s", err) - } - log.Printf("[INFO] monitor ID: %s", monitor.ID) - - log.Printf("[DEBUG] Waiting for Openstack LBaaSV2 monitor (%s) to become available.", monitor.ID) - - stateConf := &resource.StateChangeConf{ - Pending: []string{"PENDING_CREATE"}, - Target: []string{"ACTIVE"}, - Refresh: waitForMonitorActive(networkingClient, monitor.ID), - Timeout: d.Timeout(schema.TimeoutCreate), - Delay: 5 * time.Second, - MinTimeout: 3 * time.Second, + return fmt.Errorf("Unable to retrieve parent pool %s: %s", poolID, err) } - _, err = stateConf.WaitForState() + // Wait for parent pool to become active before continuing + timeout := d.Timeout(schema.TimeoutCreate) + err = waitForLBV2Pool(lbClient, parentPool, "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err + } + + log.Printf("[DEBUG] Create Options: %#v", createOpts) + log.Printf("[DEBUG] Attempting to create monitor") + var monitor *monitors.Monitor + err = resource.Retry(timeout, func() *resource.RetryError { + monitor, err = monitors.Create(lbClient, createOpts).Extract() + if err != nil { + return checkForRetryableError(err) + } + return nil + }) + + if err != nil { + return fmt.Errorf("Unable to create monitor: %s", err) + } + + // Wait for monitor to become active before continuing + err = waitForLBV2Monitor(lbClient, parentPool, monitor, "ACTIVE", lbPendingStatuses, timeout) if err != nil { return err } @@ -149,19 +166,23 @@ func resourceMonitorV2Create(d *schema.ResourceData, meta interface{}) error { func resourceMonitorV2Read(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + lbClient, err := chooseLBV2Client(d, config) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } - monitor, err := monitors.Get(networkingClient, d.Id()).Extract() + monitor, err := monitors.Get(lbClient, d.Id()).Extract() if err != nil { - return CheckDeleted(d, err, "LBV2 Monitor") + return CheckDeleted(d, err, "monitor") } - log.Printf("[DEBUG] Retrieved OpenStack LBaaSV2 Monitor %s: %+v", d.Id(), monitor) + log.Printf("[DEBUG] Retrieved monitor %s: %#v", d.Id(), monitor) + + // Required by import + if len(monitor.Pools) > 0 { + d.Set("pool_id", monitor.Pools[0].ID) + } - d.Set("id", monitor.ID) d.Set("tenant_id", monitor.TenantID) d.Set("type", monitor.Type) d.Set("delay", monitor.Delay) @@ -179,7 +200,7 @@ func resourceMonitorV2Read(d *schema.ResourceData, meta interface{}) error { func resourceMonitorV2Update(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + lbClient, err := chooseLBV2Client(d, config) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } @@ -205,17 +226,56 @@ func resourceMonitorV2Update(d *schema.ResourceData, meta interface{}) error { updateOpts.AdminStateUp = &asu } if d.HasChange("name") { - updateOpts.Name = d.Get("name").(string) + name := d.Get("name").(string) + updateOpts.Name = &name } if d.HasChange("http_method") { updateOpts.HTTPMethod = d.Get("http_method").(string) } - log.Printf("[DEBUG] Updating OpenStack LBaaSV2 Monitor %s with options: %+v", d.Id(), updateOpts) - - _, err = monitors.Update(networkingClient, d.Id(), updateOpts).Extract() + // Get a clean copy of the parent pool. + poolID := d.Get("pool_id").(string) + parentPool, err := pools.Get(lbClient, poolID).Extract() if err != nil { - return fmt.Errorf("Error updating OpenStack LBaaSV2 Monitor: %s", err) + return fmt.Errorf("Unable to retrieve parent pool %s: %s", poolID, err) + } + + // Get a clean copy of the monitor. + monitor, err := monitors.Get(lbClient, d.Id()).Extract() + if err != nil { + return fmt.Errorf("Unable to retrieve monitor %s: %s", d.Id(), err) + } + + // Wait for parent pool to become active before continuing + timeout := d.Timeout(schema.TimeoutUpdate) + err = waitForLBV2Pool(lbClient, parentPool, "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err + } + + // Wait for monitor to become active before continuing + err = waitForLBV2Monitor(lbClient, parentPool, monitor, "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating monitor %s with options: %#v", d.Id(), updateOpts) + err = resource.Retry(timeout, func() *resource.RetryError { + _, err = monitors.Update(lbClient, d.Id(), updateOpts).Extract() + if err != nil { + return checkForRetryableError(err) + } + return nil + }) + + if err != nil { + return fmt.Errorf("Unable to update monitor %s: %s", d.Id(), err) + } + + // Wait for monitor to become active before continuing + err = waitForLBV2Monitor(lbClient, parentPool, monitor, "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err } return resourceMonitorV2Read(d, meta) @@ -223,73 +283,49 @@ func resourceMonitorV2Update(d *schema.ResourceData, meta interface{}) error { func resourceMonitorV2Delete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + lbClient, err := chooseLBV2Client(d, config) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } - stateConf := &resource.StateChangeConf{ - Pending: []string{"ACTIVE", "PENDING_DELETE"}, - Target: []string{"DELETED"}, - Refresh: waitForMonitorDelete(networkingClient, d.Id()), - Timeout: d.Timeout(schema.TimeoutDelete), - Delay: 5 * time.Second, - MinTimeout: 3 * time.Second, - } - - _, err = stateConf.WaitForState() + // Get a clean copy of the parent pool. + poolID := d.Get("pool_id").(string) + parentPool, err := pools.Get(lbClient, poolID).Extract() if err != nil { - return fmt.Errorf("Error deleting OpenStack LBaaSV2 Monitor: %s", err) + return fmt.Errorf("Unable to retrieve parent pool (%s) for the monitor: %s", poolID, err) + } + + // Get a clean copy of the monitor. + monitor, err := monitors.Get(lbClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "Unable to retrieve monitor") + } + + // Wait for parent pool to become active before continuing + timeout := d.Timeout(schema.TimeoutUpdate) + err = waitForLBV2Pool(lbClient, parentPool, "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err + } + + log.Printf("[DEBUG] Deleting monitor %s", d.Id()) + err = resource.Retry(timeout, func() *resource.RetryError { + err = monitors.Delete(lbClient, d.Id()).ExtractErr() + if err != nil { + return checkForRetryableError(err) + } + return nil + }) + + if err != nil { + return CheckDeleted(d, err, "Error deleting monitor") + } + + // Wait for monitor to become DELETED + err = waitForLBV2Monitor(lbClient, parentPool, monitor, "DELETED", lbPendingDeleteStatuses, timeout) + if err != nil { + return err } - d.SetId("") return nil } - -func waitForMonitorActive(networkingClient *gophercloud.ServiceClient, monitorID string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - monitor, err := monitors.Get(networkingClient, monitorID).Extract() - if err != nil { - return nil, "", err - } - - log.Printf("[DEBUG] OpenStack LBaaSV2 Monitor: %+v", monitor) - return monitor, "ACTIVE", nil - } -} - -func waitForMonitorDelete(networkingClient *gophercloud.ServiceClient, monitorID string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - log.Printf("[DEBUG] Attempting to delete OpenStack LBaaSV2 Monitor %s", monitorID) - - monitor, err := monitors.Get(networkingClient, monitorID).Extract() - if err != nil { - if _, ok := err.(gophercloud.ErrDefault404); ok { - log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Monitor %s", monitorID) - return monitor, "DELETED", nil - } - return monitor, "ACTIVE", err - } - - log.Printf("[DEBUG] Openstack LBaaSV2 Monitor: %+v", monitor) - err = monitors.Delete(networkingClient, monitorID).ExtractErr() - if err != nil { - if _, ok := err.(gophercloud.ErrDefault404); ok { - log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Monitor %s", monitorID) - return monitor, "DELETED", nil - } - - if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok { - if errCode.Actual == 409 { - log.Printf("[DEBUG] OpenStack LBaaSV2 Monitor (%s) is still in use.", monitorID) - return monitor, "ACTIVE", nil - } - } - - return monitor, "ACTIVE", err - } - - log.Printf("[DEBUG] OpenStack LBaaSV2 Monitor %s still active.", monitorID) - return monitor, "ACTIVE", nil - } -} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_pool_v1.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_pool_v1.go index e6a13c244..9e8f7ddb9 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_pool_v1.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_pool_v1.go @@ -1,19 +1,15 @@ package openstack import ( - "bytes" "fmt" "log" "time" - "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/members" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools" - "github.com/gophercloud/gophercloud/pagination" ) func resourceLBPoolV1() *schema.Resource { @@ -32,82 +28,52 @@ func resourceLBPoolV1() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "name": &schema.Schema{ + "name": { Type: schema.TypeString, Required: true, ForceNew: false, }, - "protocol": &schema.Schema{ + "protocol": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "subnet_id": &schema.Schema{ + "subnet_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "lb_method": &schema.Schema{ + "lb_method": { Type: schema.TypeString, Required: true, ForceNew: false, }, - "lb_provider": &schema.Schema{ + "lb_provider": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "tenant_id": &schema.Schema{ + "tenant_id": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, - "member": &schema.Schema{ - Type: schema.TypeSet, - Deprecated: "Use openstack_lb_member_v1 instead. This attribute will be removed in a future version.", - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, - DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""), - }, - "tenant_id": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - ForceNew: true, - }, - "address": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "port": &schema.Schema{ - Type: schema.TypeInt, - Required: true, - ForceNew: true, - }, - "admin_state_up": &schema.Schema{ - Type: schema.TypeBool, - Required: true, - ForceNew: false, - }, - }, - }, - Set: resourceLBMemberV1Hash, + "member": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + Removed: "Use openstack_lb_member_v1 instead.", }, - "monitor_ids": &schema.Schema{ + "monitor_ids": { Type: schema.TypeSet, Optional: true, ForceNew: false, @@ -176,15 +142,6 @@ func resourceLBPoolV1Create(d *schema.ResourceData, meta interface{}) error { } } - if memberOpts := resourcePoolMembersV1(d); memberOpts != nil { - for _, memberOpt := range memberOpts { - _, err := members.Create(networkingClient, memberOpt).Extract() - if err != nil { - return fmt.Errorf("Error creating OpenStack LB member: %s", err) - } - } - } - return resourceLBPoolV1Read(d, meta) } @@ -209,7 +166,6 @@ func resourceLBPoolV1Read(d *schema.ResourceData, meta interface{}) error { d.Set("lb_provider", p.Provider) d.Set("tenant_id", p.TenantID) d.Set("monitor_ids", p.MonitorIDs) - d.Set("member_ids", p.MemberIDs) d.Set("region", GetRegion(d, config)) return nil @@ -226,17 +182,17 @@ func resourceLBPoolV1Update(d *schema.ResourceData, meta interface{}) error { // If either option changed, update both. // Gophercloud complains if one is empty. if d.HasChange("name") || d.HasChange("lb_method") { - updateOpts.Name = d.Get("name").(string) + name := d.Get("name").(string) + updateOpts.Name = &name lbMethod := resourceLBPoolV1DetermineLBMethod(d.Get("lb_method").(string)) updateOpts.LBMethod = lbMethod - } - log.Printf("[DEBUG] Updating OpenStack LB Pool %s with options: %+v", d.Id(), updateOpts) - - _, err = pools.Update(networkingClient, d.Id(), updateOpts).Extract() - if err != nil { - return fmt.Errorf("Error updating OpenStack LB Pool: %s", err) + log.Printf("[DEBUG] Updating OpenStack LB Pool %s with options: %+v", d.Id(), updateOpts) + _, err = pools.Update(networkingClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack LB Pool: %s", err) + } } if d.HasChange("monitor_ids") { @@ -266,49 +222,6 @@ func resourceLBPoolV1Update(d *schema.ResourceData, meta interface{}) error { } } - if d.HasChange("member") { - oldMembersRaw, newMembersRaw := d.GetChange("member") - oldMembersSet, newMembersSet := oldMembersRaw.(*schema.Set), newMembersRaw.(*schema.Set) - membersToAdd := newMembersSet.Difference(oldMembersSet) - membersToRemove := oldMembersSet.Difference(newMembersSet) - - log.Printf("[DEBUG] Members to add: %v", membersToAdd) - - log.Printf("[DEBUG] Members to remove: %v", membersToRemove) - - for _, m := range membersToRemove.List() { - oldMember := resourcePoolMemberV1(d, m) - listOpts := members.ListOpts{ - PoolID: d.Id(), - Address: oldMember.Address, - ProtocolPort: oldMember.ProtocolPort, - } - err = members.List(networkingClient, listOpts).EachPage(func(page pagination.Page) (bool, error) { - extractedMembers, err := members.ExtractMembers(page) - if err != nil { - return false, err - } - for _, member := range extractedMembers { - err := members.Delete(networkingClient, member.ID).ExtractErr() - if err != nil { - return false, fmt.Errorf("Error deleting member (%s) from OpenStack LB pool (%s): %s", member.ID, d.Id(), err) - } - log.Printf("[DEBUG] Deleted member (%s) from pool (%s)", member.ID, d.Id()) - } - return true, nil - }) - } - - for _, m := range membersToAdd.List() { - createOpts := resourcePoolMemberV1(d, m) - newMember, err := members.Create(networkingClient, createOpts).Extract() - if err != nil { - return fmt.Errorf("Error creating LB member: %s", err) - } - log.Printf("[DEBUG] Created member (%s) in OpenStack LB pool (%s)", newMember.ID, d.Id()) - } - } - return resourceLBPoolV1Read(d, meta) } @@ -359,42 +272,6 @@ func resourcePoolMonitorIDsV1(d *schema.ResourceData) []string { return mIDs } -func resourcePoolMembersV1(d *schema.ResourceData) []members.CreateOpts { - memberOptsRaw := d.Get("member").(*schema.Set) - memberOpts := make([]members.CreateOpts, memberOptsRaw.Len()) - for i, raw := range memberOptsRaw.List() { - rawMap := raw.(map[string]interface{}) - memberOpts[i] = members.CreateOpts{ - TenantID: rawMap["tenant_id"].(string), - Address: rawMap["address"].(string), - ProtocolPort: rawMap["port"].(int), - PoolID: d.Id(), - } - } - return memberOpts -} - -func resourcePoolMemberV1(d *schema.ResourceData, raw interface{}) members.CreateOpts { - rawMap := raw.(map[string]interface{}) - return members.CreateOpts{ - TenantID: rawMap["tenant_id"].(string), - Address: rawMap["address"].(string), - ProtocolPort: rawMap["port"].(int), - PoolID: d.Id(), - } -} - -func resourceLBMemberV1Hash(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%s-", m["region"].(string))) - buf.WriteString(fmt.Sprintf("%s-", m["tenant_id"].(string))) - buf.WriteString(fmt.Sprintf("%s-", m["address"].(string))) - buf.WriteString(fmt.Sprintf("%d-", m["port"].(int))) - - return hashcode.String(buf.String()) -} - func resourceLBPoolV1DetermineProtocol(v string) pools.LBProtocol { var protocol pools.LBProtocol switch v { diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_pool_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_pool_v2.go index 1eb9dfd51..f36ad12e3 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_pool_v2.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_pool_v2.go @@ -8,7 +8,7 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" - "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools" ) @@ -18,66 +18,70 @@ func resourcePoolV2() *schema.Resource { Read: resourcePoolV2Read, Update: resourcePoolV2Update, Delete: resourcePoolV2Delete, + Importer: &schema.ResourceImporter{ + State: resourcePoolV2Import, + }, Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), Delete: schema.DefaultTimeout(10 * time.Minute), }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "tenant_id": &schema.Schema{ + "tenant_id": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "name": &schema.Schema{ + "name": { Type: schema.TypeString, Optional: true, }, - "description": &schema.Schema{ + "description": { Type: schema.TypeString, Optional: true, }, - "protocol": &schema.Schema{ + "protocol": { Type: schema.TypeString, Required: true, ForceNew: true, ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { value := v.(string) - if value != "TCP" && value != "HTTP" && value != "HTTPS" { + if value != "TCP" && value != "HTTP" && value != "HTTPS" && value != "PROXY" { errors = append(errors, fmt.Errorf( - "Only 'TCP', 'HTTP', and 'HTTPS' are supported values for 'protocol'")) + "Only 'TCP', 'HTTP','HTTPS', and 'PROXY' are supported values for 'protocol'")) } return }, }, // One of loadbalancer_id or listener_id must be provided - "loadbalancer_id": &schema.Schema{ + "loadbalancer_id": { Type: schema.TypeString, Optional: true, ForceNew: true, }, // One of loadbalancer_id or listener_id must be provided - "listener_id": &schema.Schema{ + "listener_id": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - "lb_method": &schema.Schema{ + "lb_method": { Type: schema.TypeString, Required: true, ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { @@ -90,13 +94,13 @@ func resourcePoolV2() *schema.Resource { }, }, - "persistence": &schema.Schema{ + "persistence": { Type: schema.TypeList, Optional: true, ForceNew: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "type": &schema.Schema{ + "type": { Type: schema.TypeString, Required: true, ForceNew: true, @@ -110,57 +114,68 @@ func resourcePoolV2() *schema.Resource { }, }, - "cookie_name": &schema.Schema{ + "cookie_name": { Type: schema.TypeString, - Required: true, + Optional: true, ForceNew: true, }, }, }, }, - "admin_state_up": &schema.Schema{ + "admin_state_up": { Type: schema.TypeBool, Default: true, Optional: true, }, - - "id": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Computed: true, - }, }, } } func resourcePoolV2Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + lbClient, err := chooseLBV2Client(d, config) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } adminStateUp := d.Get("admin_state_up").(bool) + lbID := d.Get("loadbalancer_id").(string) + listenerID := d.Get("listener_id").(string) var persistence pools.SessionPersistence if p, ok := d.GetOk("persistence"); ok { pV := (p.([]interface{}))[0].(map[string]interface{}) persistence = pools.SessionPersistence{ - Type: pV["type"].(string), - CookieName: pV["cookie_name"].(string), + Type: pV["type"].(string), + } + + if persistence.Type == "APP_COOKIE" { + if pV["cookie_name"].(string) == "" { + return fmt.Errorf( + "Persistence cookie_name needs to be set if using 'APP_COOKIE' persistence type.") + } else { + persistence.CookieName = pV["cookie_name"].(string) + } + } else { + if pV["cookie_name"].(string) != "" { + return fmt.Errorf( + "Persistence cookie_name can only be set if using 'APP_COOKIE' persistence type.") + } } } + createOpts := pools.CreateOpts{ TenantID: d.Get("tenant_id").(string), Name: d.Get("name").(string), Description: d.Get("description").(string), Protocol: pools.Protocol(d.Get("protocol").(string)), - LoadbalancerID: d.Get("loadbalancer_id").(string), - ListenerID: d.Get("listener_id").(string), + LoadbalancerID: lbID, + ListenerID: listenerID, LBMethod: pools.LBMethod(d.Get("lb_method").(string)), AdminStateUp: &adminStateUp, } + // Must omit if not set if persistence != (pools.SessionPersistence{}) { createOpts.Persistence = &persistence @@ -168,46 +183,41 @@ func resourcePoolV2Create(d *schema.ResourceData, meta interface{}) error { log.Printf("[DEBUG] Create Options: %#v", createOpts) - var pool *pools.Pool - err = resource.Retry(d.Timeout(schema.TimeoutCreate), func() *resource.RetryError { - var err error - log.Printf("[DEBUG] Attempting to create LBaaSV2 pool") - pool, err = pools.Create(networkingClient, createOpts).Extract() + timeout := d.Timeout(schema.TimeoutCreate) + + // Wait for Listener or LoadBalancer to become active before continuing + if listenerID != "" { + listener, err := listeners.Get(lbClient, listenerID).Extract() if err != nil { - switch errCode := err.(type) { - case gophercloud.ErrDefault500: - log.Printf("[DEBUG] OpenStack LBaaSV2 pool is still creating.") - return resource.RetryableError(err) - case gophercloud.ErrUnexpectedResponseCode: - if errCode.Actual == 409 { - log.Printf("[DEBUG] OpenStack LBaaSV2 pool is still creating.") - return resource.RetryableError(err) - } - default: - return resource.NonRetryableError(err) - } + return err + } + + err = waitForLBV2Listener(lbClient, listener, "ACTIVE", lbPendingStatuses, timeout) + } else { + err = waitForLBV2LoadBalancer(lbClient, lbID, "ACTIVE", lbPendingStatuses, timeout) + } + + if err != nil { + return err + } + + log.Printf("[DEBUG] Attempting to create pool") + var pool *pools.Pool + err = resource.Retry(timeout, func() *resource.RetryError { + pool, err = pools.Create(lbClient, createOpts).Extract() + if err != nil { + return checkForRetryableError(err) } return nil }) if err != nil { - return fmt.Errorf("Error creating OpenStack LBaaSV2 pool: %s", err) + return fmt.Errorf("Error creating pool: %s", err) } - log.Printf("[INFO] pool ID: %s", pool.ID) - - log.Printf("[DEBUG] Waiting for Openstack LBaaSV2 pool (%s) to become available.", pool.ID) - - stateConf := &resource.StateChangeConf{ - Pending: []string{"PENDING_CREATE"}, - Target: []string{"ACTIVE"}, - Refresh: waitForPoolActive(networkingClient, pool.ID), - Timeout: d.Timeout(schema.TimeoutCreate), - Delay: 5 * time.Second, - MinTimeout: 3 * time.Second, - } - - _, err = stateConf.WaitForState() + // Pool was successfully created + // Wait for pool to become active before continuing + err = waitForLBV2Pool(lbClient, pool, "ACTIVE", lbPendingStatuses, timeout) if err != nil { return err } @@ -219,17 +229,17 @@ func resourcePoolV2Create(d *schema.ResourceData, meta interface{}) error { func resourcePoolV2Read(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + lbClient, err := chooseLBV2Client(d, config) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } - pool, err := pools.Get(networkingClient, d.Id()).Extract() + pool, err := pools.Get(lbClient, d.Id()).Extract() if err != nil { - return CheckDeleted(d, err, "LBV2 Pool") + return CheckDeleted(d, err, "pool") } - log.Printf("[DEBUG] Retrieved OpenStack LBaaSV2 Pool %s: %+v", d.Id(), pool) + log.Printf("[DEBUG] Retrieved pool %s: %#v", d.Id(), pool) d.Set("lb_method", pool.LBMethod) d.Set("protocol", pool.Protocol) @@ -237,7 +247,6 @@ func resourcePoolV2Read(d *schema.ResourceData, meta interface{}) error { d.Set("tenant_id", pool.TenantID) d.Set("admin_state_up", pool.AdminStateUp) d.Set("name", pool.Name) - d.Set("id", pool.ID) d.Set("persistence", pool.Persistence) d.Set("region", GetRegion(d, config)) @@ -246,7 +255,7 @@ func resourcePoolV2Read(d *schema.ResourceData, meta interface{}) error { func resourcePoolV2Update(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + lbClient, err := chooseLBV2Client(d, config) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } @@ -256,21 +265,49 @@ func resourcePoolV2Update(d *schema.ResourceData, meta interface{}) error { updateOpts.LBMethod = pools.LBMethod(d.Get("lb_method").(string)) } if d.HasChange("name") { - updateOpts.Name = d.Get("name").(string) + name := d.Get("name").(string) + updateOpts.Name = &name } if d.HasChange("description") { - updateOpts.Description = d.Get("description").(string) + description := d.Get("description").(string) + updateOpts.Description = &description } if d.HasChange("admin_state_up") { asu := d.Get("admin_state_up").(bool) updateOpts.AdminStateUp = &asu } - log.Printf("[DEBUG] Updating OpenStack LBaaSV2 Pool %s with options: %+v", d.Id(), updateOpts) + timeout := d.Timeout(schema.TimeoutUpdate) - _, err = pools.Update(networkingClient, d.Id(), updateOpts).Extract() + // Get a clean copy of the pool. + pool, err := pools.Get(lbClient, d.Id()).Extract() if err != nil { - return fmt.Errorf("Error updating OpenStack LBaaSV2 Pool: %s", err) + return fmt.Errorf("Unable to retrieve pool %s: %s", d.Id(), err) + } + + // Wait for pool to become active before continuing + err = waitForLBV2Pool(lbClient, pool, "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating pool %s with options: %#v", d.Id(), updateOpts) + err = resource.Retry(timeout, func() *resource.RetryError { + _, err = pools.Update(lbClient, d.Id(), updateOpts).Extract() + if err != nil { + return checkForRetryableError(err) + } + return nil + }) + + if err != nil { + return fmt.Errorf("Unable to update pool %s: %s", d.Id(), err) + } + + // Wait for pool to become active before continuing + err = waitForLBV2Pool(lbClient, pool, "ACTIVE", lbPendingStatuses, timeout) + if err != nil { + return err } return resourcePoolV2Read(d, meta) @@ -278,74 +315,62 @@ func resourcePoolV2Update(d *schema.ResourceData, meta interface{}) error { func resourcePoolV2Delete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + lbClient, err := chooseLBV2Client(d, config) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } - stateConf := &resource.StateChangeConf{ - Pending: []string{"ACTIVE", "PENDING_DELETE"}, - Target: []string{"DELETED"}, - Refresh: waitForPoolDelete(networkingClient, d.Id()), - Timeout: d.Timeout(schema.TimeoutDelete), - Delay: 5 * time.Second, - MinTimeout: 3 * time.Second, - } + timeout := d.Timeout(schema.TimeoutDelete) - _, err = stateConf.WaitForState() + // Get a clean copy of the pool. + pool, err := pools.Get(lbClient, d.Id()).Extract() if err != nil { - return fmt.Errorf("Error deleting OpenStack LBaaSV2 Pool: %s", err) + return CheckDeleted(d, err, "Unable to retrieve pool") + } + + log.Printf("[DEBUG] Attempting to delete pool %s", d.Id()) + err = resource.Retry(timeout, func() *resource.RetryError { + err = pools.Delete(lbClient, d.Id()).ExtractErr() + if err != nil { + return checkForRetryableError(err) + } + return nil + }) + + if err != nil { + return CheckDeleted(d, err, "Error deleting pool") + } + + // Wait for Pool to delete + err = waitForLBV2Pool(lbClient, pool, "DELETED", lbPendingDeleteStatuses, timeout) + if err != nil { + return err } - d.SetId("") return nil } -func waitForPoolActive(networkingClient *gophercloud.ServiceClient, poolID string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - pool, err := pools.Get(networkingClient, poolID).Extract() - if err != nil { - return nil, "", err - } - - // The pool resource has no Status attribute, so a successful Get is the best we can do - log.Printf("[DEBUG] OpenStack LBaaSV2 Pool: %+v", pool) - return pool, "ACTIVE", nil +func resourcePoolV2Import(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + lbClient, err := chooseLBV2Client(d, config) + if err != nil { + return nil, fmt.Errorf("Error creating OpenStack networking client: %s", err) } -} -func waitForPoolDelete(networkingClient *gophercloud.ServiceClient, poolID string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - log.Printf("[DEBUG] Attempting to delete OpenStack LBaaSV2 Pool %s", poolID) - - pool, err := pools.Get(networkingClient, poolID).Extract() - if err != nil { - if _, ok := err.(gophercloud.ErrDefault404); ok { - log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Pool %s", poolID) - return pool, "DELETED", nil - } - return pool, "ACTIVE", err - } - - log.Printf("[DEBUG] Openstack LBaaSV2 Pool: %+v", pool) - err = pools.Delete(networkingClient, poolID).ExtractErr() - if err != nil { - if _, ok := err.(gophercloud.ErrDefault404); ok { - log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Pool %s", poolID) - return pool, "DELETED", nil - } - - if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok { - if errCode.Actual == 409 { - log.Printf("[DEBUG] OpenStack LBaaSV2 Pool (%s) is still in use.", poolID) - return pool, "ACTIVE", nil - } - } - - return pool, "ACTIVE", err - } - - log.Printf("[DEBUG] OpenStack LBaaSV2 Pool %s still active.", poolID) - return pool, "ACTIVE", nil + pool, err := pools.Get(lbClient, d.Id()).Extract() + if err != nil { + return nil, CheckDeleted(d, err, "pool") } + + log.Printf("[DEBUG] Retrieved pool %s during the import: %#v", d.Id(), pool) + + if len(pool.Listeners) > 0 && pool.Listeners[0].ID != "" { + d.Set("listener_id", pool.Listeners[0].ID) + } else if len(pool.Loadbalancers) > 0 && pool.Loadbalancers[0].ID != "" { + d.Set("loadbalancer_id", pool.Loadbalancers[0].ID) + } else { + return nil, fmt.Errorf("Unable to detect pool's Listener ID or Load Balancer ID") + } + + return []*schema.ResourceData{d}, nil } diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_vip_v1.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_vip_v1.go index 39acb5f48..4e60be92e 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_vip_v1.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_lb_vip_v1.go @@ -28,77 +28,77 @@ func resourceLBVipV1() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "name": &schema.Schema{ + "name": { Type: schema.TypeString, Required: true, ForceNew: false, }, - "subnet_id": &schema.Schema{ + "subnet_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "protocol": &schema.Schema{ + "protocol": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "port": &schema.Schema{ + "port": { Type: schema.TypeInt, Required: true, ForceNew: true, }, - "pool_id": &schema.Schema{ + "pool_id": { Type: schema.TypeString, Required: true, ForceNew: false, }, - "tenant_id": &schema.Schema{ + "tenant_id": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "address": &schema.Schema{ + "address": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "description": &schema.Schema{ + "description": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: false, }, - "persistence": &schema.Schema{ + "persistence": { Type: schema.TypeMap, Optional: true, ForceNew: false, }, - "conn_limit": &schema.Schema{ + "conn_limit": { Type: schema.TypeInt, Optional: true, Computed: true, ForceNew: false, }, - "port_id": &schema.Schema{ + "port_id": { Type: schema.TypeString, Computed: true, ForceNew: false, }, - "floating_ip": &schema.Schema{ + "floating_ip": { Type: schema.TypeString, Optional: true, ForceNew: false, }, - "admin_state_up": &schema.Schema{ + "admin_state_up": { Type: schema.TypeBool, Optional: true, Computed: true, diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_addressscope_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_addressscope_v2.go new file mode 100644 index 000000000..e89414c7c --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_addressscope_v2.go @@ -0,0 +1,192 @@ +package openstack + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes" +) + +func resourceNetworkingAddressScopeV2() *schema.Resource { + return &schema.Resource{ + Create: resourceNetworkingAddressScopeV2Create, + Read: resourceNetworkingAddressScopeV2Read, + Update: resourceNetworkingAddressScopeV2Update, + Delete: resourceNetworkingAddressScopeV2Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: false, + }, + + "ip_version": { + Type: schema.TypeInt, + Optional: true, + Default: 4, + ForceNew: true, + }, + + "shared": { + Type: schema.TypeBool, + Optional: true, + ForceNew: false, + Computed: true, + }, + + "project_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + }, + } +} + +func resourceNetworkingAddressScopeV2Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + createOpts := addressscopes.CreateOpts{ + Name: d.Get("name").(string), + ProjectID: d.Get("project_id").(string), + IPVersion: d.Get("ip_version").(int), + Shared: d.Get("shared").(bool), + } + + log.Printf("[DEBUG] openstack_networking_addressscope_v2 create options: %#v", createOpts) + a, err := addressscopes.Create(networkingClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating openstack_networking_addressscope_v2: %s", err) + } + + log.Printf("[DEBUG] Waiting for openstack_networking_addressscope_v2 %s to become available", a.ID) + + stateConf := &resource.StateChangeConf{ + Target: []string{"ACTIVE"}, + Refresh: resourceNetworkingAddressScopeV2StateRefreshFunc(networkingClient, a.ID), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for openstack_networking_addressscope_v2 %s to become available: %s", a.ID, err) + } + + d.SetId(a.ID) + + log.Printf("[DEBUG] Created openstack_networking_addressscope_v2 %s: %#v", a.ID, a) + return resourceNetworkingAddressScopeV2Read(d, meta) +} + +func resourceNetworkingAddressScopeV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + a, err := addressscopes.Get(networkingClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "Error getting openstack_networking_addressscope_v2") + } + + log.Printf("[DEBUG] Retrieved openstack_networking_addressscope_v2 %s: %#v", d.Id(), a) + + d.Set("region", GetRegion(d, config)) + d.Set("name", a.Name) + d.Set("project_id", a.ProjectID) + d.Set("ip_version", a.IPVersion) + d.Set("shared", a.Shared) + + return nil +} + +func resourceNetworkingAddressScopeV2Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + var ( + hasChange bool + updateOpts addressscopes.UpdateOpts + ) + + if d.HasChange("name") { + hasChange = true + v := d.Get("name").(string) + updateOpts.Name = &v + } + + if d.HasChange("shared") { + hasChange = true + v := d.Get("shared").(bool) + updateOpts.Shared = &v + } + + if hasChange { + log.Printf("[DEBUG] openstack_networking_addressscope_v2 %s update options: %#v", d.Id(), updateOpts) + _, err = addressscopes.Update(networkingClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating openstack_networking_addressscope_v2 %s: %s", d.Id(), err) + } + } + + return resourceNetworkingAddressScopeV2Read(d, meta) +} + +func resourceNetworkingAddressScopeV2Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + if err := addressscopes.Delete(networkingClient, d.Id()).ExtractErr(); err != nil { + return CheckDeleted(d, err, "Error deleting openstack_networking_addressscope_v2") + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"ACTIVE"}, + Target: []string{"DELETED"}, + Refresh: resourceNetworkingAddressScopeV2StateRefreshFunc(networkingClient, d.Id()), + Timeout: d.Timeout(schema.TimeoutDelete), + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for openstack_networking_addressscope_v2 %s to delete: %s", d.Id(), err) + } + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_floatingip_associate_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_floatingip_associate_v2.go new file mode 100644 index 000000000..f77bcdf07 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_floatingip_associate_v2.go @@ -0,0 +1,118 @@ +package openstack + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform/helper/schema" + + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" +) + +func resourceNetworkingFloatingIPAssociateV2() *schema.Resource { + return &schema.Resource{ + Create: resourceNetworkingFloatingIPAssociateV2Create, + Read: resourceNetworkingFloatingIPAssociateV2Read, + Delete: resourceNetworkingFloatingIPAssociateV2Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "floating_ip": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "port_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceNetworkingFloatingIPAssociateV2Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack network client: %s", err) + } + + floatingIP := d.Get("floating_ip").(string) + portID := d.Get("port_id").(string) + + fipID, err := networkingFloatingIPV2ID(networkingClient, floatingIP) + if err != nil { + return fmt.Errorf("Unable to get ID of openstack_networking_floatingip_v2: %s", err) + } + + updateOpts := floatingips.UpdateOpts{ + PortID: &portID, + } + + log.Printf("[DEBUG] openstack_networking_floatingip_associate_v2 create options: %#v", updateOpts) + _, err = floatingips.Update(networkingClient, fipID, updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error associating openstack_networking_floatingip_v2 %s to openstack_networking_port_v2 %s: %s", + fipID, portID, err) + } + + d.SetId(fipID) + + log.Printf("[DEBUG] Created association between openstack_networking_floatingip_v2 %s and openstack_networking_port_v2 %s", + fipID, portID) + return resourceNetworkingFloatingIPAssociateV2Read(d, meta) +} + +func resourceNetworkingFloatingIPAssociateV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack network client: %s", err) + } + + fip, err := floatingips.Get(networkingClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "Error getting openstack_networking_floatingip_v2") + } + + log.Printf("[DEBUG] Retrieved openstack_networking_floatingip_v2 %s: %#v", d.Id(), fip) + + d.Set("floating_ip", fip.FloatingIP) + d.Set("port_id", fip.PortID) + d.Set("region", GetRegion(d, config)) + + return nil +} + +func resourceNetworkingFloatingIPAssociateV2Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack network client: %s", err) + } + + portID := d.Get("port_id").(string) + updateOpts := floatingips.UpdateOpts{ + PortID: new(string), + } + + log.Printf("[DEBUG] openstack_networking_floatingip_v2 disassociating options: %#v", updateOpts) + _, err = floatingips.Update(networkingClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error disassociating openstack_networking_floatingip_v2 %s from openstack_networking_port_v2 %s: %s", + d.Id(), portID, err) + } + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_floatingip_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_floatingip_v2.go index 8f2291021..930d716ae 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_floatingip_v2.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_floatingip_v2.go @@ -8,10 +8,8 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" - "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" - "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" - "github.com/gophercloud/gophercloud/pagination" ) func resourceNetworkingFloatingIPV2() *schema.Resource { @@ -30,43 +28,73 @@ func resourceNetworkingFloatingIPV2() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "address": &schema.Schema{ + + "description": { Type: schema.TypeString, - Computed: true, + Optional: true, }, - "pool": &schema.Schema{ + + "address": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "pool": { Type: schema.TypeString, Required: true, ForceNew: true, DefaultFunc: schema.EnvDefaultFunc("OS_POOL_NAME", nil), }, - "port_id": &schema.Schema{ + + "port_id": { Type: schema.TypeString, Optional: true, Computed: true, }, - "tenant_id": &schema.Schema{ + + "tenant_id": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "fixed_ip": &schema.Schema{ + + "fixed_ip": { Type: schema.TypeString, Optional: true, Computed: true, }, - "value_specs": &schema.Schema{ + + "subnet_id": { + Type: schema.TypeString, + Optional: true, + }, + + "value_specs": { Type: schema.TypeMap, Optional: true, ForceNew: true, }, + + "tags": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "all_tags": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, }, } } @@ -78,43 +106,61 @@ func resourceNetworkFloatingIPV2Create(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error creating OpenStack network client: %s", err) } - poolID, err := getNetworkID(d, meta, d.Get("pool").(string)) + poolName := d.Get("pool").(string) + poolID, err := networkingNetworkV2ID(d, meta, poolName) if err != nil { - return fmt.Errorf("Error retrieving floating IP pool name: %s", err) + return fmt.Errorf("Error retrieving ID for openstack_networking_floatingip_v2 pool name %s: %s", poolName, err) } if len(poolID) == 0 { - return fmt.Errorf("No network found with name: %s", d.Get("pool").(string)) + return fmt.Errorf("No network found with name: %s", poolName) } createOpts := FloatingIPCreateOpts{ floatingips.CreateOpts{ FloatingNetworkID: poolID, + Description: d.Get("description").(string), + FloatingIP: d.Get("address").(string), PortID: d.Get("port_id").(string), TenantID: d.Get("tenant_id").(string), FixedIP: d.Get("fixed_ip").(string), + SubnetID: d.Get("subnet_id").(string), }, MapValueSpecs(d), } - log.Printf("[DEBUG] Create Options: %#v", createOpts) - floatingIP, err := floatingips.Create(networkingClient, createOpts).Extract() + log.Printf("[DEBUG] openstack_networking_floatingip_v2 create options: %#v", createOpts) + fip, err := floatingips.Create(networkingClient, createOpts).Extract() if err != nil { - return fmt.Errorf("Error allocating floating IP: %s", err) + return fmt.Errorf("Error creating openstack_networking_floatingip_v2: %s", err) } - log.Printf("[DEBUG] Waiting for OpenStack Neutron Floating IP (%s) to become available.", floatingIP.ID) + log.Printf("[DEBUG] Waiting for openstack_networking_floatingip_v2 %s to become available.", fip.ID) stateConf := &resource.StateChangeConf{ - Target: []string{"ACTIVE"}, - Refresh: waitForFloatingIPActive(networkingClient, floatingIP.ID), + Target: []string{"ACTIVE", "DOWN"}, + Refresh: networkingFloatingIPV2StateRefreshFunc(networkingClient, fip.ID), Timeout: d.Timeout(schema.TimeoutCreate), Delay: 5 * time.Second, MinTimeout: 3 * time.Second, } _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for openstack_networking_floatingip_v2 %s to become available: %s", fip.ID, err) + } - d.SetId(floatingIP.ID) + d.SetId(fip.ID) + tags := networkV2AttributesTags(d) + if len(tags) > 0 { + tagOpts := attributestags.ReplaceAllOpts{Tags: tags} + tags, err := attributestags.ReplaceAll(networkingClient, "floatingips", fip.ID, tagOpts).Extract() + if err != nil { + return fmt.Errorf("Error setting tags on openstack_networking_floatingip_v2 %s: %s", fip.ID, err) + } + log.Printf("[DEBUG] Set tags %s on openstack_networking_floatingip_v2 %s", tags, fip.ID) + } + + log.Printf("[DEBUG] Created openstack_networking_floatingip_v2 %s: %#v", fip.ID, fip) return resourceNetworkFloatingIPV2Read(d, meta) } @@ -125,22 +171,27 @@ func resourceNetworkFloatingIPV2Read(d *schema.ResourceData, meta interface{}) e return fmt.Errorf("Error creating OpenStack network client: %s", err) } - floatingIP, err := floatingips.Get(networkingClient, d.Id()).Extract() + fip, err := floatingips.Get(networkingClient, d.Id()).Extract() if err != nil { - return CheckDeleted(d, err, "floating IP") + return CheckDeleted(d, err, "Error getting openstack_networking_floatingip_v2") } - d.Set("address", floatingIP.FloatingIP) - d.Set("port_id", floatingIP.PortID) - d.Set("fixed_ip", floatingIP.FixedIP) - poolName, err := getNetworkName(d, meta, floatingIP.FloatingNetworkID) + log.Printf("[DEBUG] Retrieved openstack_networking_floatingip_v2 %s: %#v", d.Id(), fip) + + d.Set("description", fip.Description) + d.Set("address", fip.FloatingIP) + d.Set("port_id", fip.PortID) + d.Set("fixed_ip", fip.FixedIP) + d.Set("tenant_id", fip.TenantID) + d.Set("region", GetRegion(d, config)) + + networkV2ReadAttributesTags(d, fip.Tags) + + poolName, err := networkingNetworkV2Name(d, meta, fip.FloatingNetworkID) if err != nil { - return fmt.Errorf("Error retrieving floating IP pool name: %s", err) + return fmt.Errorf("Error retrieving pool name for openstack_networking_floatingip_v2 %s: %s", d.Id(), err) } d.Set("pool", poolName) - d.Set("tenant_id", floatingIP.TenantID) - - d.Set("region", GetRegion(d, config)) return nil } @@ -152,18 +203,37 @@ func resourceNetworkFloatingIPV2Update(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error creating OpenStack network client: %s", err) } + var hasChange bool var updateOpts floatingips.UpdateOpts + if d.HasChange("description") { + hasChange = true + description := d.Get("description").(string) + updateOpts.Description = &description + } + if d.HasChange("port_id") { + hasChange = true portID := d.Get("port_id").(string) updateOpts.PortID = &portID } - log.Printf("[DEBUG] Update Options: %#v", updateOpts) + if hasChange { + log.Printf("[DEBUG] openstack_networking_floatingip_v2 %s update options: %#v", d.Id(), updateOpts) + _, err = floatingips.Update(networkingClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating openstack_networking_floatingip_v2 %s: %s", d.Id(), err) + } + } - _, err = floatingips.Update(networkingClient, d.Id(), updateOpts).Extract() - if err != nil { - return fmt.Errorf("Error updating floating IP: %s", err) + if d.HasChange("tags") { + tags := networkV2UpdateAttributesTags(d) + tagOpts := attributestags.ReplaceAllOpts{Tags: tags} + tags, err := attributestags.ReplaceAll(networkingClient, "floatingips", d.Id(), tagOpts).Extract() + if err != nil { + return fmt.Errorf("Error setting tags on openstack_networking_floatingip_v2 %s: %s", d.Id(), err) + } + log.Printf("[DEBUG] Set tags %s on openstack_networking_floatingip_v2 %s", tags, d.Id()) } return resourceNetworkFloatingIPV2Read(d, meta) @@ -176,10 +246,14 @@ func resourceNetworkFloatingIPV2Delete(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error creating OpenStack network client: %s", err) } + if err := floatingips.Delete(networkingClient, d.Id()).ExtractErr(); err != nil { + return CheckDeleted(d, err, "Error deleting openstack_networking_floatingip_v2") + } + stateConf := &resource.StateChangeConf{ - Pending: []string{"ACTIVE"}, + Pending: []string{"ACTIVE", "DOWN"}, Target: []string{"DELETED"}, - Refresh: waitForFloatingIPDelete(networkingClient, d.Id()), + Refresh: networkingFloatingIPV2StateRefreshFunc(networkingClient, d.Id()), Timeout: d.Timeout(schema.TimeoutDelete), Delay: 5 * time.Second, MinTimeout: 3 * time.Second, @@ -187,112 +261,9 @@ func resourceNetworkFloatingIPV2Delete(d *schema.ResourceData, meta interface{}) _, err = stateConf.WaitForState() if err != nil { - return fmt.Errorf("Error deleting OpenStack Neutron Floating IP: %s", err) + return fmt.Errorf("Error waiting for openstack_networking_floatingip_v2 %s to delete: %s", d.Id(), err) } d.SetId("") return nil } - -func getNetworkID(d *schema.ResourceData, meta interface{}, networkName string) (string, error) { - config := meta.(*Config) - networkingClient, err := config.networkingV2Client(GetRegion(d, config)) - if err != nil { - return "", fmt.Errorf("Error creating OpenStack network client: %s", err) - } - - opts := networks.ListOpts{Name: networkName} - pager := networks.List(networkingClient, opts) - networkID := "" - - err = pager.EachPage(func(page pagination.Page) (bool, error) { - networkList, err := networks.ExtractNetworks(page) - if err != nil { - return false, err - } - - for _, n := range networkList { - if n.Name == networkName { - networkID = n.ID - return false, nil - } - } - - return true, nil - }) - - return networkID, err -} - -func getNetworkName(d *schema.ResourceData, meta interface{}, networkID string) (string, error) { - config := meta.(*Config) - networkingClient, err := config.networkingV2Client(GetRegion(d, config)) - if err != nil { - return "", fmt.Errorf("Error creating OpenStack network client: %s", err) - } - - opts := networks.ListOpts{ID: networkID} - pager := networks.List(networkingClient, opts) - networkName := "" - - err = pager.EachPage(func(page pagination.Page) (bool, error) { - networkList, err := networks.ExtractNetworks(page) - if err != nil { - return false, err - } - - for _, n := range networkList { - if n.ID == networkID { - networkName = n.Name - return false, nil - } - } - - return true, nil - }) - - return networkName, err -} - -func waitForFloatingIPActive(networkingClient *gophercloud.ServiceClient, fId string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - f, err := floatingips.Get(networkingClient, fId).Extract() - if err != nil { - return nil, "", err - } - - log.Printf("[DEBUG] OpenStack Neutron Floating IP: %+v", f) - if f.Status == "DOWN" || f.Status == "ACTIVE" { - return f, "ACTIVE", nil - } - - return f, "", nil - } -} - -func waitForFloatingIPDelete(networkingClient *gophercloud.ServiceClient, fId string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - log.Printf("[DEBUG] Attempting to delete OpenStack Floating IP %s.\n", fId) - - f, err := floatingips.Get(networkingClient, fId).Extract() - if err != nil { - if _, ok := err.(gophercloud.ErrDefault404); ok { - log.Printf("[DEBUG] Successfully deleted OpenStack Floating IP %s", fId) - return f, "DELETED", nil - } - return f, "ACTIVE", err - } - - err = floatingips.Delete(networkingClient, fId).ExtractErr() - if err != nil { - if _, ok := err.(gophercloud.ErrDefault404); ok { - log.Printf("[DEBUG] Successfully deleted OpenStack Floating IP %s", fId) - return f, "DELETED", nil - } - return f, "ACTIVE", err - } - - log.Printf("[DEBUG] OpenStack Floating IP %s still active.\n", fId) - return f, "ACTIVE", nil - } -} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_network_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_network_v2.go index 1d24f1cc5..4280fa4ff 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_network_v2.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_network_v2.go @@ -3,14 +3,15 @@ package openstack import ( "fmt" "log" - "strconv" "time" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" - "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/provider" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent" "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" ) @@ -30,52 +31,70 @@ func resourceNetworkingNetworkV2() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, - "name": &schema.Schema{ + + "name": { Type: schema.TypeString, Optional: true, ForceNew: false, }, - "admin_state_up": &schema.Schema{ + + "description": { Type: schema.TypeString, Optional: true, ForceNew: false, + }, + + "admin_state_up": { + Type: schema.TypeBool, + Optional: true, + ForceNew: false, Computed: true, }, - "shared": &schema.Schema{ - Type: schema.TypeString, + + "shared": { + Type: schema.TypeBool, Optional: true, ForceNew: false, Computed: true, }, - "tenant_id": &schema.Schema{ + + "external": { + Type: schema.TypeBool, + Optional: true, + ForceNew: false, + Computed: true, + }, + + "tenant_id": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, - "segments": &schema.Schema{ - Type: schema.TypeList, + + "segments": { + Type: schema.TypeSet, Optional: true, ForceNew: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "physical_network": &schema.Schema{ + "physical_network": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - "network_type": &schema.Schema{ + "network_type": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - "segmentation_id": &schema.Schema{ + "segmentation_id": { Type: schema.TypeInt, Optional: true, ForceNew: true, @@ -83,11 +102,39 @@ func resourceNetworkingNetworkV2() *schema.Resource { }, }, }, - "value_specs": &schema.Schema{ + + "value_specs": { Type: schema.TypeMap, Optional: true, ForceNew: true, }, + + "tags": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "all_tags": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "availability_zone_hints": { + Type: schema.TypeSet, + Computed: true, + ForceNew: true, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "transparent_vlan": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + Computed: true, + }, }, } } @@ -99,68 +146,95 @@ func resourceNetworkingNetworkV2Create(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error creating OpenStack networking client: %s", err) } + azHints := d.Get("availability_zone_hints").(*schema.Set) + createOpts := NetworkCreateOpts{ networks.CreateOpts{ - Name: d.Get("name").(string), - TenantID: d.Get("tenant_id").(string), + Name: d.Get("name").(string), + Description: d.Get("description").(string), + TenantID: d.Get("tenant_id").(string), + AvailabilityZoneHints: expandToStringSlice(azHints.List()), }, MapValueSpecs(d), } - asuRaw := d.Get("admin_state_up").(string) - if asuRaw != "" { - asu, err := strconv.ParseBool(asuRaw) - if err != nil { - return fmt.Errorf("admin_state_up, if provided, must be either 'true' or 'false'") - } + if v, ok := d.GetOkExists("admin_state_up"); ok { + asu := v.(bool) createOpts.AdminStateUp = &asu } - sharedRaw := d.Get("shared").(string) - if sharedRaw != "" { - shared, err := strconv.ParseBool(sharedRaw) - if err != nil { - return fmt.Errorf("shared, if provided, must be either 'true' or 'false': %v", err) - } + if v, ok := d.GetOkExists("shared"); ok { + shared := v.(bool) createOpts.Shared = &shared } - segments := resourceNetworkingNetworkV2Segments(d) + segments := expandNetworkingNetworkSegmentsV2(d.Get("segments").(*schema.Set)) + isExternal := d.Get("external").(bool) + isVLANTransparent := d.Get("transparent_vlan").(bool) - n := &networks.Network{} + // Declare a finalCreateOpts interface. + var finalCreateOpts networks.CreateOptsBuilder + finalCreateOpts = createOpts + + // Add networking segments if specified. if len(segments) > 0 { - providerCreateOpts := provider.CreateOptsExt{ - CreateOptsBuilder: createOpts, + finalCreateOpts = provider.CreateOptsExt{ + CreateOptsBuilder: finalCreateOpts, Segments: segments, } - log.Printf("[DEBUG] Create Options: %#v", providerCreateOpts) - n, err = networks.Create(networkingClient, providerCreateOpts).Extract() - } else { - log.Printf("[DEBUG] Create Options: %#v", createOpts) - n, err = networks.Create(networkingClient, createOpts).Extract() } + // Add the external attribute if specified. + if isExternal { + finalCreateOpts = external.CreateOptsExt{ + CreateOptsBuilder: finalCreateOpts, + External: &isExternal, + } + } + + // Add the transparent VLAN attribute if specified. + if isVLANTransparent { + finalCreateOpts = vlantransparent.CreateOptsExt{ + CreateOptsBuilder: finalCreateOpts, + VLANTransparent: &isVLANTransparent, + } + } + + log.Printf("[DEBUG] openstack_networking_network_v2 create options: %#v", finalCreateOpts) + n, err := networks.Create(networkingClient, finalCreateOpts).Extract() if err != nil { - return fmt.Errorf("Error creating OpenStack Neutron network: %s", err) + return fmt.Errorf("Error creating openstack_networking_network_v2: %s", err) } - log.Printf("[INFO] Network ID: %s", n.ID) - - log.Printf("[DEBUG] Waiting for Network (%s) to become available", n.ID) + log.Printf("[DEBUG] Waiting for openstack_networking_network_v2 %s to become available.", n.ID) stateConf := &resource.StateChangeConf{ Pending: []string{"BUILD"}, - Target: []string{"ACTIVE"}, - Refresh: waitForNetworkActive(networkingClient, n.ID), + Target: []string{"ACTIVE", "DOWN"}, + Refresh: resourceNetworkingNetworkV2StateRefreshFunc(networkingClient, n.ID), Timeout: d.Timeout(schema.TimeoutCreate), Delay: 5 * time.Second, MinTimeout: 3 * time.Second, } _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for openstack_networking_network_v2 %s to become available: %s", n.ID, err) + } d.SetId(n.ID) + tags := networkV2AttributesTags(d) + if len(tags) > 0 { + tagOpts := attributestags.ReplaceAllOpts{Tags: tags} + tags, err := attributestags.ReplaceAll(networkingClient, "networks", n.ID, tagOpts).Extract() + if err != nil { + return fmt.Errorf("Error setting tags on openstack_networking_network_v2 %s: %s", n.ID, err) + } + log.Printf("[DEBUG] Set tags %s on openstack_networking_network_v2 %s", tags, n.ID) + } + + log.Printf("[DEBUG] Created openstack_networking_network_v2 %s: %#v", n.ID, n) return resourceNetworkingNetworkV2Read(d, meta) } @@ -171,18 +245,32 @@ func resourceNetworkingNetworkV2Read(d *schema.ResourceData, meta interface{}) e return fmt.Errorf("Error creating OpenStack networking client: %s", err) } - n, err := networks.Get(networkingClient, d.Id()).Extract() + var n struct { + networks.Network + external.NetworkExternalExt + vlantransparent.TransparentExt + } + err = networks.Get(networkingClient, d.Id()).ExtractInto(&n) if err != nil { - return CheckDeleted(d, err, "network") + return CheckDeleted(d, err, "Error getting openstack_networking_network_v2") } - log.Printf("[DEBUG] Retrieved Network %s: %+v", d.Id(), n) + log.Printf("[DEBUG] Retrieved openstack_networking_network_v2 %s: %#v", d.Id(), n) d.Set("name", n.Name) - d.Set("admin_state_up", strconv.FormatBool(n.AdminStateUp)) - d.Set("shared", strconv.FormatBool(n.Shared)) + d.Set("description", n.Description) + d.Set("admin_state_up", n.AdminStateUp) + d.Set("shared", n.Shared) + d.Set("external", n.External) d.Set("tenant_id", n.TenantID) d.Set("region", GetRegion(d, config)) + d.Set("transparent_vlan", n.VLANTransparent) + + networkV2ReadAttributesTags(d, n.Tags) + + if err := d.Set("availability_zone_hints", n.AvailabilityZoneHints); err != nil { + log.Printf("[DEBUG] Unable to set openstack_networking_network_v2 %s availability_zone_hints: %s", d.Id(), err) + } return nil } @@ -194,36 +282,57 @@ func resourceNetworkingNetworkV2Update(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error creating OpenStack networking client: %s", err) } - var updateOpts networks.UpdateOpts + // Declare finalUpdateOpts interface and basic updateOpts structure. + var ( + finalUpdateOpts networks.UpdateOptsBuilder + updateOpts networks.UpdateOpts + ) + + // Populate basic updateOpts. if d.HasChange("name") { updateOpts.Name = d.Get("name").(string) } + if d.HasChange("description") { + description := d.Get("description").(string) + updateOpts.Description = &description + } if d.HasChange("admin_state_up") { - asuRaw := d.Get("admin_state_up").(string) - if asuRaw != "" { - asu, err := strconv.ParseBool(asuRaw) - if err != nil { - return fmt.Errorf("admin_state_up, if provided, must be either 'true' or 'false'") - } - updateOpts.AdminStateUp = &asu - } + asu := d.Get("admin_state_up").(bool) + updateOpts.AdminStateUp = &asu } if d.HasChange("shared") { - sharedRaw := d.Get("shared").(string) - if sharedRaw != "" { - shared, err := strconv.ParseBool(sharedRaw) - if err != nil { - return fmt.Errorf("shared, if provided, must be either 'true' or 'false': %v", err) - } - updateOpts.Shared = &shared + shared := d.Get("shared").(bool) + updateOpts.Shared = &shared + } + + // Change tags if needed. + if d.HasChange("tags") { + tags := networkV2UpdateAttributesTags(d) + tagOpts := attributestags.ReplaceAllOpts{Tags: tags} + tags, err := attributestags.ReplaceAll(networkingClient, "networks", d.Id(), tagOpts).Extract() + if err != nil { + return fmt.Errorf("Error setting tags on openstack_networking_network_v2 %s: %s", d.Id(), err) + } + log.Printf("[DEBUG] Set tags %s on openstack_networking_network_v2 %s", tags, d.Id()) + } + + // Save basic updateOpts into finalUpdateOpts. + finalUpdateOpts = updateOpts + + // Populate extensions options. + isExternal := false + if d.HasChange("external") { + isExternal = d.Get("external").(bool) + finalUpdateOpts = external.UpdateOptsExt{ + UpdateOptsBuilder: finalUpdateOpts, + External: &isExternal, } } - log.Printf("[DEBUG] Updating Network %s with options: %+v", d.Id(), updateOpts) - - _, err = networks.Update(networkingClient, d.Id(), updateOpts).Extract() + log.Printf("[DEBUG] openstack_networking_network_v2 %s update options: %#v", d.Id(), finalUpdateOpts) + _, err = networks.Update(networkingClient, d.Id(), finalUpdateOpts).Extract() if err != nil { - return fmt.Errorf("Error updating OpenStack Neutron Network: %s", err) + return fmt.Errorf("Error updating openstack_networking_network_v2 %s: %s", d.Id(), err) } return resourceNetworkingNetworkV2Read(d, meta) @@ -236,10 +345,14 @@ func resourceNetworkingNetworkV2Delete(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error creating OpenStack networking client: %s", err) } + if err := networks.Delete(networkingClient, d.Id()).ExtractErr(); err != nil { + return CheckDeleted(d, err, "Error deleting openstack_networking_network_v2") + } + stateConf := &resource.StateChangeConf{ Pending: []string{"ACTIVE"}, Target: []string{"DELETED"}, - Refresh: waitForNetworkDelete(networkingClient, d.Id()), + Refresh: resourceNetworkingNetworkV2StateRefreshFunc(networkingClient, d.Id()), Timeout: d.Timeout(schema.TimeoutDelete), Delay: 5 * time.Second, MinTimeout: 3 * time.Second, @@ -247,80 +360,9 @@ func resourceNetworkingNetworkV2Delete(d *schema.ResourceData, meta interface{}) _, err = stateConf.WaitForState() if err != nil { - return fmt.Errorf("Error deleting OpenStack Neutron Network: %s", err) + return fmt.Errorf("Error waiting for openstack_networking_network_v2 %s to delete: %s", d.Id(), err) } d.SetId("") return nil } - -func resourceNetworkingNetworkV2Segments(d *schema.ResourceData) (providerSegments []provider.Segment) { - segments := d.Get("segments").([]interface{}) - for _, v := range segments { - var segment provider.Segment - segmentMap := v.(map[string]interface{}) - - if v, ok := segmentMap["physical_network"].(string); ok { - segment.PhysicalNetwork = v - } - - if v, ok := segmentMap["network_type"].(string); ok { - segment.NetworkType = v - } - - if v, ok := segmentMap["segmentation_id"].(int); ok { - segment.SegmentationID = v - } - - providerSegments = append(providerSegments, segment) - } - return -} - -func waitForNetworkActive(networkingClient *gophercloud.ServiceClient, networkId string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - n, err := networks.Get(networkingClient, networkId).Extract() - if err != nil { - return nil, "", err - } - - log.Printf("[DEBUG] OpenStack Neutron Network: %+v", n) - if n.Status == "DOWN" || n.Status == "ACTIVE" { - return n, "ACTIVE", nil - } - - return n, n.Status, nil - } -} - -func waitForNetworkDelete(networkingClient *gophercloud.ServiceClient, networkId string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - log.Printf("[DEBUG] Attempting to delete OpenStack Network %s.\n", networkId) - - n, err := networks.Get(networkingClient, networkId).Extract() - if err != nil { - if _, ok := err.(gophercloud.ErrDefault404); ok { - log.Printf("[DEBUG] Successfully deleted OpenStack Network %s", networkId) - return n, "DELETED", nil - } - return n, "ACTIVE", err - } - - err = networks.Delete(networkingClient, networkId).ExtractErr() - if err != nil { - if _, ok := err.(gophercloud.ErrDefault404); ok { - log.Printf("[DEBUG] Successfully deleted OpenStack Network %s", networkId) - return n, "DELETED", nil - } - if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok { - if errCode.Actual == 409 { - return n, "ACTIVE", nil - } - } - return n, "ACTIVE", err - } - - log.Printf("[DEBUG] OpenStack Network %s still active.\n", networkId) - return n, "ACTIVE", nil - } -} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_port_secgroup_associate_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_port_secgroup_associate_v2.go new file mode 100644 index 000000000..bfc4a5814 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_port_secgroup_associate_v2.go @@ -0,0 +1,208 @@ +package openstack + +import ( + "fmt" + "log" + "strings" + + "github.com/hashicorp/terraform/helper/schema" + + "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" +) + +func resourceNetworkingPortSecGroupAssociateV2() *schema.Resource { + return &schema.Resource{ + Create: resourceNetworkingPortSecGroupAssociateV2Create, + Read: resourceNetworkingPortSecGroupAssociateV2Read, + Update: resourceNetworkingPortSecGroupAssociateV2Update, + Delete: resourceNetworkingPortSecGroupAssociateV2Delete, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "port_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "security_group_ids": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + + "enforce": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "all_security_group_ids": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + }, + } +} + +func resourceNetworkingPortSecGroupAssociateV2Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + securityGroups := expandToStringSlice(d.Get("security_group_ids").(*schema.Set).List()) + portID := d.Get("port_id").(string) + + port, err := ports.Get(networkingClient, portID).Extract() + if err != nil { + return fmt.Errorf("Unable to get %s Port: %s", portID, err) + } + + log.Printf("[DEBUG] Retrieved Port %s: %+v", portID, port) + + var updateOpts ports.UpdateOpts + var enforce bool + if v, ok := d.GetOkExists("enforce"); ok { + enforce = v.(bool) + } + + if enforce { + updateOpts.SecurityGroups = &securityGroups + } else { + // append security groups + sg := sliceUnion(port.SecurityGroups, securityGroups) + updateOpts.SecurityGroups = &sg + } + + log.Printf("[DEBUG] Port Security Group Associate Options: %#v", updateOpts.SecurityGroups) + + _, err = ports.Update(networkingClient, portID, updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error associating %s port with '%s' security groups: %s", portID, strings.Join(securityGroups, ","), err) + } + + d.SetId(portID) + + return resourceNetworkingPortSecGroupAssociateV2Read(d, meta) +} + +func resourceNetworkingPortSecGroupAssociateV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + port, err := ports.Get(networkingClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "Error fetching port security groups") + } + + var enforce bool + if v, ok := d.GetOkExists("enforce"); ok { + enforce = v.(bool) + } + + d.Set("all_security_group_ids", port.SecurityGroups) + + if enforce { + d.Set("security_group_ids", port.SecurityGroups) + } else { + allSet := d.Get("all_security_group_ids").(*schema.Set) + desiredSet := d.Get("security_group_ids").(*schema.Set) + actualSet := allSet.Intersection(desiredSet) + if !actualSet.Equal(desiredSet) { + d.Set("security_group_ids", expandToStringSlice(actualSet.List())) + } + } + + d.Set("region", GetRegion(d, config)) + + return nil +} + +func resourceNetworkingPortSecGroupAssociateV2Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + var updateOpts ports.UpdateOpts + var enforce bool + if v, ok := d.GetOkExists("enforce"); ok { + enforce = v.(bool) + } + + if enforce { + securityGroups := expandToStringSlice(d.Get("security_group_ids").(*schema.Set).List()) + updateOpts.SecurityGroups = &securityGroups + } else { + allSet := d.Get("all_security_group_ids").(*schema.Set) + oldIDs, newIDs := d.GetChange("security_group_ids") + oldSet, newSet := oldIDs.(*schema.Set), newIDs.(*schema.Set) + + allWithoutOld := allSet.Difference(oldSet) + + newSecurityGroups := expandToStringSlice(allWithoutOld.Union(newSet).List()) + + updateOpts.SecurityGroups = &newSecurityGroups + } + + if d.HasChange("security_group_ids") || d.HasChange("enforce") { + log.Printf("[DEBUG] Port Security Group Update Options: %#v", updateOpts.SecurityGroups) + _, err = ports.Update(networkingClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack Neutron Port: %s", err) + } + } + + return resourceNetworkingPortSecGroupAssociateV2Read(d, meta) +} + +func resourceNetworkingPortSecGroupAssociateV2Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + var updateOpts ports.UpdateOpts + var enforce bool + if v, ok := d.GetOkExists("enforce"); ok { + enforce = v.(bool) + } + + if enforce { + updateOpts.SecurityGroups = &[]string{} + } else { + allSet := d.Get("all_security_group_ids").(*schema.Set) + oldSet := d.Get("security_group_ids").(*schema.Set) + + allWithoutOld := allSet.Difference(oldSet) + + newSecurityGroups := expandToStringSlice(allWithoutOld.List()) + + updateOpts.SecurityGroups = &newSecurityGroups + } + + log.Printf("[DEBUG] Port security groups disassociation options: %#v", updateOpts.SecurityGroups) + + _, err = ports.Update(networkingClient, d.Id(), updateOpts).Extract() + if err != nil { + return CheckDeleted(d, err, "Error disassociating port security groups") + } + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_port_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_port_v2.go index 6e7ddc53b..53e310797 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_port_v2.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_port_v2.go @@ -1,17 +1,16 @@ package openstack import ( - "bytes" "fmt" "log" "time" - "github.com/hashicorp/terraform/helper/hashcode" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/extradhcpopts" + "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" - - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" + "github.com/hashicorp/terraform/helper/validation" ) func resourceNetworkingPortV2() *schema.Resource { @@ -30,107 +29,179 @@ func resourceNetworkingPortV2() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "name": &schema.Schema{ + + "name": { Type: schema.TypeString, Optional: true, ForceNew: false, }, - "network_id": &schema.Schema{ + + "description": { + Type: schema.TypeString, + Optional: true, + }, + + "network_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "admin_state_up": &schema.Schema{ + + "admin_state_up": { Type: schema.TypeBool, Optional: true, ForceNew: false, Computed: true, }, - "mac_address": &schema.Schema{ + + "mac_address": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, - "tenant_id": &schema.Schema{ + + "tenant_id": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, - "device_owner": &schema.Schema{ + + "device_owner": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, - "security_group_ids": &schema.Schema{ + + "security_group_ids": { Type: schema.TypeSet, Optional: true, ForceNew: false, - Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, Set: schema.HashString, }, - "device_id": &schema.Schema{ + + "no_security_groups": { + Type: schema.TypeBool, + Optional: true, + ForceNew: false, + }, + + "device_id": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, - "fixed_ip": &schema.Schema{ - Type: schema.TypeList, - Optional: true, - ForceNew: false, + + "fixed_ip": { + Type: schema.TypeList, + Optional: true, + ForceNew: false, + ConflictsWith: []string{"no_fixed_ip"}, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "subnet_id": &schema.Schema{ + "subnet_id": { Type: schema.TypeString, Required: true, }, - "ip_address": &schema.Schema{ - Type: schema.TypeString, - Optional: true, + "ip_address": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.SingleIP(), }, }, }, }, - "allowed_address_pairs": &schema.Schema{ + + "no_fixed_ip": { + Type: schema.TypeBool, + Optional: true, + ForceNew: false, + ConflictsWith: []string{"fixed_ip"}, + }, + + "allowed_address_pairs": { Type: schema.TypeSet, Optional: true, ForceNew: false, - Computed: true, - Set: allowedAddressPairsHash, + Set: resourceNetworkingPortV2AllowedAddressPairsHash, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "ip_address": &schema.Schema{ - Type: schema.TypeString, - Required: true, + "ip_address": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.SingleIP(), }, - "mac_address": &schema.Schema{ + "mac_address": { Type: schema.TypeString, Optional: true, - Computed: true, }, }, }, }, - "value_specs": &schema.Schema{ + + "extra_dhcp_option": { + Type: schema.TypeSet, + Optional: true, + ForceNew: false, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "value": { + Type: schema.TypeString, + Required: true, + }, + "ip_version": { + Type: schema.TypeInt, + Default: 4, + Optional: true, + }, + }, + }, + }, + + "value_specs": { Type: schema.TypeMap, Optional: true, ForceNew: true, }, - "all_fixed_ips": &schema.Schema{ + + "all_fixed_ips": { Type: schema.TypeList, Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, + + "all_security_group_ids": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + + "tags": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "all_tags": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, }, } } @@ -142,43 +213,100 @@ func resourceNetworkingPortV2Create(d *schema.ResourceData, meta interface{}) er return fmt.Errorf("Error creating OpenStack networking client: %s", err) } + securityGroups := expandToStringSlice(d.Get("security_group_ids").(*schema.Set).List()) + noSecurityGroups := d.Get("no_security_groups").(bool) + + // Check and make sure an invalid security group configuration wasn't given. + if noSecurityGroups && len(securityGroups) > 0 { + return fmt.Errorf("Cannot have both no_security_groups and security_group_ids set for openstack_networking_port_v2") + } + + allowedAddressPairs := d.Get("allowed_address_pairs").(*schema.Set) createOpts := PortCreateOpts{ ports.CreateOpts{ Name: d.Get("name").(string), - AdminStateUp: resourcePortAdminStateUpV2(d), + Description: d.Get("description").(string), NetworkID: d.Get("network_id").(string), MACAddress: d.Get("mac_address").(string), TenantID: d.Get("tenant_id").(string), DeviceOwner: d.Get("device_owner").(string), - SecurityGroups: resourcePortSecurityGroupsV2(d), DeviceID: d.Get("device_id").(string), - FixedIPs: resourcePortFixedIpsV2(d), - AllowedAddressPairs: resourceAllowedAddressPairsV2(d), + FixedIPs: expandNetworkingPortFixedIPV2(d), + AllowedAddressPairs: expandNetworkingPortAllowedAddressPairsV2(allowedAddressPairs), }, MapValueSpecs(d), } - log.Printf("[DEBUG] Create Options: %#v", createOpts) - p, err := ports.Create(networkingClient, createOpts).Extract() - if err != nil { - return fmt.Errorf("Error creating OpenStack Neutron network: %s", err) + if v, ok := d.GetOkExists("admin_state_up"); ok { + asu := v.(bool) + createOpts.AdminStateUp = &asu } - log.Printf("[INFO] Network ID: %s", p.ID) - log.Printf("[DEBUG] Waiting for OpenStack Neutron Port (%s) to become available.", p.ID) + if noSecurityGroups { + securityGroups = []string{} + createOpts.SecurityGroups = &securityGroups + } + + // Only set SecurityGroups if one was specified. + // Otherwise this would mimic the no_security_groups action. + if len(securityGroups) > 0 { + createOpts.SecurityGroups = &securityGroups + } + + // Declare a finalCreateOpts interface to hold either the + // base create options or the extended DHCP options. + var finalCreateOpts ports.CreateOptsBuilder + finalCreateOpts = createOpts + + dhcpOpts := d.Get("extra_dhcp_option").(*schema.Set) + if dhcpOpts.Len() > 0 { + finalCreateOpts = extradhcpopts.CreateOptsExt{ + CreateOptsBuilder: createOpts, + ExtraDHCPOpts: expandNetworkingPortDHCPOptsV2Create(dhcpOpts), + } + } + + log.Printf("[DEBUG] openstack_networking_port_v2 create options: %#v", finalCreateOpts) + + // Create a Neutron port and set extra DHCP options if they're specified. + var p struct { + ports.Port + extradhcpopts.ExtraDHCPOptsExt + } + + err = ports.Create(networkingClient, finalCreateOpts).ExtractInto(&p) + if err != nil { + return fmt.Errorf("Error creating openstack_networking_port_v2: %s", err) + } + + log.Printf("[DEBUG] Waiting for openstack_networking_port_v2 %s to become available.", p.ID) stateConf := &resource.StateChangeConf{ - Target: []string{"ACTIVE"}, - Refresh: waitForNetworkPortActive(networkingClient, p.ID), + Target: []string{"ACTIVE", "DOWN"}, + Refresh: resourceNetworkingPortV2StateRefreshFunc(networkingClient, p.ID), Timeout: d.Timeout(schema.TimeoutCreate), Delay: 5 * time.Second, MinTimeout: 3 * time.Second, } _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for openstack_networking_port_v2 %s to become available: %s", p.ID, err) + } d.SetId(p.ID) + tags := networkV2AttributesTags(d) + if len(tags) > 0 { + tagOpts := attributestags.ReplaceAllOpts{Tags: tags} + tags, err := attributestags.ReplaceAll(networkingClient, "ports", p.ID, tagOpts).Extract() + if err != nil { + return fmt.Errorf("Error setting tags on openstack_networking_port_v2 %s: %s", p.ID, err) + } + log.Printf("[DEBUG] Set tags %s on openstack_networking_port_v2 %s", tags, p.ID) + } + + log.Printf("[DEBUG] Created openstack_networking_port_v2 %s: %#v", p.ID, p) return resourceNetworkingPortV2Read(d, meta) } @@ -189,40 +317,40 @@ func resourceNetworkingPortV2Read(d *schema.ResourceData, meta interface{}) erro return fmt.Errorf("Error creating OpenStack networking client: %s", err) } - p, err := ports.Get(networkingClient, d.Id()).Extract() + var p struct { + ports.Port + extradhcpopts.ExtraDHCPOptsExt + } + err = ports.Get(networkingClient, d.Id()).ExtractInto(&p) if err != nil { - return CheckDeleted(d, err, "port") + return CheckDeleted(d, err, "Error getting openstack_networking_port_v2") } - log.Printf("[DEBUG] Retrieved Port %s: %+v", d.Id(), p) + log.Printf("[DEBUG] Retrieved openstack_networking_port_v2 %s: %#v", d.Id(), p) d.Set("name", p.Name) + d.Set("description", p.Description) d.Set("admin_state_up", p.AdminStateUp) d.Set("network_id", p.NetworkID) d.Set("mac_address", p.MACAddress) d.Set("tenant_id", p.TenantID) d.Set("device_owner", p.DeviceOwner) - d.Set("security_group_ids", p.SecurityGroups) d.Set("device_id", p.DeviceID) - // Create a slice of all returned Fixed IPs. + networkV2ReadAttributesTags(d, p.Tags) + + // Set a slice of all returned Fixed IPs. // This will be in the order returned by the API, // which is usually alpha-numeric. - var ips []string - for _, ipObject := range p.FixedIPs { - ips = append(ips, ipObject.IPAddress) - } - d.Set("all_fixed_ips", ips) + d.Set("all_fixed_ips", expandNetworkingPortFixedIPToStringSlice(p.FixedIPs)) - // Convert AllowedAddressPairs to list of map - var pairs []map[string]interface{} - for _, pairObject := range p.AllowedAddressPairs { - pair := make(map[string]interface{}) - pair["ip_address"] = pairObject.IPAddress - pair["mac_address"] = pairObject.MACAddress - pairs = append(pairs, pair) - } - d.Set("allowed_address_pairs", pairs) + // Set all security groups. + // This can be different from what the user specified since + // the port can have the "default" group automatically applied. + d.Set("all_security_group_ids", p.SecurityGroups) + + d.Set("allowed_address_pairs", flattenNetworkingPortAllowedAddressPairsV2(p.MACAddress, p.AllowedAddressPairs)) + d.Set("extra_dhcp_option", flattenNetworkingPortDHCPOptsV2(p.ExtraDHCPOptsExt)) d.Set("region", GetRegion(d, config)) @@ -236,40 +364,131 @@ func resourceNetworkingPortV2Update(d *schema.ResourceData, meta interface{}) er return fmt.Errorf("Error creating OpenStack networking client: %s", err) } - // security_group_ids and allowed_address_pairs are able to send empty arrays - // to denote the removal of each. But their default zero-value is translated - // to "null", which has been reported to cause problems in vendor-modified - // OpenStack clouds. Therefore, we must set them in each request update. - updateOpts := ports.UpdateOpts{ - AllowedAddressPairs: resourceAllowedAddressPairsV2(d), - SecurityGroups: resourcePortSecurityGroupsV2(d), + securityGroups := expandToStringSlice(d.Get("security_group_ids").(*schema.Set).List()) + noSecurityGroups := d.Get("no_security_groups").(bool) + + // Check and make sure an invalid security group configuration wasn't given. + if noSecurityGroups && len(securityGroups) > 0 { + return fmt.Errorf("Cannot have both no_security_groups and security_group_ids set for openstack_networking_port_v2") + } + + var hasChange bool + var updateOpts ports.UpdateOpts + + if d.HasChange("allowed_address_pairs") { + hasChange = true + allowedAddressPairs := d.Get("allowed_address_pairs").(*schema.Set) + aap := expandNetworkingPortAllowedAddressPairsV2(allowedAddressPairs) + updateOpts.AllowedAddressPairs = &aap + } + + if d.HasChange("no_security_groups") { + if noSecurityGroups { + hasChange = true + v := []string{} + updateOpts.SecurityGroups = &v + } + } + + if d.HasChange("security_group_ids") { + hasChange = true + updateOpts.SecurityGroups = &securityGroups } if d.HasChange("name") { - updateOpts.Name = d.Get("name").(string) + hasChange = true + name := d.Get("name").(string) + updateOpts.Name = &name + } + + if d.HasChange("description") { + hasChange = true + description := d.Get("description").(string) + updateOpts.Description = &description } if d.HasChange("admin_state_up") { - updateOpts.AdminStateUp = resourcePortAdminStateUpV2(d) + hasChange = true + asu := d.Get("admin_state_up").(bool) + updateOpts.AdminStateUp = &asu } if d.HasChange("device_owner") { - updateOpts.DeviceOwner = d.Get("device_owner").(string) + hasChange = true + deviceOwner := d.Get("device_owner").(string) + updateOpts.DeviceOwner = &deviceOwner } if d.HasChange("device_id") { - updateOpts.DeviceID = d.Get("device_id").(string) + hasChange = true + deviceId := d.Get("device_id").(string) + updateOpts.DeviceID = &deviceId } - if d.HasChange("fixed_ip") { - updateOpts.FixedIPs = resourcePortFixedIpsV2(d) + if d.HasChange("fixed_ip") || d.HasChange("no_fixed_ip") { + fixedIPs := expandNetworkingPortFixedIPV2(d) + if fixedIPs != nil { + hasChange = true + updateOpts.FixedIPs = fixedIPs + } } - log.Printf("[DEBUG] Updating Port %s with options: %+v", d.Id(), updateOpts) + // At this point, perform the update for all "standard" port changes. + if hasChange { + log.Printf("[DEBUG] openstack_networking_port_v2 %s update options: %#v", d.Id(), updateOpts) + _, err = ports.Update(networkingClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack Neutron Port: %s", err) + } + } - _, err = ports.Update(networkingClient, d.Id(), updateOpts).Extract() - if err != nil { - return fmt.Errorf("Error updating OpenStack Neutron Network: %s", err) + // Next, perform any dhcp option changes. + if d.HasChange("extra_dhcp_option") { + o, n := d.GetChange("extra_dhcp_option") + oldDHCPOpts := o.(*schema.Set) + newDHCPOpts := n.(*schema.Set) + + // Delete all old DHCP options, regardless of if they still exist. + // If they do still exist, they will be re-added below. + if oldDHCPOpts.Len() != 0 { + deleteExtraDHCPOpts := expandNetworkingPortDHCPOptsV2Delete(oldDHCPOpts) + dhcpUpdateOpts := extradhcpopts.UpdateOptsExt{ + UpdateOptsBuilder: &ports.UpdateOpts{}, + ExtraDHCPOpts: deleteExtraDHCPOpts, + } + + log.Printf("[DEBUG] Deleting old DHCP opts for openstack_networking_port_v2 %s", d.Id()) + _, err = ports.Update(networkingClient, d.Id(), dhcpUpdateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack Neutron Port: %s", err) + } + } + + // Add any new DHCP options and re-add previously set DHCP options. + if newDHCPOpts.Len() != 0 { + updateExtraDHCPOpts := expandNetworkingPortDHCPOptsV2Update(newDHCPOpts) + dhcpUpdateOpts := extradhcpopts.UpdateOptsExt{ + UpdateOptsBuilder: &ports.UpdateOpts{}, + ExtraDHCPOpts: updateExtraDHCPOpts, + } + + log.Printf("[DEBUG] Updating openstack_networking_port_v2 %s with options: %#v", d.Id(), dhcpUpdateOpts) + _, err = ports.Update(networkingClient, d.Id(), dhcpUpdateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating openstack_networking_port_v2 %s: %s", d.Id(), err) + } + } + } + + // Next, perform any required updates to the tags. + if d.HasChange("tags") { + tags := networkV2UpdateAttributesTags(d) + tagOpts := attributestags.ReplaceAllOpts{Tags: tags} + tags, err := attributestags.ReplaceAll(networkingClient, "ports", d.Id(), tagOpts).Extract() + if err != nil { + return fmt.Errorf("Error setting tags on openstack_networking_port_v2 %s: %s", d.Id(), err) + } + log.Printf("[DEBUG] Set tags %s on openstack_networking_port_v2 %s", tags, d.Id()) } return resourceNetworkingPortV2Read(d, meta) @@ -282,10 +501,14 @@ func resourceNetworkingPortV2Delete(d *schema.ResourceData, meta interface{}) er return fmt.Errorf("Error creating OpenStack networking client: %s", err) } + if err := ports.Delete(networkingClient, d.Id()).ExtractErr(); err != nil { + return CheckDeleted(d, err, "Error deleting openstack_networking_port_v2") + } + stateConf := &resource.StateChangeConf{ Pending: []string{"ACTIVE"}, Target: []string{"DELETED"}, - Refresh: waitForNetworkPortDelete(networkingClient, d.Id()), + Refresh: resourceNetworkingPortV2StateRefreshFunc(networkingClient, d.Id()), Timeout: d.Timeout(schema.TimeoutDelete), Delay: 5 * time.Second, MinTimeout: 3 * time.Second, @@ -293,112 +516,9 @@ func resourceNetworkingPortV2Delete(d *schema.ResourceData, meta interface{}) er _, err = stateConf.WaitForState() if err != nil { - return fmt.Errorf("Error deleting OpenStack Neutron Network: %s", err) + return fmt.Errorf("Error waiting for openstack_networking_port_v2 %s to delete: %s", d.Id(), err) } d.SetId("") return nil } - -func resourcePortSecurityGroupsV2(d *schema.ResourceData) []string { - rawSecurityGroups := d.Get("security_group_ids").(*schema.Set) - groups := make([]string, rawSecurityGroups.Len()) - for i, raw := range rawSecurityGroups.List() { - groups[i] = raw.(string) - } - return groups -} - -func resourcePortFixedIpsV2(d *schema.ResourceData) interface{} { - rawIP := d.Get("fixed_ip").([]interface{}) - - if len(rawIP) == 0 { - return nil - } - - ip := make([]ports.IP, len(rawIP)) - for i, raw := range rawIP { - rawMap := raw.(map[string]interface{}) - ip[i] = ports.IP{ - SubnetID: rawMap["subnet_id"].(string), - IPAddress: rawMap["ip_address"].(string), - } - } - return ip -} - -func resourceAllowedAddressPairsV2(d *schema.ResourceData) []ports.AddressPair { - // ports.AddressPair - rawPairs := d.Get("allowed_address_pairs").(*schema.Set).List() - - pairs := make([]ports.AddressPair, len(rawPairs)) - for i, raw := range rawPairs { - rawMap := raw.(map[string]interface{}) - pairs[i] = ports.AddressPair{ - IPAddress: rawMap["ip_address"].(string), - MACAddress: rawMap["mac_address"].(string), - } - } - return pairs -} - -func resourcePortAdminStateUpV2(d *schema.ResourceData) *bool { - value := false - - if raw, ok := d.GetOk("admin_state_up"); ok && raw == true { - value = true - } - - return &value -} - -func allowedAddressPairsHash(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%s", m["ip_address"].(string))) - - return hashcode.String(buf.String()) -} - -func waitForNetworkPortActive(networkingClient *gophercloud.ServiceClient, portId string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - p, err := ports.Get(networkingClient, portId).Extract() - if err != nil { - return nil, "", err - } - - log.Printf("[DEBUG] OpenStack Neutron Port: %+v", p) - if p.Status == "DOWN" || p.Status == "ACTIVE" { - return p, "ACTIVE", nil - } - - return p, p.Status, nil - } -} - -func waitForNetworkPortDelete(networkingClient *gophercloud.ServiceClient, portId string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - log.Printf("[DEBUG] Attempting to delete OpenStack Neutron Port %s", portId) - - p, err := ports.Get(networkingClient, portId).Extract() - if err != nil { - if _, ok := err.(gophercloud.ErrDefault404); ok { - log.Printf("[DEBUG] Successfully deleted OpenStack Port %s", portId) - return p, "DELETED", nil - } - return p, "ACTIVE", err - } - - err = ports.Delete(networkingClient, portId).ExtractErr() - if err != nil { - if _, ok := err.(gophercloud.ErrDefault404); ok { - log.Printf("[DEBUG] Successfully deleted OpenStack Port %s", portId) - return p, "DELETED", nil - } - return p, "ACTIVE", err - } - - log.Printf("[DEBUG] OpenStack Port %s still active.\n", portId) - return p, "ACTIVE", nil - } -} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_router_interface_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_router_interface_v2.go index 2b1cfe84c..4a082a30c 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_router_interface_v2.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_router_interface_v2.go @@ -18,6 +18,9 @@ func resourceNetworkingRouterInterfaceV2() *schema.Resource { Create: resourceNetworkingRouterInterfaceV2Create, Read: resourceNetworkingRouterInterfaceV2Read, Delete: resourceNetworkingRouterInterfaceV2Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(10 * time.Minute), @@ -25,25 +28,30 @@ func resourceNetworkingRouterInterfaceV2() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "router_id": &schema.Schema{ + + "router_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "subnet_id": &schema.Schema{ + + "subnet_id": { Type: schema.TypeString, Optional: true, + Computed: true, ForceNew: true, }, - "port_id": &schema.Schema{ + + "port_id": { Type: schema.TypeString, Optional: true, + Computed: true, ForceNew: true, }, }, @@ -62,28 +70,31 @@ func resourceNetworkingRouterInterfaceV2Create(d *schema.ResourceData, meta inte PortID: d.Get("port_id").(string), } - log.Printf("[DEBUG] Create Options: %#v", createOpts) - n, err := routers.AddInterface(networkingClient, d.Get("router_id").(string), createOpts).Extract() + log.Printf("[DEBUG] openstack_networking_router_interface_v2 create options: %#v", createOpts) + r, err := routers.AddInterface(networkingClient, d.Get("router_id").(string), createOpts).Extract() if err != nil { - return fmt.Errorf("Error creating OpenStack Neutron router interface: %s", err) + return fmt.Errorf("Error creating openstack_networking_router_interface_v2: %s", err) } - log.Printf("[INFO] Router interface Port ID: %s", n.PortID) - log.Printf("[DEBUG] Waiting for Router Interface (%s) to become available", n.PortID) + log.Printf("[DEBUG] Waiting for openstack_networking_router_interface_v2 %s to become available", r.PortID) stateConf := &resource.StateChangeConf{ Pending: []string{"BUILD", "PENDING_CREATE", "PENDING_UPDATE"}, - Target: []string{"ACTIVE"}, - Refresh: waitForRouterInterfaceActive(networkingClient, n.PortID), + Target: []string{"ACTIVE", "DOWN"}, + Refresh: resourceNetworkingRouterInterfaceV2StateRefreshFunc(networkingClient, r.PortID), Timeout: d.Timeout(schema.TimeoutCreate), Delay: 5 * time.Second, MinTimeout: 3 * time.Second, } _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for openstack_networking_router_interface_v2 %s to become available: %s", r.ID, err) + } - d.SetId(n.PortID) + d.SetId(r.PortID) + log.Printf("[DEBUG] Created openstack_networking_router_interface_v2 %s: %#v", r.ID, r) return resourceNetworkingRouterInterfaceV2Read(d, meta) } @@ -94,20 +105,33 @@ func resourceNetworkingRouterInterfaceV2Read(d *schema.ResourceData, meta interf return fmt.Errorf("Error creating OpenStack networking client: %s", err) } - n, err := ports.Get(networkingClient, d.Id()).Extract() + r, err := ports.Get(networkingClient, d.Id()).Extract() if err != nil { if _, ok := err.(gophercloud.ErrDefault404); ok { d.SetId("") return nil } - return fmt.Errorf("Error retrieving OpenStack Neutron Router Interface: %s", err) + return fmt.Errorf("Error retrieving openstack_networking_router_interface_v2: %s", err) } - log.Printf("[DEBUG] Retrieved Router Interface %s: %+v", d.Id(), n) + log.Printf("[DEBUG] Retrieved openstack_networking_router_interface_v2 %s: %#v", d.Id(), r) + d.Set("router_id", r.DeviceID) + d.Set("port_id", r.ID) d.Set("region", GetRegion(d, config)) + // Set the subnet ID by looking at the port's FixedIPs. + // If there's more than one FixedIP, do not set the subnet + // as it's not possible to confidently determine which subnet + // belongs to this interface. However, that situation should + // not happen. + if len(r.FixedIPs) != 1 { + log.Printf("[DEBUG] Unable to set openstack_networking_router_interface_v2 %s subnet_id", d.Id()) + } else { + d.Set("subnet_id", r.FixedIPs[0].SubnetID) + } + return nil } @@ -121,7 +145,7 @@ func resourceNetworkingRouterInterfaceV2Delete(d *schema.ResourceData, meta inte stateConf := &resource.StateChangeConf{ Pending: []string{"ACTIVE"}, Target: []string{"DELETED"}, - Refresh: waitForRouterInterfaceDelete(networkingClient, d), + Refresh: resourceNetworkingRouterInterfaceV2DeleteRefreshFunc(networkingClient, d), Timeout: d.Timeout(schema.TimeoutDelete), Delay: 5 * time.Second, MinTimeout: 3 * time.Second, @@ -129,63 +153,9 @@ func resourceNetworkingRouterInterfaceV2Delete(d *schema.ResourceData, meta inte _, err = stateConf.WaitForState() if err != nil { - return fmt.Errorf("Error deleting OpenStack Neutron Router Interface: %s", err) + return fmt.Errorf("Error waiting for openstack_networking_router_interface_v2 %s to delete: %s", d.Id(), err) } d.SetId("") return nil } - -func waitForRouterInterfaceActive(networkingClient *gophercloud.ServiceClient, rId string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - r, err := ports.Get(networkingClient, rId).Extract() - if err != nil { - return nil, "", err - } - - log.Printf("[DEBUG] OpenStack Neutron Router Interface: %+v", r) - return r, r.Status, nil - } -} - -func waitForRouterInterfaceDelete(networkingClient *gophercloud.ServiceClient, d *schema.ResourceData) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - routerId := d.Get("router_id").(string) - routerInterfaceId := d.Id() - - log.Printf("[DEBUG] Attempting to delete OpenStack Router Interface %s.", routerInterfaceId) - - removeOpts := routers.RemoveInterfaceOpts{ - SubnetID: d.Get("subnet_id").(string), - PortID: d.Get("port_id").(string), - } - - r, err := ports.Get(networkingClient, routerInterfaceId).Extract() - if err != nil { - if _, ok := err.(gophercloud.ErrDefault404); ok { - log.Printf("[DEBUG] Successfully deleted OpenStack Router Interface %s", routerInterfaceId) - return r, "DELETED", nil - } - return r, "ACTIVE", err - } - - _, err = routers.RemoveInterface(networkingClient, routerId, removeOpts).Extract() - if err != nil { - if _, ok := err.(gophercloud.ErrDefault404); ok { - log.Printf("[DEBUG] Successfully deleted OpenStack Router Interface %s.", routerInterfaceId) - return r, "DELETED", nil - } - if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok { - if errCode.Actual == 409 { - log.Printf("[DEBUG] Router Interface %s is still in use.", routerInterfaceId) - return r, "ACTIVE", nil - } - } - - return r, "ACTIVE", err - } - - log.Printf("[DEBUG] OpenStack Router Interface %s is still active.", routerInterfaceId) - return r, "ACTIVE", nil - } -} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_router_route_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_router_route_v2.go index 291e8e13f..1556c852f 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_router_route_v2.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_router_route_v2.go @@ -3,6 +3,7 @@ package openstack import ( "fmt" "log" + "strings" "github.com/hashicorp/terraform/helper/schema" @@ -15,25 +16,28 @@ func resourceNetworkingRouterRouteV2() *schema.Resource { Create: resourceNetworkingRouterRouteV2Create, Read: resourceNetworkingRouterRouteV2Read, Delete: resourceNetworkingRouterRouteV2Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "router_id": &schema.Schema{ + "router_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "destination_cidr": &schema.Schema{ + "destination_cidr": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "next_hop": &schema.Schema{ + "next_hop": { Type: schema.TypeString, Required: true, ForceNew: true, @@ -115,6 +119,26 @@ func resourceNetworkingRouterRouteV2Read(d *schema.ResourceData, meta interface{ return fmt.Errorf("Error creating OpenStack networking client: %s", err) } + destCidr := d.Get("destination_cidr").(string) + nextHop := d.Get("next_hop").(string) + + routeIDParts := []string{} + if d.Id() != "" && strings.Contains(d.Id(), "-route-") { + routeIDParts = strings.Split(d.Id(), "-route-") + routeLastIDParts := strings.Split(routeIDParts[1], "-") + + if routerId == "" { + routerId = routeIDParts[0] + d.Set("router_id", routerId) + } + if destCidr == "" { + destCidr = routeLastIDParts[0] + } + if nextHop == "" { + nextHop = routeLastIDParts[1] + } + } + n, err := routers.Get(networkingClient, routerId).Extract() if err != nil { if _, ok := err.(gophercloud.ErrDefault404); ok { @@ -127,9 +151,6 @@ func resourceNetworkingRouterRouteV2Read(d *schema.ResourceData, meta interface{ log.Printf("[DEBUG] Retrieved Router %s: %+v", routerId, n) - var destCidr string = d.Get("destination_cidr").(string) - var nextHop string = d.Get("next_hop").(string) - d.Set("next_hop", "") d.Set("destination_cidr", "") diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_router_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_router_v2.go index bda3f2a4f..7cbdf1d14 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_router_v2.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_router_v2.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform/helper/schema" "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers" ) @@ -18,6 +19,9 @@ func resourceNetworkingRouterV2() *schema.Resource { Read: resourceNetworkingRouterV2Read, Update: resourceNetworkingRouterV2Update, Delete: resourceNetworkingRouterV2Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(10 * time.Minute), @@ -25,45 +29,117 @@ func resourceNetworkingRouterV2() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, - DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""), - }, - "name": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - ForceNew: false, - }, - "admin_state_up": &schema.Schema{ - Type: schema.TypeBool, - Optional: true, - ForceNew: false, - Computed: true, - }, - "distributed": &schema.Schema{ - Type: schema.TypeBool, - Optional: true, - ForceNew: true, - Computed: true, - }, - "external_gateway": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - ForceNew: false, - }, - "tenant_id": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, - "value_specs": &schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + "description": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + "admin_state_up": { + Type: schema.TypeBool, + Optional: true, + ForceNew: false, + Computed: true, + }, + "distributed": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + Computed: true, + }, + "external_gateway": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + Computed: true, + Deprecated: "use external_network_id instead", + ConflictsWith: []string{"external_network_id"}, + }, + "external_network_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + Computed: true, + ConflictsWith: []string{"external_gateway"}, + }, + "enable_snat": { + Type: schema.TypeBool, + Optional: true, + ForceNew: false, + Computed: true, + }, + "external_fixed_ip": { + Type: schema.TypeList, + Optional: true, + ForceNew: false, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "subnet_id": { + Type: schema.TypeString, + Optional: true, + }, + "ip_address": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "tenant_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + "value_specs": { Type: schema.TypeMap, Optional: true, ForceNew: true, }, + "availability_zone_hints": { + Type: schema.TypeList, + Computed: true, + ForceNew: true, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "vendor_options": { + Type: schema.TypeSet, + Optional: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "set_router_gateway_after_create": { + Type: schema.TypeBool, + Default: false, + Optional: true, + }, + }, + }, + }, + "tags": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "all_tags": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, }, } } @@ -77,8 +153,10 @@ func resourceNetworkingRouterV2Create(d *schema.ResourceData, meta interface{}) createOpts := RouterCreateOpts{ routers.CreateOpts{ - Name: d.Get("name").(string), - TenantID: d.Get("tenant_id").(string), + Name: d.Get("name").(string), + Description: d.Get("description").(string), + TenantID: d.Get("tenant_id").(string), + AvailabilityZoneHints: resourceNetworkingAvailabilityZoneHintsV2(d), }, MapValueSpecs(d), } @@ -88,16 +166,53 @@ func resourceNetworkingRouterV2Create(d *schema.ResourceData, meta interface{}) createOpts.AdminStateUp = &asu } - if dRaw, ok := d.GetOk("distributed"); ok { + if dRaw, ok := d.GetOkExists("distributed"); ok { d := dRaw.(bool) createOpts.Distributed = &d } - externalGateway := d.Get("external_gateway").(string) - if externalGateway != "" { - gatewayInfo := routers.GatewayInfo{ - NetworkID: externalGateway, + // Get Vendor_options + vendorOptionsRaw := d.Get("vendor_options").(*schema.Set) + var vendorUpdateGateway bool + if vendorOptionsRaw.Len() > 0 { + vendorOptions := expandVendorOptions(vendorOptionsRaw.List()) + vendorUpdateGateway = vendorOptions["set_router_gateway_after_create"].(bool) + } + + // Gateway settings + var externalNetworkID string + var gatewayInfo routers.GatewayInfo + if v := d.Get("external_gateway").(string); v != "" { + externalNetworkID = v + gatewayInfo.NetworkID = externalNetworkID + } + + if v := d.Get("external_network_id").(string); v != "" { + externalNetworkID = v + gatewayInfo.NetworkID = externalNetworkID + } + + if esRaw, ok := d.GetOkExists("enable_snat"); ok { + if externalNetworkID == "" { + return fmt.Errorf("setting enable_snat requires external_network_id to be set") } + es := esRaw.(bool) + gatewayInfo.EnableSNAT = &es + } + + externalFixedIPs := resourceRouterExternalFixedIPsV2(d) + if len(externalFixedIPs) > 0 { + if externalNetworkID == "" { + return fmt.Errorf("setting an external_fixed_ip requires external_network_id to be set") + } + gatewayInfo.ExternalFixedIPs = externalFixedIPs + } + + // vendorUpdateGateway is a flag for certain vendor-specific virtual routers + // which do not allow gateway settings to be set during router creation. + // If this flag was not enabled, then we can safely set the gateway + // information during create. + if !vendorUpdateGateway && externalNetworkID != "" { createOpts.GatewayInfo = &gatewayInfo } @@ -122,6 +237,31 @@ func resourceNetworkingRouterV2Create(d *schema.ResourceData, meta interface{}) d.SetId(n.ID) + // If the vendorUpdateGateway flag was specified and if an external network + // was specified, then set the gateway information after router creation. + if vendorUpdateGateway && externalNetworkID != "" { + log.Printf("[DEBUG] Adding External Network %s to router ID %s", externalNetworkID, d.Id()) + + var updateOpts routers.UpdateOpts + updateOpts.GatewayInfo = &gatewayInfo + + log.Printf("[DEBUG] Assigning external gateway to Router %s with options: %+v", d.Id(), updateOpts) + _, err = routers.Update(networkingClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack Neutron Router: %s", err) + } + } + + tags := networkV2AttributesTags(d) + if len(tags) > 0 { + tagOpts := attributestags.ReplaceAllOpts{Tags: tags} + tags, err := attributestags.ReplaceAll(networkingClient, "routers", n.ID, tagOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating Tags on Router: %s", err) + } + log.Printf("[DEBUG] Set Tags = %+v on Router %+v", tags, n.ID) + } + return resourceNetworkingRouterV2Read(d, meta) } @@ -145,12 +285,35 @@ func resourceNetworkingRouterV2Read(d *schema.ResourceData, meta interface{}) er log.Printf("[DEBUG] Retrieved Router %s: %+v", d.Id(), n) d.Set("name", n.Name) + d.Set("description", n.Description) d.Set("admin_state_up", n.AdminStateUp) d.Set("distributed", n.Distributed) d.Set("tenant_id", n.TenantID) - d.Set("external_gateway", n.GatewayInfo.NetworkID) d.Set("region", GetRegion(d, config)) + networkV2ReadAttributesTags(d, n.Tags) + + if err := d.Set("availability_zone_hints", n.AvailabilityZoneHints); err != nil { + log.Printf("[DEBUG] unable to set availability_zone_hints: %s", err) + } + + // Gateway settings + d.Set("external_gateway", n.GatewayInfo.NetworkID) + d.Set("external_network_id", n.GatewayInfo.NetworkID) + d.Set("enable_snat", n.GatewayInfo.EnableSNAT) + + var externalFixedIPs []map[string]string + for _, v := range n.GatewayInfo.ExternalFixedIPs { + externalFixedIPs = append(externalFixedIPs, map[string]string{ + "subnet_id": v.SubnetID, + "ip_address": v.IPAddress, + }) + } + + if err = d.Set("external_fixed_ip", externalFixedIPs); err != nil { + log.Printf("[DEBUG] unable to set external_fixed_ip: %s", err) + } + return nil } @@ -165,29 +328,91 @@ func resourceNetworkingRouterV2Update(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error creating OpenStack networking client: %s", err) } + var hasChange bool var updateOpts routers.UpdateOpts if d.HasChange("name") { + hasChange = true updateOpts.Name = d.Get("name").(string) } + if d.HasChange("description") { + hasChange = true + description := d.Get("description").(string) + updateOpts.Description = &description + } if d.HasChange("admin_state_up") { + hasChange = true asu := d.Get("admin_state_up").(bool) updateOpts.AdminStateUp = &asu } + + // Gateway settings + var updateGatewaySettings bool + var externalNetworkID string + gatewayInfo := routers.GatewayInfo{} + + if v := d.Get("external_gateway").(string); v != "" { + externalNetworkID = v + } + + if v := d.Get("external_network_id").(string); v != "" { + externalNetworkID = v + } + + if externalNetworkID != "" { + gatewayInfo.NetworkID = externalNetworkID + } + if d.HasChange("external_gateway") { - externalGateway := d.Get("external_gateway").(string) - if externalGateway != "" { - gatewayInfo := routers.GatewayInfo{ - NetworkID: externalGateway, + updateGatewaySettings = true + } + + if d.HasChange("external_network_id") { + updateGatewaySettings = true + } + + if d.HasChange("enable_snat") { + updateGatewaySettings = true + if externalNetworkID == "" { + return fmt.Errorf("setting enable_snat requires external_network_id to be set") + } + + enableSNAT := d.Get("enable_snat").(bool) + gatewayInfo.EnableSNAT = &enableSNAT + } + + if d.HasChange("external_fixed_ip") { + updateGatewaySettings = true + + externalFixedIPs := resourceRouterExternalFixedIPsV2(d) + gatewayInfo.ExternalFixedIPs = externalFixedIPs + if len(externalFixedIPs) > 0 { + if externalNetworkID == "" { + return fmt.Errorf("setting an external_fixed_ip requires external_network_id to be set") } - updateOpts.GatewayInfo = &gatewayInfo } } - log.Printf("[DEBUG] Updating Router %s with options: %+v", d.Id(), updateOpts) + if updateGatewaySettings { + hasChange = true + updateOpts.GatewayInfo = &gatewayInfo + } - _, err = routers.Update(networkingClient, d.Id(), updateOpts).Extract() - if err != nil { - return fmt.Errorf("Error updating OpenStack Neutron Router: %s", err) + if hasChange { + log.Printf("[DEBUG] Updating Router %s with options: %+v", d.Id(), updateOpts) + _, err = routers.Update(networkingClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack Neutron Router: %s", err) + } + } + + if d.HasChange("tags") { + tags := networkV2UpdateAttributesTags(d) + tagOpts := attributestags.ReplaceAllOpts{Tags: tags} + tags, err := attributestags.ReplaceAll(networkingClient, "routers", d.Id(), tagOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating Tags on Router: %s", err) + } + log.Printf("[DEBUG] Updated Tags = %+v on Router %+v", tags, d.Id()) } return resourceNetworkingRouterV2Read(d, meta) @@ -256,3 +481,19 @@ func waitForRouterDelete(networkingClient *gophercloud.ServiceClient, routerId s return r, "ACTIVE", nil } } + +func resourceRouterExternalFixedIPsV2(d *schema.ResourceData) []routers.ExternalFixedIP { + var externalFixedIPs []routers.ExternalFixedIP + eFIPs := d.Get("external_fixed_ip").([]interface{}) + + for _, eFIP := range eFIPs { + v := eFIP.(map[string]interface{}) + fip := routers.ExternalFixedIP{ + SubnetID: v["subnet_id"].(string), + IPAddress: v["ip_address"].(string), + } + externalFixedIPs = append(externalFixedIPs, fip) + } + + return externalFixedIPs +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_secgroup_rule_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_secgroup_rule_v2.go index 33e68eb2e..5cfa77a6d 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_secgroup_rule_v2.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_secgroup_rule_v2.go @@ -28,47 +28,53 @@ func resourceNetworkingSecGroupRuleV2() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "direction": &schema.Schema{ + "description": { + Type: schema.TypeString, + Optional: true, + Computed: false, + ForceNew: true, + }, + "direction": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "ethertype": &schema.Schema{ + "ethertype": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "port_range_min": &schema.Schema{ + "port_range_min": { Type: schema.TypeInt, Optional: true, ForceNew: true, Computed: true, }, - "port_range_max": &schema.Schema{ + "port_range_max": { Type: schema.TypeInt, Optional: true, ForceNew: true, Computed: true, }, - "protocol": &schema.Schema{ + "protocol": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, - "remote_group_id": &schema.Schema{ + "remote_group_id": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, - "remote_ip_prefix": &schema.Schema{ + "remote_ip_prefix": { Type: schema.TypeString, Optional: true, ForceNew: true, @@ -77,12 +83,12 @@ func resourceNetworkingSecGroupRuleV2() *schema.Resource { return strings.ToLower(v.(string)) }, }, - "security_group_id": &schema.Schema{ + "security_group_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "tenant_id": &schema.Schema{ + "tenant_id": { Type: schema.TypeString, Optional: true, ForceNew: true, @@ -111,12 +117,13 @@ func resourceNetworkingSecGroupRuleV2Create(d *schema.ResourceData, meta interfa } opts := rules.CreateOpts{ + Description: d.Get("description").(string), SecGroupID: d.Get("security_group_id").(string), PortRangeMin: d.Get("port_range_min").(int), PortRangeMax: d.Get("port_range_max").(int), RemoteGroupID: d.Get("remote_group_id").(string), RemoteIPPrefix: d.Get("remote_ip_prefix").(string), - TenantID: d.Get("tenant_id").(string), + ProjectID: d.Get("tenant_id").(string), } if v, ok := d.GetOk("direction"); ok { @@ -133,7 +140,6 @@ func resourceNetworkingSecGroupRuleV2Create(d *schema.ResourceData, meta interfa protocol := resourceNetworkingSecGroupRuleV2DetermineProtocol(v.(string)) opts.Protocol = protocol } - log.Printf("[DEBUG] Create OpenStack Neutron security group: %#v", opts) security_group_rule, err := rules.Create(networkingClient, opts).Extract() @@ -162,7 +168,7 @@ func resourceNetworkingSecGroupRuleV2Read(d *schema.ResourceData, meta interface if err != nil { return CheckDeleted(d, err, "OpenStack Security Group Rule") } - + d.Set("description", security_group_rule.Description) d.Set("direction", security_group_rule.Direction) d.Set("ethertype", security_group_rule.EtherType) d.Set("protocol", security_group_rule.Protocol) diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_secgroup_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_secgroup_v2.go index 2687627f1..ef826e242 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_secgroup_v2.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_secgroup_v2.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform/helper/schema" "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules" ) @@ -28,32 +29,42 @@ func resourceNetworkingSecGroupV2() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "name": &schema.Schema{ + "name": { Type: schema.TypeString, Required: true, }, - "description": &schema.Schema{ + "description": { Type: schema.TypeString, Optional: true, Computed: true, }, - "tenant_id": &schema.Schema{ + "tenant_id": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, - "delete_default_rules": &schema.Schema{ + "delete_default_rules": { Type: schema.TypeBool, Optional: true, ForceNew: true, }, + "tags": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "all_tags": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, }, } } @@ -82,6 +93,10 @@ func resourceNetworkingSecGroupV2Create(d *schema.ResourceData, meta interface{} // Delete the default security group rules if it has been requested. deleteDefaultRules := d.Get("delete_default_rules").(bool) if deleteDefaultRules { + security_group, err := groups.Get(networkingClient, security_group.ID).Extract() + if err != nil { + return err + } for _, rule := range security_group.Rules { if err := rules.Delete(networkingClient, rule.ID).ExtractErr(); err != nil { return fmt.Errorf( @@ -94,6 +109,16 @@ func resourceNetworkingSecGroupV2Create(d *schema.ResourceData, meta interface{} d.SetId(security_group.ID) + tags := networkV2AttributesTags(d) + if len(tags) > 0 { + tagOpts := attributestags.ReplaceAllOpts{Tags: tags} + tags, err := attributestags.ReplaceAll(networkingClient, "security-groups", security_group.ID, tagOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating Tags on SecurityGroup: %s", err) + } + log.Printf("[DEBUG] Set Tags = %+v on SecurityGroup %+v", tags, security_group.ID) + } + return resourceNetworkingSecGroupV2Read(d, meta) } @@ -117,6 +142,8 @@ func resourceNetworkingSecGroupV2Read(d *schema.ResourceData, meta interface{}) d.Set("name", security_group.Name) d.Set("region", GetRegion(d, config)) + networkV2ReadAttributesTags(d, security_group.Tags) + return nil } @@ -137,7 +164,8 @@ func resourceNetworkingSecGroupV2Update(d *schema.ResourceData, meta interface{} if d.HasChange("description") { update = true - updateOpts.Name = d.Get("description").(string) + description := d.Get("description").(string) + updateOpts.Description = &description } if update { @@ -148,6 +176,16 @@ func resourceNetworkingSecGroupV2Update(d *schema.ResourceData, meta interface{} } } + if d.HasChange("tags") { + tags := networkV2UpdateAttributesTags(d) + tagOpts := attributestags.ReplaceAllOpts{Tags: tags} + tags, err := attributestags.ReplaceAll(networkingClient, "security-groups", d.Id(), tagOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating Tags on SecurityGroup: %s", err) + } + log.Printf("[DEBUG] Updated Tags = %+v on SecurityGroup %+v", tags, d.Id()) + } + return resourceNetworkingSecGroupV2Read(d, meta) } diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_subnet_route_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_subnet_route_v2.go new file mode 100644 index 000000000..19098d7f6 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_subnet_route_v2.go @@ -0,0 +1,224 @@ +package openstack + +import ( + "fmt" + "log" + "strings" + + "github.com/hashicorp/terraform/helper/schema" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/subnets" +) + +func resourceNetworkingSubnetRouteV2() *schema.Resource { + return &schema.Resource{ + Create: resourceNetworkingSubnetRouteV2Create, + Read: resourceNetworkingSubnetRouteV2Read, + Delete: resourceNetworkingSubnetRouteV2Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "subnet_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "destination_cidr": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "next_hop": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceNetworkingSubnetRouteV2Create(d *schema.ResourceData, meta interface{}) error { + + subnetId := d.Get("subnet_id").(string) + osMutexKV.Lock(subnetId) + defer osMutexKV.Unlock(subnetId) + + var destCidr string = d.Get("destination_cidr").(string) + var nextHop string = d.Get("next_hop").(string) + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + subnet, err := subnets.Get(networkingClient, subnetId).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + d.SetId("") + return nil + } + + return fmt.Errorf("Error retrieving OpenStack Neutron Subnet: %s", err) + } + + var updateOpts subnets.UpdateOpts + var routeExists bool = false + + var rts []subnets.HostRoute = subnet.HostRoutes + for _, r := range rts { + + if r.DestinationCIDR == destCidr && r.NextHop == nextHop { + routeExists = true + break + } + } + + if routeExists { + return fmt.Errorf("Subnet %s has route already", subnetId) + } + + if destCidr != "" && nextHop != "" { + r := subnets.HostRoute{DestinationCIDR: destCidr, NextHop: nextHop} + log.Printf("[INFO] Adding route %s", r) + rts = append(rts, r) + } + + updateOpts.HostRoutes = &rts + + log.Printf("[DEBUG] Updating Subnet %s with options: %+v", subnetId, updateOpts) + + _, err = subnets.Update(networkingClient, subnetId, updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack Neutron Subnet: %s", err) + } + id := fmt.Sprintf("%s-route-%s-%s", subnetId, destCidr, nextHop) + d.SetId(id) + + return resourceNetworkingSubnetRouteV2Read(d, meta) +} + +func resourceNetworkingSubnetRouteV2Read(d *schema.ResourceData, meta interface{}) error { + + subnetId := d.Get("subnet_id").(string) + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + destCidr := d.Get("destination_cidr").(string) + nextHop := d.Get("next_hop").(string) + + routeIDParts := []string{} + if d.Id() != "" && strings.Contains(d.Id(), "-route-") { + routeIDParts = strings.Split(d.Id(), "-route-") + routeLastIDParts := strings.Split(routeIDParts[1], "-") + + if subnetId == "" { + subnetId = routeIDParts[0] + d.Set("subnet_id", subnetId) + } + if destCidr == "" { + destCidr = routeLastIDParts[0] + } + if nextHop == "" { + nextHop = routeLastIDParts[1] + } + } + + subnet, err := subnets.Get(networkingClient, subnetId).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + d.SetId("") + return nil + } + + return fmt.Errorf("Error retrieving OpenStack Neutron Subnet: %s", err) + } + + log.Printf("[DEBUG] Retrieved Subnet %s: %+v", subnetId, subnet) + + var exists bool + for _, r := range subnet.HostRoutes { + if r.DestinationCIDR == destCidr && r.NextHop == nextHop { + exists = true + } + } + + if !exists { + return fmt.Errorf("Route doesn't exist") + } + + d.Set("next_hop", nextHop) + d.Set("destination_cidr", destCidr) + + d.Set("region", GetRegion(d, config)) + + return nil +} + +func resourceNetworkingSubnetRouteV2Delete(d *schema.ResourceData, meta interface{}) error { + + subnetId := d.Get("subnet_id").(string) + osMutexKV.Lock(subnetId) + defer osMutexKV.Unlock(subnetId) + + config := meta.(*Config) + + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + subnet, err := subnets.Get(networkingClient, subnetId).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + return nil + } + + return fmt.Errorf("Error retrieving OpenStack Neutron Subnet: %s", err) + } + + var updateOpts subnets.UpdateOpts + + var destCidr string = d.Get("destination_cidr").(string) + var nextHop string = d.Get("next_hop").(string) + + var oldRts []subnets.HostRoute = subnet.HostRoutes + var newRts []subnets.HostRoute + + for _, r := range oldRts { + + if r.DestinationCIDR != destCidr || r.NextHop != nextHop { + newRts = append(newRts, r) + } + } + + if len(oldRts) == len(newRts) { + return fmt.Errorf("Route did not exist already") + } + + r := subnets.HostRoute{DestinationCIDR: destCidr, NextHop: nextHop} + log.Printf("[INFO] Deleting route %s", r) + updateOpts.HostRoutes = &newRts + + log.Printf("[DEBUG] Updating Subnet %s with options: %+v", subnetId, updateOpts) + + _, err = subnets.Update(networkingClient, subnetId, updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack Neutron Subnet: %s", err) + } + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_subnet_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_subnet_v2.go index 266d2de2e..3bab68ddc 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_subnet_v2.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_subnet_v2.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform/helper/schema" "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags" "github.com/gophercloud/gophercloud/openstack/networking/v2/subnets" ) @@ -28,102 +29,139 @@ func resourceNetworkingSubnetV2() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "network_id": &schema.Schema{ + "network_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "cidr": &schema.Schema{ + "cidr": { Type: schema.TypeString, - Required: true, + Optional: true, + Computed: true, ForceNew: true, }, - "name": &schema.Schema{ + "name": { Type: schema.TypeString, Optional: true, ForceNew: false, }, - "tenant_id": &schema.Schema{ + "description": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + "tenant_id": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, - "allocation_pools": &schema.Schema{ + "allocation_pools": { Type: schema.TypeList, Optional: true, Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "start": &schema.Schema{ + "start": { Type: schema.TypeString, Required: true, }, - "end": &schema.Schema{ + "end": { Type: schema.TypeString, Required: true, }, }, }, }, - "gateway_ip": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - ForceNew: false, - Computed: true, + "gateway_ip": { + Type: schema.TypeString, + ConflictsWith: []string{"no_gateway"}, + Optional: true, + ForceNew: false, + Computed: true, }, - "no_gateway": &schema.Schema{ - Type: schema.TypeBool, - Optional: true, - ForceNew: false, + "no_gateway": { + Type: schema.TypeBool, + ConflictsWith: []string{"gateway_ip"}, + Optional: true, + Default: false, + ForceNew: false, }, - "ip_version": &schema.Schema{ + "ip_version": { Type: schema.TypeInt, Optional: true, Default: 4, ForceNew: true, }, - "enable_dhcp": &schema.Schema{ + "enable_dhcp": { Type: schema.TypeBool, Optional: true, ForceNew: false, Default: true, }, - "dns_nameservers": &schema.Schema{ - Type: schema.TypeSet, + "dns_nameservers": { + Type: schema.TypeList, Optional: true, ForceNew: false, Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, }, - "host_routes": &schema.Schema{ + "host_routes": { Type: schema.TypeList, Optional: true, ForceNew: false, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "destination_cidr": &schema.Schema{ + "destination_cidr": { Type: schema.TypeString, Required: true, }, - "next_hop": &schema.Schema{ + "next_hop": { Type: schema.TypeString, Required: true, }, }, }, }, - "value_specs": &schema.Schema{ + "ipv6_address_mode": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + ValidateFunc: validateSubnetV2IPv6Mode, + }, + "ipv6_ra_mode": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + ValidateFunc: validateSubnetV2IPv6Mode, + }, + "subnetpool_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "value_specs": { Type: schema.TypeMap, Optional: true, ForceNew: true, }, + "tags": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "all_tags": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, }, } } @@ -135,34 +173,41 @@ func resourceNetworkingSubnetV2Create(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error creating OpenStack networking client: %s", err) } + if err = resourceSubnetDNSNameserversV2CheckIsSet(d); err != nil { + return err + } + createOpts := SubnetCreateOpts{ subnets.CreateOpts{ NetworkID: d.Get("network_id").(string), - CIDR: d.Get("cidr").(string), Name: d.Get("name").(string), + Description: d.Get("description").(string), TenantID: d.Get("tenant_id").(string), + IPv6AddressMode: d.Get("ipv6_address_mode").(string), + IPv6RAMode: d.Get("ipv6_ra_mode").(string), AllocationPools: resourceSubnetAllocationPoolsV2(d), DNSNameservers: resourceSubnetDNSNameserversV2(d), HostRoutes: resourceSubnetHostRoutesV2(d), + SubnetPoolID: d.Get("subnetpool_id").(string), EnableDHCP: nil, }, MapValueSpecs(d), } - noGateway := d.Get("no_gateway").(bool) - gatewayIP := d.Get("gateway_ip").(string) - - if gatewayIP != "" && noGateway { - return fmt.Errorf("Both gateway_ip and no_gateway cannot be set") + if v, ok := d.GetOk("cidr"); ok { + cidr := v.(string) + createOpts.CIDR = cidr } - if gatewayIP != "" { + if v, ok := d.GetOk("gateway_ip"); ok { + gatewayIP := v.(string) createOpts.GatewayIP = &gatewayIP } + noGateway := d.Get("no_gateway").(bool) if noGateway { - disableGateway := "" - createOpts.GatewayIP = &disableGateway + gatewayIP := "" + createOpts.GatewayIP = &gatewayIP } enableDHCP := d.Get("enable_dhcp").(bool) @@ -191,6 +236,16 @@ func resourceNetworkingSubnetV2Create(d *schema.ResourceData, meta interface{}) d.SetId(s.ID) + tags := networkV2AttributesTags(d) + if len(tags) > 0 { + tagOpts := attributestags.ReplaceAllOpts{Tags: tags} + tags, err := attributestags.ReplaceAll(networkingClient, "subnets", s.ID, tagOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating Tags on Subnet: %s", err) + } + log.Printf("[DEBUG] Set Tags = %+v on Subnet %+v", tags, s.ID) + } + log.Printf("[DEBUG] Created Subnet %s: %#v", s.ID, s) return resourceNetworkingSubnetV2Read(d, meta) } @@ -213,12 +268,17 @@ func resourceNetworkingSubnetV2Read(d *schema.ResourceData, meta interface{}) er d.Set("cidr", s.CIDR) d.Set("ip_version", s.IPVersion) d.Set("name", s.Name) + d.Set("description", s.Description) d.Set("tenant_id", s.TenantID) - d.Set("gateway_ip", s.GatewayIP) d.Set("dns_nameservers", s.DNSNameservers) d.Set("host_routes", s.HostRoutes) d.Set("enable_dhcp", s.EnableDHCP) d.Set("network_id", s.NetworkID) + d.Set("ipv6_address_mode", s.IPv6AddressMode) + d.Set("ipv6_ra_mode", s.IPv6RAMode) + d.Set("subnetpool_id", s.SubnetPoolID) + + networkV2ReadAttributesTags(d, s.Tags) // Set the allocation_pools var allocationPools []map[string]interface{} @@ -231,6 +291,17 @@ func resourceNetworkingSubnetV2Read(d *schema.ResourceData, meta interface{}) er } d.Set("allocation_pools", allocationPools) + // Set the subnet's Gateway IP. + gatewayIP := s.GatewayIP + d.Set("gateway_ip", s.GatewayIP) + + // Based on the subnet's Gateway IP, set `no_gateway` accordingly. + if gatewayIP == "" { + d.Set("no_gateway", true) + } else { + d.Set("no_gateway", false) + } + d.Set("region", GetRegion(d, config)) return nil @@ -243,28 +314,22 @@ func resourceNetworkingSubnetV2Update(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error creating OpenStack networking client: %s", err) } - // Check if both gateway_ip and no_gateway are set - if _, ok := d.GetOk("gateway_ip"); ok { - noGateway := d.Get("no_gateway").(bool) - if noGateway { - return fmt.Errorf("Both gateway_ip and no_gateway cannot be set.") - } - } - + var hasChange bool var updateOpts subnets.UpdateOpts - noGateway := d.Get("no_gateway").(bool) - gatewayIP := d.Get("gateway_ip").(string) - - if gatewayIP != "" && noGateway { - return fmt.Errorf("Both gateway_ip and no_gateway cannot be set") - } - if d.HasChange("name") { + hasChange = true updateOpts.Name = d.Get("name").(string) } + if d.HasChange("description") { + hasChange = true + description := d.Get("description").(string) + updateOpts.Description = &description + } + if d.HasChange("gateway_ip") { + hasChange = true updateOpts.GatewayIP = nil if v, ok := d.GetOk("gateway_ip"); ok { gatewayIP := v.(string) @@ -274,33 +339,53 @@ func resourceNetworkingSubnetV2Update(d *schema.ResourceData, meta interface{}) if d.HasChange("no_gateway") { if d.Get("no_gateway").(bool) { + hasChange = true gatewayIP := "" updateOpts.GatewayIP = &gatewayIP } } if d.HasChange("dns_nameservers") { + if err = resourceSubnetDNSNameserversV2CheckIsSet(d); err != nil { + return err + } + hasChange = true updateOpts.DNSNameservers = resourceSubnetDNSNameserversV2(d) } if d.HasChange("host_routes") { - updateOpts.HostRoutes = resourceSubnetHostRoutesV2(d) + hasChange = true + newHostRoutes := resourceSubnetHostRoutesV2(d) + updateOpts.HostRoutes = &newHostRoutes } if d.HasChange("enable_dhcp") { + hasChange = true v := d.Get("enable_dhcp").(bool) updateOpts.EnableDHCP = &v } if d.HasChange("allocation_pools") { + hasChange = true updateOpts.AllocationPools = resourceSubnetAllocationPoolsV2(d) } - log.Printf("[DEBUG] Updating Subnet %s with options: %+v", d.Id(), updateOpts) + if hasChange { + log.Printf("[DEBUG] Updating Subnet %s with options: %+v", d.Id(), updateOpts) + _, err = subnets.Update(networkingClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack Neutron Subnet: %s", err) + } + } - _, err = subnets.Update(networkingClient, d.Id(), updateOpts).Extract() - if err != nil { - return fmt.Errorf("Error updating OpenStack Neutron Subnet: %s", err) + if d.HasChange("tags") { + tags := networkV2UpdateAttributesTags(d) + tagOpts := attributestags.ReplaceAllOpts{Tags: tags} + tags, err := attributestags.ReplaceAll(networkingClient, "subnets", d.Id(), tagOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating Tags on Subnet: %s", err) + } + log.Printf("[DEBUG] Updated Tags = %+v on Subnet %+v", tags, d.Id()) } return resourceNetworkingSubnetV2Read(d, meta) @@ -345,14 +430,28 @@ func resourceSubnetAllocationPoolsV2(d *schema.ResourceData) []subnets.Allocatio } func resourceSubnetDNSNameserversV2(d *schema.ResourceData) []string { - rawDNSN := d.Get("dns_nameservers").(*schema.Set) - dnsn := make([]string, rawDNSN.Len()) - for i, raw := range rawDNSN.List() { + rawDNSN := d.Get("dns_nameservers").([]interface{}) + dnsn := make([]string, len(rawDNSN)) + for i, raw := range rawDNSN { dnsn[i] = raw.(string) } return dnsn } +func resourceSubnetDNSNameserversV2CheckIsSet(d *schema.ResourceData) error { + rawDNSN := d.Get("dns_nameservers").([]interface{}) + set := make(map[string]*string) + for _, raw := range rawDNSN { + dns := raw.(string) + if set[dns] != nil { + return fmt.Errorf("DNS nameservers must appear exactly once: %q", dns) + } else { + set[dns] = &dns + } + } + return nil +} + func resourceSubnetHostRoutesV2(d *schema.ResourceData) []subnets.HostRoute { rawHR := d.Get("host_routes").([]interface{}) hr := make([]subnets.HostRoute, len(rawHR)) diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_subnetpool_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_subnetpool_v2.go new file mode 100644 index 000000000..79f5b5973 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_subnetpool_v2.go @@ -0,0 +1,377 @@ +package openstack + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/subnetpools" +) + +func resourceNetworkingSubnetPoolV2() *schema.Resource { + return &schema.Resource{ + Create: resourceNetworkingSubnetPoolV2Create, + Read: resourceNetworkingSubnetPoolV2Read, + Update: resourceNetworkingSubnetPoolV2Update, + Delete: resourceNetworkingSubnetPoolV2Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: false, + }, + + "default_quota": { + Type: schema.TypeInt, + Optional: true, + ForceNew: false, + }, + + "project_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + + "created_at": { + Type: schema.TypeString, + ForceNew: false, + Computed: true, + }, + + "updated_at": { + Type: schema.TypeString, + ForceNew: false, + Computed: true, + }, + + "prefixes": { + Type: schema.TypeList, + Required: true, + ForceNew: false, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "default_prefixlen": { + Type: schema.TypeInt, + Optional: true, + ForceNew: false, + Computed: true, + }, + + "min_prefixlen": { + Type: schema.TypeInt, + Optional: true, + ForceNew: false, + Computed: true, + }, + + "max_prefixlen": { + Type: schema.TypeInt, + Optional: true, + ForceNew: false, + Computed: true, + }, + + "address_scope_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + + "ip_version": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + value := v.(int) + if value != 4 && value != 6 { + errors = append(errors, fmt.Errorf( + "Only 4 and 6 are supported values for 'ip_version'")) + } + return + }, + }, + + "shared": { + Type: schema.TypeBool, + Optional: true, + ForceNew: false, + }, + + "description": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + + "is_default": { + Type: schema.TypeBool, + Optional: true, + ForceNew: false, + }, + + "revision_number": { + Type: schema.TypeInt, + ForceNew: false, + Computed: true, + }, + + "value_specs": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + }, + + "tags": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "all_tags": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func resourceNetworkingSubnetPoolV2Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + createOpts := SubnetPoolCreateOpts{ + subnetpools.CreateOpts{ + Name: d.Get("name").(string), + DefaultQuota: d.Get("default_quota").(int), + ProjectID: d.Get("project_id").(string), + Prefixes: expandToStringSlice(d.Get("prefixes").([]interface{})), + DefaultPrefixLen: d.Get("default_prefixlen").(int), + MinPrefixLen: d.Get("min_prefixlen").(int), + MaxPrefixLen: d.Get("max_prefixlen").(int), + AddressScopeID: d.Get("address_scope_id").(string), + Shared: d.Get("shared").(bool), + Description: d.Get("description").(string), + IsDefault: d.Get("is_default").(bool), + }, + MapValueSpecs(d), + } + + log.Printf("[DEBUG] openstack_networking_subnetpool_v2 create options: %#v", createOpts) + s, err := subnetpools.Create(networkingClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating openstack_networking_subnetpool_v2: %s", err) + } + + log.Printf("[DEBUG] Waiting for openstack_networking_subnetpool_v2 %s to become available.", s.ID) + + stateConf := &resource.StateChangeConf{ + Target: []string{"ACTIVE"}, + Refresh: networkingSubnetpoolV2StateRefreshFunc(networkingClient, s.ID), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for openstack_networking_subnetpool_v2 %s to become available: %s", s.ID, err) + } + + d.SetId(s.ID) + + tags := networkV2AttributesTags(d) + if len(tags) > 0 { + tagOpts := attributestags.ReplaceAllOpts{Tags: tags} + tags, err := attributestags.ReplaceAll(networkingClient, "subnetpools", s.ID, tagOpts).Extract() + if err != nil { + return fmt.Errorf("Error setting tags on openstack_networking_subnetpool_v2 %s: %s", s.ID, err) + } + log.Printf("[DEBUG] Set tags %s on openstack_networking_subnetpool_v2 %s", tags, s.ID) + } + + log.Printf("[DEBUG] Created openstack_networking_subnetpool_v2 %s: %#v", s.ID, s) + return resourceNetworkingSubnetPoolV2Read(d, meta) +} + +func resourceNetworkingSubnetPoolV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + s, err := subnetpools.Get(networkingClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "Error getting openstack_networking_subnetpool_v2") + } + + log.Printf("[DEBUG] Retrieved openstack_networking_subnetpool_v2 %s: %#v", d.Id(), s) + + d.Set("name", s.Name) + d.Set("default_quota", s.DefaultQuota) + d.Set("project_id", s.ProjectID) + d.Set("default_prefixlen", s.DefaultPrefixLen) + d.Set("min_prefixlen", s.MinPrefixLen) + d.Set("max_prefixlen", s.MaxPrefixLen) + d.Set("address_scope_id", s.AddressScopeID) + d.Set("ip_version", s.IPversion) + d.Set("shared", s.Shared) + d.Set("is_default", s.IsDefault) + d.Set("description", s.Description) + d.Set("revision_number", s.RevisionNumber) + d.Set("region", GetRegion(d, config)) + + networkV2ReadAttributesTags(d, s.Tags) + + if err := d.Set("created_at", s.CreatedAt.Format(time.RFC3339)); err != nil { + log.Printf("[DEBUG] Unable to set openstack_networking_subnetpool_v2 created_at: %s", err) + } + if err := d.Set("updated_at", s.UpdatedAt.Format(time.RFC3339)); err != nil { + log.Printf("[DEBUG] Unable to set openstack_networking_subnetpool_v2 updated_at: %s", err) + } + if err := d.Set("prefixes", s.Prefixes); err != nil { + log.Printf("[DEBUG] Unable to set openstack_networking_subnetpool_v2 prefixes: %s", err) + } + + return nil +} + +func resourceNetworkingSubnetPoolV2Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + var hasChange bool + var updateOpts subnetpools.UpdateOpts + + if d.HasChange("name") { + hasChange = true + updateOpts.Name = d.Get("name").(string) + } + + if d.HasChange("default_quota") { + hasChange = true + v := d.Get("default_quota").(int) + updateOpts.DefaultQuota = &v + } + + if d.HasChange("project_id") { + hasChange = true + updateOpts.ProjectID = d.Get("project_id").(string) + } + + if d.HasChange("prefixes") { + hasChange = true + updateOpts.Prefixes = expandToStringSlice(d.Get("prefixes").([]interface{})) + } + + if d.HasChange("default_prefixlen") { + hasChange = true + updateOpts.DefaultPrefixLen = d.Get("default_prefixlen").(int) + } + + if d.HasChange("min_prefixlen") { + hasChange = true + updateOpts.MinPrefixLen = d.Get("min_prefixlen").(int) + } + + if d.HasChange("max_prefixlen") { + hasChange = true + updateOpts.MaxPrefixLen = d.Get("max_prefixlen").(int) + } + + if d.HasChange("address_scope_id") { + hasChange = true + v := d.Get("address_scope_id").(string) + updateOpts.AddressScopeID = &v + } + + if d.HasChange("description") { + hasChange = true + v := d.Get("description").(string) + updateOpts.Description = &v + } + + if d.HasChange("is_default") { + hasChange = true + v := d.Get("is_default").(bool) + updateOpts.IsDefault = &v + } + + if hasChange { + log.Printf("[DEBUG] openstack_networking_subnetpool_v2 %s update options: %#v", d.Id(), updateOpts) + _, err = subnetpools.Update(networkingClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating openstack_networking_subnetpool_v2 %s: %s", d.Id(), err) + } + } + + if d.HasChange("tags") { + tags := networkV2UpdateAttributesTags(d) + tagOpts := attributestags.ReplaceAllOpts{Tags: tags} + tags, err := attributestags.ReplaceAll(networkingClient, "subnetpools", d.Id(), tagOpts).Extract() + if err != nil { + return fmt.Errorf("Error setting tags on openstack_networking_subnetpool_v2 %s: %s", d.Id(), err) + } + log.Printf("[DEBUG] Set tags %s on openstack_networking_subnetpool_v2 %s", tags, d.Id()) + } + + return resourceNetworkingSubnetPoolV2Read(d, meta) +} + +func resourceNetworkingSubnetPoolV2Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + if err := subnetpools.Delete(networkingClient, d.Id()).ExtractErr(); err != nil { + return CheckDeleted(d, err, "Error deleting openstack_networking_subnetpool_v2") + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"ACTIVE"}, + Target: []string{"DELETED"}, + Refresh: networkingSubnetpoolV2StateRefreshFunc(networkingClient, d.Id()), + Timeout: d.Timeout(schema.TimeoutDelete), + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for openstack_networking_subnetpool_v2 %s to delete: %s", d.Id(), err) + } + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_trunk_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_trunk_v2.go new file mode 100644 index 000000000..1cf1372e9 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_networking_trunk_v2.go @@ -0,0 +1,305 @@ +package openstack + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks" +) + +func resourceNetworkingTrunkV2() *schema.Resource { + return &schema.Resource{ + Create: resourceNetworkingTrunkV2Create, + Read: resourceNetworkingTrunkV2Read, + Update: resourceNetworkingTrunkV2Update, + Delete: resourceNetworkingTrunkV2Delete, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(5 * time.Minute), + Delete: schema.DefaultTimeout(5 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "name": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + + "description": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + + "port_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "admin_state_up": { + Type: schema.TypeBool, + Optional: true, + }, + + "tenant_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + + "sub_port": { + Type: schema.TypeSet, + Optional: true, + ForceNew: false, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "port_id": { + Type: schema.TypeString, + Required: true, + }, + "segmentation_type": { + Type: schema.TypeString, + Required: true, + }, + "segmentation_id": { + Type: schema.TypeInt, + Required: true, + }, + }, + }, + }, + + "tags": { + Type: schema.TypeSet, + Optional: true, + ForceNew: false, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "all_tags": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func resourceNetworkingTrunkV2Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + client, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + createOpts := trunks.CreateOpts{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + PortID: d.Get("port_id").(string), + TenantID: d.Get("tenant_id").(string), + Subports: expandNetworkingTrunkV2Subports(d.Get("sub_port").(*schema.Set)), + } + + if v, ok := d.GetOkExists("admin_state_up"); ok { + asu := v.(bool) + createOpts.AdminStateUp = &asu + } + + log.Printf("[DEBUG] openstack_networking_trunk_v2 create options: %#v", createOpts) + trunk, err := trunks.Create(client, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating openstack_networking_trunk_v2: %s", err) + } + + log.Printf("[DEBUG] Waiting for openstack_networking_trunk_v2 %s to become available.", trunk.ID) + + stateConf := &resource.StateChangeConf{ + Target: []string{"ACTIVE", "DOWN"}, + Refresh: networkingTrunkV2StateRefreshFunc(client, trunk.ID), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for openstack_networking_trunk_v2 %s to become available: %s", trunk.ID, err) + } + + d.SetId(trunk.ID) + + tags := networkV2AttributesTags(d) + if len(tags) > 0 { + tagOpts := attributestags.ReplaceAllOpts{Tags: tags} + tags, err := attributestags.ReplaceAll(client, "trunks", trunk.ID, tagOpts).Extract() + if err != nil { + return fmt.Errorf("Error setting tags on openstack_networking_trunk_v2 %s: %s", trunk.ID, err) + } + log.Printf("[DEBUG] Set tags %s on openstack_networking_trunk_v2 %s", tags, trunk.ID) + } + + log.Printf("[DEBUG] Created openstack_networking_trunk_v2 %s: %#v", trunk.ID, trunk) + return resourceNetworkingTrunkV2Read(d, meta) +} + +func resourceNetworkingTrunkV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + client, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + trunk, err := trunks.Get(client, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "Error getting openstack_networking_trunk_v2") + } + + log.Printf("[DEBUG] Retrieved openstack_networking_trunk_v2 %s: %#v", d.Id(), trunk) + + d.Set("region", GetRegion(d, config)) + d.Set("name", trunk.Name) + d.Set("description", trunk.Description) + d.Set("port_id", trunk.PortID) + d.Set("admin_state_up", trunk.AdminStateUp) + d.Set("tenant_id", trunk.TenantID) + + networkV2ReadAttributesTags(d, trunk.Tags) + + err = d.Set("sub_port", flattenNetworkingTrunkV2Subports(trunk.Subports)) + if err != nil { + log.Printf("[DEBUG] Unable to set openstack_networking_trunk_v2 %s sub_port: %s", d.Id(), err) + } + + return nil +} + +func resourceNetworkingTrunkV2Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + client, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + // Work with basic trunk update options. + var ( + updateTrunk bool + updateOpts trunks.UpdateOpts + ) + + if d.HasChange("name") { + updateTrunk = true + name := d.Get("name").(string) + updateOpts.Name = &name + } + + if d.HasChange("description") { + updateTrunk = true + description := d.Get("description").(string) + updateOpts.Description = &description + } + + if d.HasChange("admin_state_up") { + updateTrunk = true + asu := d.Get("admin_state_up").(bool) + updateOpts.AdminStateUp = &asu + } + + if updateTrunk { + log.Printf("[DEBUG] openstack_networking_trunk_v2 %s update options: %#v", d.Id(), updateOpts) + _, err = trunks.Update(client, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating openstack_networking_trunk_v2 %s: %s", d.Id(), err) + } + } + + // Update subports if needed. + if d.HasChange("sub_port") { + o, n := d.GetChange("sub_port") + oldSubport := o.(*schema.Set) + newSubport := n.(*schema.Set) + + // Delete all old subports, regardless of if they still exist. + // If they do still exist, they will be re-added below. + if oldSubport.Len() != 0 { + removeSubports := expandNetworkingTrunkV2SubportsRemove(oldSubport) + removeSubportsOpts := trunks.RemoveSubportsOpts{ + Subports: removeSubports, + } + + log.Printf("[DEBUG] Deleting old subports for openstack_networking_trunk_v2 %s: %#v", d.Id(), removeSubportsOpts) + _, err := trunks.RemoveSubports(client, d.Id(), removeSubportsOpts).Extract() + if err != nil { + return fmt.Errorf("Error removing subports for openstack_networking_trunk_v2 %s: %s", d.Id(), err) + } + } + + // Add any new sub_port and re-add previously set subports. + if newSubport.Len() != 0 { + addSubports := expandNetworkingTrunkV2Subports(newSubport) + addSubportsOpts := trunks.AddSubportsOpts{ + Subports: addSubports, + } + + log.Printf("[DEBUG] openstack_networking_trunk_v2 %s subports update options: %#v", d.Id(), addSubports) + _, err := trunks.AddSubports(client, d.Id(), addSubportsOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating openstack_networking_trunk_v2 %s subports: %s", d.Id(), err) + } + } + } + + if d.HasChange("tags") { + tags := networkV2UpdateAttributesTags(d) + tagOpts := attributestags.ReplaceAllOpts{Tags: tags} + tags, err := attributestags.ReplaceAll(client, "trunks", d.Id(), tagOpts).Extract() + if err != nil { + return fmt.Errorf("Error setting tags on openstack_networking_trunk_v2 %s: %s", d.Id(), err) + } + log.Printf("[DEBUG] Set tags %s on openstack_networking_trunk_v2 %s", tags, d.Id()) + } + + return resourceNetworkingTrunkV2Read(d, meta) +} + +func resourceNetworkingTrunkV2Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + client, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + if err := trunks.Delete(client, d.Id()).ExtractErr(); err != nil { + return CheckDeleted(d, err, "Error deleting openstack_networking_trunk_v2") + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"ACTIVE", "DOWN"}, + Target: []string{"DELETED"}, + Refresh: networkingTrunkV2StateRefreshFunc(client, d.Id()), + Timeout: d.Timeout(schema.TimeoutDelete), + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for openstack_networking_trunk_v2 %s to delete: %s", d.Id(), err) + } + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_objectstorage_container_v1.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_objectstorage_container_v1.go index e4cbe5be8..6e0fe0e8c 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_objectstorage_container_v1.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_objectstorage_container_v1.go @@ -4,8 +4,12 @@ import ( "fmt" "log" + "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers" + "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects" + "github.com/gophercloud/gophercloud/pagination" "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" ) func resourceObjectStorageContainerV1() *schema.Resource { @@ -16,47 +20,72 @@ func resourceObjectStorageContainerV1() *schema.Resource { Delete: resourceObjectStorageContainerV1Delete, Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "name": &schema.Schema{ + "name": { Type: schema.TypeString, Required: true, ForceNew: false, }, - "container_read": &schema.Schema{ + "container_read": { Type: schema.TypeString, Optional: true, ForceNew: false, }, - "container_sync_to": &schema.Schema{ + "container_sync_to": { Type: schema.TypeString, Optional: true, ForceNew: false, }, - "container_sync_key": &schema.Schema{ + "container_sync_key": { Type: schema.TypeString, Optional: true, ForceNew: false, }, - "container_write": &schema.Schema{ + "container_write": { Type: schema.TypeString, Optional: true, ForceNew: false, }, - "content_type": &schema.Schema{ + "content_type": { Type: schema.TypeString, Optional: true, ForceNew: false, }, - "metadata": &schema.Schema{ + "versioning": { + Type: schema.TypeSet, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "versions", "history", + }, true), + }, + "location": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "metadata": { Type: schema.TypeMap, Optional: true, ForceNew: false, }, + "force_destroy": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, }, } } @@ -79,6 +108,19 @@ func resourceObjectStorageContainerV1Create(d *schema.ResourceData, meta interfa Metadata: resourceContainerMetadataV2(d), } + versioning := d.Get("versioning").(*schema.Set) + if versioning.Len() > 0 { + vParams := versioning.List()[0] + if vRaw, ok := vParams.(map[string]interface{}); ok { + switch vRaw["type"].(string) { + case "versions": + createOpts.VersionsLocation = vRaw["location"].(string) + case "history": + createOpts.HistoryLocation = vRaw["location"].(string) + } + } + } + log.Printf("[DEBUG] Create Options: %#v", createOpts) _, err = containers.Create(objectStorageClient, cn, createOpts).Extract() if err != nil { @@ -94,6 +136,18 @@ func resourceObjectStorageContainerV1Create(d *schema.ResourceData, meta interfa func resourceObjectStorageContainerV1Read(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) + + objectStorageClient, err := config.objectStorageV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack object storage client: %s", err) + } + + result := containers.Get(objectStorageClient, d.Id(), nil) + + if result.Err != nil { + return CheckDeleted(d, result.Err, "container") + } + d.Set("region", GetRegion(d, config)) return nil @@ -114,6 +168,28 @@ func resourceObjectStorageContainerV1Update(d *schema.ResourceData, meta interfa ContentType: d.Get("content_type").(string), } + if d.HasChange("versioning") { + versioning := d.Get("versioning").(*schema.Set) + if versioning.Len() == 0 { + updateOpts.RemoveVersionsLocation = "true" + updateOpts.RemoveHistoryLocation = "true" + } else { + vParams := versioning.List()[0] + if vRaw, ok := vParams.(map[string]interface{}); ok { + if len(vRaw["location"].(string)) == 0 || len(vRaw["type"].(string)) == 0 { + updateOpts.RemoveVersionsLocation = "true" + updateOpts.RemoveHistoryLocation = "true" + } + switch vRaw["type"].(string) { + case "versions": + updateOpts.VersionsLocation = vRaw["location"].(string) + case "history": + updateOpts.HistoryLocation = vRaw["location"].(string) + } + } + } + } + if d.HasChange("metadata") { updateOpts.Metadata = resourceContainerMetadataV2(d) } @@ -135,6 +211,37 @@ func resourceObjectStorageContainerV1Delete(d *schema.ResourceData, meta interfa _, err = containers.Delete(objectStorageClient, d.Id()).Extract() if err != nil { + gopherErr, ok := err.(gophercloud.ErrUnexpectedResponseCode) + if ok && gopherErr.Actual == 409 && d.Get("force_destroy").(bool) { + // Container may have things. Delete them. + log.Printf("[DEBUG] Attempting to forceDestroy Openstack container %+v", err) + + container := d.Id() + opts := &objects.ListOpts{ + Full: false, + } + // Retrieve a pager (i.e. a paginated collection) + pager := objects.List(objectStorageClient, container, opts) + // Define an anonymous function to be executed on each page's iteration + err := pager.EachPage(func(page pagination.Page) (bool, error) { + + objectList, err := objects.ExtractNames(page) + if err != nil { + return false, fmt.Errorf("Error extracting names from objects from page %+v", err) + } + for _, object := range objectList { + _, err = objects.Delete(objectStorageClient, container, object, objects.DeleteOpts{}).Extract() + if err != nil { + return false, fmt.Errorf("Error deleting object from container %+v", err) + } + } + return true, nil + }) + if err != nil { + return err + } + return resourceObjectStorageContainerV1Delete(d, meta) + } return fmt.Errorf("Error deleting OpenStack container: %s", err) } diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_objectstorage_object_v1.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_objectstorage_object_v1.go new file mode 100644 index 000000000..2d87294fe --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_objectstorage_object_v1.go @@ -0,0 +1,422 @@ +package openstack + +import ( + "bytes" + "fmt" + "log" + "os" + "time" + + "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects" + "github.com/hashicorp/terraform/helper/schema" + "github.com/mitchellh/go-homedir" +) + +func resourceObjectStorageObjectV1() *schema.Resource { + return &schema.Resource{ + Create: resourceObjectStorageObjectV1Create, + Read: resourceObjectStorageObjectV1Read, + Update: resourceObjectStorageObjectV1Update, + Delete: resourceObjectStorageObjectV1Delete, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "container_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "content_disposition": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ConflictsWith: []string{"copy_from"}, + }, + + "content_encoding": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ConflictsWith: []string{"copy_from"}, + }, + + "content_type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ConflictsWith: []string{"copy_from"}, + }, + + "content": { + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"source", "copy_from", "object_manifest"}, + }, + + "copy_from": { + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"content", "source", "object_manifest"}, + }, + + "delete_after": { + Type: schema.TypeInt, + Optional: true, + }, + + "delete_at": { + Type: schema.TypeString, + Computed: true, + Optional: true, + DiffSuppressFunc: suppressEquivilentTimeDiffs, + }, + + "detect_content_type": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + // this attribute is used to trigger resource updates + // if the file content is changed + "etag": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "metadata": { + Type: schema.TypeMap, + Optional: true, + }, + + "object_manifest": { + Type: schema.TypeString, + Computed: true, + Optional: true, + ConflictsWith: []string{"copy_from", "source", "content"}, + }, + + "source": { + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"content", "copy_from", "object_manifest"}, + }, + + // Read Only + "content_length": { + Type: schema.TypeInt, + Computed: true, + }, + + "date": { + Type: schema.TypeString, + Computed: true, + }, + + "last_modified": { + Type: schema.TypeString, + Computed: true, + }, + + "trans_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceObjectStorageObjectV1Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + objectStorageClient, err := config.objectStorageV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack object storage client: %s", err) + } + + name := d.Get("name").(string) + cn := d.Get("container_name").(string) + + createOpts := &objects.CreateOpts{ + Metadata: resourceObjectMetadataV1(d), + TransferEncoding: "chunked", + } + + var isValid bool + if v, ok := d.GetOk("source"); ok { + isValid = true + file, size, err := resourceObjectSourceV1(v.(string)) + if err != nil { + return err + } + + createOpts.Content = file + createOpts.ContentLength = size + defer file.Close() + } + + if v, ok := d.GetOk("content"); ok { + isValid = true + content := v.(string) + createOpts.Content = bytes.NewReader([]byte(content)) + createOpts.ContentLength = int64(len(content)) + } + + if v, ok := d.GetOk("copy_from"); ok { + isValid = true + createOpts.CopyFrom = v.(string) + createOpts.Content = bytes.NewReader([]byte("")) + } + + if v, ok := d.GetOk("object_manifest"); ok { + isValid = true + createOpts.Content = bytes.NewReader([]byte("")) + createOpts.ObjectManifest = v.(string) + } + + if !isValid { + return fmt.Errorf("Must specify \"source\", \"content\", \"copy_from\" or \"object_manifest\" field") + } + + if v, ok := d.GetOk("content_disposition"); ok { + createOpts.ContentDisposition = v.(string) + } + + if v, ok := d.GetOk("content_encoding"); ok { + createOpts.ContentEncoding = v.(string) + } + + if v, ok := d.GetOk("content_type"); ok { + createOpts.ContentType = v.(string) + } + + if v, ok := d.GetOk("delete_after"); ok { + createOpts.DeleteAfter = v.(int) + } + + if v, ok := d.GetOk("delete_at"); ok && v != "" { + t, err := time.Parse(time.RFC3339, fmt.Sprintf("%s", v)) + if err != nil { + return fmt.Errorf("Error Parsing Swift Object Lifecycle Expiration Date: %s, %s", err.Error(), v) + } + + createOpts.DeleteAt = int(t.Unix()) + } + + if v, ok := d.GetOk("detect_content_type"); ok && v.(bool) { + createOpts.DetectContentType = "true" + } + + if v, ok := d.GetOk("etag"); ok { + createOpts.ETag = v.(string) + } + + log.Printf("[DEBUG] Create Options: %#v", createOpts) + _, err = objects.Create(objectStorageClient, cn, name, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating OpenStack container object: %s", err) + } + + // Store the ID now + d.SetId(fmt.Sprintf("%s/%s", cn, name)) + + return resourceObjectStorageObjectV1Read(d, meta) +} + +func resourceObjectStorageObjectV1Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + objectStorageClient, err := config.objectStorageV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack object storage client: %s", err) + } + + name := d.Get("name").(string) + cn := d.Get("container_name").(string) + + getOpts := &objects.GetOpts{} + + if v, ok := d.GetOk("tmp_url_sig"); ok { + getOpts.Signature = v.(string) + } + if v, ok := d.GetOk("tmp_url_expires"); ok { + getOpts.Expires = v.(string) + } + + log.Printf("[DEBUG] Get Options: %#v", getOpts) + result, err := objects.Get(objectStorageClient, cn, name, getOpts).Extract() + if err != nil { + return fmt.Errorf("Error getting OpenStack container object: %s", err) + } + + log.Printf("[DEBUG] Retrieved OpenStack Object Storage Object: %#v", result) + + d.Set("etag", result.ETag) + d.Set("content_disposition", result.ContentDisposition) + d.Set("content_encoding", result.ContentEncoding) + d.Set("content_length", result.ContentLength) + d.Set("content_type", result.ContentType) + if result.Date.Unix() > 0 { + d.Set("date", result.Date.Format(time.RFC3339)) + } + if result.DeleteAt.Unix() > 0 { + d.Set("delete_at", result.DeleteAt.Format(time.RFC3339)) + } + if result.LastModified.Unix() > 0 { + d.Set("last_modified", result.LastModified.Format(time.RFC3339)) + } + d.Set("object_manifest", result.ObjectManifest) + d.Set("trans_id", result.TransID) + + return nil +} + +func resourceObjectStorageObjectV1Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + objectStorageClient, err := config.objectStorageV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack object storage client: %s", err) + } + + name := d.Get("name").(string) + cn := d.Get("container_name").(string) + + // This is not a typo. Reusing CreateOpts for the update. + createOpts := &objects.CreateOpts{ + NoETag: true, + TransferEncoding: "chunked", + } + + if d.HasChange("metadata") { + createOpts.Metadata = resourceObjectMetadataV1(d) + } + + if d.HasChange("source") { + v := d.Get("source").(string) + file, size, err := resourceObjectSourceV1(v) + if err != nil { + return err + } + + createOpts.Content = file + createOpts.ContentLength = size + defer file.Close() + } + + if d.HasChange("content") { + v := d.Get("content").(string) + createOpts.Content = bytes.NewReader([]byte(v)) + createOpts.ContentLength = int64(len(v)) + } + + if d.HasChange("copy_from") { + v := d.Get("copy_from").(string) + createOpts.CopyFrom = v + createOpts.Content = bytes.NewReader([]byte("")) + } + + if d.HasChange("object_manifest") { + v := d.Get("object_manifest").(string) + createOpts.ObjectManifest = v + createOpts.Content = bytes.NewReader([]byte("")) + } + + if v, ok := d.GetOk("content_disposition"); ok { + createOpts.ContentDisposition = v.(string) + } + + if v, ok := d.GetOk("content_encoding"); ok { + createOpts.ContentEncoding = v.(string) + } + + if v, ok := d.GetOk("content_type"); ok { + createOpts.ContentType = v.(string) + } + + if v, ok := d.GetOk("delete_after"); ok { + createOpts.DeleteAfter = v.(int) + } + + if v, ok := d.GetOk("delete_at"); ok && v != "" { + t, err := time.Parse(time.RFC3339, fmt.Sprintf("%s", v)) + if err != nil { + return fmt.Errorf("Error Parsing Swift Object Lifecycle Expiration Date: %s, %s", err.Error(), v) + } + + createOpts.DeleteAt = int(t.Unix()) + } + + if v, ok := d.GetOk("detect_content_type"); ok && v.(bool) { + createOpts.DetectContentType = "true" + } + + if d.HasChange("etag") { + createOpts.NoETag = false + createOpts.ETag = d.Get("etag").(string) + } + + log.Printf("[DEBUG] Update Options: %#v", createOpts) + _, err = objects.Create(objectStorageClient, cn, name, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack container object: %s", err) + } + + return resourceObjectStorageObjectV1Read(d, meta) +} + +func resourceObjectStorageObjectV1Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + objectStorageClient, err := config.objectStorageV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack object storage client: %s", err) + } + + name := d.Get("name").(string) + cn := d.Get("container_name").(string) + deleteOpts := &objects.DeleteOpts{} + + _, err = objects.Delete(objectStorageClient, cn, name, deleteOpts).Extract() + if err != nil { + return fmt.Errorf("Error getting OpenStack container object: %s", err) + } + return nil +} + +func resourceObjectMetadataV1(d *schema.ResourceData) map[string]string { + m := make(map[string]string) + for key, val := range d.Get("metadata").(map[string]interface{}) { + m[key] = val.(string) + } + return m +} + +func resourceObjectSourceV1(source string) (*os.File, int64, error) { + path, err := homedir.Expand(source) + if err != nil { + return nil, 0, fmt.Errorf("Error expanding homedir in source (%s): %s", source, err) + } + + file, err := os.Open(path) + if err != nil { + return nil, 0, fmt.Errorf("Error opening openstack swift object source (%s): %s", source, err) + } + + fileinfo, err := file.Stat() + if err != nil { + return nil, 0, fmt.Errorf("Error opening openstack swift object source (%s): %s", source, err) + } + + return file, fileinfo.Size(), nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_objectstorage_tempurl_v1.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_objectstorage_tempurl_v1.go new file mode 100644 index 000000000..9266ab33c --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_objectstorage_tempurl_v1.go @@ -0,0 +1,159 @@ +package openstack + +import ( + "crypto/md5" + "encoding/hex" + "fmt" + "log" + "net/url" + "strconv" + "time" + + "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects" + + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceObjectstorageTempurlV1() *schema.Resource { + return &schema.Resource{ + Create: resourceObjectstorageTempurlV1Create, + Read: resourceObjectstorageTempurlV1Read, + Delete: schema.RemoveFromState, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "container": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "object": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "method": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "get", + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value != "get" && value != "post" { + errors = append(errors, fmt.Errorf( + "Only 'get', and 'post' are supported values for 'method'")) + } + return + }, + }, + + "ttl": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + + "split": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "regenerate": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + + "url": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +// resourceObjectstorageTempurlV1Create performs the image lookup. +func resourceObjectstorageTempurlV1Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + objectStorageClient, err := config.objectStorageV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } + + method := objects.GET + switch d.Get("method") { + case "post": + method = objects.POST + // gophercloud doesn't have support for PUT yet, + // although it's a valid method for swift + //case "put": + // method = objects.PUT + } + + turlOptions := objects.CreateTempURLOpts{ + Method: method, + TTL: d.Get("ttl").(int), + Split: d.Get("split").(string), + } + + containerName := d.Get("container").(string) + objectName := d.Get("object").(string) + + log.Printf("[DEBUG] Create temporary url Options: %#v", turlOptions) + + url, err := objects.CreateTempURL(objectStorageClient, containerName, objectName, turlOptions) + if err != nil { + return fmt.Errorf("Unable to generate a temporary url for the object %s in container %s: %s", + objectName, containerName, err) + } + + log.Printf("[DEBUG] URL Generated: %s", url) + + // Set the URL and Id fields. + hasher := md5.New() + hasher.Write([]byte(url)) + d.SetId(hex.EncodeToString(hasher.Sum(nil))) + d.Set("url", url) + return nil +} + +// resourceObjectstorageTempurlV1Read performs the image lookup. +func resourceObjectstorageTempurlV1Read(d *schema.ResourceData, meta interface{}) error { + turl := d.Get("url").(string) + u, err := url.Parse(turl) + if err != nil { + return fmt.Errorf("Failed to read the temporary url %s: %s", turl, err) + } + + qp, err := url.ParseQuery(u.RawQuery) + if err != nil { + return fmt.Errorf("Failed to parse the temporary url %s query string: %s", turl, err) + } + + tempURLExpires := qp.Get("temp_url_expires") + expiry, err := strconv.ParseInt(tempURLExpires, 10, 64) + if err != nil { + return fmt.Errorf( + "Failed to parse the temporary url %s expiration time %s: %s", + turl, tempURLExpires, err) + } + + // Regenerate the URL if it has expired and if the user requested it to be. + regen := d.Get("regenerate").(bool) + now := time.Now().Unix() + if expiry < now && regen { + log.Printf("[DEBUG] temporary url %s expired, generating a new one", turl) + d.SetId("") + } + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_sharedfilesystem_securityservice_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_sharedfilesystem_securityservice_v2.go new file mode 100644 index 000000000..ac86c329f --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_sharedfilesystem_securityservice_v2.go @@ -0,0 +1,255 @@ +package openstack + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + + "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/securityservices" +) + +const ( + minManilaMicroversion = "2.7" + minOUManilaMicroversion = "2.44" +) + +func resourceSharedFilesystemSecurityServiceV2() *schema.Resource { + return &schema.Resource{ + Create: resourceSharedFilesystemSecurityServiceV2Create, + Read: resourceSharedFilesystemSecurityServiceV2Read, + Update: resourceSharedFilesystemSecurityServiceV2Update, + Delete: resourceSharedFilesystemSecurityServiceV2Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "project_id": { + Type: schema.TypeString, + Computed: true, + }, + + "name": { + Type: schema.TypeString, + Optional: true, + }, + + "description": { + Type: schema.TypeString, + Optional: true, + }, + + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "active_directory", "kerberos", "ldap", + }, true), + }, + + "dns_ip": { + Type: schema.TypeString, + Optional: true, + }, + + "ou": { + Type: schema.TypeString, + Optional: true, + }, + + "user": { + Type: schema.TypeString, + Optional: true, + }, + + "password": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + }, + + "domain": { + Type: schema.TypeString, + Optional: true, + }, + + "server": { + Type: schema.TypeString, + Optional: true, + }, + }, + } +} + +func resourceSharedFilesystemSecurityServiceV2Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + sfsClient, err := config.sharedfilesystemV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack sharedfilesystem client: %s", err) + } + + sfsClient.Microversion = minManilaMicroversion + + createOpts := securityservices.CreateOpts{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + Type: securityservices.SecurityServiceType(d.Get("type").(string)), + DNSIP: d.Get("dns_ip").(string), + User: d.Get("user").(string), + Domain: d.Get("domain").(string), + Server: d.Get("server").(string), + } + + if v, ok := d.GetOkExists("ou"); ok { + createOpts.OU = v.(string) + + sfsClient.Microversion = minOUManilaMicroversion + } + + log.Printf("[DEBUG] Create Options: %#v", createOpts) + + createOpts.Password = d.Get("password").(string) + securityservice, err := securityservices.Create(sfsClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating : %s", err) + } + + d.SetId(securityservice.ID) + + return resourceSharedFilesystemSecurityServiceV2Read(d, meta) +} + +func resourceSharedFilesystemSecurityServiceV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + sfsClient, err := config.sharedfilesystemV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack sharedfilesystem client: %s", err) + } + + sfsClient.Microversion = minManilaMicroversion + + if _, ok := d.GetOkExists("ou"); ok { + sfsClient.Microversion = minOUManilaMicroversion + } + + securityservice, err := securityservices.Get(sfsClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "securityservice") + } + + // Workaround for resource import + if securityservice.OU == "" { + sfsClient.Microversion = minOUManilaMicroversion + securityserviceOU, err := securityservices.Get(sfsClient, d.Id()).Extract() + if err == nil { + d.Set("ou", securityserviceOU.OU) + } + } + + nopassword := securityservice + nopassword.Password = "" + log.Printf("[DEBUG] Retrieved securityservice %s: %#v", d.Id(), nopassword) + + d.Set("name", securityservice.Name) + d.Set("description", securityservice.Description) + d.Set("type", securityservice.Type) + d.Set("domain", securityservice.Domain) + d.Set("dns_ip", securityservice.DNSIP) + d.Set("user", securityservice.User) + d.Set("server", securityservice.Server) + // Computed + d.Set("project_id", securityservice.ProjectID) + d.Set("region", GetRegion(d, config)) + + return nil +} + +func resourceSharedFilesystemSecurityServiceV2Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + sfsClient, err := config.sharedfilesystemV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack sharedfilesystem client: %s", err) + } + + sfsClient.Microversion = minManilaMicroversion + + var updateOpts securityservices.UpdateOpts + // Name should always be sent, otherwise it is vanished by manila backend + name := d.Get("name").(string) + updateOpts.Name = &name + if d.HasChange("description") { + description := d.Get("description").(string) + updateOpts.Description = &description + } + if d.HasChange("type") { + updateOpts.Type = d.Get("type").(string) + } + if d.HasChange("dns_ip") { + dnsIP := d.Get("dns_ip").(string) + updateOpts.DNSIP = &dnsIP + } + if d.HasChange("ou") { + ou := d.Get("ou").(string) + updateOpts.OU = &ou + + sfsClient.Microversion = minOUManilaMicroversion + } + if d.HasChange("user") { + user := d.Get("user").(string) + updateOpts.User = &user + } + if d.HasChange("domain") { + domain := d.Get("domain").(string) + updateOpts.Domain = &domain + } + if d.HasChange("server") { + server := d.Get("server").(string) + updateOpts.Server = &server + } + + log.Printf("[DEBUG] Updating securityservice %s with options: %#v", d.Id(), updateOpts) + + if d.HasChange("password") { + password := d.Get("password").(string) + updateOpts.Password = &password + } + + _, err = securityservices.Update(sfsClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Unable to update securityservice %s: %s", d.Id(), err) + } + + return resourceSharedFilesystemSecurityServiceV2Read(d, meta) +} + +func resourceSharedFilesystemSecurityServiceV2Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + sfsClient, err := config.sharedfilesystemV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack sharedfilesystem client: %s", err) + } + + log.Printf("[DEBUG] Attempting to delete securityservice %s", d.Id()) + err = securityservices.Delete(sfsClient, d.Id()).ExtractErr() + if err != nil { + return CheckDeleted(d, err, "Error deleting securityservice") + } + + return nil +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_sharedfilesystem_share_access_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_sharedfilesystem_share_access_v2.go new file mode 100644 index 000000000..23b6a5cad --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_sharedfilesystem_share_access_v2.go @@ -0,0 +1,295 @@ +package openstack + +import ( + "fmt" + "log" + "strings" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/errors" + "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares" +) + +func resourceSharedFilesystemShareAccessV2() *schema.Resource { + return &schema.Resource{ + Create: resourceSharedFilesystemShareAccessV2Create, + Read: resourceSharedFilesystemShareAccessV2Read, + Delete: resourceSharedFilesystemShareAccessV2Delete, + Importer: &schema.ResourceImporter{ + State: resourceSharedFilesystemShareAccessV2Import, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "share_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "access_type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + "ip", "user", "cert", + }, true), + }, + + "access_to": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "access_level": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + "rw", "ro", + }, true), + }, + }, + } +} + +func resourceSharedFilesystemShareAccessV2Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + sfsClient, err := config.sharedfilesystemV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack sharedfilesystem client: %s", err) + } + + sfsClient.Microversion = minManilaMicroversion + + shareID := d.Get("share_id").(string) + + grantOpts := shares.GrantAccessOpts{ + AccessType: d.Get("access_type").(string), + AccessTo: d.Get("access_to").(string), + AccessLevel: d.Get("access_level").(string), + } + + log.Printf("[DEBUG] Create Options: %#v", grantOpts) + + timeout := d.Timeout(schema.TimeoutCreate) + + log.Printf("[DEBUG] Attempting to grant access") + var access *shares.AccessRight + err = resource.Retry(timeout, func() *resource.RetryError { + access, err = shares.GrantAccess(sfsClient, shareID, grantOpts).Extract() + if err != nil { + return checkForRetryableError(err) + } + return nil + }) + + if err != nil { + detailedErr := errors.ErrorDetails{} + e := errors.ExtractErrorInto(err, &detailedErr) + if e != nil { + return fmt.Errorf("Error granting access: %s: %s", err, e) + } + for k, msg := range detailedErr { + return fmt.Errorf("Error granting access: %s (%d): %s", k, msg.Code, msg.Message) + } + } + + d.SetId(access.ID) + + pending := []string{"new", "queued_to_apply", "applying"} + // Wait for access to become active before continuing + err = waitForSFV2Access(sfsClient, shareID, access.ID, "active", pending, timeout) + if err != nil { + return err + } + + return resourceSharedFilesystemShareAccessV2Read(d, meta) +} + +func resourceSharedFilesystemShareAccessV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + sfsClient, err := config.sharedfilesystemV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack sharedfilesystem client: %s", err) + } + + sfsClient.Microversion = minManilaMicroversion + + shareID := d.Get("share_id").(string) + + access, err := shares.ListAccessRights(sfsClient, shareID).Extract() + if err != nil { + return CheckDeleted(d, err, "share_access") + } + + for _, v := range access { + if v.ID == d.Id() { + log.Printf("[DEBUG] Retrieved %s share ACL: %#v", d.Id(), v) + + d.Set("access_type", v.AccessType) + d.Set("access_to", v.AccessTo) + d.Set("access_level", v.AccessLevel) + d.Set("region", GetRegion(d, config)) + + return nil + } + } + + log.Printf("[DEBUG] Unable to find %s share access", d.Id()) + d.SetId("") + + return nil +} + +func resourceSharedFilesystemShareAccessV2Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + sfsClient, err := config.sharedfilesystemV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack sharedfilesystem client: %s", err) + } + + sfsClient.Microversion = minManilaMicroversion + + shareID := d.Get("share_id").(string) + + revokeOpts := shares.RevokeAccessOpts{AccessID: d.Id()} + + timeout := d.Timeout(schema.TimeoutDelete) + + log.Printf("[DEBUG] Attempting to revoke access %s", d.Id()) + err = resource.Retry(timeout, func() *resource.RetryError { + err = shares.RevokeAccess(sfsClient, shareID, revokeOpts).ExtractErr() + if err != nil { + return checkForRetryableError(err) + } + return nil + }) + + if err != nil { + e := CheckDeleted(d, err, "") + if e == nil { + return nil + } + detailedErr := errors.ErrorDetails{} + e = errors.ExtractErrorInto(err, &detailedErr) + if e != nil { + return fmt.Errorf("Error waiting for OpenStack share ACL on %s to be removed: %s: %s", shareID, err, e) + } + for k, msg := range detailedErr { + return fmt.Errorf("Error waiting for OpenStack share ACL on %s to be removed: %s (%d): %s", shareID, k, msg.Code, msg.Message) + } + } + + // Wait for access to become deleted before continuing + pending := []string{"active", "new", "queued_to_deny", "denying"} + err = waitForSFV2Access(sfsClient, shareID, d.Id(), "denied", pending, timeout) + if err != nil { + return err + } + + return nil +} + +func resourceSharedFilesystemShareAccessV2Import(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + parts := strings.SplitN(d.Id(), "/", 2) + if len(parts) != 2 { + err := fmt.Errorf("Invalid format specified for Openstack share ACL. Format must be /") + return nil, err + } + + config := meta.(*Config) + sfsClient, err := config.sharedfilesystemV2Client(GetRegion(d, config)) + if err != nil { + return nil, fmt.Errorf("Error creating OpenStack sharedfilesystem client: %s", err) + } + + sfsClient.Microversion = minManilaMicroversion + + shareID := parts[0] + accessID := parts[1] + + access, err := shares.ListAccessRights(sfsClient, shareID).Extract() + if err != nil { + return nil, fmt.Errorf("Unable to get %s Openstack share and its ACL's: %s", shareID, err) + } + + for _, v := range access { + if v.ID == accessID { + log.Printf("[DEBUG] Retrieved %s share ACL: %#v", accessID, v) + + d.SetId(accessID) + d.Set("share_id", shareID) + d.Set("access_type", v.AccessType) + d.Set("access_to", v.AccessTo) + d.Set("access_level", v.AccessLevel) + d.Set("region", GetRegion(d, config)) + + return []*schema.ResourceData{d}, nil + } + } + + return nil, fmt.Errorf("[DEBUG] Unable to find %s share access", accessID) +} + +// Full list of the share access statuses: https://developer.openstack.org/api-ref/shared-file-system/?expanded=list-services-detail,list-access-rules-detail#list-access-rules +func waitForSFV2Access(sfsClient *gophercloud.ServiceClient, shareID string, id string, target string, pending []string, timeout time.Duration) error { + log.Printf("[DEBUG] Waiting for access %s to become %s.", id, target) + + stateConf := &resource.StateChangeConf{ + Target: []string{target}, + Pending: pending, + Refresh: resourceSFV2AccessRefreshFunc(sfsClient, shareID, id), + Timeout: timeout, + Delay: 1 * time.Second, + MinTimeout: 1 * time.Second, + } + + _, err := stateConf.WaitForState() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + switch target { + case "denied": + return nil + default: + return fmt.Errorf("Error: access %s not found: %s", id, err) + } + } + return fmt.Errorf("Error waiting for access %s to become %s: %s", id, target, err) + } + + return nil +} + +func resourceSFV2AccessRefreshFunc(sfsClient *gophercloud.ServiceClient, shareID string, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + access, err := shares.ListAccessRights(sfsClient, shareID).Extract() + if err != nil { + return nil, "", err + } + for _, v := range access { + if v.ID == id { + return v, v.State, nil + } + } + return nil, "", gophercloud.ErrDefault404{} + } +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_sharedfilesystem_share_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_sharedfilesystem_share_v2.go new file mode 100644 index 000000000..78de743c7 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_sharedfilesystem_share_v2.go @@ -0,0 +1,508 @@ +package openstack + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/errors" + "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages" + "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares" +) + +const ( + // major share functionality appeared in 2.14 + minManilaShareMicroversion = "2.14" +) + +func resourceSharedFilesystemShareV2() *schema.Resource { + return &schema.Resource{ + Create: resourceSharedFilesystemShareV2Create, + Read: resourceSharedFilesystemShareV2Read, + Update: resourceSharedFilesystemShareV2Update, + Delete: resourceSharedFilesystemShareV2Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "project_id": { + Type: schema.TypeString, + Computed: true, + }, + + "name": { + Type: schema.TypeString, + Required: true, + }, + + "description": { + Type: schema.TypeString, + Optional: true, + }, + + "share_proto": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + "NFS", "CIFS", "CEPHFS", "GLUSTERFS", "HDFS", "MAPRFS", + }, true), + }, + + "size": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(1), + }, + + "share_type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "snapshot_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "is_public": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "metadata": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + }, + + "share_network_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "availability_zone": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "export_locations": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "path": { + Type: schema.TypeString, + Computed: true, + }, + "preferred": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + + "has_replicas": { + Type: schema.TypeBool, + Computed: true, + }, + + "host": { + Type: schema.TypeString, + Computed: true, + }, + + "replication_type": { + Type: schema.TypeString, + Computed: true, + }, + + "share_server_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceSharedFilesystemShareV2Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + sfsClient, err := config.sharedfilesystemV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack sharedfilesystem client: %s", err) + } + + sfsClient.Microversion = minManilaShareMicroversion + + isPublic := d.Get("is_public").(bool) + + metadataRaw := d.Get("metadata").(map[string]interface{}) + metadata := make(map[string]string, len(metadataRaw)) + for k, v := range metadataRaw { + if stringVal, ok := v.(string); ok { + metadata[k] = stringVal + } + } + + createOpts := shares.CreateOpts{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + ShareProto: d.Get("share_proto").(string), + Size: d.Get("size").(int), + SnapshotID: d.Get("snapshot_id").(string), + IsPublic: &isPublic, + Metadata: metadata, + ShareNetworkID: d.Get("share_network_id").(string), + AvailabilityZone: d.Get("availability_zone").(string), + } + + if v, ok := d.GetOkExists("share_type"); ok { + createOpts.ShareType = v.(string) + } + + log.Printf("[DEBUG] Create Options: %#v", createOpts) + + timeout := d.Timeout(schema.TimeoutCreate) + + log.Printf("[DEBUG] Attempting to create share") + var share *shares.Share + err = resource.Retry(timeout, func() *resource.RetryError { + share, err = shares.Create(sfsClient, createOpts).Extract() + if err != nil { + return checkForRetryableError(err) + } + return nil + }) + + if err != nil { + detailedErr := errors.ErrorDetails{} + e := errors.ExtractErrorInto(err, &detailedErr) + if e != nil { + return fmt.Errorf("Error creating share: %s: %s", err, e) + } + for k, msg := range detailedErr { + return fmt.Errorf("Error creating share: %s (%d): %s", k, msg.Code, msg.Message) + } + } + + d.SetId(share.ID) + + // Wait for share to become active before continuing + err = waitForSFV2Share(sfsClient, share.ID, "available", []string{"creating", "manage_starting"}, timeout) + if err != nil { + return err + } + + return resourceSharedFilesystemShareV2Read(d, meta) +} + +func resourceSharedFilesystemShareV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + sfsClient, err := config.sharedfilesystemV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack sharedfilesystem client: %s", err) + } + + sfsClient.Microversion = minManilaShareMicroversion + + share, err := shares.Get(sfsClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "share") + } + + log.Printf("[DEBUG] Retrieved share %s: %#v", d.Id(), share) + + exportLocationsRaw, err := shares.GetExportLocations(sfsClient, d.Id()).Extract() + if err != nil { + return fmt.Errorf("Failed to retrieve share's export_locations %s: %s", d.Id(), err) + } + + log.Printf("[DEBUG] Retrieved share's export_locations %s: %#v", d.Id(), exportLocationsRaw) + + var exportLocations []map[string]string + for _, v := range exportLocationsRaw { + exportLocations = append(exportLocations, map[string]string{ + "path": v.Path, + "preferred": fmt.Sprint(v.Preferred), + }) + } + if err = d.Set("export_locations", exportLocations); err != nil { + log.Printf("[DEBUG] Unable to set export_locations: %s", err) + } + + d.Set("name", share.Name) + d.Set("description", share.Description) + d.Set("share_proto", share.ShareProto) + d.Set("size", share.Size) + d.Set("share_type", share.ShareTypeName) + d.Set("snapshot_id", share.SnapshotID) + d.Set("is_public", share.IsPublic) + d.Set("metadata", share.Metadata) + d.Set("share_network_id", share.ShareNetworkID) + d.Set("availability_zone", share.AvailabilityZone) + // Computed + d.Set("region", GetRegion(d, config)) + d.Set("project_id", share.ProjectID) + d.Set("has_replicas", share.HasReplicas) + d.Set("host", share.Host) + d.Set("replication_type", share.ReplicationType) + d.Set("share_server_id", share.ShareServerID) + + return nil +} + +func resourceSharedFilesystemShareV2Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + sfsClient, err := config.sharedfilesystemV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack sharedfilesystem client: %s", err) + } + + sfsClient.Microversion = minManilaShareMicroversion + + timeout := d.Timeout(schema.TimeoutUpdate) + + var updateOpts shares.UpdateOpts + + if d.HasChange("name") { + name := d.Get("name").(string) + updateOpts.DisplayName = &name + } + if d.HasChange("description") { + description := d.Get("description").(string) + updateOpts.DisplayDescription = &description + } + if d.HasChange("is_public") { + isPublic := d.Get("is_public").(bool) + updateOpts.IsPublic = &isPublic + } + + if updateOpts != (shares.UpdateOpts{}) { + // Wait for share to become active before continuing + err = waitForSFV2Share(sfsClient, d.Id(), "available", []string{"creating", "manage_starting", "extending", "shrinking"}, timeout) + if err != nil { + return err + } + + log.Printf("[DEBUG] Attempting to update share") + err = resource.Retry(timeout, func() *resource.RetryError { + _, err := shares.Update(sfsClient, d.Id(), updateOpts).Extract() + if err != nil { + return checkForRetryableError(err) + } + return nil + }) + + if err != nil { + detailedErr := errors.ErrorDetails{} + e := errors.ExtractErrorInto(err, &detailedErr) + if e != nil { + return fmt.Errorf("Error updating %s share: %s: %s", d.Id(), err, e) + } + for k, msg := range detailedErr { + return fmt.Errorf("Error updating %s share: %s (%d): %s", d.Id(), k, msg.Code, msg.Message) + } + } + + // Wait for share to become active before continuing + err = waitForSFV2Share(sfsClient, d.Id(), "available", []string{"creating", "manage_starting", "extending", "shrinking"}, timeout) + if err != nil { + return err + } + } + + if d.HasChange("size") { + var pending []string + oldSize, newSize := d.GetChange("size") + + if newSize.(int) > oldSize.(int) { + pending = append(pending, "extending") + resizeOpts := shares.ExtendOpts{NewSize: newSize.(int)} + log.Printf("[DEBUG] Resizing share %s with options: %#v", d.Id(), resizeOpts) + err = resource.Retry(timeout, func() *resource.RetryError { + err := shares.Extend(sfsClient, d.Id(), resizeOpts).Err + log.Printf("[DEBUG] Resizing share %s with options: %#v", d.Id(), resizeOpts) + if err != nil { + return checkForRetryableError(err) + } + return nil + }) + } else if newSize.(int) < oldSize.(int) { + pending = append(pending, "shrinking") + resizeOpts := shares.ShrinkOpts{NewSize: newSize.(int)} + log.Printf("[DEBUG] Resizing share %s with options: %#v", d.Id(), resizeOpts) + err = resource.Retry(timeout, func() *resource.RetryError { + err := shares.Shrink(sfsClient, d.Id(), resizeOpts).Err + log.Printf("[DEBUG] Resizing share %s with options: %#v", d.Id(), resizeOpts) + if err != nil { + return checkForRetryableError(err) + } + return nil + }) + } + + if err != nil { + detailedErr := errors.ErrorDetails{} + e := errors.ExtractErrorInto(err, &detailedErr) + if e != nil { + return fmt.Errorf("Unable to resize %s share: %s: %s", d.Id(), err, e) + } + for k, msg := range detailedErr { + return fmt.Errorf("Unable to resize %s share: %s (%d): %s", d.Id(), k, msg.Code, msg.Message) + } + } + + // Wait for share to become active before continuing + err = waitForSFV2Share(sfsClient, d.Id(), "available", pending, timeout) + if err != nil { + return err + } + } + + return resourceSharedFilesystemShareV2Read(d, meta) +} + +func resourceSharedFilesystemShareV2Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + sfsClient, err := config.sharedfilesystemV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack sharedfilesystem client: %s", err) + } + + timeout := d.Timeout(schema.TimeoutDelete) + + log.Printf("[DEBUG] Attempting to delete share %s", d.Id()) + err = resource.Retry(timeout, func() *resource.RetryError { + err = shares.Delete(sfsClient, d.Id()).ExtractErr() + if err != nil { + return checkForRetryableError(err) + } + return nil + }) + + if err != nil { + e := CheckDeleted(d, err, "") + if e == nil { + return nil + } + detailedErr := errors.ErrorDetails{} + e = errors.ExtractErrorInto(err, &detailedErr) + if e != nil { + return fmt.Errorf("Unable to delete %s share: %s: %s", d.Id(), err, e) + } + for k, msg := range detailedErr { + return fmt.Errorf("Unable to delete %s share: %s (%d): %s", d.Id(), k, msg.Code, msg.Message) + } + } + + // Wait for share to become deleted before continuing + pending := []string{"", "deleting", "available"} + err = waitForSFV2Share(sfsClient, d.Id(), "deleted", pending, timeout) + if err != nil { + return err + } + + return nil +} + +// Full list of the share statuses: https://developer.openstack.org/api-ref/shared-file-system/#shares +func waitForSFV2Share(sfsClient *gophercloud.ServiceClient, id string, target string, pending []string, timeout time.Duration) error { + log.Printf("[DEBUG] Waiting for share %s to become %s.", id, target) + + stateConf := &resource.StateChangeConf{ + Target: []string{target}, + Pending: pending, + Refresh: resourceSFV2ShareRefreshFunc(sfsClient, id), + Timeout: timeout, + Delay: 1 * time.Second, + MinTimeout: 1 * time.Second, + } + + _, err := stateConf.WaitForState() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + switch target { + case "deleted": + return nil + default: + return fmt.Errorf("Error: share %s not found: %s", id, err) + } + } + errorMessage := fmt.Sprintf("Error waiting for share %s to become %s", id, target) + msg := resourceSFSV2ShareManilaMessage(sfsClient, id) + if msg == nil { + return fmt.Errorf("%s: %s", errorMessage, err) + } + return fmt.Errorf("%s: %s: the latest manila message (%s): %s", errorMessage, err, msg.CreatedAt, msg.UserMessage) + } + + return nil +} + +func resourceSFV2ShareRefreshFunc(sfsClient *gophercloud.ServiceClient, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + share, err := shares.Get(sfsClient, id).Extract() + if err != nil { + return nil, "", err + } + return share, share.Status, nil + } +} + +func resourceSFSV2ShareManilaMessage(sfsClient *gophercloud.ServiceClient, id string) *messages.Message { + // we can simply set this, because this function is called after the error occurred + sfsClient.Microversion = "2.37" + + listOpts := messages.ListOpts{ + ResourceID: id, + SortKey: "created_at", + SortDir: "desc", + Limit: 1, + } + allPages, err := messages.List(sfsClient, listOpts).AllPages() + if err != nil { + log.Printf("[DEBUG] Unable to retrieve messages: %v", err) + return nil + } + + allMessages, err := messages.ExtractMessages(allPages) + if err != nil { + log.Printf("[DEBUG] Unable to extract messages: %v", err) + return nil + } + + if len(allMessages) == 0 { + log.Printf("[DEBUG] No messages found") + return nil + } + + return &allMessages[0] +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_sharedfilesystem_sharenetwork_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_sharedfilesystem_sharenetwork_v2.go new file mode 100644 index 000000000..dcfcb15fb --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_sharedfilesystem_sharenetwork_v2.go @@ -0,0 +1,277 @@ +package openstack + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/schema" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/securityservices" + "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharenetworks" +) + +func resourceSharedFilesystemShareNetworkV2() *schema.Resource { + return &schema.Resource{ + Create: resourceSharedFilesystemShareNetworkV2Create, + Read: resourceSharedFilesystemShareNetworkV2Read, + Update: resourceSharedFilesystemShareNetworkV2Update, + Delete: resourceSharedFilesystemShareNetworkV2Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "project_id": { + Type: schema.TypeString, + Computed: true, + }, + + "name": { + Type: schema.TypeString, + Optional: true, + }, + + "description": { + Type: schema.TypeString, + Optional: true, + }, + + "neutron_net_id": { + Type: schema.TypeString, + Required: true, + }, + + "neutron_subnet_id": { + Type: schema.TypeString, + Required: true, + }, + + "security_service_ids": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + + "network_type": { + Type: schema.TypeString, + Computed: true, + }, + + "segmentation_id": { + Type: schema.TypeInt, + Computed: true, + }, + + "cidr": { + Type: schema.TypeString, + Computed: true, + }, + + "ip_version": { + Type: schema.TypeInt, + Computed: true, + }, + }, + } +} + +func resourceSharedFilesystemShareNetworkV2Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + sfsClient, err := config.sharedfilesystemV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack sharedfilesystem client: %s", err) + } + + createOpts := sharenetworks.CreateOpts{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + NeutronNetID: d.Get("neutron_net_id").(string), + NeutronSubnetID: d.Get("neutron_subnet_id").(string), + } + + log.Printf("[DEBUG] Create Options: %#v", createOpts) + + log.Printf("[DEBUG] Attempting to create sharenetwork") + sharenetwork, err := sharenetworks.Create(sfsClient, createOpts).Extract() + + if err != nil { + return fmt.Errorf("Error creating sharenetwork: %s", err) + } + + d.SetId(sharenetwork.ID) + + securityServiceIDs := resourceSharedFilesystemShareNetworkV2SecSvcToArray(d.Get("security_service_ids").(*schema.Set)) + for _, securityServiceID := range securityServiceIDs { + log.Printf("[DEBUG] Adding %s security service to sharenetwork %s", securityServiceID, sharenetwork.ID) + securityServiceOpts := sharenetworks.AddSecurityServiceOpts{SecurityServiceID: securityServiceID} + _, err = sharenetworks.AddSecurityService(sfsClient, sharenetwork.ID, securityServiceOpts).Extract() + if err != nil { + return fmt.Errorf("Error adding %s security service to sharenetwork: %s", securityServiceID, err) + } + } + + return resourceSharedFilesystemShareNetworkV2Read(d, meta) +} + +func resourceSharedFilesystemShareNetworkV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + sfsClient, err := config.sharedfilesystemV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack sharedfilesystem client: %s", err) + } + + sharenetwork, err := sharenetworks.Get(sfsClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "sharenetwork") + } + + log.Printf("[DEBUG] Retrieved sharenetwork %s: %#v", d.Id(), sharenetwork) + + securityServiceIDs, err := resourceSharedFilesystemShareNetworkV2GetSvcByShareNetID(sfsClient, d.Id()) + if err != nil { + return err + } + + d.Set("security_service_ids", securityServiceIDs) + + d.Set("name", sharenetwork.Name) + d.Set("description", sharenetwork.Description) + d.Set("neutron_net_id", sharenetwork.NeutronNetID) + d.Set("neutron_subnet_id", sharenetwork.NeutronSubnetID) + // Computed + d.Set("project_id", sharenetwork.ProjectID) + d.Set("region", GetRegion(d, config)) + d.Set("network_type", sharenetwork.NetworkType) + d.Set("segmentation_id", sharenetwork.SegmentationID) + d.Set("cidr", sharenetwork.CIDR) + d.Set("ip_version", sharenetwork.IPVersion) + + return nil +} + +func resourceSharedFilesystemShareNetworkV2Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + sfsClient, err := config.sharedfilesystemV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack sharedfilesystem client: %s", err) + } + + var updateOpts sharenetworks.UpdateOpts + if d.HasChange("name") { + name := d.Get("name").(string) + updateOpts.Name = &name + } + if d.HasChange("description") { + description := d.Get("description").(string) + updateOpts.Description = &description + } + if d.HasChange("neutron_net_id") { + updateOpts.NeutronNetID = d.Get("neutron_net_id").(string) + } + if d.HasChange("neutron_subnet_id") { + updateOpts.NeutronSubnetID = d.Get("neutron_subnet_id").(string) + } + + if updateOpts != (sharenetworks.UpdateOpts{}) { + log.Printf("[DEBUG] Updating sharenetwork %s with options: %#v", d.Id(), updateOpts) + _, err = sharenetworks.Update(sfsClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Unable to update sharenetwork %s: %s", d.Id(), err) + } + } + + if d.HasChange("security_service_ids") { + old, new := d.GetChange("security_service_ids") + + oldList, newList := old.(*schema.Set), new.(*schema.Set) + newSecurityServiceIDs := newList.Difference(oldList) + oldSecurityServiceIDs := oldList.Difference(newList) + + for _, newSecurityServiceID := range newSecurityServiceIDs.List() { + id := newSecurityServiceID.(string) + log.Printf("[DEBUG] Adding new %s security service to sharenetwork %s", id, d.Id()) + securityServiceOpts := sharenetworks.AddSecurityServiceOpts{SecurityServiceID: id} + _, err = sharenetworks.AddSecurityService(sfsClient, d.Id(), securityServiceOpts).Extract() + if err != nil { + return fmt.Errorf("Error adding new %s security service to sharenetwork: %s", id, err) + } + } + for _, oldSecurityServiceID := range oldSecurityServiceIDs.List() { + id := oldSecurityServiceID.(string) + log.Printf("[DEBUG] Removing old %s security service from sharenetwork %s", id, d.Id()) + securityServiceOpts := sharenetworks.RemoveSecurityServiceOpts{SecurityServiceID: id} + _, err = sharenetworks.RemoveSecurityService(sfsClient, d.Id(), securityServiceOpts).Extract() + if err != nil { + return fmt.Errorf("Error removing old %s security service from sharenetwork: %s", id, err) + } + } + } + + return resourceSharedFilesystemShareNetworkV2Read(d, meta) +} + +func resourceSharedFilesystemShareNetworkV2Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + sfsClient, err := config.sharedfilesystemV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack sharedfilesystem client: %s", err) + } + + log.Printf("[DEBUG] Attempting to delete sharenetwork %s", d.Id()) + err = sharenetworks.Delete(sfsClient, d.Id()).ExtractErr() + if err != nil { + return CheckDeleted(d, err, "Error deleting sharenetwork") + } + + return nil +} + +func resourceSharedFilesystemShareNetworkV2GetSvcByShareNetID(sfsClient *gophercloud.ServiceClient, shareNetworkID string) ([]string, error) { + securityServiceListOpts := securityservices.ListOpts{ShareNetworkID: shareNetworkID} + securityServicePages, err := securityservices.List(sfsClient, securityServiceListOpts).AllPages() + if err != nil { + return nil, fmt.Errorf("Unable to list security services for sharenetwork %s: %s", shareNetworkID, err) + } + securityServiceList, err := securityservices.ExtractSecurityServices(securityServicePages) + if err != nil { + return nil, fmt.Errorf("Unable to extract security services for sharenetwork %s: %s", shareNetworkID, err) + } + log.Printf("[DEBUG] Retrieved security services for sharenetwork %s: %#v", shareNetworkID, securityServiceList) + + return resourceSharedFilesystemShareNetworkV2SecSvcToArray(&securityServiceList), nil +} + +func resourceSharedFilesystemShareNetworkV2SecSvcToArray(v interface{}) []string { + var securityServicesIDs []string + + switch t := v.(type) { + case *schema.Set: + for _, securityService := range (*v.(*schema.Set)).List() { + securityServicesIDs = append(securityServicesIDs, securityService.(string)) + } + case *[]securityservices.SecurityService: + for _, securityService := range *v.(*[]securityservices.SecurityService) { + securityServicesIDs = append(securityServicesIDs, securityService.ID) + } + default: + log.Printf("[DEBUG] Invalid type provided to get the list of security service IDs: %s", t) + } + + return securityServicesIDs +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_vpn_ike_policy_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_vpn_ike_policy_v2.go new file mode 100644 index 000000000..749401f66 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_vpn_ike_policy_v2.go @@ -0,0 +1,427 @@ +package openstack + +import ( + "fmt" + "log" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ikepolicies" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceIKEPolicyV2() *schema.Resource { + return &schema.Resource{ + Create: resourceIKEPolicyV2Create, + Read: resourceIKEPolicyV2Read, + Update: resourceIKEPolicyV2Update, + Delete: resourceIKEPolicyV2Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "name": { + Type: schema.TypeString, + Optional: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "auth_algorithm": { + Type: schema.TypeString, + Optional: true, + Default: "sha1", + }, + "encryption_algorithm": { + Type: schema.TypeString, + Optional: true, + Default: "aes-128", + }, + "pfs": { + Type: schema.TypeString, + Optional: true, + Default: "group5", + }, + "phase1_negotiation_mode": { + Type: schema.TypeString, + Optional: true, + Default: "main", + }, + "ike_version": { + Type: schema.TypeString, + Optional: true, + Default: "v1", + }, + "lifetime": { + Type: schema.TypeSet, + Computed: true, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "units": { + Type: schema.TypeString, + Computed: true, + Optional: true, + }, + "value": { + Type: schema.TypeInt, + Computed: true, + Optional: true, + }, + }, + }, + }, + "tenant_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + "value_specs": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func resourceIKEPolicyV2Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + lifetime := resourceIKEPolicyV2LifetimeCreateOpts(d.Get("lifetime").(*schema.Set)) + authAlgorithm := resourceIKEPolicyV2AuthAlgorithm(d.Get("auth_algorithm").(string)) + encryptionAlgorithm := resourceIKEPolicyV2EncryptionAlgorithm(d.Get("encryption_algorithm").(string)) + pfs := resourceIKEPolicyV2PFS(d.Get("pfs").(string)) + ikeVersion := resourceIKEPolicyV2IKEVersion(d.Get("ike_version").(string)) + phase1NegotationMode := resourceIKEPolicyV2Phase1NegotiationMode(d.Get("phase1_negotiation_mode").(string)) + + opts := IKEPolicyCreateOpts{ + ikepolicies.CreateOpts{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + TenantID: d.Get("tenant_id").(string), + Lifetime: &lifetime, + AuthAlgorithm: authAlgorithm, + EncryptionAlgorithm: encryptionAlgorithm, + PFS: pfs, + IKEVersion: ikeVersion, + Phase1NegotiationMode: phase1NegotationMode, + }, + MapValueSpecs(d), + } + log.Printf("[DEBUG] Create IKE policy: %#v", opts) + + policy, err := ikepolicies.Create(networkingClient, opts).Extract() + if err != nil { + return err + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"PENDING_CREATE"}, + Target: []string{"ACTIVE"}, + Refresh: waitForIKEPolicyCreation(networkingClient, policy.ID), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 0, + MinTimeout: 2 * time.Second, + } + _, err = stateConf.WaitForState() + + log.Printf("[DEBUG] IKE policy created: %#v", policy) + + d.SetId(policy.ID) + + return resourceIKEPolicyV2Read(d, meta) +} + +func resourceIKEPolicyV2Read(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Retrieve information about IKE policy: %s", d.Id()) + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + policy, err := ikepolicies.Get(networkingClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "IKE policy") + } + + log.Printf("[DEBUG] Read OpenStack IKE Policy %s: %#v", d.Id(), policy) + + d.Set("name", policy.Name) + d.Set("description", policy.Description) + d.Set("auth_algorithm", policy.AuthAlgorithm) + d.Set("encryption_algorithm", policy.EncryptionAlgorithm) + d.Set("tenant_id", policy.TenantID) + d.Set("pfs", policy.PFS) + d.Set("phase1_negotiation_mode", policy.Phase1NegotiationMode) + d.Set("ike_version", policy.IKEVersion) + d.Set("region", GetRegion(d, config)) + + // Set the lifetime + var lifetimeMap map[string]interface{} + lifetimeMap = make(map[string]interface{}) + lifetimeMap["units"] = policy.Lifetime.Units + lifetimeMap["value"] = policy.Lifetime.Value + var lifetime []map[string]interface{} + lifetime = append(lifetime, lifetimeMap) + if err := d.Set("lifetime", &lifetime); err != nil { + log.Printf("[WARN] unable to set IKE policy lifetime") + } + + return nil +} + +func resourceIKEPolicyV2Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + opts := ikepolicies.UpdateOpts{} + + var hasChange bool + + if d.HasChange("name") { + name := d.Get("name").(string) + opts.Name = &name + hasChange = true + } + + if d.HasChange("description") { + description := d.Get("description").(string) + opts.Description = &description + hasChange = true + } + + if d.HasChange("pfs") { + opts.PFS = resourceIKEPolicyV2PFS(d.Get("pfs").(string)) + hasChange = true + } + if d.HasChange("auth_algorithm") { + opts.AuthAlgorithm = resourceIKEPolicyV2AuthAlgorithm(d.Get("auth_algorithm").(string)) + hasChange = true + } + if d.HasChange("encryption_algorithm") { + opts.EncryptionAlgorithm = resourceIKEPolicyV2EncryptionAlgorithm(d.Get("encryption_algorithm").(string)) + hasChange = true + } + if d.HasChange("phase_1_negotiation_mode") { + opts.Phase1NegotiationMode = resourceIKEPolicyV2Phase1NegotiationMode(d.Get("phase_1_negotiation_mode").(string)) + hasChange = true + } + if d.HasChange("ike_version") { + opts.IKEVersion = resourceIKEPolicyV2IKEVersion(d.Get("ike_version").(string)) + hasChange = true + } + + if d.HasChange("lifetime") { + lifetime := resourceIKEPolicyV2LifetimeUpdateOpts(d.Get("lifetime").(*schema.Set)) + opts.Lifetime = &lifetime + hasChange = true + } + + log.Printf("[DEBUG] Updating IKE policy with id %s: %#v", d.Id(), opts) + + if hasChange { + err = ikepolicies.Update(networkingClient, d.Id(), opts).Err + if err != nil { + return err + } + stateConf := &resource.StateChangeConf{ + Pending: []string{"PENDING_UPDATE"}, + Target: []string{"ACTIVE"}, + Refresh: waitForIKEPolicyUpdate(networkingClient, d.Id()), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 0, + MinTimeout: 2 * time.Second, + } + if _, err = stateConf.WaitForState(); err != nil { + return err + } + } + + return resourceIKEPolicyV2Read(d, meta) +} + +func resourceIKEPolicyV2Delete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Destroy IKE policy: %s", d.Id()) + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"ACTIVE"}, + Target: []string{"DELETED"}, + Refresh: waitForIKEPolicyDeletion(networkingClient, d.Id()), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 0, + MinTimeout: 2 * time.Second, + } + + if _, err = stateConf.WaitForState(); err != nil { + return err + } + + return nil +} + +func waitForIKEPolicyDeletion(networkingClient *gophercloud.ServiceClient, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + err := ikepolicies.Delete(networkingClient, id).Err + if err == nil { + return "", "DELETED", nil + } + + return nil, "ACTIVE", err + } +} + +func waitForIKEPolicyCreation(networkingClient *gophercloud.ServiceClient, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + policy, err := ikepolicies.Get(networkingClient, id).Extract() + if err != nil { + return "", "PENDING_CREATE", nil + } + return policy, "ACTIVE", nil + } +} + +func waitForIKEPolicyUpdate(networkingClient *gophercloud.ServiceClient, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + policy, err := ikepolicies.Get(networkingClient, id).Extract() + if err != nil { + return "", "PENDING_UPDATE", nil + } + return policy, "ACTIVE", nil + } +} + +func resourceIKEPolicyV2AuthAlgorithm(v string) ikepolicies.AuthAlgorithm { + var authAlgorithm ikepolicies.AuthAlgorithm + switch v { + case "sha1": + authAlgorithm = ikepolicies.AuthAlgorithmSHA1 + case "sha256": + authAlgorithm = ikepolicies.AuthAlgorithmSHA256 + case "sha384": + authAlgorithm = ikepolicies.AuthAlgorithmSHA384 + case "sha512": + authAlgorithm = ikepolicies.AuthAlgorithmSHA512 + } + + return authAlgorithm +} + +func resourceIKEPolicyV2EncryptionAlgorithm(v string) ikepolicies.EncryptionAlgorithm { + var encryptionAlgorithm ikepolicies.EncryptionAlgorithm + switch v { + case "3des": + encryptionAlgorithm = ikepolicies.EncryptionAlgorithm3DES + case "aes-128": + encryptionAlgorithm = ikepolicies.EncryptionAlgorithmAES128 + case "aes-192": + encryptionAlgorithm = ikepolicies.EncryptionAlgorithmAES192 + case "aes-256": + encryptionAlgorithm = ikepolicies.EncryptionAlgorithmAES256 + } + + return encryptionAlgorithm +} + +func resourceIKEPolicyV2PFS(v string) ikepolicies.PFS { + var pfs ikepolicies.PFS + switch v { + case "group5": + pfs = ikepolicies.PFSGroup5 + case "group2": + pfs = ikepolicies.PFSGroup2 + case "group14": + pfs = ikepolicies.PFSGroup14 + } + return pfs +} + +func resourceIKEPolicyV2IKEVersion(v string) ikepolicies.IKEVersion { + var ikeVersion ikepolicies.IKEVersion + switch v { + case "v1": + ikeVersion = ikepolicies.IKEVersionv1 + case "v2": + ikeVersion = ikepolicies.IKEVersionv2 + } + return ikeVersion +} + +func resourceIKEPolicyV2Phase1NegotiationMode(v string) ikepolicies.Phase1NegotiationMode { + var phase1NegotiationMode ikepolicies.Phase1NegotiationMode + switch v { + case "main": + phase1NegotiationMode = ikepolicies.Phase1NegotiationModeMain + } + return phase1NegotiationMode +} + +func resourceIKEPolicyV2Unit(v string) ikepolicies.Unit { + var unit ikepolicies.Unit + switch v { + case "kilobytes": + unit = ikepolicies.UnitKilobytes + case "seconds": + unit = ikepolicies.UnitSeconds + } + return unit +} + +func resourceIKEPolicyV2LifetimeCreateOpts(d *schema.Set) ikepolicies.LifetimeCreateOpts { + lifetimeCreateOpts := ikepolicies.LifetimeCreateOpts{} + + rawPairs := d.List() + for _, raw := range rawPairs { + rawMap := raw.(map[string]interface{}) + lifetimeCreateOpts.Units = resourceIKEPolicyV2Unit(rawMap["units"].(string)) + + value := rawMap["value"].(int) + lifetimeCreateOpts.Value = value + } + return lifetimeCreateOpts + +} + +func resourceIKEPolicyV2LifetimeUpdateOpts(d *schema.Set) ikepolicies.LifetimeUpdateOpts { + lifetimeUpdateOpts := ikepolicies.LifetimeUpdateOpts{} + + rawPairs := d.List() + for _, raw := range rawPairs { + rawMap := raw.(map[string]interface{}) + lifetimeUpdateOpts.Units = resourceIKEPolicyV2Unit(rawMap["units"].(string)) + + value := rawMap["value"].(int) + lifetimeUpdateOpts.Value = value + } + return lifetimeUpdateOpts + +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_vpnaas_endpoint_group_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_vpnaas_endpoint_group_v2.go new file mode 100644 index 000000000..0c3dbf7e0 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_vpnaas_endpoint_group_v2.go @@ -0,0 +1,291 @@ +package openstack + +import ( + "fmt" + "log" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/endpointgroups" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceEndpointGroupV2() *schema.Resource { + return &schema.Resource{ + Create: resourceEndpointGroupV2Create, + Read: resourceEndpointGroupV2Read, + Update: resourceEndpointGroupV2Update, + Delete: resourceEndpointGroupV2Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "name": { + Type: schema.TypeString, + Optional: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "tenant_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Computed: true, + Optional: true, + ForceNew: true, + }, + "endpoints": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "value_specs": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func resourceEndpointGroupV2Create(d *schema.ResourceData, meta interface{}) error { + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + var createOpts endpointgroups.CreateOptsBuilder + + endpointType := resourceEndpointGroupV2EndpointType(d.Get("type").(string)) + v := d.Get("endpoints").([]interface{}) + endpoints := make([]string, len(v)) + for i, v := range v { + endpoints[i] = v.(string) + } + + createOpts = EndpointGroupCreateOpts{ + endpointgroups.CreateOpts{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + TenantID: d.Get("tenant_id").(string), + Endpoints: endpoints, + Type: endpointType, + }, + MapValueSpecs(d), + } + + log.Printf("[DEBUG] Create group: %#v", createOpts) + + group, err := endpointgroups.Create(networkingClient, createOpts).Extract() + if err != nil { + return err + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"PENDING_CREATE"}, + Target: []string{"ACTIVE"}, + Refresh: waitForEndpointGroupCreation(networkingClient, group.ID), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 0, + MinTimeout: 2 * time.Second, + } + _, err = stateConf.WaitForState() + + if err != nil { + return err + } + + log.Printf("[DEBUG] EndpointGroup created: %#v", group) + + d.SetId(group.ID) + + return resourceEndpointGroupV2Read(d, meta) +} + +func resourceEndpointGroupV2Read(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Retrieve information about group: %s", d.Id()) + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + group, err := endpointgroups.Get(networkingClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "group") + } + + log.Printf("[DEBUG] Read OpenStack Endpoint EndpointGroup %s: %#v", d.Id(), group) + + d.Set("name", group.Name) + d.Set("description", group.Description) + d.Set("tenant_id", group.TenantID) + d.Set("type", group.Type) + d.Set("endpoints", group.Endpoints) + d.Set("region", GetRegion(d, config)) + + return nil +} + +func resourceEndpointGroupV2Update(d *schema.ResourceData, meta interface{}) error { + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + opts := endpointgroups.UpdateOpts{} + + var hasChange bool + + if d.HasChange("name") { + name := d.Get("name").(string) + opts.Name = &name + hasChange = true + } + + if d.HasChange("description") { + description := d.Get("description").(string) + opts.Description = &description + hasChange = true + } + + var updateOpts endpointgroups.UpdateOptsBuilder + updateOpts = opts + + log.Printf("[DEBUG] Updating endpoint group with id %s: %#v", d.Id(), updateOpts) + + if hasChange { + group, err := endpointgroups.Update(networkingClient, d.Id(), updateOpts).Extract() + if err != nil { + return err + } + stateConf := &resource.StateChangeConf{ + Pending: []string{"PENDING_UPDATE"}, + Target: []string{"UPDATED"}, + Refresh: waitForEndpointGroupUpdate(networkingClient, group.ID), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 0, + MinTimeout: 2 * time.Second, + } + _, err = stateConf.WaitForState() + + if err != nil { + return err + } + + log.Printf("[DEBUG] Updated group with id %s", d.Id()) + } + + return resourceEndpointGroupV2Read(d, meta) +} + +func resourceEndpointGroupV2Delete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Destroy group: %s", d.Id()) + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + err = endpointgroups.Delete(networkingClient, d.Id()).Err + + if err != nil { + return err + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"DELETING"}, + Target: []string{"DELETED"}, + Refresh: waitForEndpointGroupDeletion(networkingClient, d.Id()), + Timeout: d.Timeout(schema.TimeoutDelete), + Delay: 0, + MinTimeout: 2 * time.Second, + } + + _, err = stateConf.WaitForState() + + return err +} + +func waitForEndpointGroupDeletion(networkingClient *gophercloud.ServiceClient, id string) resource.StateRefreshFunc { + + return func() (interface{}, string, error) { + group, err := endpointgroups.Get(networkingClient, id).Extract() + log.Printf("[DEBUG] Got group %s => %#v", id, group) + + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + log.Printf("[DEBUG] EndpointGroup %s is actually deleted", id) + return "", "DELETED", nil + } + return nil, "", fmt.Errorf("Unexpected error: %s", err) + } + + log.Printf("[DEBUG] EndpointGroup %s deletion is pending", id) + return group, "DELETING", nil + } +} + +func waitForEndpointGroupCreation(networkingClient *gophercloud.ServiceClient, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + group, err := endpointgroups.Get(networkingClient, id).Extract() + if err != nil { + return "", "PENDING_CREATE", nil + } + return group, "ACTIVE", nil + } +} + +func waitForEndpointGroupUpdate(networkingClient *gophercloud.ServiceClient, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + group, err := endpointgroups.Get(networkingClient, id).Extract() + if err != nil { + return "", "PENDING_UPDATE", nil + } + return group, "UPDATED", nil + } +} + +func resourceEndpointGroupV2EndpointType(epType string) endpointgroups.EndpointType { + var et endpointgroups.EndpointType + switch epType { + case "subnet": + et = endpointgroups.TypeSubnet + case "cidr": + et = endpointgroups.TypeCIDR + case "vlan": + et = endpointgroups.TypeVLAN + case "router": + et = endpointgroups.TypeRouter + case "network": + et = endpointgroups.TypeNetwork + } + return et +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_vpnaas_ipsec_policy_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_vpnaas_ipsec_policy_v2.go new file mode 100644 index 000000000..cfdafc3db --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_vpnaas_ipsec_policy_v2.go @@ -0,0 +1,437 @@ +package openstack + +import ( + "fmt" + "log" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ipsecpolicies" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceIPSecPolicyV2() *schema.Resource { + return &schema.Resource{ + Create: resourceIPSecPolicyV2Create, + Read: resourceIPSecPolicyV2Read, + Update: resourceIPSecPolicyV2Update, + Delete: resourceIPSecPolicyV2Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "name": { + Type: schema.TypeString, + Optional: true, + }, + "auth_algorithm": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "encapsulation_mode": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "pfs": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "encryption_algorithm": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "transform_protocol": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "tenant_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + "lifetime": { + Type: schema.TypeSet, + Computed: true, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "units": { + Type: schema.TypeString, + Computed: true, + Optional: true, + }, + "value": { + Type: schema.TypeInt, + Computed: true, + Optional: true, + }, + }, + }, + }, + "value_specs": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func resourceIPSecPolicyV2Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + encapsulationMode := resourceIPSecPolicyV2EncapsulationMode(d.Get("encapsulation_mode").(string)) + authAlgorithm := resourceIPSecPolicyV2AuthAlgorithm(d.Get("auth_algorithm").(string)) + encryptionAlgorithm := resourceIPSecPolicyV2EncryptionAlgorithm(d.Get("encryption_algorithm").(string)) + pfs := resourceIPSecPolicyV2PFS(d.Get("pfs").(string)) + transformProtocol := resourceIPSecPolicyV2TransformProtocol(d.Get("transform_protocol").(string)) + lifetime := resourceIPSecPolicyV2LifetimeCreateOpts(d.Get("lifetime").(*schema.Set)) + + opts := IPSecPolicyCreateOpts{ + ipsecpolicies.CreateOpts{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + TenantID: d.Get("tenant_id").(string), + EncapsulationMode: encapsulationMode, + AuthAlgorithm: authAlgorithm, + EncryptionAlgorithm: encryptionAlgorithm, + PFS: pfs, + TransformProtocol: transformProtocol, + Lifetime: &lifetime, + }, + MapValueSpecs(d), + } + + log.Printf("[DEBUG] Create IPSec policy: %#v", opts) + + policy, err := ipsecpolicies.Create(networkingClient, opts).Extract() + if err != nil { + return err + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"PENDING_CREATE"}, + Target: []string{"ACTIVE"}, + Refresh: waitForIPSecPolicyCreation(networkingClient, policy.ID), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 0, + MinTimeout: 2 * time.Second, + } + _, err = stateConf.WaitForState() + + log.Printf("[DEBUG] IPSec policy created: %#v", policy) + + d.SetId(policy.ID) + + return resourceIPSecPolicyV2Read(d, meta) +} + +func resourceIPSecPolicyV2Read(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Retrieve information about IPSec policy: %s", d.Id()) + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + policy, err := ipsecpolicies.Get(networkingClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "IPSec policy") + } + + log.Printf("[DEBUG] Read OpenStack IPSec policy %s: %#v", d.Id(), policy) + + d.Set("name", policy.Name) + d.Set("description", policy.Description) + d.Set("tenant_id", policy.TenantID) + d.Set("encapsulation_mode", policy.EncapsulationMode) + d.Set("encryption_algorithm", policy.EncryptionAlgorithm) + d.Set("transform_protocol", policy.TransformProtocol) + d.Set("pfs", policy.PFS) + d.Set("auth_algorithm", policy.AuthAlgorithm) + d.Set("region", GetRegion(d, config)) + + // Set the lifetime + var lifetimeMap map[string]interface{} + lifetimeMap = make(map[string]interface{}) + lifetimeMap["units"] = policy.Lifetime.Units + lifetimeMap["value"] = policy.Lifetime.Value + var lifetime []map[string]interface{} + lifetime = append(lifetime, lifetimeMap) + if err := d.Set("lifetime", &lifetime); err != nil { + log.Printf("[WARN] unable to set IPSec policy lifetime") + } + + return nil +} + +func resourceIPSecPolicyV2Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + var hasChange bool + opts := ipsecpolicies.UpdateOpts{} + + if d.HasChange("name") { + name := d.Get("name").(string) + opts.Name = &name + hasChange = true + } + + if d.HasChange("description") { + description := d.Get("description").(string) + opts.Description = &description + hasChange = true + } + + if d.HasChange("auth_algorithm") { + opts.AuthAlgorithm = resourceIPSecPolicyV2AuthAlgorithm(d.Get("auth_algorithm").(string)) + hasChange = true + } + + if d.HasChange("encryption_algorithm") { + opts.EncryptionAlgorithm = resourceIPSecPolicyV2EncryptionAlgorithm(d.Get("encryption_algorithm").(string)) + hasChange = true + } + + if d.HasChange("transform_protocol") { + opts.TransformProtocol = resourceIPSecPolicyV2TransformProtocol(d.Get("transform_protocol").(string)) + hasChange = true + } + + if d.HasChange("pfs") { + opts.PFS = resourceIPSecPolicyV2PFS(d.Get("pfs").(string)) + hasChange = true + } + + if d.HasChange("encapsulation_mode") { + opts.EncapsulationMode = resourceIPSecPolicyV2EncapsulationMode(d.Get("encapsulation_mode").(string)) + hasChange = true + } + + if d.HasChange("lifetime") { + lifetime := resourceIPSecPolicyV2LifetimeUpdateOpts(d.Get("lifetime").(*schema.Set)) + opts.Lifetime = &lifetime + hasChange = true + } + + log.Printf("[DEBUG] Updating IPSec policy with id %s: %#v", d.Id(), opts) + + if hasChange { + _, err = ipsecpolicies.Update(networkingClient, d.Id(), opts).Extract() + if err != nil { + return err + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"PENDING_UPDATE"}, + Target: []string{"ACTIVE"}, + Refresh: waitForIPSecPolicyUpdate(networkingClient, d.Id()), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 0, + MinTimeout: 2 * time.Second, + } + if _, err = stateConf.WaitForState(); err != nil { + return err + } + } + return resourceIPSecPolicyV2Read(d, meta) +} + +func resourceIPSecPolicyV2Delete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Destroy IPSec policy: %s", d.Id()) + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"ACTIVE"}, + Target: []string{"DELETED"}, + Refresh: waitForIPSecPolicyDeletion(networkingClient, d.Id()), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 0, + MinTimeout: 2 * time.Second, + } + + if _, err = stateConf.WaitForState(); err != nil { + return err + } + + return nil +} + +func waitForIPSecPolicyDeletion(networkingClient *gophercloud.ServiceClient, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + err := ipsecpolicies.Delete(networkingClient, id).Err + if err == nil { + return "", "DELETED", nil + } + + if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok { + if errCode.Actual == 409 { + return nil, "ACTIVE", nil + } + } + + return nil, "ACTIVE", err + } +} + +func waitForIPSecPolicyCreation(networkingClient *gophercloud.ServiceClient, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + policy, err := ipsecpolicies.Get(networkingClient, id).Extract() + if err != nil { + return "", "PENDING_CREATE", nil + } + return policy, "ACTIVE", nil + } +} + +func waitForIPSecPolicyUpdate(networkingClient *gophercloud.ServiceClient, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + policy, err := ipsecpolicies.Get(networkingClient, id).Extract() + if err != nil { + return "", "PENDING_UPDATE", nil + } + return policy, "ACTIVE", nil + } +} + +func resourceIPSecPolicyV2TransformProtocol(trp string) ipsecpolicies.TransformProtocol { + var protocol ipsecpolicies.TransformProtocol + switch trp { + case "esp": + protocol = ipsecpolicies.TransformProtocolESP + case "ah": + protocol = ipsecpolicies.TransformProtocolAH + case "ah-esp": + protocol = ipsecpolicies.TransformProtocolAHESP + } + return protocol + +} +func resourceIPSecPolicyV2PFS(pfsString string) ipsecpolicies.PFS { + var pfs ipsecpolicies.PFS + switch pfsString { + case "group2": + pfs = ipsecpolicies.PFSGroup2 + case "group5": + pfs = ipsecpolicies.PFSGroup5 + case "group14": + pfs = ipsecpolicies.PFSGroup14 + } + return pfs + +} +func resourceIPSecPolicyV2EncryptionAlgorithm(encryptionAlgo string) ipsecpolicies.EncryptionAlgorithm { + var alg ipsecpolicies.EncryptionAlgorithm + switch encryptionAlgo { + case "3des": + alg = ipsecpolicies.EncryptionAlgorithm3DES + case "aes-128": + alg = ipsecpolicies.EncryptionAlgorithmAES128 + case "aes-256": + alg = ipsecpolicies.EncryptionAlgorithmAES256 + case "aes-192": + alg = ipsecpolicies.EncryptionAlgorithmAES192 + } + return alg +} +func resourceIPSecPolicyV2AuthAlgorithm(authAlgo string) ipsecpolicies.AuthAlgorithm { + var alg ipsecpolicies.AuthAlgorithm + switch authAlgo { + case "sha1": + alg = ipsecpolicies.AuthAlgorithmSHA1 + case "sha256": + alg = ipsecpolicies.AuthAlgorithmSHA256 + case "sha384": + alg = ipsecpolicies.AuthAlgorithmSHA384 + case "sha512": + alg = ipsecpolicies.AuthAlgorithmSHA512 + } + return alg +} +func resourceIPSecPolicyV2EncapsulationMode(encMode string) ipsecpolicies.EncapsulationMode { + var mode ipsecpolicies.EncapsulationMode + switch encMode { + case "tunnel": + mode = ipsecpolicies.EncapsulationModeTunnel + case "transport": + mode = ipsecpolicies.EncapsulationModeTransport + } + return mode +} + +func resourceIPSecPolicyV2LifetimeCreateOpts(d *schema.Set) ipsecpolicies.LifetimeCreateOpts { + lifetime := ipsecpolicies.LifetimeCreateOpts{} + + rawPairs := d.List() + for _, raw := range rawPairs { + rawMap := raw.(map[string]interface{}) + lifetime.Units = resourceIPSecPolicyV2Unit(rawMap["units"].(string)) + + value := rawMap["value"].(int) + lifetime.Value = value + } + return lifetime +} + +func resourceIPSecPolicyV2Unit(units string) ipsecpolicies.Unit { + var unit ipsecpolicies.Unit + switch units { + case "seconds": + unit = ipsecpolicies.UnitSeconds + case "kilobytes": + unit = ipsecpolicies.UnitKilobytes + } + return unit +} + +func resourceIPSecPolicyV2LifetimeUpdateOpts(d *schema.Set) ipsecpolicies.LifetimeUpdateOpts { + lifetimeUpdateOpts := ipsecpolicies.LifetimeUpdateOpts{} + + rawPairs := d.List() + for _, raw := range rawPairs { + rawMap := raw.(map[string]interface{}) + lifetimeUpdateOpts.Units = resourceIPSecPolicyV2Unit(rawMap["units"].(string)) + + value := rawMap["value"].(int) + lifetimeUpdateOpts.Value = value + } + return lifetimeUpdateOpts + +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_vpnaas_service_v2.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_vpnaas_service_v2.go new file mode 100644 index 000000000..1416491fb --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_vpnaas_service_v2.go @@ -0,0 +1,295 @@ +package openstack + +import ( + "fmt" + "log" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/services" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceServiceV2() *schema.Resource { + return &schema.Resource{ + Create: resourceServiceV2Create, + Read: resourceServiceV2Read, + Update: resourceServiceV2Update, + Delete: resourceServiceV2Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "name": { + Type: schema.TypeString, + Optional: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "admin_state_up": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "tenant_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + "subnet_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "router_id": { + Type: schema.TypeString, + Required: true, + Computed: false, + ForceNew: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "external_v6_ip": { + Type: schema.TypeString, + Computed: true, + }, + "external_v4_ip": { + Type: schema.TypeString, + Computed: true, + }, + "value_specs": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func resourceServiceV2Create(d *schema.ResourceData, meta interface{}) error { + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + var createOpts services.CreateOptsBuilder + + adminStateUp := d.Get("admin_state_up").(bool) + createOpts = ServiceCreateOpts{ + services.CreateOpts{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + AdminStateUp: &adminStateUp, + TenantID: d.Get("tenant_id").(string), + SubnetID: d.Get("subnet_id").(string), + RouterID: d.Get("router_id").(string), + }, + MapValueSpecs(d), + } + + log.Printf("[DEBUG] Create service: %#v", createOpts) + + service, err := services.Create(networkingClient, createOpts).Extract() + if err != nil { + return err + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"NOT_CREATED"}, + Target: []string{"PENDING_CREATE"}, + Refresh: waitForServiceCreation(networkingClient, service.ID), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 0, + MinTimeout: 2 * time.Second, + } + _, err = stateConf.WaitForState() + + if err != nil { + return err + } + + log.Printf("[DEBUG] Service created: %#v", service) + + d.SetId(service.ID) + + return resourceServiceV2Read(d, meta) +} + +func resourceServiceV2Read(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Retrieve information about service: %s", d.Id()) + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + service, err := services.Get(networkingClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "service") + } + + log.Printf("[DEBUG] Read OpenStack Service %s: %#v", d.Id(), service) + + d.Set("name", service.Name) + d.Set("description", service.Description) + d.Set("subnet_id", service.SubnetID) + d.Set("admin_state_up", service.AdminStateUp) + d.Set("tenant_id", service.TenantID) + d.Set("router_id", service.RouterID) + d.Set("status", service.Status) + d.Set("external_v6_ip", service.ExternalV6IP) + d.Set("external_v4_ip", service.ExternalV4IP) + d.Set("region", GetRegion(d, config)) + + return nil +} + +func resourceServiceV2Update(d *schema.ResourceData, meta interface{}) error { + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + opts := services.UpdateOpts{} + + var hasChange bool + + if d.HasChange("name") { + name := d.Get("name").(string) + opts.Name = &name + hasChange = true + } + + if d.HasChange("description") { + description := d.Get("description").(string) + opts.Description = &description + hasChange = true + } + + if d.HasChange("admin_state_up") { + adminStateUp := d.Get("admin_state_up").(bool) + opts.AdminStateUp = &adminStateUp + hasChange = true + } + + var updateOpts services.UpdateOptsBuilder + updateOpts = opts + + log.Printf("[DEBUG] Updating service with id %s: %#v", d.Id(), updateOpts) + + if hasChange { + service, err := services.Update(networkingClient, d.Id(), updateOpts).Extract() + if err != nil { + return err + } + stateConf := &resource.StateChangeConf{ + Pending: []string{"PENDING_UPDATE"}, + Target: []string{"UPDATED"}, + Refresh: waitForServiceUpdate(networkingClient, service.ID), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 0, + MinTimeout: 2 * time.Second, + } + _, err = stateConf.WaitForState() + + if err != nil { + return err + } + + log.Printf("[DEBUG] Updated service with id %s", d.Id()) + } + + return resourceServiceV2Read(d, meta) +} + +func resourceServiceV2Delete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Destroy service: %s", d.Id()) + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + err = services.Delete(networkingClient, d.Id()).Err + + if err != nil { + return err + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"DELETING"}, + Target: []string{"DELETED"}, + Refresh: waitForServiceDeletion(networkingClient, d.Id()), + Timeout: d.Timeout(schema.TimeoutDelete), + Delay: 0, + MinTimeout: 2 * time.Second, + } + + _, err = stateConf.WaitForState() + + return err +} + +func waitForServiceDeletion(networkingClient *gophercloud.ServiceClient, id string) resource.StateRefreshFunc { + + return func() (interface{}, string, error) { + serv, err := services.Get(networkingClient, id).Extract() + log.Printf("[DEBUG] Got service %s => %#v", id, serv) + + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + log.Printf("[DEBUG] Service %s is actually deleted", id) + return "", "DELETED", nil + } + return nil, "", fmt.Errorf("Unexpected error: %s", err) + } + + log.Printf("[DEBUG] Service %s deletion is pending", id) + return serv, "DELETING", nil + } +} + +func waitForServiceCreation(networkingClient *gophercloud.ServiceClient, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + service, err := services.Get(networkingClient, id).Extract() + if err != nil { + return "", "NOT_CREATED", nil + } + return service, "PENDING_CREATE", nil + } +} + +func waitForServiceUpdate(networkingClient *gophercloud.ServiceClient, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + service, err := services.Get(networkingClient, id).Extract() + if err != nil { + return "", "PENDING_UPDATE", nil + } + return service, "UPDATED", nil + } +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_vpnaas_site_connection.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_vpnaas_site_connection.go new file mode 100644 index 000000000..a8d4e26b7 --- /dev/null +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/resource_openstack_vpnaas_site_connection.go @@ -0,0 +1,503 @@ +package openstack + +import ( + "fmt" + "log" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/siteconnections" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceSiteConnectionV2() *schema.Resource { + return &schema.Resource{ + Create: resourceSiteConnectionV2Create, + Read: resourceSiteConnectionV2Read, + Update: resourceSiteConnectionV2Update, + Delete: resourceSiteConnectionV2Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "name": { + Type: schema.TypeString, + Optional: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "ikepolicy_id": { + Type: schema.TypeString, + ForceNew: true, + Required: true, + }, + "peer_id": { + Type: schema.TypeString, + Required: true, + }, + "peer_address": { + Type: schema.TypeString, + Required: true, + }, + "peer_ep_group_id": { + Type: schema.TypeString, + Optional: true, + }, + "local_id": { + Type: schema.TypeString, + Optional: true, + }, + "vpnservice_id": { + Type: schema.TypeString, + ForceNew: true, + Required: true, + }, + "local_ep_group_id": { + Type: schema.TypeString, + Optional: true, + }, + "ipsecpolicy_id": { + Type: schema.TypeString, + ForceNew: true, + Required: true, + }, + "admin_state_up": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "psk": { + Type: schema.TypeString, + Required: true, + }, + "initiator": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "mtu": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "tenant_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + "peer_cidrs": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "dpd": { + Type: schema.TypeSet, + Computed: true, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "action": { + Type: schema.TypeString, + Computed: true, + Optional: true, + }, + "timeout": { + Type: schema.TypeInt, + Computed: true, + Optional: true, + }, + "interval": { + Type: schema.TypeInt, + Computed: true, + Optional: true, + }, + }, + }, + }, + "value_specs": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func resourceSiteConnectionV2Create(d *schema.ResourceData, meta interface{}) error { + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + var createOpts siteconnections.CreateOptsBuilder + + dpd := resourceSiteConnectionV2DPDCreateOpts(d.Get("dpd").(*schema.Set)) + + v := d.Get("peer_cidrs").([]interface{}) + peerCidrs := make([]string, len(v)) + for i, v := range v { + peerCidrs[i] = v.(string) + } + + adminStateUp := d.Get("admin_state_up").(bool) + initiator := resourceSiteConnectionV2Initiator(d.Get("initiator").(string)) + + createOpts = SiteConnectionCreateOpts{ + siteconnections.CreateOpts{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + AdminStateUp: &adminStateUp, + Initiator: initiator, + IKEPolicyID: d.Get("ikepolicy_id").(string), + TenantID: d.Get("tenant_id").(string), + PeerID: d.Get("peer_id").(string), + PeerAddress: d.Get("peer_address").(string), + PeerEPGroupID: d.Get("peer_ep_group_id").(string), + LocalID: d.Get("local_id").(string), + VPNServiceID: d.Get("vpnservice_id").(string), + LocalEPGroupID: d.Get("local_ep_group_id").(string), + IPSecPolicyID: d.Get("ipsecpolicy_id").(string), + PSK: d.Get("psk").(string), + MTU: d.Get("mtu").(int), + PeerCIDRs: peerCidrs, + DPD: &dpd, + }, + MapValueSpecs(d), + } + + log.Printf("[DEBUG] Create site connection: %#v", createOpts) + + conn, err := siteconnections.Create(networkingClient, createOpts).Extract() + if err != nil { + return err + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"NOT_CREATED"}, + Target: []string{"PENDING_CREATE"}, + Refresh: waitForSiteConnectionCreation(networkingClient, conn.ID), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 0, + MinTimeout: 2 * time.Second, + } + _, err = stateConf.WaitForState() + + if err != nil { + return err + } + + log.Printf("[DEBUG] SiteConnection created: %#v", conn) + + d.SetId(conn.ID) + + return resourceSiteConnectionV2Read(d, meta) +} + +func resourceSiteConnectionV2Read(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Retrieve information about site connection: %s", d.Id()) + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + conn, err := siteconnections.Get(networkingClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "site_connection") + } + + log.Printf("[DEBUG] Read OpenStack SiteConnection %s: %#v", d.Id(), conn) + + d.Set("name", conn.Name) + d.Set("description", conn.Description) + d.Set("admin_state_up", conn.AdminStateUp) + d.Set("tenant_id", conn.TenantID) + d.Set("initiator", conn.Initiator) + d.Set("ikepolicy_id", conn.IKEPolicyID) + d.Set("peer_id", conn.PeerID) + d.Set("peer_address", conn.PeerAddress) + d.Set("local_id", conn.LocalID) + d.Set("peer_ep_group_id", conn.PeerEPGroupID) + d.Set("vpnservice_id", conn.VPNServiceID) + d.Set("local_ep_group_id", conn.LocalEPGroupID) + d.Set("ipsecpolicy_id", conn.IPSecPolicyID) + d.Set("psk", conn.PSK) + d.Set("mtu", conn.MTU) + d.Set("peer_cidrs", conn.PeerCIDRs) + + // Set the dpd + var dpdMap map[string]interface{} + dpdMap = make(map[string]interface{}) + dpdMap["action"] = conn.DPD.Action + dpdMap["interval"] = conn.DPD.Interval + dpdMap["timeout"] = conn.DPD.Timeout + + var dpd []map[string]interface{} + dpd = append(dpd, dpdMap) + if err := d.Set("dpd", &dpd); err != nil { + log.Printf("[WARN] unable to set Site connection DPD") + } + + return nil +} + +func resourceSiteConnectionV2Update(d *schema.ResourceData, meta interface{}) error { + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + opts := siteconnections.UpdateOpts{} + + var hasChange bool + + if d.HasChange("name") { + name := d.Get("name").(string) + opts.Name = &name + hasChange = true + } + + if d.HasChange("description") { + description := d.Get("description").(string) + opts.Description = &description + hasChange = true + } + + if d.HasChange("admin_state_up") { + adminStateUp := d.Get("admin_state_up").(bool) + opts.AdminStateUp = &adminStateUp + hasChange = true + } + + if d.HasChange("local_id") { + opts.LocalID = d.Get("local_id").(string) + hasChange = true + } + + if d.HasChange("peer_address") { + opts.PeerAddress = d.Get("peer_address").(string) + hasChange = true + } + + if d.HasChange("peer_id") { + opts.PeerID = d.Get("peer_id").(string) + hasChange = true + } + + if d.HasChange("local_ep_group_id") { + opts.LocalEPGroupID = d.Get("local_ep_group_id").(string) + hasChange = true + } + + if d.HasChange("peer_ep_group_id") { + opts.PeerEPGroupID = d.Get("peer_ep_group_id").(string) + hasChange = true + } + + if d.HasChange("psk") { + opts.PSK = d.Get("psk").(string) + hasChange = true + } + + if d.HasChange("mtu") { + opts.MTU = d.Get("mtu").(int) + hasChange = true + } + + if d.HasChange("initiator") { + initiator := resourceSiteConnectionV2Initiator(d.Get("initiator").(string)) + opts.Initiator = initiator + hasChange = true + } + + if d.HasChange("peer_cidrs") { + opts.PeerCIDRs = d.Get("peer_cidrs").([]string) + hasChange = true + } + + if d.HasChange("dpd") { + dpdUpdateOpts := resourceSiteConnectionV2DPDUpdateOpts(d.Get("dpd").(*schema.Set)) + opts.DPD = &dpdUpdateOpts + hasChange = true + } + + var updateOpts siteconnections.UpdateOptsBuilder + updateOpts = opts + + log.Printf("[DEBUG] Updating site connection with id %s: %#v", d.Id(), updateOpts) + + if hasChange { + conn, err := siteconnections.Update(networkingClient, d.Id(), updateOpts).Extract() + if err != nil { + return err + } + stateConf := &resource.StateChangeConf{ + Pending: []string{"PENDING_UPDATE"}, + Target: []string{"UPDATED"}, + Refresh: waitForSiteConnectionUpdate(networkingClient, conn.ID), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 0, + MinTimeout: 2 * time.Second, + } + _, err = stateConf.WaitForState() + + if err != nil { + return err + } + + log.Printf("[DEBUG] Updated connection with id %s", d.Id()) + } + + return resourceSiteConnectionV2Read(d, meta) +} + +func resourceSiteConnectionV2Delete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Destroy service: %s", d.Id()) + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + err = siteconnections.Delete(networkingClient, d.Id()).Err + + if err != nil { + return err + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"DELETING"}, + Target: []string{"DELETED"}, + Refresh: waitForSiteConnectionDeletion(networkingClient, d.Id()), + Timeout: d.Timeout(schema.TimeoutDelete), + Delay: 0, + MinTimeout: 2 * time.Second, + } + + _, err = stateConf.WaitForState() + + return err +} + +func waitForSiteConnectionDeletion(networkingClient *gophercloud.ServiceClient, id string) resource.StateRefreshFunc { + + return func() (interface{}, string, error) { + conn, err := siteconnections.Get(networkingClient, id).Extract() + log.Printf("[DEBUG] Got site connection %s => %#v", id, conn) + + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + log.Printf("[DEBUG] SiteConnection %s is actually deleted", id) + return "", "DELETED", nil + } + return nil, "", fmt.Errorf("Unexpected error: %s", err) + } + + log.Printf("[DEBUG] SiteConnection %s deletion is pending", id) + return conn, "DELETING", nil + } +} + +func waitForSiteConnectionCreation(networkingClient *gophercloud.ServiceClient, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + service, err := siteconnections.Get(networkingClient, id).Extract() + if err != nil { + return "", "NOT_CREATED", nil + } + return service, "PENDING_CREATE", nil + } +} + +func waitForSiteConnectionUpdate(networkingClient *gophercloud.ServiceClient, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + conn, err := siteconnections.Get(networkingClient, id).Extract() + if err != nil { + return "", "PENDING_UPDATE", nil + } + return conn, "UPDATED", nil + } +} + +func resourceSiteConnectionV2Initiator(initatorString string) siteconnections.Initiator { + var ini siteconnections.Initiator + switch initatorString { + case "bi-directional": + ini = siteconnections.InitiatorBiDirectional + case "response-only": + ini = siteconnections.InitiatorResponseOnly + } + return ini +} + +func resourceSiteConnectionV2DPDCreateOpts(d *schema.Set) siteconnections.DPDCreateOpts { + dpd := siteconnections.DPDCreateOpts{} + + rawPairs := d.List() + for _, raw := range rawPairs { + rawMap := raw.(map[string]interface{}) + dpd.Action = resourceSiteConnectionV2Action(rawMap["action"].(string)) + + timeout := rawMap["timeout"].(int) + dpd.Timeout = timeout + + interval := rawMap["interval"].(int) + dpd.Interval = interval + } + return dpd +} +func resourceSiteConnectionV2Action(actionString string) siteconnections.Action { + var act siteconnections.Action + switch actionString { + case "hold": + act = siteconnections.ActionHold + case "restart": + act = siteconnections.ActionRestart + case "disabled": + act = siteconnections.ActionDisabled + case "restart-by-peer": + act = siteconnections.ActionRestartByPeer + case "clear": + act = siteconnections.ActionClear + } + return act +} + +func resourceSiteConnectionV2DPDUpdateOpts(d *schema.Set) siteconnections.DPDUpdateOpts { + dpd := siteconnections.DPDUpdateOpts{} + + rawPairs := d.List() + for _, raw := range rawPairs { + rawMap := raw.(map[string]interface{}) + dpd.Action = resourceSiteConnectionV2Action(rawMap["action"].(string)) + + timeout := rawMap["timeout"].(int) + dpd.Timeout = timeout + + interval := rawMap["interval"].(int) + dpd.Interval = interval + } + return dpd +} diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/types.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/types.go index fd4fca56f..e1f71f818 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/types.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/types.go @@ -10,16 +10,18 @@ import ( "net/http" "strings" - "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs" - "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups" - "github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets" - "github.com/gophercloud/gophercloud/openstack/dns/v2/zones" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/routerinsertion" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/rules" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/subnetpools" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/endpointgroups" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ikepolicies" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ipsecpolicies" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/services" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/siteconnections" "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" "github.com/gophercloud/gophercloud/openstack/networking/v2/subnets" @@ -28,8 +30,9 @@ import ( // LogRoundTripper satisfies the http.RoundTripper interface and is used to // customize the default http client RoundTripper to allow for logging. type LogRoundTripper struct { - Rt http.RoundTripper - OsDebug bool + Rt http.RoundTripper + OsDebug bool + MaxRetries int } // RoundTrip performs a round-trip HTTP request and logs relevant information about it. @@ -47,7 +50,7 @@ func (lrt *LogRoundTripper) RoundTrip(request *http.Request) (*http.Response, er if lrt.OsDebug { log.Printf("[DEBUG] OpenStack Request URL: %s %s", request.Method, request.URL) - log.Printf("[DEBUG] Openstack Request Headers:\n%s", FormatHeaders(request.Header, "\n")) + log.Printf("[DEBUG] OpenStack Request Headers:\n%s", FormatHeaders(request.Header, "\n")) if request.Body != nil { request.Body, err = lrt.logRequest(request.Body, request.Header.Get("Content-Type")) @@ -58,13 +61,28 @@ func (lrt *LogRoundTripper) RoundTrip(request *http.Request) (*http.Response, er } response, err := lrt.Rt.RoundTrip(request) - if response == nil { - return nil, err + + // If the first request didn't return a response, retry up to `max_retries`. + retry := 1 + for response == nil { + if retry > lrt.MaxRetries { + if lrt.OsDebug { + log.Printf("[DEBUG] OpenStack connection error, retries exhausted. Aborting") + } + err = fmt.Errorf("OpenStack connection error, retries exhausted. Aborting. Last error was: %s", err) + return nil, err + } + + if lrt.OsDebug { + log.Printf("[DEBUG] OpenStack connection error, retry number %d: %s", retry, err) + } + response, err = lrt.Rt.RoundTrip(request) + retry += 1 } if lrt.OsDebug { - log.Printf("[DEBUG] Openstack Response Code: %d", response.StatusCode) - log.Printf("[DEBUG] Openstack Response Headers:\n%s", FormatHeaders(response.Header, "\n")) + log.Printf("[DEBUG] OpenStack Response Code: %d", response.StatusCode) + log.Printf("[DEBUG] OpenStack Response Headers:\n%s", FormatHeaders(response.Header, "\n")) response.Body, err = lrt.logResponse(response.Body, response.Header.Get("Content-Type")) } @@ -87,8 +105,6 @@ func (lrt *LogRoundTripper) logRequest(original io.ReadCloser, contentType strin if strings.HasPrefix(contentType, "application/json") { debugInfo := lrt.formatJSON(bs.Bytes()) log.Printf("[DEBUG] OpenStack Request Body: %s", debugInfo) - } else { - log.Printf("[DEBUG] OpenStack Request Body: %s", bs.String()) } return ioutil.NopCloser(strings.NewReader(bs.String())), nil @@ -134,6 +150,12 @@ func (lrt *LogRoundTripper) formatJSON(raw []byte) string { v["password"] = "***" } } + if v, ok := v["application_credential"].(map[string]interface{}); ok { + v["secret"] = "***" + } + if v, ok := v["token"].(map[string]interface{}); ok { + v["id"] = "***" + } } } @@ -161,19 +183,14 @@ type Firewall struct { // FirewallCreateOpts represents the attributes used when creating a new firewall. type FirewallCreateOpts struct { - firewalls.CreateOptsBuilder + firewalls.CreateOpts ValueSpecs map[string]string `json:"value_specs,omitempty"` } // ToFirewallCreateMap casts a CreateOptsExt struct to a map. // It overrides firewalls.ToFirewallCreateMap to add the ValueSpecs field. func (opts FirewallCreateOpts) ToFirewallCreateMap() (map[string]interface{}, error) { - body, err := opts.CreateOptsBuilder.ToFirewallCreateMap() - if err != nil { - return nil, err - } - - return AddValueSpecs(body), nil + return BuildRequest(opts, "firewall") } //FirewallUpdateOpts @@ -197,18 +214,6 @@ func (opts FloatingIPCreateOpts) ToFloatingIPCreateMap() (map[string]interface{} return BuildRequest(opts, "floatingip") } -// KeyPairCreateOpts represents the attributes used when creating a new keypair. -type KeyPairCreateOpts struct { - keypairs.CreateOpts - ValueSpecs map[string]string `json:"value_specs,omitempty"` -} - -// ToKeyPairCreateMap casts a CreateOpts struct to a map. -// It overrides keypairs.ToKeyPairCreateMap to add the ValueSpecs field. -func (opts KeyPairCreateOpts) ToKeyPairCreateMap() (map[string]interface{}, error) { - return BuildRequest(opts, "keypair") -} - // NetworkCreateOpts represents the attributes used when creating a new network. type NetworkCreateOpts struct { networks.CreateOpts @@ -227,6 +232,18 @@ type PolicyCreateOpts struct { ValueSpecs map[string]string `json:"value_specs,omitempty"` } +// IKEPolicyCreateOpts represents the attributes used when creating a new IKE policy. +type IKEPolicyCreateOpts struct { + ikepolicies.CreateOpts + ValueSpecs map[string]string `json:"value_specs,omitempty"` +} + +// IKEPolicyLifetimeCreateOpts represents the attributes used when creating a new lifetime for an IKE policy. +type IKEPolicyLifetimeCreateOpts struct { + ikepolicies.LifetimeCreateOpts + ValueSpecs map[string]string `json:"value_specs,omitempty"` +} + // ToPolicyCreateMap casts a CreateOpts struct to a map. // It overrides policies.ToFirewallPolicyCreateMap to add the ValueSpecs field. func (opts PolicyCreateOpts) ToFirewallPolicyCreateMap() (map[string]interface{}, error) { @@ -245,27 +262,6 @@ func (opts PortCreateOpts) ToPortCreateMap() (map[string]interface{}, error) { return BuildRequest(opts, "port") } -// RecordSetCreateOpts represents the attributes used when creating a new DNS record set. -type RecordSetCreateOpts struct { - recordsets.CreateOpts - ValueSpecs map[string]string `json:"value_specs,omitempty"` -} - -// ToRecordSetCreateMap casts a CreateOpts struct to a map. -// It overrides recordsets.ToRecordSetCreateMap to add the ValueSpecs field. -func (opts RecordSetCreateOpts) ToRecordSetCreateMap() (map[string]interface{}, error) { - b, err := BuildRequest(opts, "") - if err != nil { - return nil, err - } - - if m, ok := b[""].(map[string]interface{}); ok { - return m, nil - } - - return nil, fmt.Errorf("Expected map but got %T", b[""]) -} - // RouterCreateOpts represents the attributes used when creating a new router. type RouterCreateOpts struct { routers.CreateOpts @@ -299,18 +295,6 @@ func (opts RuleCreateOpts) ToRuleCreateMap() (map[string]interface{}, error) { return b, nil } -// ServerGroupCreateOpts represents the attributes used when creating a new router. -type ServerGroupCreateOpts struct { - servergroups.CreateOpts - ValueSpecs map[string]string `json:"value_specs,omitempty"` -} - -// ToServerGroupCreateMap casts a CreateOpts struct to a map. -// It overrides routers.ToServerGroupCreateMap to add the ValueSpecs field. -func (opts ServerGroupCreateOpts) ToServerGroupCreateMap() (map[string]interface{}, error) { - return BuildRequest(opts, "server_group") -} - // SubnetCreateOpts represents the attributes used when creating a new subnet. type SubnetCreateOpts struct { subnets.CreateOpts @@ -332,27 +316,32 @@ func (opts SubnetCreateOpts) ToSubnetCreateMap() (map[string]interface{}, error) return b, nil } -// ZoneCreateOpts represents the attributes used when creating a new DNS zone. -type ZoneCreateOpts struct { - zones.CreateOpts +// SubnetPoolCreateOpts represents the attributes used when creating a new subnet pool. +type SubnetPoolCreateOpts struct { + subnetpools.CreateOpts ValueSpecs map[string]string `json:"value_specs,omitempty"` } -// ToZoneCreateMap casts a CreateOpts struct to a map. -// It overrides zones.ToZoneCreateMap to add the ValueSpecs field. -func (opts ZoneCreateOpts) ToZoneCreateMap() (map[string]interface{}, error) { - b, err := BuildRequest(opts, "") - if err != nil { - return nil, err - } - - if m, ok := b[""].(map[string]interface{}); ok { - if opts.TTL > 0 { - m["ttl"] = opts.TTL - } - - return m, nil - } - - return nil, fmt.Errorf("Expected map but got %T", b[""]) +// IPSecPolicyCreateOpts represents the attributes used when creating a new IPSec policy. +type IPSecPolicyCreateOpts struct { + ipsecpolicies.CreateOpts + ValueSpecs map[string]string `json:"value_specs,omitempty"` +} + +// ServiceCreateOpts represents the attributes used when creating a new VPN service. +type ServiceCreateOpts struct { + services.CreateOpts + ValueSpecs map[string]string `json:"value_specs,omitempty"` +} + +// EndpointGroupCreateOpts represents the attributes used when creating a new endpoint group. +type EndpointGroupCreateOpts struct { + endpointgroups.CreateOpts + ValueSpecs map[string]string `json:"value_specs,omitempty"` +} + +// SiteConnectionCreateOpts represents the attributes used when creating a new IPSec site connection. +type SiteConnectionCreateOpts struct { + siteconnections.CreateOpts + ValueSpecs map[string]string `json:"value_specs,omitempty"` } diff --git a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/util.go b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/util.go index 397f03378..484339978 100644 --- a/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/util.go +++ b/vendor/github.com/terraform-providers/terraform-provider-openstack/openstack/util.go @@ -3,12 +3,18 @@ package openstack import ( "fmt" "net/http" + "reflect" "sort" + "strconv" "strings" + "time" "github.com/Unknwon/com" "github.com/gophercloud/gophercloud" + "github.com/hashicorp/terraform/flatmap" + "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" ) // BuildRequest takes an opts struct and builds a request body for @@ -32,7 +38,7 @@ func CheckDeleted(d *schema.ResourceData, err error, msg string) error { return nil } - return fmt.Errorf("%s: %s", msg, err) + return fmt.Errorf("%s %s: %s", msg, d.Id(), err) } // GetRegion returns the region that was specified in the resource. If a @@ -95,3 +101,177 @@ func FormatHeaders(headers http.Header, seperator string) string { return strings.Join(redactedHeaders, seperator) } + +func checkForRetryableError(err error) *resource.RetryError { + switch errCode := err.(type) { + case gophercloud.ErrDefault500: + return resource.RetryableError(err) + case gophercloud.ErrUnexpectedResponseCode: + switch errCode.Actual { + case 409, 503: + return resource.RetryableError(err) + default: + return resource.NonRetryableError(err) + } + default: + return resource.NonRetryableError(err) + } +} + +func suppressEquivilentTimeDiffs(k, old, new string, d *schema.ResourceData) bool { + oldTime, err := time.Parse(time.RFC3339, old) + if err != nil { + return false + } + + newTime, err := time.Parse(time.RFC3339, new) + if err != nil { + return false + } + + return oldTime.Equal(newTime) +} + +func validateSubnetV2IPv6Mode(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value != "slaac" && value != "dhcpv6-stateful" && value != "dhcpv6-stateless" { + err := fmt.Errorf("%s must be one of slaac, dhcpv6-stateful or dhcpv6-stateless", k) + errors = append(errors, err) + } + return +} + +func resourceNetworkingAvailabilityZoneHintsV2(d *schema.ResourceData) []string { + rawAZH := d.Get("availability_zone_hints").([]interface{}) + azh := make([]string, len(rawAZH)) + for i, raw := range rawAZH { + azh[i] = raw.(string) + } + return azh +} + +func expandVendorOptions(vendOptsRaw []interface{}) map[string]interface{} { + vendorOptions := make(map[string]interface{}) + + for _, option := range vendOptsRaw { + for optKey, optValue := range option.(map[string]interface{}) { + vendorOptions[optKey] = optValue + } + + } + + return vendorOptions +} + +func networkV2ReadAttributesTags(d *schema.ResourceData, tags []string) { + d.Set("all_tags", tags) + + allTags := d.Get("all_tags").(*schema.Set) + desiredTags := d.Get("tags").(*schema.Set) + actualTags := allTags.Intersection(desiredTags) + if !actualTags.Equal(desiredTags) { + d.Set("tags", expandToStringSlice(actualTags.List())) + } +} + +func networkV2UpdateAttributesTags(d *schema.ResourceData) (tags []string) { + allTags := d.Get("all_tags").(*schema.Set) + oldTagsRaw, newTagsRaw := d.GetChange("tags") + oldTags, newTags := oldTagsRaw.(*schema.Set), newTagsRaw.(*schema.Set) + + allWithoutOld := allTags.Difference(oldTags) + + return expandToStringSlice(allWithoutOld.Union(newTags).List()) +} + +func networkV2AttributesTags(d *schema.ResourceData) (tags []string) { + rawTags := d.Get("tags").(*schema.Set).List() + tags = make([]string, len(rawTags)) + + for i, raw := range rawTags { + tags[i] = raw.(string) + } + return +} + +func testAccCheckNetworkingV2Tags(name string, tags []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + + if !ok { + return fmt.Errorf("resource not found: %s", name) + } + + var tagLen int64 + var err error + if count, ok := rs.Primary.Attributes["tags.#"]; !ok { + return fmt.Errorf("resource tags not found: %s.tags", name) + } else { + tagLen, err = strconv.ParseInt(count, 10, 64) + if err != nil { + return fmt.Errorf("Failed to parse tag amount: %s", err) + } + } + + rtags := make([]string, tagLen) + itags := flatmap.Expand(rs.Primary.Attributes, "tags").([]interface{}) + for i, val := range itags { + rtags[i] = val.(string) + } + sort.Strings(rtags) + sort.Strings(tags) + if !reflect.DeepEqual(rtags, tags) { + return fmt.Errorf( + "%s.tags: expected: %#v, got %#v", name, tags, rtags) + } + return nil + } +} + +func expandToMapStringString(v map[string]interface{}) map[string]string { + m := make(map[string]string) + for key, val := range v { + if strVal, ok := val.(string); ok { + m[key] = strVal + } + } + + return m +} + +func expandToStringSlice(v []interface{}) []string { + s := make([]string, len(v)) + for i, val := range v { + if strVal, ok := val.(string); ok { + s[i] = strVal + } + } + + return s +} + +// strSliceContains checks if a given string is contained in a slice +// When anybody asks why Go needs generics, here you go. +func strSliceContains(haystack []string, needle string) bool { + for _, s := range haystack { + if s == needle { + return true + } + } + return false +} + +func sliceUnion(a, b []string) []string { + var res []string + for _, i := range a { + if !strSliceContains(res, i) { + res = append(res, i) + } + } + for _, k := range b { + if !strSliceContains(res, k) { + res = append(res, k) + } + } + return res +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 37bd969dc..8222736fc 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -250,7 +250,7 @@ github.com/google/go-cmp/cmp/internal/value github.com/google/go-querystring/query # github.com/googleapis/gax-go v0.0.0-20161107002406-da06d194a00e github.com/googleapis/gax-go -# github.com/gophercloud/gophercloud v0.0.0-20170524130959-3027adb1ce72 +# github.com/gophercloud/gophercloud v0.0.0-20190208042652-bc37892e1968 github.com/gophercloud/gophercloud github.com/gophercloud/gophercloud/openstack github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers @@ -262,7 +262,11 @@ github.com/gophercloud/gophercloud/pagination github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes +github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes +github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots +github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes +github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips @@ -276,20 +280,37 @@ github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach github.com/gophercloud/gophercloud/openstack/compute/v2/flavors github.com/gophercloud/gophercloud/openstack/compute/v2/images github.com/gophercloud/gophercloud/openstack/compute/v2/servers +github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters +github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates +github.com/gophercloud/gophercloud/openstack/db/v1/configurations +github.com/gophercloud/gophercloud/openstack/db/v1/databases +github.com/gophercloud/gophercloud/openstack/db/v1/instances +github.com/gophercloud/gophercloud/openstack/db/v1/users github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets github.com/gophercloud/gophercloud/openstack/dns/v2/zones +github.com/gophercloud/gophercloud/openstack/identity/v3/endpoints +github.com/gophercloud/gophercloud/openstack/identity/v3/groups +github.com/gophercloud/gophercloud/openstack/identity/v3/projects +github.com/gophercloud/gophercloud/openstack/identity/v3/roles +github.com/gophercloud/gophercloud/openstack/identity/v3/services +github.com/gophercloud/gophercloud/openstack/identity/v3/users github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata github.com/gophercloud/gophercloud/openstack/imageservice/v2/images +github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags +github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external +github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/extradhcpopts github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/routerinsertion github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/rules +github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/addressscopes github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/members github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/monitors github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/vips +github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors @@ -297,11 +318,29 @@ github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/p github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/provider github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules +github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/subnetpools +github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks +github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vlantransparent +github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/endpointgroups +github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ikepolicies +github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/ipsecpolicies +github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/services +github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/vpnaas/siteconnections github.com/gophercloud/gophercloud/openstack/networking/v2/networks github.com/gophercloud/gophercloud/openstack/networking/v2/ports github.com/gophercloud/gophercloud/openstack/networking/v2/subnets github.com/gophercloud/gophercloud/openstack/objectstorage/v1/swauth +github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/errors +github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages +github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/securityservices +github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharenetworks +github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares +github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots github.com/gophercloud/gophercloud/openstack/identity/v2/tenants +github.com/gophercloud/gophercloud/openstack/db/v1/datastores +github.com/gophercloud/gophercloud/internal +# github.com/gophercloud/utils v0.0.0-20190128072930-fbb6ab446f01 +github.com/gophercloud/utils/openstack/clientconfig # github.com/hashicorp/consul v0.0.0-20171026175957-610f3c86a089 github.com/hashicorp/consul/api github.com/hashicorp/consul/testutil @@ -466,7 +505,7 @@ github.com/spf13/afero/mem github.com/svanharmelen/jsonapi # github.com/terraform-providers/terraform-provider-aws v1.52.0 github.com/terraform-providers/terraform-provider-aws/aws -# 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-openstack/openstack # github.com/ugorji/go v0.0.0-20180813092308-00b869d2f4a5 github.com/ugorji/go/codec