mirror of
https://github.com/prometheus/prometheus.git
synced 2024-11-09 23:24:05 -08:00
Merge branch 'master' into dev-2.0
This commit is contained in:
commit
669075c6b9
147
CHANGELOG.md
147
CHANGELOG.md
|
@ -1,18 +1,18 @@
|
|||
## v1.6.3 / 2017-05-18
|
||||
## 1.6.3 / 2017-05-18
|
||||
|
||||
* [BUGFIX] Fix disappearing Alertmanger targets in Alertmanager discovery.
|
||||
* [BUGFIX] Fix panic with remote_write on ARMv7.
|
||||
* [BUGFIX] Fix stacked graphs to adapt min/max values.
|
||||
|
||||
## v1.6.2 / 2017-05-11
|
||||
## 1.6.2 / 2017-05-11
|
||||
|
||||
* [BUGFIX] Fix potential memory leak in Kubernetes service discovery
|
||||
|
||||
## v1.6.1 / 2017-04-19
|
||||
## 1.6.1 / 2017-04-19
|
||||
|
||||
* [BUGFIX] Don't panic if storage has no FPs even after initial wait
|
||||
|
||||
## v1.6.0 / 2017-04-14
|
||||
## 1.6.0 / 2017-04-14
|
||||
|
||||
* [CHANGE] Replaced the remote write implementations for various backends by a
|
||||
generic write interface with example adapter implementation for various
|
||||
|
@ -77,11 +77,11 @@
|
|||
* [BUGFIX] Fix deadlock in Zookeeper SD.
|
||||
* [BUGFIX] Fix fuzzy search problems in the web-UI auto-completion.
|
||||
|
||||
## v1.5.3 / 2017-05-11
|
||||
## 1.5.3 / 2017-05-11
|
||||
|
||||
* [BUGFIX] Fix potential memory leak in Kubernetes service discovery
|
||||
|
||||
## v1.5.2 / 2017-02-10
|
||||
## 1.5.2 / 2017-02-10
|
||||
|
||||
* [BUGFIX] Fix series corruption in a special case of series maintenance where
|
||||
the minimum series-file-shrink-ratio kicks in.
|
||||
|
@ -89,14 +89,14 @@
|
|||
scheduled to be quarantined.
|
||||
* [ENHANCEMENT] Binaries built with Go1.7.5.
|
||||
|
||||
## v1.5.1 / 2017-02-07
|
||||
## 1.5.1 / 2017-02-07
|
||||
|
||||
* [BUGFIX] Don't lose fully persisted memory series during checkpointing.
|
||||
* [BUGFIX] Fix intermittently failing relabeling.
|
||||
* [BUGFIX] Make `-storage.local.series-file-shrink-ratio` work.
|
||||
* [BUGFIX] Remove race condition from TestLoop.
|
||||
|
||||
## v1.5.0 / 2017-01-23
|
||||
## 1.5.0 / 2017-01-23
|
||||
|
||||
* [CHANGE] Use lexicographic order to sort alerts by name.
|
||||
* [FEATURE] Add Joyent Triton discovery.
|
||||
|
@ -113,11 +113,11 @@
|
|||
* [BUGFIX] Ignore dotfiles in data directory.
|
||||
* [BUGFIX] Abort on intermediate federation errors.
|
||||
|
||||
## v1.4.1 / 2016-11-28
|
||||
## 1.4.1 / 2016-11-28
|
||||
|
||||
* [BUGFIX] Fix Consul service discovery
|
||||
|
||||
## v1.4.0 / 2016-11-25
|
||||
## 1.4.0 / 2016-11-25
|
||||
|
||||
* [FEATURE] Allow configuring Alertmanagers via service discovery
|
||||
* [FEATURE] Display used Alertmanagers on runtime page in the UI
|
||||
|
@ -130,16 +130,27 @@
|
|||
* [BUGFIX] Use proper float64 modulo in PromQL `%` binary operations
|
||||
* [BUGFIX] Fix crash bug in Kubernetes service discovery
|
||||
|
||||
## v1.3.1 / 2016-11-04
|
||||
## 1.3.1 / 2016-11-04
|
||||
|
||||
This bug-fix release pulls in the fixes from the v1.2.3 release.
|
||||
This bug-fix release pulls in the fixes from the 1.2.3 release.
|
||||
|
||||
* [BUGFIX] Correctly handle empty Regex entry in relabel config.
|
||||
* [BUGFIX] MOD (`%`) operator doesn't panic with small floating point numbers.
|
||||
* [BUGFIX] Updated miekg/dns vendoring to pick up upstream bug fixes.
|
||||
* [ENHANCEMENT] Improved DNS error reporting.
|
||||
|
||||
## v1.3.0 / 2016-11-01
|
||||
## 1.2.3 / 2016-11-04
|
||||
|
||||
Note that this release is chronologically after 1.3.0.
|
||||
|
||||
* [BUGFIX] Correctly handle end time before start time in range queries.
|
||||
* [BUGFIX] Error on negative `-storage.staleness-delta`
|
||||
* [BUGFIX] Correctly handle empty Regex entry in relabel config.
|
||||
* [BUGFIX] MOD (`%`) operator doesn't panic with small floating point numbers.
|
||||
* [BUGFIX] Updated miekg/dns vendoring to pick up upstream bug fixes.
|
||||
* [ENHANCEMENT] Improved DNS error reporting.
|
||||
|
||||
## 1.3.0 / 2016-11-01
|
||||
|
||||
This is a breaking change to the Kubernetes service discovery.
|
||||
|
||||
|
@ -153,16 +164,7 @@ This is a breaking change to the Kubernetes service discovery.
|
|||
* [BUGFIX] Validate query end time is not before start time.
|
||||
* [BUGFIX] Error on negative `-storage.staleness-delta`
|
||||
|
||||
## v1.2.3 / 2016-11-04
|
||||
|
||||
* [BUGFIX] Correctly handle end time before start time in range queries.
|
||||
* [BUGFIX] Error on negative `-storage.staleness-delta`
|
||||
* [BUGFIX] Correctly handle empty Regex entry in relabel config.
|
||||
* [BUGFIX] MOD (`%`) operator doesn't panic with small floating point numbers.
|
||||
* [BUGFIX] Updated miekg/dns vendoring to pick up upstream bug fixes.
|
||||
* [ENHANCEMENT] Improved DNS error reporting.
|
||||
|
||||
## v1.2.2 / 2016-10-30
|
||||
## 1.2.2 / 2016-10-30
|
||||
|
||||
* [BUGFIX] Correctly handle on() in alerts.
|
||||
* [BUGFIX] UI: Deal properly with aborted requests.
|
||||
|
@ -171,13 +173,13 @@ This is a breaking change to the Kubernetes service discovery.
|
|||
* [BUGFIX] Remote storage: Re-add accidentally removed timeout flag.
|
||||
* [BUGFIX] Updated a number of vendored packages to pick up upstream bug fixes.
|
||||
|
||||
## v1.2.1 / 2016-10-10
|
||||
## 1.2.1 / 2016-10-10
|
||||
|
||||
* [BUGFIX] Count chunk evictions properly so that the server doesn't
|
||||
assume it runs out of memory and subsequencly throttles ingestion.
|
||||
* [BUGFIX] Use Go1.7.1 for prebuilt binaries to fix issues on MacOS Sierra.
|
||||
|
||||
## v1.2.0 / 2016-10-07
|
||||
## 1.2.0 / 2016-10-07
|
||||
|
||||
* [FEATURE] Cleaner encoding of query parameters in `/graph` URLs.
|
||||
* [FEATURE] PromQL: Add `minute()` function.
|
||||
|
@ -200,22 +202,22 @@ This is a breaking change to the Kubernetes service discovery.
|
|||
* [FEATURE] **Experimental** remote write path: Add HTTP basic auth and TLS.
|
||||
* [FEATURE] **Experimental** remote write path: Support for relabelling.
|
||||
|
||||
## v1.1.3 / 2016-09-16
|
||||
## 1.1.3 / 2016-09-16
|
||||
|
||||
* [ENHANCEMENT] Use golang-builder base image for tests in CircleCI.
|
||||
* [ENHANCEMENT] Added unit tests for federation.
|
||||
* [BUGFIX] Correctly de-dup metric families in federation output.
|
||||
|
||||
## v1.1.2 / 2016-09-08
|
||||
## 1.1.2 / 2016-09-08
|
||||
|
||||
* [BUGFIX] Allow label names that coincide with keywords.
|
||||
|
||||
## v1.1.1 / 2016-09-07
|
||||
## 1.1.1 / 2016-09-07
|
||||
|
||||
* [BUGFIX] Fix IPv6 escaping in service discovery integrations
|
||||
* [BUGFIX] Fix default scrape port assignment for IPv6
|
||||
|
||||
## v1.1.0 / 2016-09-03
|
||||
## 1.1.0 / 2016-09-03
|
||||
|
||||
* [FEATURE] Add `quantile` and `quantile_over_time`.
|
||||
* [FEATURE] Add `stddev_over_time` and `stdvar_over_time`.
|
||||
|
@ -245,17 +247,17 @@ This is a breaking change to the Kubernetes service discovery.
|
|||
* [BUGFIX] Fix rule HTML escaping issues.
|
||||
* [BUGFIX] Remove internal labels from alerts sent to AM.
|
||||
|
||||
## v1.0.2 / 2016-08-24
|
||||
## 1.0.2 / 2016-08-24
|
||||
|
||||
* [BUGFIX] Clean up old targets after config reload.
|
||||
|
||||
## v1.0.1 / 2016-07-21
|
||||
## 1.0.1 / 2016-07-21
|
||||
|
||||
* [BUGFIX] Exit with error on non-flag command-line arguments.
|
||||
* [BUGFIX] Update example console templates to new HTTP API.
|
||||
* [BUGFIX] Re-add logging flags.
|
||||
|
||||
## v1.0.0 / 2016-07-18
|
||||
## 1.0.0 / 2016-07-18
|
||||
|
||||
* [CHANGE] Remove deprecated query language keywords
|
||||
* [CHANGE] Change Kubernetes SD to require specifying Kubernetes role
|
||||
|
@ -274,7 +276,7 @@ This is a breaking change to the Kubernetes service discovery.
|
|||
* [BUGFIX] Fix edge case handling in crash recovery
|
||||
* [BUGFIX] Hide testing package flags from help output
|
||||
|
||||
## v0.20.0 / 2016-06-15
|
||||
## 0.20.0 / 2016-06-15
|
||||
|
||||
This release contains multiple breaking changes to the configuration schema.
|
||||
|
||||
|
@ -292,23 +294,23 @@ This release contains multiple breaking changes to the configuration schema.
|
|||
* [CHANGE] Rename `names` to `files` in file SD configuration
|
||||
* [CHANGE] Remove kubelet port config option in Kubernetes SD configuration
|
||||
|
||||
## v0.19.3 / 2016-06-14
|
||||
## 0.19.3 / 2016-06-14
|
||||
|
||||
* [BUGFIX] Handle Marathon apps with zero ports
|
||||
* [BUGFIX] Fix startup panic in retrieval layer
|
||||
|
||||
## v0.19.2 / 2016-05-29
|
||||
## 0.19.2 / 2016-05-29
|
||||
|
||||
* [BUGFIX] Correctly handle `GROUP_LEFT` and `GROUP_RIGHT` without labels in
|
||||
string representation of expressions and in rules.
|
||||
* [BUGFIX] Use `-web.external-url` for new status endpoints.
|
||||
|
||||
## v0.19.1 / 2016-05-25
|
||||
## 0.19.1 / 2016-05-25
|
||||
|
||||
* [BUGFIX] Handle service discovery panic affecting Kubernetes SD
|
||||
* [BUGFIX] Fix web UI display issue in some browsers
|
||||
|
||||
## v0.19.0 / 2016-05-24
|
||||
## 0.19.0 / 2016-05-24
|
||||
|
||||
This version contains a breaking change to the query language. Please read
|
||||
the documentation on the grouping behavior of vector matching:
|
||||
|
@ -323,7 +325,7 @@ https://prometheus.io/docs/querying/operators/#vector-matching
|
|||
* [ENHANCEMENT] Partition status page into individual pages
|
||||
* [BUGFIX] Fix issue of hanging target scrapes
|
||||
|
||||
## v0.18.0 / 2016-04-18
|
||||
## 0.18.0 / 2016-04-18
|
||||
|
||||
* [BUGFIX] Fix operator precedence in PromQL
|
||||
* [BUGFIX] Never drop still open head chunk
|
||||
|
@ -343,16 +345,16 @@ https://prometheus.io/docs/querying/operators/#vector-matching
|
|||
* [ENHANCEMENT] Instrument retrieval layer
|
||||
* [ENHANCEMENT] Add Go version to `prometheus_build_info` metric
|
||||
|
||||
## v0.17.0 / 2016-03-02
|
||||
## 0.17.0 / 2016-03-02
|
||||
|
||||
This version no longer works with Alertmanager v0.0.4 and earlier!
|
||||
This version no longer works with Alertmanager 0.0.4 and earlier!
|
||||
The alerting rule syntax has changed as well but the old syntax is supported
|
||||
up until version v0.18.
|
||||
up until version 0.18.
|
||||
|
||||
All regular expressions in PromQL are anchored now, matching the behavior of
|
||||
regular expressions in config files.
|
||||
|
||||
* [CHANGE] Integrate with Alertmanager v0.1.0 and higher
|
||||
* [CHANGE] Integrate with Alertmanager 0.1.0 and higher
|
||||
* [CHANGE] Degraded storage mode renamed to rushed mode
|
||||
* [CHANGE] New alerting rule syntax
|
||||
* [CHANGE] Add label validation on ingestion
|
||||
|
@ -373,7 +375,7 @@ regular expressions in config files.
|
|||
* [BUGFIX] Properly handle creation of target with bad TLS config
|
||||
* [BUGFIX] Fix of checkpoint timing issue
|
||||
|
||||
## v0.16.2 / 2016-01-18
|
||||
## 0.16.2 / 2016-01-18
|
||||
|
||||
* [FEATURE] Multiple authentication options for EC2 discovery added
|
||||
* [FEATURE] Several meta labels for EC2 discovery added
|
||||
|
@ -404,14 +406,14 @@ regular expressions in config files.
|
|||
Some changes to the Kubernetes service discovery were integration since
|
||||
it was released as a beta feature.
|
||||
|
||||
## v0.16.1 / 2015-10-16
|
||||
## 0.16.1 / 2015-10-16
|
||||
|
||||
* [FEATURE] Add `irate()` function.
|
||||
* [ENHANCEMENT] Improved auto-completion in expression browser.
|
||||
* [CHANGE] Kubernetes SD moves node label to instance label.
|
||||
* [BUGFIX] Escape regexes in console templates.
|
||||
|
||||
## v0.16.0 / 2015-10-09
|
||||
## 0.16.0 / 2015-10-09
|
||||
|
||||
BREAKING CHANGES:
|
||||
|
||||
|
@ -574,14 +576,13 @@ All changes:
|
|||
the same series upon append.
|
||||
* [CLEANUP] Resolve relative paths during configuration loading.
|
||||
|
||||
## v0.15.1 / 2015-07-27
|
||||
|
||||
## 0.15.1 / 2015-07-27
|
||||
* [BUGFIX] Fix vector matching behavior when there is a mix of equality and
|
||||
non-equality matchers in a vector selector and one matcher matches no series.
|
||||
* [ENHANCEMENT] Allow overriding `GOARCH` and `GOOS` in Makefile.INCLUDE.
|
||||
* [ENHANCEMENT] Update vendored dependencies.
|
||||
|
||||
## v0.15.0 / 2015-07-21
|
||||
## 0.15.0 / 2015-07-21
|
||||
|
||||
BREAKING CHANGES:
|
||||
|
||||
|
@ -692,8 +693,7 @@ All changes:
|
|||
* [CLEANUP] Use `templates.TemplateExpander` for all page templates.
|
||||
* [CLEANUP] Use new v1 HTTP API for querying and graphing.
|
||||
|
||||
## v0.14.0 / 2015-06-01
|
||||
|
||||
## 0.14.0 / 2015-06-01
|
||||
* [CHANGE] Configuration format changed and switched to YAML.
|
||||
(See the provided [migration tool](https://github.com/prometheus/migrate/releases).)
|
||||
* [ENHANCEMENT] Redesign of state-preserving target discovery.
|
||||
|
@ -718,12 +718,10 @@ All changes:
|
|||
* [FEATURE] Add increase() query function to calculate a counter's increase.
|
||||
* [ENHANCEMENT] Limit retrievable samples to the storage's retention window.
|
||||
|
||||
## v0.13.4 / 2015-05-23
|
||||
|
||||
## 0.13.4 / 2015-05-23
|
||||
* [BUGFIX] Fix a race while checkpointing fingerprint mappings.
|
||||
|
||||
## v0.13.3 / 2015-05-11
|
||||
|
||||
## 0.13.3 / 2015-05-11
|
||||
* [BUGFIX] Handle fingerprint collisions properly.
|
||||
* [CHANGE] Comments in rules file must start with `#`. (The undocumented `//`
|
||||
and `/*...*/` comment styles are no longer supported.)
|
||||
|
@ -733,8 +731,7 @@ All changes:
|
|||
* [ENHANCEMENT] Limit maximum number of concurrent queries.
|
||||
* [ENHANCEMENT] Terminate running queries during shutdown.
|
||||
|
||||
## v0.13.2 / 2015-05-05
|
||||
|
||||
## 0.13.2 / 2015-05-05
|
||||
* [MAINTENANCE] Updated vendored dependencies to their newest versions.
|
||||
* [MAINTENANCE] Include rule_checker and console templates in release tarball.
|
||||
* [BUGFIX] Sort NaN as the lowest value.
|
||||
|
@ -744,13 +741,11 @@ All changes:
|
|||
reading from disk.
|
||||
* [BUGFIX] Show correct error on wrong DNS response.
|
||||
|
||||
## v0.13.1 / 2015-04-09
|
||||
|
||||
## 0.13.1 / 2015-04-09
|
||||
* [BUGFIX] Treat memory series with zero chunks correctly in series maintenance.
|
||||
* [ENHANCEMENT] Improve readability of usage text even more.
|
||||
|
||||
## v0.13.0 / 2015-04-08
|
||||
|
||||
## 0.13.0 / 2015-04-08
|
||||
* [ENHANCEMENT] Double-delta encoding for chunks, saving typically 40% of
|
||||
space, both in RAM and on disk.
|
||||
* [ENHANCEMENT] Redesign of chunk persistence queuing, increasing performance
|
||||
|
@ -777,8 +772,7 @@ All changes:
|
|||
* [CLEANUP] Misc. other code cleanups.
|
||||
* [MAINTENANCE] Updated vendored dependcies to their newest versions.
|
||||
|
||||
## v0.12.0 / 2015-03-04
|
||||
|
||||
## 0.12.0 / 2015-03-04
|
||||
* [CHANGE] Use client_golang v0.3.1. THIS CHANGES FINGERPRINTING AND INVALIDATES
|
||||
ALL PERSISTED FINGERPRINTS. You have to wipe your storage to use this or
|
||||
later versions. There is a version guard in place that will prevent you to
|
||||
|
@ -793,9 +787,8 @@ All changes:
|
|||
(rather than /tmp/metrics).
|
||||
* [CHANGE] Makefile uses Go 1.4.2.
|
||||
|
||||
## v0.11.1 / 2015-02-27
|
||||
|
||||
* [BUGFIX] Make series maintenance complete again. (Ever since v0.9.0rc4,
|
||||
## 0.11.1 / 2015-02-27
|
||||
* [BUGFIX] Make series maintenance complete again. (Ever since 0.9.0rc4,
|
||||
or commit 0851945, series would not be archived, chunk descriptors would
|
||||
not be evicted, and stale head chunks would never be closed. This happened
|
||||
due to accidental deletion of a line calling a (well tested :) function.
|
||||
|
@ -806,8 +799,7 @@ All changes:
|
|||
* [CLEANUP] Code cleanups.
|
||||
* [ENHANCEMENT] Limit the number of 'dirty' series counted during checkpointing.
|
||||
|
||||
## v0.11.0 / 2015-02-23
|
||||
|
||||
## 0.11.0 / 2015-02-23
|
||||
* [FEATURE] Introduce new metric type Histogram with server-side aggregation.
|
||||
* [FEATURE] Add offset operator.
|
||||
* [FEATURE] Add floor, ceil and round functions.
|
||||
|
@ -831,8 +823,7 @@ All changes:
|
|||
* [BUGFIX] Fix Rickshaw/D3 version mismatch.
|
||||
* [CLEANUP] Various code cleanups.
|
||||
|
||||
## v0.10.0 / 2015-01-26
|
||||
|
||||
## 0.10.0 / 2015-01-26
|
||||
* [CHANGE] More efficient JSON result format in query API. This requires
|
||||
up-to-date versions of PromDash and prometheus_cli, too.
|
||||
* [ENHANCEMENT] Excluded non-minified Bootstrap assets and the Bootstrap maps
|
||||
|
@ -844,8 +835,7 @@ All changes:
|
|||
* [BUGFIX] Several fixes to graphs in consoles.
|
||||
* [CLEANUP] Removed a file size check that did not check anything.
|
||||
|
||||
## v0.9.0 / 2015-01-23
|
||||
|
||||
## 0.9.0 / 2015-01-23
|
||||
* [CHANGE] Reworked command line flags, now more consistent and taking into
|
||||
account needs of the new storage backend (see below).
|
||||
* [CHANGE] Metric names are dropped after certain transformations.
|
||||
|
@ -879,20 +869,18 @@ All changes:
|
|||
* [ENHANCEMENT] Switched from Go 1.3 to Go 1.4.
|
||||
* [ENHANCEMENT] Vendored external dependencies with godeps.
|
||||
* [ENHANCEMENT] Numerous Web UI improvements, moved to Bootstrap3 and
|
||||
Rickshaw v1.5.1.
|
||||
Rickshaw 1.5.1.
|
||||
* [ENHANCEMENT] Improved Docker integration.
|
||||
* [ENHANCEMENT] Simplified the Makefile contraption.
|
||||
* [CLEANUP] Put meta-data files into proper shape (LICENSE, README.md etc.)
|
||||
* [CLEANUP] Removed all legitimate 'go vet' and 'golint' warnings.
|
||||
* [CLEANUP] Removed dead code.
|
||||
|
||||
## v0.8.0 / 2014-09-04
|
||||
|
||||
## 0.8.0 / 2014-09-04
|
||||
* [ENHANCEMENT] Stagger scrapes to spread out load.
|
||||
* [BUGFIX] Correctly quote HTTP Accept header.
|
||||
|
||||
## v0.7.0 / 2014-08-06
|
||||
|
||||
## 0.7.0 / 2014-08-06
|
||||
* [FEATURE] Added new functions: abs(), topk(), bottomk(), drop_common_labels().
|
||||
* [FEATURE] Let console templates get graph links from expressions.
|
||||
* [FEATURE] Allow console templates to dynamically include other templates.
|
||||
|
@ -906,8 +894,7 @@ All changes:
|
|||
* [ENHANCEMENT] Removed incremental backoffs for unhealthy targets.
|
||||
* [ENHANCEMENT] Dockerfile also builds Prometheus support tools now.
|
||||
|
||||
## v0.6.0 / 2014-06-30
|
||||
|
||||
## 0.6.0 / 2014-06-30
|
||||
* [FEATURE] Added console and alert templates support, along with various template functions.
|
||||
* [PERFORMANCE] Much faster and more memory-efficient flushing to disk.
|
||||
* [ENHANCEMENT] Query results are now only logged when debugging.
|
||||
|
@ -917,7 +904,7 @@ All changes:
|
|||
* [BUGFIX] Added installation step for missing dependency to Dockerfile.
|
||||
* [BUGFIX] Removed broken and unused "User Dashboard" link.
|
||||
|
||||
## v0.5.0 / 2014-05-28
|
||||
## 0.5.0 / 2014-05-28
|
||||
|
||||
* [BUGFIX] Fixed next retrieval time display on status page.
|
||||
* [BUGFIX] Updated some variable references in tools subdir.
|
||||
|
@ -927,7 +914,7 @@ All changes:
|
|||
* [ENHANCEMENT] Added internal check to verify temporal order of streams.
|
||||
* [ENHANCEMENT] Some internal refactorings.
|
||||
|
||||
## v0.4.0 / 2014-04-17
|
||||
## 0.4.0 / 2014-04-17
|
||||
|
||||
* [FEATURE] Vectors and scalars may now be reversed in binary operations (`<scalar> <binop> <vector>`).
|
||||
* [FEATURE] It's possible to shutdown Prometheus via a `/-/quit` web endpoint now.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
FROM quay.io/prometheus/busybox:latest
|
||||
MAINTAINER The Prometheus Authors <prometheus-developers@googlegroups.com>
|
||||
LABEL maintainer "The Prometheus Authors <prometheus-developers@googlegroups.com>"
|
||||
|
||||
COPY prometheus /bin/prometheus
|
||||
COPY promtool /bin/promtool
|
||||
|
|
|
@ -291,7 +291,7 @@ func parseAlertmanagerURLToConfig(us string) (*config.AlertmanagerConfig, error)
|
|||
}
|
||||
|
||||
if password, isSet := u.User.Password(); isSet {
|
||||
acfg.HTTPClientConfig.BasicAuth.Password = password
|
||||
acfg.HTTPClientConfig.BasicAuth.Password = config.Secret(password)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -95,8 +95,8 @@ func Main() int {
|
|||
// reloadables = append(reloadables, remoteStorage)
|
||||
|
||||
var (
|
||||
notifier = notifier.New(&cfg.notifier)
|
||||
targetManager = retrieval.NewTargetManager(localStorage)
|
||||
notifier = notifier.New(&cfg.notifier, log.Base())
|
||||
targetManager = retrieval.NewTargetManager(localStorage, log.Base())
|
||||
queryEngine = promql.NewEngine(localStorage, &cfg.queryEngine)
|
||||
ctx, cancelCtx = context.WithCancel(context.Background())
|
||||
)
|
||||
|
|
|
@ -30,7 +30,6 @@ import (
|
|||
var (
|
||||
patFileSDName = regexp.MustCompile(`^[^*]*(\*[^/]*)?\.(json|yml|yaml|JSON|YML|YAML)$`)
|
||||
patRulePath = regexp.MustCompile(`^[^*]*(\*[^/]*)?$`)
|
||||
patAuthLine = regexp.MustCompile(`((?:password|bearer_token|secret_key|client_secret):\s+)(".+"|'.+'|[^\s]+)`)
|
||||
relabelTarget = regexp.MustCompile(`^(?:(?:[a-zA-Z_]|\$(?:\{\w+\}|\w+))+\w*)+$`)
|
||||
)
|
||||
|
||||
|
@ -150,6 +149,12 @@ var (
|
|||
RefreshInterval: model.Duration(60 * time.Second),
|
||||
}
|
||||
|
||||
// DefaultOpenstackSDConfig is the default OpenStack SD configuration.
|
||||
DefaultOpenstackSDConfig = OpenstackSDConfig{
|
||||
Port: 80,
|
||||
RefreshInterval: model.Duration(60 * time.Second),
|
||||
}
|
||||
|
||||
// DefaultAzureSDConfig is the default Azure SD configuration.
|
||||
DefaultAzureSDConfig = AzureSDConfig{
|
||||
Port: 80,
|
||||
|
@ -219,6 +224,23 @@ type Config struct {
|
|||
original string
|
||||
}
|
||||
|
||||
// Secret special type for storing secrets.
|
||||
type Secret string
|
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface for Secrets.
|
||||
func (s *Secret) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
type plain Secret
|
||||
return unmarshal((*plain)(s))
|
||||
}
|
||||
|
||||
// MarshalYAML implements the yaml.Marshaler interface for Secrets.
|
||||
func (s Secret) MarshalYAML() (interface{}, error) {
|
||||
if s != "" {
|
||||
return "<secret>", nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// resolveFilepaths joins all relative paths in a configuration
|
||||
// with a given base directory.
|
||||
func resolveFilepaths(baseDir string, cfg *Config) {
|
||||
|
@ -281,17 +303,11 @@ func checkOverflow(m map[string]interface{}, ctx string) error {
|
|||
}
|
||||
|
||||
func (c Config) String() string {
|
||||
var s string
|
||||
if c.original != "" {
|
||||
s = c.original
|
||||
} else {
|
||||
b, err := yaml.Marshal(c)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("<error creating config string: %s>", err)
|
||||
}
|
||||
s = string(b)
|
||||
b, err := yaml.Marshal(c)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("<error creating config string: %s>", err)
|
||||
}
|
||||
return patAuthLine.ReplaceAllString(s, "${1}<hidden>")
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||
|
@ -370,7 +386,7 @@ func (c *GlobalConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
if err := unmarshal((*plain)(gc)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkOverflow(c.XXX, "global config"); err != nil {
|
||||
if err := checkOverflow(gc.XXX, "global config"); err != nil {
|
||||
return err
|
||||
}
|
||||
// First set the correct scrape interval, then check that the timeout
|
||||
|
@ -454,6 +470,8 @@ type ServiceDiscoveryConfig struct {
|
|||
GCESDConfigs []*GCESDConfig `yaml:"gce_sd_configs,omitempty"`
|
||||
// List of EC2 service discovery configurations.
|
||||
EC2SDConfigs []*EC2SDConfig `yaml:"ec2_sd_configs,omitempty"`
|
||||
// List of OpenStack service discovery configurations.
|
||||
OpenstackSDConfigs []*OpenstackSDConfig `yaml:"openstack_sd_configs,omitempty"`
|
||||
// List of Azure service discovery configurations.
|
||||
AzureSDConfigs []*AzureSDConfig `yaml:"azure_sd_configs,omitempty"`
|
||||
// List of Triton service discovery configurations.
|
||||
|
@ -480,7 +498,7 @@ type HTTPClientConfig struct {
|
|||
// The HTTP basic authentication credentials for the targets.
|
||||
BasicAuth *BasicAuth `yaml:"basic_auth,omitempty"`
|
||||
// The bearer token for the targets.
|
||||
BearerToken string `yaml:"bearer_token,omitempty"`
|
||||
BearerToken Secret `yaml:"bearer_token,omitempty"`
|
||||
// The bearer token file for the targets.
|
||||
BearerTokenFile string `yaml:"bearer_token_file,omitempty"`
|
||||
// HTTP proxy server to use to connect to the targets.
|
||||
|
@ -544,7 +562,7 @@ func (c *ScrapeConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkOverflow(c.XXX, "scrape_config"); err != nil {
|
||||
if err = checkOverflow(c.XXX, "scrape_config"); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(c.JobName) == 0 {
|
||||
|
@ -554,7 +572,7 @@ func (c *ScrapeConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
// The UnmarshalYAML method of HTTPClientConfig is not being called because it's not a pointer.
|
||||
// We cannot make it a pointer as the parser panics for inlined pointer structs.
|
||||
// Thus we just do its validation here.
|
||||
if err := c.HTTPClientConfig.validate(); err != nil {
|
||||
if err = c.HTTPClientConfig.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -660,7 +678,7 @@ func CheckTargetAddress(address model.LabelValue) error {
|
|||
// BasicAuth contains basic HTTP authentication credentials.
|
||||
type BasicAuth struct {
|
||||
Username string `yaml:"username"`
|
||||
Password string `yaml:"password"`
|
||||
Password Secret `yaml:"password"`
|
||||
|
||||
// Catches all undefined fields and must be empty after parsing.
|
||||
XXX map[string]interface{} `yaml:",inline"`
|
||||
|
@ -669,7 +687,7 @@ type BasicAuth struct {
|
|||
// ClientCert contains client cert credentials.
|
||||
type ClientCert struct {
|
||||
Cert string `yaml:"cert"`
|
||||
Key string `yaml:"key"`
|
||||
Key Secret `yaml:"key"`
|
||||
|
||||
// Catches all undefined fields and must be empty after parsing.
|
||||
XXX map[string]interface{} `yaml:",inline"`
|
||||
|
@ -718,7 +736,7 @@ func (tg *TargetGroup) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
})
|
||||
}
|
||||
tg.Labels = g.Labels
|
||||
return checkOverflow(g.XXX, "target_group")
|
||||
return checkOverflow(g.XXX, "static_config")
|
||||
}
|
||||
|
||||
// MarshalYAML implements the yaml.Marshaler interface.
|
||||
|
@ -825,12 +843,12 @@ func (c *FileSDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
// ConsulSDConfig is the configuration for Consul service discovery.
|
||||
type ConsulSDConfig struct {
|
||||
Server string `yaml:"server"`
|
||||
Token string `yaml:"token,omitempty"`
|
||||
Token Secret `yaml:"token,omitempty"`
|
||||
Datacenter string `yaml:"datacenter,omitempty"`
|
||||
TagSeparator string `yaml:"tag_separator,omitempty"`
|
||||
Scheme string `yaml:"scheme,omitempty"`
|
||||
Username string `yaml:"username,omitempty"`
|
||||
Password string `yaml:"password,omitempty"`
|
||||
Password Secret `yaml:"password,omitempty"`
|
||||
// The list of services for which targets are discovered.
|
||||
// Defaults to all services if empty.
|
||||
Services []string `yaml:"services"`
|
||||
|
@ -933,7 +951,7 @@ type MarathonSDConfig struct {
|
|||
Timeout model.Duration `yaml:"timeout,omitempty"`
|
||||
RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"`
|
||||
TLSConfig TLSConfig `yaml:"tls_config,omitempty"`
|
||||
BearerToken string `yaml:"bearer_token,omitempty"`
|
||||
BearerToken Secret `yaml:"bearer_token,omitempty"`
|
||||
BearerTokenFile string `yaml:"bearer_token_file,omitempty"`
|
||||
|
||||
// Catches all undefined fields and must be empty after parsing.
|
||||
|
@ -990,7 +1008,7 @@ type KubernetesSDConfig struct {
|
|||
APIServer URL `yaml:"api_server"`
|
||||
Role KubernetesRole `yaml:"role"`
|
||||
BasicAuth *BasicAuth `yaml:"basic_auth,omitempty"`
|
||||
BearerToken string `yaml:"bearer_token,omitempty"`
|
||||
BearerToken Secret `yaml:"bearer_token,omitempty"`
|
||||
BearerTokenFile string `yaml:"bearer_token_file,omitempty"`
|
||||
TLSConfig TLSConfig `yaml:"tls_config,omitempty"`
|
||||
NamespaceDiscovery KubernetesNamespaceDiscovery `yaml:"namespaces"`
|
||||
|
@ -1095,7 +1113,7 @@ func (c *GCESDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
type EC2SDConfig struct {
|
||||
Region string `yaml:"region"`
|
||||
AccessKey string `yaml:"access_key,omitempty"`
|
||||
SecretKey string `yaml:"secret_key,omitempty"`
|
||||
SecretKey Secret `yaml:"secret_key,omitempty"`
|
||||
Profile string `yaml:"profile,omitempty"`
|
||||
RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"`
|
||||
Port int `yaml:"port"`
|
||||
|
@ -1121,13 +1139,42 @@ func (c *EC2SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// OpenstackSDConfig is the configuration for OpenStack based service discovery.
|
||||
type OpenstackSDConfig struct {
|
||||
IdentityEndpoint string `yaml:"identity_endpoint"`
|
||||
Username string `yaml:"username"`
|
||||
UserID string `yaml:"userid"`
|
||||
Password Secret `yaml:"password"`
|
||||
ProjectName string `yaml:"project_name"`
|
||||
ProjectID string `yaml:"project_id"`
|
||||
DomainName string `yaml:"domain_name"`
|
||||
DomainID string `yaml:"domain_id"`
|
||||
Region string `yaml:"region"`
|
||||
RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"`
|
||||
Port int `yaml:"port"`
|
||||
|
||||
// Catches all undefined fields and must be empty after parsing.
|
||||
XXX map[string]interface{} `yaml:",inline"`
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||
func (c *OpenstackSDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
*c = DefaultOpenstackSDConfig
|
||||
type plain OpenstackSDConfig
|
||||
err := unmarshal((*plain)(c))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return checkOverflow(c.XXX, "openstack_sd_config")
|
||||
}
|
||||
|
||||
// AzureSDConfig is the configuration for Azure based service discovery.
|
||||
type AzureSDConfig struct {
|
||||
Port int `yaml:"port"`
|
||||
SubscriptionID string `yaml:"subscription_id"`
|
||||
TenantID string `yaml:"tenant_id,omitempty"`
|
||||
ClientID string `yaml:"client_id,omitempty"`
|
||||
ClientSecret string `yaml:"client_secret,omitempty"`
|
||||
ClientSecret Secret `yaml:"client_secret,omitempty"`
|
||||
RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"`
|
||||
|
||||
// Catches all undefined fields and must be empty after parsing.
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"io/ioutil"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -144,6 +145,7 @@ var expectedConf = &Config{
|
|||
},
|
||||
},
|
||||
{
|
||||
|
||||
JobName: "service-x",
|
||||
|
||||
ScrapeInterval: model.Duration(50 * time.Second),
|
||||
|
@ -153,7 +155,7 @@ var expectedConf = &Config{
|
|||
HTTPClientConfig: HTTPClientConfig{
|
||||
BasicAuth: &BasicAuth{
|
||||
Username: "admin_name",
|
||||
Password: "admin_password",
|
||||
Password: "multiline\nmysecret\ntest",
|
||||
},
|
||||
},
|
||||
MetricsPath: "/my_path",
|
||||
|
@ -245,6 +247,7 @@ var expectedConf = &Config{
|
|||
ConsulSDConfigs: []*ConsulSDConfig{
|
||||
{
|
||||
Server: "localhost:1234",
|
||||
Token: "mysecret",
|
||||
Services: []string{"nginx", "cache", "mysql"},
|
||||
TagSeparator: DefaultConsulSDConfig.TagSeparator,
|
||||
Scheme: "https",
|
||||
|
@ -284,7 +287,7 @@ var expectedConf = &Config{
|
|||
KeyFile: "testdata/valid_key_file",
|
||||
},
|
||||
|
||||
BearerToken: "avalidtoken",
|
||||
BearerToken: "mysecret",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -303,7 +306,7 @@ var expectedConf = &Config{
|
|||
Role: KubernetesRoleEndpoint,
|
||||
BasicAuth: &BasicAuth{
|
||||
Username: "myusername",
|
||||
Password: "mypassword",
|
||||
Password: "mysecret",
|
||||
},
|
||||
NamespaceDiscovery: KubernetesNamespaceDiscovery{},
|
||||
},
|
||||
|
@ -372,7 +375,7 @@ var expectedConf = &Config{
|
|||
{
|
||||
Region: "us-east-1",
|
||||
AccessKey: "access",
|
||||
SecretKey: "secret",
|
||||
SecretKey: "mysecret",
|
||||
Profile: "profile",
|
||||
RefreshInterval: model.Duration(60 * time.Second),
|
||||
Port: 80,
|
||||
|
@ -395,7 +398,7 @@ var expectedConf = &Config{
|
|||
SubscriptionID: "11AAAA11-A11A-111A-A111-1111A1111A11",
|
||||
TenantID: "BBBB222B-B2B2-2B22-B222-2BB2222BB2B2",
|
||||
ClientID: "333333CC-3C33-3333-CCC3-33C3CCCCC33C",
|
||||
ClientSecret: "nAdvAK2oBuVym4IXix",
|
||||
ClientSecret: "mysecret",
|
||||
RefreshInterval: model.Duration(5 * time.Minute),
|
||||
Port: 9100,
|
||||
},
|
||||
|
@ -538,9 +541,12 @@ func TestLoadConfig(t *testing.T) {
|
|||
|
||||
// String method must not reveal authentication credentials.
|
||||
s := c.String()
|
||||
if strings.Contains(s, "admin_password") {
|
||||
secretRe := regexp.MustCompile("<secret>")
|
||||
matches := secretRe.FindAllStringIndex(s, -1)
|
||||
if len(matches) != 6 || strings.Contains(s, "mysecret") {
|
||||
t.Fatalf("config's String method reveals authentication credentials.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var expectedErrors = []struct {
|
||||
|
@ -634,6 +640,9 @@ var expectedErrors = []struct {
|
|||
}, {
|
||||
filename: "target_label_hashmod_missing.bad.yml",
|
||||
errMsg: "relabel configuration for hashmod action requires 'target_label' value",
|
||||
}, {
|
||||
filename: "unknown_global_attr.bad.yml",
|
||||
errMsg: "unknown fields in global config: nonexistent_field",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
11
config/testdata/conf.good.yml
vendored
11
config/testdata/conf.good.yml
vendored
|
@ -67,7 +67,7 @@ scrape_configs:
|
|||
|
||||
basic_auth:
|
||||
username: admin_name
|
||||
password: admin_password
|
||||
password: "multiline\nmysecret\ntest"
|
||||
|
||||
scrape_interval: 50s
|
||||
scrape_timeout: 5s
|
||||
|
@ -113,6 +113,7 @@ scrape_configs:
|
|||
|
||||
consul_sd_configs:
|
||||
- server: 'localhost:1234'
|
||||
token: mysecret
|
||||
services: ['nginx', 'cache', 'mysql']
|
||||
scheme: https
|
||||
tls_config:
|
||||
|
@ -134,7 +135,7 @@ scrape_configs:
|
|||
cert_file: valid_cert_file
|
||||
key_file: valid_key_file
|
||||
|
||||
bearer_token: avalidtoken
|
||||
bearer_token: mysecret
|
||||
|
||||
- job_name: service-kubernetes
|
||||
|
||||
|
@ -144,7 +145,7 @@ scrape_configs:
|
|||
|
||||
basic_auth:
|
||||
username: 'myusername'
|
||||
password: 'mypassword'
|
||||
password: 'mysecret'
|
||||
|
||||
- job_name: service-kubernetes-namespaces
|
||||
|
||||
|
@ -168,7 +169,7 @@ scrape_configs:
|
|||
ec2_sd_configs:
|
||||
- region: us-east-1
|
||||
access_key: access
|
||||
secret_key: secret
|
||||
secret_key: mysecret
|
||||
profile: profile
|
||||
|
||||
- job_name: service-azure
|
||||
|
@ -176,7 +177,7 @@ scrape_configs:
|
|||
- subscription_id: 11AAAA11-A11A-111A-A111-1111A1111A11
|
||||
tenant_id: BBBB222B-B2B2-2B22-B222-2BB2222BB2B2
|
||||
client_id: 333333CC-3C33-3333-CCC3-33C3CCCCC33C
|
||||
client_secret: nAdvAK2oBuVym4IXix
|
||||
client_secret: mysecret
|
||||
port: 9100
|
||||
|
||||
- job_name: service-nerve
|
||||
|
|
2
config/testdata/unknown_global_attr.bad.yml
vendored
Normal file
2
config/testdata/unknown_global_attr.bad.yml
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
global:
|
||||
nonexistent_field: test
|
|
@ -66,14 +66,16 @@ type Discovery struct {
|
|||
cfg *config.AzureSDConfig
|
||||
interval time.Duration
|
||||
port int
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// NewDiscovery returns a new AzureDiscovery which periodically refreshes its targets.
|
||||
func NewDiscovery(cfg *config.AzureSDConfig) *Discovery {
|
||||
func NewDiscovery(cfg *config.AzureSDConfig, logger log.Logger) *Discovery {
|
||||
return &Discovery{
|
||||
cfg: cfg,
|
||||
interval: time.Duration(cfg.RefreshInterval),
|
||||
port: cfg.Port,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,7 +93,7 @@ func (d *Discovery) Run(ctx context.Context, ch chan<- []*config.TargetGroup) {
|
|||
|
||||
tg, err := d.refresh()
|
||||
if err != nil {
|
||||
log.Errorf("unable to refresh during Azure discovery: %s", err)
|
||||
d.logger.Errorf("unable to refresh during Azure discovery: %s", err)
|
||||
} else {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
|
@ -120,7 +122,7 @@ func createAzureClient(cfg config.AzureSDConfig) (azureClient, error) {
|
|||
if err != nil {
|
||||
return azureClient{}, err
|
||||
}
|
||||
spt, err := azure.NewServicePrincipalToken(*oauthConfig, cfg.ClientID, cfg.ClientSecret, azure.PublicCloud.ResourceManagerEndpoint)
|
||||
spt, err := azure.NewServicePrincipalToken(*oauthConfig, cfg.ClientID, string(cfg.ClientSecret), azure.PublicCloud.ResourceManagerEndpoint)
|
||||
if err != nil {
|
||||
return azureClient{}, err
|
||||
}
|
||||
|
@ -141,13 +143,13 @@ type azureResource struct {
|
|||
}
|
||||
|
||||
// Create a new azureResource object from an ID string.
|
||||
func newAzureResourceFromID(id string) (azureResource, error) {
|
||||
func newAzureResourceFromID(id string, logger log.Logger) (azureResource, error) {
|
||||
// Resource IDs have the following format.
|
||||
// /subscriptions/SUBSCRIPTION_ID/resourceGroups/RESOURCE_GROUP/providers/PROVIDER/TYPE/NAME
|
||||
s := strings.Split(id, "/")
|
||||
if len(s) != 9 {
|
||||
err := fmt.Errorf("invalid ID '%s'. Refusing to create azureResource", id)
|
||||
log.Error(err)
|
||||
logger.Error(err)
|
||||
return azureResource{}, err
|
||||
}
|
||||
return azureResource{
|
||||
|
@ -185,7 +187,7 @@ func (d *Discovery) refresh() (tg *config.TargetGroup, err error) {
|
|||
}
|
||||
machines = append(machines, *result.Value...)
|
||||
}
|
||||
log.Debugf("Found %d virtual machines during Azure discovery.", len(machines))
|
||||
d.logger.Debugf("Found %d virtual machines during Azure discovery.", len(machines))
|
||||
|
||||
// We have the slice of machines. Now turn them into targets.
|
||||
// Doing them in go routines because the network interface calls are slow.
|
||||
|
@ -197,7 +199,7 @@ func (d *Discovery) refresh() (tg *config.TargetGroup, err error) {
|
|||
ch := make(chan target, len(machines))
|
||||
for i, vm := range machines {
|
||||
go func(i int, vm compute.VirtualMachine) {
|
||||
r, err := newAzureResourceFromID(*vm.ID)
|
||||
r, err := newAzureResourceFromID(*vm.ID, d.logger)
|
||||
if err != nil {
|
||||
ch <- target{labelSet: nil, err: err}
|
||||
return
|
||||
|
@ -219,14 +221,14 @@ func (d *Discovery) refresh() (tg *config.TargetGroup, err error) {
|
|||
|
||||
// Get the IP address information via separate call to the network provider.
|
||||
for _, nic := range *vm.Properties.NetworkProfile.NetworkInterfaces {
|
||||
r, err := newAzureResourceFromID(*nic.ID)
|
||||
r, err := newAzureResourceFromID(*nic.ID, d.logger)
|
||||
if err != nil {
|
||||
ch <- target{labelSet: nil, err: err}
|
||||
return
|
||||
}
|
||||
networkInterface, err := client.nic.Get(r.ResourceGroup, r.Name, "")
|
||||
if err != nil {
|
||||
log.Errorf("Unable to get network interface %s: %s", r.Name, err)
|
||||
d.logger.Errorf("Unable to get network interface %s: %s", r.Name, err)
|
||||
ch <- target{labelSet: nil, err: err}
|
||||
// Get out of this routine because we cannot continue without a network interface.
|
||||
return
|
||||
|
@ -237,7 +239,7 @@ func (d *Discovery) refresh() (tg *config.TargetGroup, err error) {
|
|||
// yet support this. On deallocated machines, this value happens to be nil so it
|
||||
// is a cheap and easy way to determine if a machine is allocated or not.
|
||||
if networkInterface.Properties.Primary == nil {
|
||||
log.Debugf("Virtual machine %s is deallocated. Skipping during Azure SD.", *vm.Name)
|
||||
d.logger.Debugf("Virtual machine %s is deallocated. Skipping during Azure SD.", *vm.Name)
|
||||
ch <- target{}
|
||||
return
|
||||
}
|
||||
|
@ -272,6 +274,6 @@ func (d *Discovery) refresh() (tg *config.TargetGroup, err error) {
|
|||
}
|
||||
}
|
||||
|
||||
log.Debugf("Azure discovery completed.")
|
||||
d.logger.Debugf("Azure discovery completed.")
|
||||
return tg, nil
|
||||
}
|
||||
|
|
|
@ -89,10 +89,11 @@ type Discovery struct {
|
|||
clientDatacenter string
|
||||
tagSeparator string
|
||||
watchedServices []string // Set of services which will be discovered.
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// NewDiscovery returns a new Discovery for the given config.
|
||||
func NewDiscovery(conf *config.ConsulSDConfig) (*Discovery, error) {
|
||||
func NewDiscovery(conf *config.ConsulSDConfig, logger log.Logger) (*Discovery, error) {
|
||||
tls, err := httputil.NewTLSConfig(conf.TLSConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -104,10 +105,10 @@ func NewDiscovery(conf *config.ConsulSDConfig) (*Discovery, error) {
|
|||
Address: conf.Server,
|
||||
Scheme: conf.Scheme,
|
||||
Datacenter: conf.Datacenter,
|
||||
Token: conf.Token,
|
||||
Token: string(conf.Token),
|
||||
HttpAuth: &consul.HttpBasicAuth{
|
||||
Username: conf.Username,
|
||||
Password: conf.Password,
|
||||
Password: string(conf.Password),
|
||||
},
|
||||
HttpClient: wrapper,
|
||||
}
|
||||
|
@ -121,6 +122,7 @@ func NewDiscovery(conf *config.ConsulSDConfig) (*Discovery, error) {
|
|||
tagSeparator: conf.TagSeparator,
|
||||
watchedServices: conf.Services,
|
||||
clientDatacenter: clientConf.Datacenter,
|
||||
logger: logger,
|
||||
}
|
||||
return cd, nil
|
||||
}
|
||||
|
@ -163,7 +165,7 @@ func (d *Discovery) Run(ctx context.Context, ch chan<- []*config.TargetGroup) {
|
|||
}
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Error refreshing service list: %s", err)
|
||||
d.logger.Errorf("Error refreshing service list: %s", err)
|
||||
rpcFailuresCount.Inc()
|
||||
time.Sleep(retryInterval)
|
||||
continue
|
||||
|
@ -179,7 +181,7 @@ func (d *Discovery) Run(ctx context.Context, ch chan<- []*config.TargetGroup) {
|
|||
if d.clientDatacenter == "" {
|
||||
info, err := d.client.Agent().Self()
|
||||
if err != nil {
|
||||
log.Errorf("Error retrieving datacenter name: %s", err)
|
||||
d.logger.Errorf("Error retrieving datacenter name: %s", err)
|
||||
time.Sleep(retryInterval)
|
||||
continue
|
||||
}
|
||||
|
@ -203,6 +205,7 @@ func (d *Discovery) Run(ctx context.Context, ch chan<- []*config.TargetGroup) {
|
|||
datacenterLabel: model.LabelValue(d.clientDatacenter),
|
||||
},
|
||||
tagSeparator: d.tagSeparator,
|
||||
logger: d.logger,
|
||||
}
|
||||
|
||||
wctx, cancel := context.WithCancel(ctx)
|
||||
|
@ -235,6 +238,7 @@ type consulService struct {
|
|||
labels model.LabelSet
|
||||
client *consul.Client
|
||||
tagSeparator string
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
func (srv *consulService) watch(ctx context.Context, ch chan<- []*config.TargetGroup) {
|
||||
|
@ -258,7 +262,7 @@ func (srv *consulService) watch(ctx context.Context, ch chan<- []*config.TargetG
|
|||
}
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Error refreshing service %s: %s", srv.name, err)
|
||||
srv.logger.Errorf("Error refreshing service %s: %s", srv.name, err)
|
||||
rpcFailuresCount.Inc()
|
||||
time.Sleep(retryInterval)
|
||||
continue
|
||||
|
|
|
@ -16,13 +16,14 @@ package consul
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/prometheus/prometheus/config"
|
||||
)
|
||||
|
||||
func TestConfiguredService(t *testing.T) {
|
||||
conf := &config.ConsulSDConfig{
|
||||
Services: []string{"configuredServiceName"}}
|
||||
consulDiscovery, err := NewDiscovery(conf)
|
||||
consulDiscovery, err := NewDiscovery(conf, log.Base())
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error when initialising discovery %v", err)
|
||||
|
@ -37,7 +38,7 @@ func TestConfiguredService(t *testing.T) {
|
|||
|
||||
func TestNonConfiguredService(t *testing.T) {
|
||||
conf := &config.ConsulSDConfig{}
|
||||
consulDiscovery, err := NewDiscovery(conf)
|
||||
consulDiscovery, err := NewDiscovery(conf, log.Base())
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error when initialising discovery %v", err)
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/prometheus/prometheus/discovery/gce"
|
||||
"github.com/prometheus/prometheus/discovery/kubernetes"
|
||||
"github.com/prometheus/prometheus/discovery/marathon"
|
||||
"github.com/prometheus/prometheus/discovery/openstack"
|
||||
"github.com/prometheus/prometheus/discovery/triton"
|
||||
"github.com/prometheus/prometheus/discovery/zookeeper"
|
||||
"golang.org/x/net/context"
|
||||
|
@ -50,7 +51,7 @@ type TargetProvider interface {
|
|||
}
|
||||
|
||||
// ProvidersFromConfig returns all TargetProviders configured in cfg.
|
||||
func ProvidersFromConfig(cfg config.ServiceDiscoveryConfig) map[string]TargetProvider {
|
||||
func ProvidersFromConfig(cfg config.ServiceDiscoveryConfig, logger log.Logger) map[string]TargetProvider {
|
||||
providers := map[string]TargetProvider{}
|
||||
|
||||
app := func(mech string, i int, tp TargetProvider) {
|
||||
|
@ -58,59 +59,68 @@ func ProvidersFromConfig(cfg config.ServiceDiscoveryConfig) map[string]TargetPro
|
|||
}
|
||||
|
||||
for i, c := range cfg.DNSSDConfigs {
|
||||
app("dns", i, dns.NewDiscovery(c))
|
||||
app("dns", i, dns.NewDiscovery(c, logger))
|
||||
}
|
||||
for i, c := range cfg.FileSDConfigs {
|
||||
app("file", i, file.NewDiscovery(c))
|
||||
app("file", i, file.NewDiscovery(c, logger))
|
||||
}
|
||||
for i, c := range cfg.ConsulSDConfigs {
|
||||
k, err := consul.NewDiscovery(c)
|
||||
k, err := consul.NewDiscovery(c, logger)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot create Consul discovery: %s", err)
|
||||
logger.Errorf("Cannot create Consul discovery: %s", err)
|
||||
continue
|
||||
}
|
||||
app("consul", i, k)
|
||||
}
|
||||
for i, c := range cfg.MarathonSDConfigs {
|
||||
m, err := marathon.NewDiscovery(c)
|
||||
m, err := marathon.NewDiscovery(c, logger)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot create Marathon discovery: %s", err)
|
||||
logger.Errorf("Cannot create Marathon discovery: %s", err)
|
||||
continue
|
||||
}
|
||||
app("marathon", i, m)
|
||||
}
|
||||
for i, c := range cfg.KubernetesSDConfigs {
|
||||
k, err := kubernetes.New(log.Base(), c)
|
||||
k, err := kubernetes.New(logger, c)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot create Kubernetes discovery: %s", err)
|
||||
logger.Errorf("Cannot create Kubernetes discovery: %s", err)
|
||||
continue
|
||||
}
|
||||
app("kubernetes", i, k)
|
||||
}
|
||||
for i, c := range cfg.ServersetSDConfigs {
|
||||
app("serverset", i, zookeeper.NewServersetDiscovery(c))
|
||||
app("serverset", i, zookeeper.NewServersetDiscovery(c, logger))
|
||||
}
|
||||
for i, c := range cfg.NerveSDConfigs {
|
||||
app("nerve", i, zookeeper.NewNerveDiscovery(c))
|
||||
app("nerve", i, zookeeper.NewNerveDiscovery(c, logger))
|
||||
}
|
||||
for i, c := range cfg.EC2SDConfigs {
|
||||
app("ec2", i, ec2.NewDiscovery(c))
|
||||
app("ec2", i, ec2.NewDiscovery(c, logger))
|
||||
}
|
||||
for i, c := range cfg.GCESDConfigs {
|
||||
gced, err := gce.NewDiscovery(c)
|
||||
for i, c := range cfg.OpenstackSDConfigs {
|
||||
openstackd, err := openstack.NewDiscovery(c)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot initialize GCE discovery: %s", err)
|
||||
log.Errorf("Cannot initialize OpenStack discovery: %s", err)
|
||||
continue
|
||||
}
|
||||
app("openstack", i, openstackd)
|
||||
}
|
||||
|
||||
for i, c := range cfg.GCESDConfigs {
|
||||
gced, err := gce.NewDiscovery(c, logger)
|
||||
if err != nil {
|
||||
logger.Errorf("Cannot initialize GCE discovery: %s", err)
|
||||
continue
|
||||
}
|
||||
app("gce", i, gced)
|
||||
}
|
||||
for i, c := range cfg.AzureSDConfigs {
|
||||
app("azure", i, azure.NewDiscovery(c))
|
||||
app("azure", i, azure.NewDiscovery(c, logger))
|
||||
}
|
||||
for i, c := range cfg.TritonSDConfigs {
|
||||
t, err := triton.New(log.With("sd", "triton"), c)
|
||||
t, err := triton.New(logger.With("sd", "triton"), c)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot create Triton discovery: %s", err)
|
||||
logger.Errorf("Cannot create Triton discovery: %s", err)
|
||||
continue
|
||||
}
|
||||
app("triton", i, t)
|
||||
|
|
|
@ -16,6 +16,7 @@ package discovery
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/prometheus/prometheus/config"
|
||||
"golang.org/x/net/context"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
@ -53,7 +54,7 @@ static_configs:
|
|||
|
||||
go ts.Run(ctx)
|
||||
|
||||
ts.UpdateProviders(ProvidersFromConfig(*cfg))
|
||||
ts.UpdateProviders(ProvidersFromConfig(*cfg, log.Base()))
|
||||
<-called
|
||||
|
||||
verifyPresence(ts.tgroups, "static/0/0", true)
|
||||
|
@ -67,7 +68,7 @@ static_configs:
|
|||
t.Fatalf("Unable to load YAML config sTwo: %s", err)
|
||||
}
|
||||
|
||||
ts.UpdateProviders(ProvidersFromConfig(*cfg))
|
||||
ts.UpdateProviders(ProvidersFromConfig(*cfg, log.Base()))
|
||||
<-called
|
||||
|
||||
verifyPresence(ts.tgroups, "static/0/0", true)
|
||||
|
|
|
@ -66,10 +66,11 @@ type Discovery struct {
|
|||
interval time.Duration
|
||||
port int
|
||||
qtype uint16
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// NewDiscovery returns a new Discovery which periodically refreshes its targets.
|
||||
func NewDiscovery(conf *config.DNSSDConfig) *Discovery {
|
||||
func NewDiscovery(conf *config.DNSSDConfig, logger log.Logger) *Discovery {
|
||||
qtype := dns.TypeSRV
|
||||
switch strings.ToUpper(conf.Type) {
|
||||
case "A":
|
||||
|
@ -84,6 +85,7 @@ func NewDiscovery(conf *config.DNSSDConfig) *Discovery {
|
|||
interval: time.Duration(conf.RefreshInterval),
|
||||
qtype: qtype,
|
||||
port: conf.Port,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,7 +114,7 @@ func (d *Discovery) refreshAll(ctx context.Context, ch chan<- []*config.TargetGr
|
|||
for _, name := range d.names {
|
||||
go func(n string) {
|
||||
if err := d.refresh(ctx, n, ch); err != nil {
|
||||
log.Errorf("Error refreshing DNS targets: %s", err)
|
||||
d.logger.Errorf("Error refreshing DNS targets: %s", err)
|
||||
}
|
||||
wg.Done()
|
||||
}(name)
|
||||
|
@ -122,7 +124,7 @@ func (d *Discovery) refreshAll(ctx context.Context, ch chan<- []*config.TargetGr
|
|||
}
|
||||
|
||||
func (d *Discovery) refresh(ctx context.Context, name string, ch chan<- []*config.TargetGroup) error {
|
||||
response, err := lookupAll(name, d.qtype)
|
||||
response, err := lookupAll(name, d.qtype, d.logger)
|
||||
dnsSDLookupsCount.Inc()
|
||||
if err != nil {
|
||||
dnsSDLookupFailuresCount.Inc()
|
||||
|
@ -147,7 +149,7 @@ func (d *Discovery) refresh(ctx context.Context, name string, ch chan<- []*confi
|
|||
case *dns.AAAA:
|
||||
target = hostPort(addr.AAAA.String(), d.port)
|
||||
default:
|
||||
log.Warnf("%q is not a valid SRV record", record)
|
||||
d.logger.Warnf("%q is not a valid SRV record", record)
|
||||
continue
|
||||
|
||||
}
|
||||
|
@ -167,7 +169,7 @@ func (d *Discovery) refresh(ctx context.Context, name string, ch chan<- []*confi
|
|||
return nil
|
||||
}
|
||||
|
||||
func lookupAll(name string, qtype uint16) (*dns.Msg, error) {
|
||||
func lookupAll(name string, qtype uint16, logger log.Logger) (*dns.Msg, error) {
|
||||
conf, err := dns.ClientConfigFromFile(resolvConf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not load resolv.conf: %s", err)
|
||||
|
@ -181,7 +183,7 @@ func lookupAll(name string, qtype uint16) (*dns.Msg, error) {
|
|||
for _, lname := range conf.NameList(name) {
|
||||
response, err = lookup(lname, qtype, client, servAddr, false)
|
||||
if err != nil {
|
||||
log.
|
||||
logger.
|
||||
With("server", server).
|
||||
With("name", name).
|
||||
With("reason", err).
|
||||
|
|
|
@ -72,11 +72,12 @@ type Discovery struct {
|
|||
interval time.Duration
|
||||
profile string
|
||||
port int
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// NewDiscovery returns a new EC2Discovery which periodically refreshes its targets.
|
||||
func NewDiscovery(conf *config.EC2SDConfig) *Discovery {
|
||||
creds := credentials.NewStaticCredentials(conf.AccessKey, conf.SecretKey, "")
|
||||
func NewDiscovery(conf *config.EC2SDConfig, logger log.Logger) *Discovery {
|
||||
creds := credentials.NewStaticCredentials(conf.AccessKey, string(conf.SecretKey), "")
|
||||
if conf.AccessKey == "" && conf.SecretKey == "" {
|
||||
creds = nil
|
||||
}
|
||||
|
@ -88,6 +89,7 @@ func NewDiscovery(conf *config.EC2SDConfig) *Discovery {
|
|||
profile: conf.Profile,
|
||||
interval: time.Duration(conf.RefreshInterval),
|
||||
port: conf.Port,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -99,7 +101,7 @@ func (d *Discovery) Run(ctx context.Context, ch chan<- []*config.TargetGroup) {
|
|||
// Get an initial set right away.
|
||||
tg, err := d.refresh()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
d.logger.Error(err)
|
||||
} else {
|
||||
select {
|
||||
case ch <- []*config.TargetGroup{tg}:
|
||||
|
@ -113,7 +115,7 @@ func (d *Discovery) Run(ctx context.Context, ch chan<- []*config.TargetGroup) {
|
|||
case <-ticker.C:
|
||||
tg, err := d.refresh()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
d.logger.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
@ -63,13 +63,15 @@ type Discovery struct {
|
|||
// and how many target groups they contained.
|
||||
// This is used to detect deleted target groups.
|
||||
lastRefresh map[string]int
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// NewDiscovery returns a new file discovery for the given paths.
|
||||
func NewDiscovery(conf *config.FileSDConfig) *Discovery {
|
||||
func NewDiscovery(conf *config.FileSDConfig, logger log.Logger) *Discovery {
|
||||
return &Discovery{
|
||||
paths: conf.Files,
|
||||
interval: time.Duration(conf.RefreshInterval),
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,7 +81,7 @@ func (d *Discovery) listFiles() []string {
|
|||
for _, p := range d.paths {
|
||||
files, err := filepath.Glob(p)
|
||||
if err != nil {
|
||||
log.Errorf("Error expanding glob %q: %s", p, err)
|
||||
d.logger.Errorf("Error expanding glob %q: %s", p, err)
|
||||
continue
|
||||
}
|
||||
paths = append(paths, files...)
|
||||
|
@ -100,7 +102,7 @@ func (d *Discovery) watchFiles() {
|
|||
p = "./"
|
||||
}
|
||||
if err := d.watcher.Add(p); err != nil {
|
||||
log.Errorf("Error adding file watch for %q: %s", p, err)
|
||||
d.logger.Errorf("Error adding file watch for %q: %s", p, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -111,7 +113,7 @@ func (d *Discovery) Run(ctx context.Context, ch chan<- []*config.TargetGroup) {
|
|||
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Errorf("Error creating file watcher: %s", err)
|
||||
d.logger.Errorf("Error creating file watcher: %s", err)
|
||||
return
|
||||
}
|
||||
d.watcher = watcher
|
||||
|
@ -149,7 +151,7 @@ func (d *Discovery) Run(ctx context.Context, ch chan<- []*config.TargetGroup) {
|
|||
|
||||
case err := <-d.watcher.Errors:
|
||||
if err != nil {
|
||||
log.Errorf("Error on file watch: %s", err)
|
||||
d.logger.Errorf("Error on file watch: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -157,7 +159,7 @@ func (d *Discovery) Run(ctx context.Context, ch chan<- []*config.TargetGroup) {
|
|||
|
||||
// stop shuts down the file watcher.
|
||||
func (d *Discovery) stop() {
|
||||
log.Debugf("Stopping file discovery for %s...", d.paths)
|
||||
d.logger.Debugf("Stopping file discovery for %s...", d.paths)
|
||||
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
|
@ -175,10 +177,10 @@ func (d *Discovery) stop() {
|
|||
}
|
||||
}()
|
||||
if err := d.watcher.Close(); err != nil {
|
||||
log.Errorf("Error closing file watcher for %s: %s", d.paths, err)
|
||||
d.logger.Errorf("Error closing file watcher for %s: %s", d.paths, err)
|
||||
}
|
||||
|
||||
log.Debugf("File discovery for %s stopped.", d.paths)
|
||||
d.logger.Debugf("File discovery for %s stopped.", d.paths)
|
||||
}
|
||||
|
||||
// refresh reads all files matching the discovery's patterns and sends the respective
|
||||
|
@ -194,7 +196,7 @@ func (d *Discovery) refresh(ctx context.Context, ch chan<- []*config.TargetGroup
|
|||
tgroups, err := readFile(p)
|
||||
if err != nil {
|
||||
fileSDReadErrorsCount.Inc()
|
||||
log.Errorf("Error reading file %q: %s", p, err)
|
||||
d.logger.Errorf("Error reading file %q: %s", p, err)
|
||||
// Prevent deletion down below.
|
||||
ref[p] = d.lastRefresh[p]
|
||||
continue
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/prometheus/common/model"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
|
@ -41,7 +42,7 @@ func testFileSD(t *testing.T, ext string) {
|
|||
conf.RefreshInterval = model.Duration(1 * time.Hour)
|
||||
|
||||
var (
|
||||
fsd = NewDiscovery(&conf)
|
||||
fsd = NewDiscovery(&conf, log.Base())
|
||||
ch = make(chan []*config.TargetGroup)
|
||||
ctx, cancel = context.WithCancel(context.Background())
|
||||
)
|
||||
|
@ -60,7 +61,7 @@ func testFileSD(t *testing.T, ext string) {
|
|||
}
|
||||
defer newf.Close()
|
||||
|
||||
f, err := os.Open("fixtures/target_groups" + ext)
|
||||
f, err := os.Open("fixtures/valid" + ext)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -76,10 +76,11 @@ type Discovery struct {
|
|||
interval time.Duration
|
||||
port int
|
||||
tagSeparator string
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// NewDiscovery returns a new Discovery which periodically refreshes its targets.
|
||||
func NewDiscovery(conf *config.GCESDConfig) (*Discovery, error) {
|
||||
func NewDiscovery(conf *config.GCESDConfig, logger log.Logger) (*Discovery, error) {
|
||||
gd := &Discovery{
|
||||
project: conf.Project,
|
||||
zone: conf.Zone,
|
||||
|
@ -87,6 +88,7 @@ func NewDiscovery(conf *config.GCESDConfig) (*Discovery, error) {
|
|||
interval: time.Duration(conf.RefreshInterval),
|
||||
port: conf.Port,
|
||||
tagSeparator: conf.TagSeparator,
|
||||
logger: logger,
|
||||
}
|
||||
var err error
|
||||
gd.client, err = google.DefaultClient(oauth2.NoContext, compute.ComputeReadonlyScope)
|
||||
|
@ -106,7 +108,7 @@ func (d *Discovery) Run(ctx context.Context, ch chan<- []*config.TargetGroup) {
|
|||
// Get an initial set right away.
|
||||
tg, err := d.refresh()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
d.logger.Error(err)
|
||||
} else {
|
||||
select {
|
||||
case ch <- []*config.TargetGroup{tg}:
|
||||
|
@ -122,7 +124,7 @@ func (d *Discovery) Run(ctx context.Context, ch chan<- []*config.TargetGroup) {
|
|||
case <-ticker.C:
|
||||
tg, err := d.refresh()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
d.logger.Error(err)
|
||||
continue
|
||||
}
|
||||
select {
|
||||
|
|
|
@ -124,7 +124,7 @@ func New(l log.Logger, conf *config.KubernetesSDConfig) (*Discovery, error) {
|
|||
Insecure: conf.TLSConfig.InsecureSkipVerify,
|
||||
},
|
||||
}
|
||||
token := conf.BearerToken
|
||||
token := string(conf.BearerToken)
|
||||
if conf.BearerTokenFile != "" {
|
||||
bf, err := ioutil.ReadFile(conf.BearerTokenFile)
|
||||
if err != nil {
|
||||
|
@ -136,7 +136,7 @@ func New(l log.Logger, conf *config.KubernetesSDConfig) (*Discovery, error) {
|
|||
|
||||
if conf.BasicAuth != nil {
|
||||
kcfg.Username = conf.BasicAuth.Username
|
||||
kcfg.Password = conf.BasicAuth.Password
|
||||
kcfg.Password = string(conf.BasicAuth.Password)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -85,16 +85,17 @@ type Discovery struct {
|
|||
lastRefresh map[string]*config.TargetGroup
|
||||
appsClient AppListClient
|
||||
token string
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// NewDiscovery returns a new Marathon Discovery.
|
||||
func NewDiscovery(conf *config.MarathonSDConfig) (*Discovery, error) {
|
||||
func NewDiscovery(conf *config.MarathonSDConfig, logger log.Logger) (*Discovery, error) {
|
||||
tls, err := httputil.NewTLSConfig(conf.TLSConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token := conf.BearerToken
|
||||
token := string(conf.BearerToken)
|
||||
if conf.BearerTokenFile != "" {
|
||||
bf, err := ioutil.ReadFile(conf.BearerTokenFile)
|
||||
if err != nil {
|
||||
|
@ -116,6 +117,7 @@ func NewDiscovery(conf *config.MarathonSDConfig) (*Discovery, error) {
|
|||
refreshInterval: time.Duration(conf.RefreshInterval),
|
||||
appsClient: fetchApps,
|
||||
token: token,
|
||||
logger: logger,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -128,7 +130,7 @@ func (d *Discovery) Run(ctx context.Context, ch chan<- []*config.TargetGroup) {
|
|||
case <-time.After(d.refreshInterval):
|
||||
err := d.updateServices(ctx, ch)
|
||||
if err != nil {
|
||||
log.Errorf("Error while updating services: %s", err)
|
||||
d.logger.Errorf("Error while updating services: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -167,7 +169,7 @@ func (d *Discovery) updateServices(ctx context.Context, ch chan<- []*config.Targ
|
|||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case ch <- []*config.TargetGroup{{Source: source}}:
|
||||
log.Debugf("Removing group for %s", source)
|
||||
d.logger.Debugf("Removing group for %s", source)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/prometheus/common/model"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
|
@ -32,7 +33,7 @@ var (
|
|||
)
|
||||
|
||||
func testUpdateServices(client AppListClient, ch chan []*config.TargetGroup) error {
|
||||
md, err := NewDiscovery(&conf)
|
||||
md, err := NewDiscovery(&conf, log.Base())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -140,7 +141,7 @@ func TestMarathonSDSendGroup(t *testing.T) {
|
|||
|
||||
func TestMarathonSDRemoveApp(t *testing.T) {
|
||||
var ch = make(chan []*config.TargetGroup, 1)
|
||||
md, err := NewDiscovery(&conf)
|
||||
md, err := NewDiscovery(&conf, log.Base())
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
|
@ -176,7 +177,7 @@ func TestMarathonSDRunAndStop(t *testing.T) {
|
|||
ch = make(chan []*config.TargetGroup)
|
||||
doneCh = make(chan error)
|
||||
)
|
||||
md, err := NewDiscovery(&conf)
|
||||
md, err := NewDiscovery(&conf, log.Base())
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
|
|
435
discovery/openstack/mock.go
Normal file
435
discovery/openstack/mock.go
Normal file
|
@ -0,0 +1,435 @@
|
|||
// Copyright 2017 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package openstack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// SDMock is the interface for the OpenStack mock
|
||||
type SDMock struct {
|
||||
t *testing.T
|
||||
Server *httptest.Server
|
||||
Mux *http.ServeMux
|
||||
}
|
||||
|
||||
// NewSDMock returns a new SDMock.
|
||||
func NewSDMock(t *testing.T) *SDMock {
|
||||
return &SDMock{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
// Endpoint returns the URI to the mock server
|
||||
func (m *SDMock) Endpoint() string {
|
||||
return m.Server.URL + "/"
|
||||
}
|
||||
|
||||
// Setup creates the mock server
|
||||
func (m *SDMock) Setup() {
|
||||
m.Mux = http.NewServeMux()
|
||||
m.Server = httptest.NewServer(m.Mux)
|
||||
}
|
||||
|
||||
// ShutdownServer creates the mock server
|
||||
func (m *SDMock) ShutdownServer() {
|
||||
m.Server.Close()
|
||||
}
|
||||
|
||||
const tokenID = "cbc36478b0bd8e67e89469c7749d4127"
|
||||
|
||||
func testMethod(t *testing.T, r *http.Request, expected string) {
|
||||
if expected != r.Method {
|
||||
t.Errorf("Request method = %v, expected %v", r.Method, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func testHeader(t *testing.T, r *http.Request, header string, expected string) {
|
||||
if actual := r.Header.Get(header); expected != actual {
|
||||
t.Errorf("Header %s = %s, expected %s", header, actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleVersionsSuccessfully mocks version call
|
||||
func (m *SDMock) HandleVersionsSuccessfully() {
|
||||
m.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, `
|
||||
{
|
||||
"versions": {
|
||||
"values": [
|
||||
{
|
||||
"status": "stable",
|
||||
"id": "v3.0",
|
||||
"links": [
|
||||
{ "href": "%s", "rel": "self" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"status": "stable",
|
||||
"id": "v2.0",
|
||||
"links": [
|
||||
{ "href": "%s", "rel": "self" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`, m.Endpoint()+"v3/", m.Endpoint()+"v2.0/")
|
||||
})
|
||||
}
|
||||
|
||||
// HandleAuthSuccessfully mocks auth call
|
||||
func (m *SDMock) HandleAuthSuccessfully() {
|
||||
m.Mux.HandleFunc("/v3/auth/tokens", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("X-Subject-Token", tokenID)
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
fmt.Fprintf(w, `
|
||||
{
|
||||
"token": {
|
||||
"audit_ids": ["VcxU2JYqT8OzfUVvrjEITQ", "qNUTIJntTzO1-XUk5STybw"],
|
||||
"catalog": [
|
||||
{
|
||||
"endpoints": [
|
||||
{
|
||||
"id": "39dc322ce86c4111b4f06c2eeae0841b",
|
||||
"interface": "public",
|
||||
"region": "RegionOne",
|
||||
"url": "http://localhost:5000"
|
||||
},
|
||||
{
|
||||
"id": "ec642f27474842e78bf059f6c48f4e99",
|
||||
"interface": "internal",
|
||||
"region": "RegionOne",
|
||||
"url": "http://localhost:5000"
|
||||
},
|
||||
{
|
||||
"id": "c609fc430175452290b62a4242e8a7e8",
|
||||
"interface": "admin",
|
||||
"region": "RegionOne",
|
||||
"url": "http://localhost:35357"
|
||||
}
|
||||
],
|
||||
"id": "4363ae44bdf34a3981fde3b823cb9aa2",
|
||||
"type": "identity",
|
||||
"name": "keystone"
|
||||
},
|
||||
{
|
||||
"endpoints": [
|
||||
{
|
||||
"id": "e2ffee808abc4a60916715b1d4b489dd",
|
||||
"interface": "public",
|
||||
"region": "RegionOne",
|
||||
"region_id": "RegionOne",
|
||||
"url": "%s"
|
||||
}
|
||||
],
|
||||
"id": "b7f2a5b1a019459cb956e43a8cb41e31",
|
||||
"type": "compute"
|
||||
}
|
||||
|
||||
],
|
||||
"expires_at": "2013-02-27T18:30:59.999999Z",
|
||||
"is_domain": false,
|
||||
"issued_at": "2013-02-27T16:30:59.999999Z",
|
||||
"methods": [
|
||||
"password"
|
||||
],
|
||||
"project": {
|
||||
"domain": {
|
||||
"id": "1789d1",
|
||||
"name": "example.com"
|
||||
},
|
||||
"id": "263fd9",
|
||||
"name": "project-x"
|
||||
},
|
||||
"roles": [
|
||||
{
|
||||
"id": "76e72a",
|
||||
"name": "admin"
|
||||
},
|
||||
{
|
||||
"id": "f4f392",
|
||||
"name": "member"
|
||||
}
|
||||
],
|
||||
"user": {
|
||||
"domain": {
|
||||
"id": "1789d1",
|
||||
"name": "example.com"
|
||||
},
|
||||
"id": "0ca8f6",
|
||||
"name": "Joe",
|
||||
"password_expires_at": "2016-11-06T15:32:17.000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
`, m.Endpoint())
|
||||
})
|
||||
}
|
||||
|
||||
const serverListBody = `
|
||||
{
|
||||
"servers": [
|
||||
{
|
||||
"status": "ACTIVE",
|
||||
"updated": "2014-09-25T13:10:10Z",
|
||||
"hostId": "29d3c8c896a45aa4c34e52247875d7fefc3d94bbcc9f622b5d204362",
|
||||
"OS-EXT-SRV-ATTR:host": "devstack",
|
||||
"addresses": {
|
||||
"private": [
|
||||
{
|
||||
"OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:7c:1b:2b",
|
||||
"version": 4,
|
||||
"addr": "10.0.0.32",
|
||||
"OS-EXT-IPS:type": "fixed"
|
||||
}
|
||||
]
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"href": "http://104.130.131.164:8774/v2/fcad67a6189847c4aecfa3c81a05783b/servers/ef079b0c-e610-4dfb-b1aa-b49f07ac48e5",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/servers/ef079b0c-e610-4dfb-b1aa-b49f07ac48e5",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"key_name": null,
|
||||
"image": {
|
||||
"id": "f90f6034-2570-4974-8351-6b49732ef2eb",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/images/f90f6034-2570-4974-8351-6b49732ef2eb",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
},
|
||||
"OS-EXT-STS:task_state": null,
|
||||
"OS-EXT-STS:vm_state": "active",
|
||||
"OS-EXT-SRV-ATTR:instance_name": "instance-0000001e",
|
||||
"OS-SRV-USG:launched_at": "2014-09-25T13:10:10.000000",
|
||||
"OS-EXT-SRV-ATTR:hypervisor_hostname": "devstack",
|
||||
"flavor": {
|
||||
"id": "1",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/flavors/1",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": "ef079b0c-e610-4dfb-b1aa-b49f07ac48e5",
|
||||
"security_groups": [
|
||||
{
|
||||
"name": "default"
|
||||
}
|
||||
],
|
||||
"OS-SRV-USG:terminated_at": null,
|
||||
"OS-EXT-AZ:availability_zone": "nova",
|
||||
"user_id": "9349aff8be7545ac9d2f1d00999a23cd",
|
||||
"name": "herp",
|
||||
"created": "2014-09-25T13:10:02Z",
|
||||
"tenant_id": "fcad67a6189847c4aecfa3c81a05783b",
|
||||
"OS-DCF:diskConfig": "MANUAL",
|
||||
"os-extended-volumes:volumes_attached": [],
|
||||
"accessIPv4": "",
|
||||
"accessIPv6": "",
|
||||
"progress": 0,
|
||||
"OS-EXT-STS:power_state": 1,
|
||||
"config_drive": "",
|
||||
"metadata": {}
|
||||
},
|
||||
{
|
||||
"status": "ACTIVE",
|
||||
"updated": "2014-09-25T13:04:49Z",
|
||||
"hostId": "29d3c8c896a45aa4c34e52247875d7fefc3d94bbcc9f622b5d204362",
|
||||
"OS-EXT-SRV-ATTR:host": "devstack",
|
||||
"addresses": {
|
||||
"private": [
|
||||
{
|
||||
"OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:9e:89:be",
|
||||
"version": 4,
|
||||
"addr": "10.0.0.31",
|
||||
"OS-EXT-IPS:type": "fixed"
|
||||
}
|
||||
]
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"href": "http://104.130.131.164:8774/v2/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"key_name": null,
|
||||
"image": {
|
||||
"id": "f90f6034-2570-4974-8351-6b49732ef2eb",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/images/f90f6034-2570-4974-8351-6b49732ef2eb",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
},
|
||||
"OS-EXT-STS:task_state": null,
|
||||
"OS-EXT-STS:vm_state": "active",
|
||||
"OS-EXT-SRV-ATTR:instance_name": "instance-0000001d",
|
||||
"OS-SRV-USG:launched_at": "2014-09-25T13:04:49.000000",
|
||||
"OS-EXT-SRV-ATTR:hypervisor_hostname": "devstack",
|
||||
"flavor": {
|
||||
"id": "1",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/flavors/1",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": "9e5476bd-a4ec-4653-93d6-72c93aa682ba",
|
||||
"security_groups": [
|
||||
{
|
||||
"name": "default"
|
||||
}
|
||||
],
|
||||
"OS-SRV-USG:terminated_at": null,
|
||||
"OS-EXT-AZ:availability_zone": "nova",
|
||||
"user_id": "9349aff8be7545ac9d2f1d00999a23cd",
|
||||
"name": "derp",
|
||||
"created": "2014-09-25T13:04:41Z",
|
||||
"tenant_id": "fcad67a6189847c4aecfa3c81a05783b",
|
||||
"OS-DCF:diskConfig": "MANUAL",
|
||||
"os-extended-volumes:volumes_attached": [],
|
||||
"accessIPv4": "",
|
||||
"accessIPv6": "",
|
||||
"progress": 0,
|
||||
"OS-EXT-STS:power_state": 1,
|
||||
"config_drive": "",
|
||||
"metadata": {}
|
||||
},
|
||||
{
|
||||
"status": "ACTIVE",
|
||||
"updated": "2014-09-25T13:04:49Z",
|
||||
"hostId": "29d3c8c896a45aa4c34e52247875d7fefc3d94bbcc9f622b5d204362",
|
||||
"OS-EXT-SRV-ATTR:host": "devstack",
|
||||
"addresses": {
|
||||
"private": [
|
||||
{
|
||||
"OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:9e:89:be",
|
||||
"version": 4,
|
||||
"addr": "10.0.0.31",
|
||||
"OS-EXT-IPS:type": "fixed"
|
||||
}
|
||||
]
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"href": "http://104.130.131.164:8774/v2/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"key_name": null,
|
||||
"image": "",
|
||||
"OS-EXT-STS:task_state": null,
|
||||
"OS-EXT-STS:vm_state": "active",
|
||||
"OS-EXT-SRV-ATTR:instance_name": "instance-0000001d",
|
||||
"OS-SRV-USG:launched_at": "2014-09-25T13:04:49.000000",
|
||||
"OS-EXT-SRV-ATTR:hypervisor_hostname": "devstack",
|
||||
"flavor": {
|
||||
"id": "1",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/flavors/1",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": "9e5476bd-a4ec-4653-93d6-72c93aa682bb",
|
||||
"security_groups": [
|
||||
{
|
||||
"name": "default"
|
||||
}
|
||||
],
|
||||
"OS-SRV-USG:terminated_at": null,
|
||||
"OS-EXT-AZ:availability_zone": "nova",
|
||||
"user_id": "9349aff8be7545ac9d2f1d00999a23cd",
|
||||
"name": "merp",
|
||||
"created": "2014-09-25T13:04:41Z",
|
||||
"tenant_id": "fcad67a6189847c4aecfa3c81a05783b",
|
||||
"OS-DCF:diskConfig": "MANUAL",
|
||||
"os-extended-volumes:volumes_attached": [],
|
||||
"accessIPv4": "",
|
||||
"accessIPv6": "",
|
||||
"progress": 0,
|
||||
"OS-EXT-STS:power_state": 1,
|
||||
"config_drive": "",
|
||||
"metadata": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
// HandleServerListSuccessfully mocks server detail call
|
||||
func (m *SDMock) HandleServerListSuccessfully() {
|
||||
m.Mux.HandleFunc("/servers/detail", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(m.t, r, "GET")
|
||||
testHeader(m.t, r, "X-Auth-Token", tokenID)
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, serverListBody)
|
||||
})
|
||||
}
|
||||
|
||||
const listOutput = `
|
||||
{
|
||||
"floating_ips": [
|
||||
{
|
||||
"fixed_ip": null,
|
||||
"id": "1",
|
||||
"instance_id": null,
|
||||
"ip": "10.10.10.1",
|
||||
"pool": "nova"
|
||||
},
|
||||
{
|
||||
"fixed_ip": "166.78.185.201",
|
||||
"id": "2",
|
||||
"instance_id": "ef079b0c-e610-4dfb-b1aa-b49f07ac48e5",
|
||||
"ip": "10.10.10.2",
|
||||
"pool": "nova"
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
// HandleFloatingIPListSuccessfully mocks floating ips call
|
||||
func (m *SDMock) HandleFloatingIPListSuccessfully() {
|
||||
m.Mux.HandleFunc("/os-floating-ips", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(m.t, r, "GET")
|
||||
testHeader(m.t, r, "X-Auth-Token", tokenID)
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, listOutput)
|
||||
})
|
||||
}
|
256
discovery/openstack/openstack.go
Normal file
256
discovery/openstack/openstack.go
Normal file
|
@ -0,0 +1,256 @@
|
|||
// Copyright 2017 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package openstack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/prometheus/common/model"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/prometheus/prometheus/config"
|
||||
"github.com/prometheus/prometheus/util/strutil"
|
||||
)
|
||||
|
||||
const (
|
||||
openstackLabelPrefix = model.MetaLabelPrefix + "openstack_"
|
||||
openstackLabelInstanceID = openstackLabelPrefix + "instance_id"
|
||||
openstackLabelInstanceName = openstackLabelPrefix + "instance_name"
|
||||
openstackLabelInstanceStatus = openstackLabelPrefix + "instance_status"
|
||||
openstackLabelInstanceFlavor = openstackLabelPrefix + "instance_flavor"
|
||||
openstackLabelPublicIP = openstackLabelPrefix + "public_ip"
|
||||
openstackLabelPrivateIP = openstackLabelPrefix + "private_ip"
|
||||
openstackLabelTagPrefix = openstackLabelPrefix + "tag_"
|
||||
)
|
||||
|
||||
var (
|
||||
refreshFailuresCount = prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: "prometheus_sd_openstack_refresh_failures_total",
|
||||
Help: "The number of OpenStack-SD scrape failures.",
|
||||
})
|
||||
refreshDuration = prometheus.NewSummary(
|
||||
prometheus.SummaryOpts{
|
||||
Name: "prometheus_sd_openstack_refresh_duration_seconds",
|
||||
Help: "The duration of an OpenStack-SD refresh in seconds.",
|
||||
})
|
||||
)
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(refreshFailuresCount)
|
||||
prometheus.MustRegister(refreshDuration)
|
||||
}
|
||||
|
||||
// Discovery periodically performs OpenStack-SD requests. It implements
|
||||
// the TargetProvider interface.
|
||||
type Discovery struct {
|
||||
authOpts *gophercloud.AuthOptions
|
||||
region string
|
||||
interval time.Duration
|
||||
port int
|
||||
}
|
||||
|
||||
// NewDiscovery returns a new OpenStackDiscovery which periodically refreshes its targets.
|
||||
func NewDiscovery(conf *config.OpenstackSDConfig) (*Discovery, error) {
|
||||
opts := gophercloud.AuthOptions{
|
||||
IdentityEndpoint: conf.IdentityEndpoint,
|
||||
Username: conf.Username,
|
||||
UserID: conf.UserID,
|
||||
Password: string(conf.Password),
|
||||
TenantName: conf.ProjectName,
|
||||
TenantID: conf.ProjectID,
|
||||
DomainName: conf.DomainName,
|
||||
DomainID: conf.DomainID,
|
||||
}
|
||||
|
||||
return &Discovery{
|
||||
authOpts: &opts,
|
||||
region: conf.Region,
|
||||
interval: time.Duration(conf.RefreshInterval),
|
||||
port: conf.Port,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Run implements the TargetProvider interface.
|
||||
func (d *Discovery) Run(ctx context.Context, ch chan<- []*config.TargetGroup) {
|
||||
// Get an initial set right away.
|
||||
tg, err := d.refresh()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
} else {
|
||||
select {
|
||||
case ch <- []*config.TargetGroup{tg}:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(d.interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
tg, err := d.refresh()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
select {
|
||||
case ch <- []*config.TargetGroup{tg}:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Discovery) refresh() (tg *config.TargetGroup, err error) {
|
||||
t0 := time.Now()
|
||||
defer func() {
|
||||
refreshDuration.Observe(time.Since(t0).Seconds())
|
||||
if err != nil {
|
||||
refreshFailuresCount.Inc()
|
||||
}
|
||||
}()
|
||||
|
||||
provider, err := openstack.AuthenticatedClient(*d.authOpts)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create OpenStack session: %s", err)
|
||||
}
|
||||
client, err := openstack.NewComputeV2(provider, gophercloud.EndpointOpts{
|
||||
Region: d.region,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create OpenStack compute session: %s", err)
|
||||
}
|
||||
|
||||
opts := servers.ListOpts{}
|
||||
pager := servers.List(client, opts)
|
||||
|
||||
tg = &config.TargetGroup{
|
||||
Source: fmt.Sprintf("OS_%s", d.region),
|
||||
}
|
||||
|
||||
pagerFIP := floatingips.List(client)
|
||||
floatingIPList := make(map[string][]string)
|
||||
|
||||
err = pagerFIP.EachPage(func(page pagination.Page) (bool, error) {
|
||||
result, err := floatingips.ExtractFloatingIPs(page)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
for _, ip := range result {
|
||||
// Skip not associated ips
|
||||
if ip.InstanceID != "" {
|
||||
floatingIPList[ip.InstanceID] = append(floatingIPList[ip.InstanceID], ip.IP)
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not describe floating IPs: %s", err)
|
||||
}
|
||||
|
||||
err = pager.EachPage(func(page pagination.Page) (bool, error) {
|
||||
serverList, err := servers.ExtractServers(page)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("could not extract servers: %s", err)
|
||||
}
|
||||
|
||||
for _, s := range serverList {
|
||||
labels := model.LabelSet{
|
||||
openstackLabelInstanceID: model.LabelValue(s.ID),
|
||||
}
|
||||
|
||||
for _, address := range s.Addresses {
|
||||
md, ok := address.([]interface{})
|
||||
if !ok {
|
||||
log.Warn("Invalid type for address, expected array")
|
||||
continue
|
||||
}
|
||||
|
||||
if len(md) == 0 {
|
||||
log.Debugf("Got no IP address for instance %s", s.ID)
|
||||
continue
|
||||
}
|
||||
|
||||
md1, ok := md[0].(map[string]interface{})
|
||||
if !ok {
|
||||
log.Warn("Invalid type for address, expected dict")
|
||||
continue
|
||||
}
|
||||
|
||||
addr, ok := md1["addr"].(string)
|
||||
if !ok {
|
||||
log.Warn("Invalid type for address, expected string")
|
||||
continue
|
||||
}
|
||||
|
||||
labels[openstackLabelPrivateIP] = model.LabelValue(addr)
|
||||
|
||||
addr = net.JoinHostPort(addr, fmt.Sprintf("%d", d.port))
|
||||
|
||||
labels[model.AddressLabel] = model.LabelValue(addr)
|
||||
|
||||
// Only use first private IP
|
||||
break
|
||||
}
|
||||
|
||||
if val, ok := floatingIPList[s.ID]; ok {
|
||||
if len(val) > 0 {
|
||||
labels[openstackLabelPublicIP] = model.LabelValue(val[0])
|
||||
}
|
||||
}
|
||||
|
||||
labels[openstackLabelInstanceStatus] = model.LabelValue(s.Status)
|
||||
labels[openstackLabelInstanceName] = model.LabelValue(s.Name)
|
||||
id, ok := s.Flavor["id"].(string)
|
||||
if !ok {
|
||||
log.Warn("Invalid type for instance id, excepted string")
|
||||
continue
|
||||
}
|
||||
labels[openstackLabelInstanceFlavor] = model.LabelValue(id)
|
||||
|
||||
for k, v := range s.Metadata {
|
||||
name := strutil.SanitizeLabelName(k)
|
||||
labels[openstackLabelTagPrefix+model.LabelName(name)] = model.LabelValue(v)
|
||||
}
|
||||
|
||||
tg.Targets = append(tg.Targets, labels)
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not describe instances: %s", err)
|
||||
}
|
||||
|
||||
return tg, nil
|
||||
}
|
87
discovery/openstack/openstack_test.go
Normal file
87
discovery/openstack/openstack_test.go
Normal file
|
@ -0,0 +1,87 @@
|
|||
// Copyright 2017 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package openstack
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/prometheus/prometheus/config"
|
||||
)
|
||||
|
||||
type OpenstackSDTestSuite struct {
|
||||
suite.Suite
|
||||
Mock *SDMock
|
||||
}
|
||||
|
||||
func (s *OpenstackSDTestSuite) TearDownSuite() {
|
||||
s.Mock.ShutdownServer()
|
||||
}
|
||||
|
||||
func (s *OpenstackSDTestSuite) SetupTest() {
|
||||
s.Mock = NewSDMock(s.T())
|
||||
s.Mock.Setup()
|
||||
|
||||
s.Mock.HandleServerListSuccessfully()
|
||||
s.Mock.HandleFloatingIPListSuccessfully()
|
||||
|
||||
s.Mock.HandleVersionsSuccessfully()
|
||||
s.Mock.HandleAuthSuccessfully()
|
||||
}
|
||||
|
||||
func TestOpenstackSDSuite(t *testing.T) {
|
||||
suite.Run(t, new(OpenstackSDTestSuite))
|
||||
}
|
||||
|
||||
func (s *OpenstackSDTestSuite) openstackAuthSuccess() (*Discovery, error) {
|
||||
conf := config.OpenstackSDConfig{
|
||||
IdentityEndpoint: s.Mock.Endpoint(),
|
||||
Password: "test",
|
||||
Username: "test",
|
||||
DomainName: "12345",
|
||||
Region: "RegionOne",
|
||||
}
|
||||
|
||||
return NewDiscovery(&conf)
|
||||
}
|
||||
|
||||
func (s *OpenstackSDTestSuite) TestOpenstackSDRefresh() {
|
||||
d, _ := s.openstackAuthSuccess()
|
||||
|
||||
tg, err := d.refresh()
|
||||
assert.Nil(s.T(), err)
|
||||
require.NotNil(s.T(), tg)
|
||||
require.NotNil(s.T(), tg.Targets)
|
||||
require.Len(s.T(), tg.Targets, 3)
|
||||
|
||||
assert.Equal(s.T(), tg.Targets[0]["__address__"], model.LabelValue("10.0.0.32:0"))
|
||||
assert.Equal(s.T(), tg.Targets[0]["__meta_openstack_instance_flavor"], model.LabelValue("1"))
|
||||
assert.Equal(s.T(), tg.Targets[0]["__meta_openstack_instance_id"], model.LabelValue("ef079b0c-e610-4dfb-b1aa-b49f07ac48e5"))
|
||||
assert.Equal(s.T(), tg.Targets[0]["__meta_openstack_instance_name"], model.LabelValue("herp"))
|
||||
assert.Equal(s.T(), tg.Targets[0]["__meta_openstack_instance_status"], model.LabelValue("ACTIVE"))
|
||||
assert.Equal(s.T(), tg.Targets[0]["__meta_openstack_private_ip"], model.LabelValue("10.0.0.32"))
|
||||
assert.Equal(s.T(), tg.Targets[0]["__meta_openstack_public_ip"], model.LabelValue("10.10.10.2"))
|
||||
|
||||
assert.Equal(s.T(), tg.Targets[1]["__address__"], model.LabelValue("10.0.0.31:0"))
|
||||
assert.Equal(s.T(), tg.Targets[1]["__meta_openstack_instance_flavor"], model.LabelValue("1"))
|
||||
assert.Equal(s.T(), tg.Targets[1]["__meta_openstack_instance_id"], model.LabelValue("9e5476bd-a4ec-4653-93d6-72c93aa682ba"))
|
||||
assert.Equal(s.T(), tg.Targets[1]["__meta_openstack_instance_name"], model.LabelValue("derp"))
|
||||
assert.Equal(s.T(), tg.Targets[1]["__meta_openstack_instance_status"], model.LabelValue("ACTIVE"))
|
||||
assert.Equal(s.T(), tg.Targets[1]["__meta_openstack_private_ip"], model.LabelValue("10.0.0.31"))
|
||||
|
||||
}
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/samuel/go-zookeeper/zk"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/prometheus/prometheus/config"
|
||||
"github.com/prometheus/prometheus/util/strutil"
|
||||
"github.com/prometheus/prometheus/util/treecache"
|
||||
|
@ -39,17 +40,18 @@ type Discovery struct {
|
|||
updates chan treecache.ZookeeperTreeCacheEvent
|
||||
treeCaches []*treecache.ZookeeperTreeCache
|
||||
|
||||
parse func(data []byte, path string) (model.LabelSet, error)
|
||||
parse func(data []byte, path string) (model.LabelSet, error)
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// NewNerveDiscovery returns a new Discovery for the given Nerve config.
|
||||
func NewNerveDiscovery(conf *config.NerveSDConfig) *Discovery {
|
||||
return NewDiscovery(conf.Servers, time.Duration(conf.Timeout), conf.Paths, parseNerveMember)
|
||||
func NewNerveDiscovery(conf *config.NerveSDConfig, logger log.Logger) *Discovery {
|
||||
return NewDiscovery(conf.Servers, time.Duration(conf.Timeout), conf.Paths, logger, parseNerveMember)
|
||||
}
|
||||
|
||||
// NewServersetDiscovery returns a new Discovery for the given serverset config.
|
||||
func NewServersetDiscovery(conf *config.ServersetSDConfig) *Discovery {
|
||||
return NewDiscovery(conf.Servers, time.Duration(conf.Timeout), conf.Paths, parseServersetMember)
|
||||
func NewServersetDiscovery(conf *config.ServersetSDConfig, logger log.Logger) *Discovery {
|
||||
return NewDiscovery(conf.Servers, time.Duration(conf.Timeout), conf.Paths, logger, parseServersetMember)
|
||||
}
|
||||
|
||||
// NewDiscovery returns a new discovery along Zookeeper parses with
|
||||
|
@ -58,6 +60,7 @@ func NewDiscovery(
|
|||
srvs []string,
|
||||
timeout time.Duration,
|
||||
paths []string,
|
||||
logger log.Logger,
|
||||
pf func(data []byte, path string) (model.LabelSet, error),
|
||||
) *Discovery {
|
||||
conn, _, err := zk.Connect(srvs, timeout)
|
||||
|
@ -71,6 +74,7 @@ func NewDiscovery(
|
|||
updates: updates,
|
||||
sources: map[string]*config.TargetGroup{},
|
||||
parse: pf,
|
||||
logger: logger,
|
||||
}
|
||||
for _, path := range paths {
|
||||
sd.treeCaches = append(sd.treeCaches, treecache.NewZookeeperTreeCache(conn, path, updates))
|
||||
|
|
|
@ -104,7 +104,7 @@ func (c *Client) Write(samples model.Samples) error {
|
|||
func (c *Client) Read(req *remote.ReadRequest) (*remote.ReadResponse, error) {
|
||||
labelsToSeries := map[string]*remote.TimeSeries{}
|
||||
for _, q := range req.Queries {
|
||||
command, err := buildCommand(q)
|
||||
command, err := c.buildCommand(q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -134,7 +134,7 @@ func (c *Client) Read(req *remote.ReadRequest) (*remote.ReadResponse, error) {
|
|||
return &resp, nil
|
||||
}
|
||||
|
||||
func buildCommand(q *remote.Query) (string, error) {
|
||||
func (c *Client) buildCommand(q *remote.Query) (string, error) {
|
||||
matchers := make([]string, 0, len(q.Matchers))
|
||||
// If we don't find a metric name matcher, query all metrics
|
||||
// (InfluxDB measurements) by default.
|
||||
|
@ -143,9 +143,9 @@ func buildCommand(q *remote.Query) (string, error) {
|
|||
if m.Name == model.MetricNameLabel {
|
||||
switch m.Type {
|
||||
case remote.MatchType_EQUAL:
|
||||
from = fmt.Sprintf("FROM %q", m.Value)
|
||||
from = fmt.Sprintf("FROM %q.%q", c.retentionPolicy, m.Value)
|
||||
case remote.MatchType_REGEX_MATCH:
|
||||
from = fmt.Sprintf("FROM /^%s$/", escapeSlashes(m.Value))
|
||||
from = fmt.Sprintf("FROM %q./^%s$/", c.retentionPolicy, escapeSlashes(m.Value))
|
||||
default:
|
||||
// TODO: Figure out how to support these efficiently.
|
||||
return "", fmt.Errorf("non-equal or regex-non-equal matchers are not supported on the metric name yet")
|
||||
|
|
|
@ -113,6 +113,7 @@ type Notifier struct {
|
|||
|
||||
alertmanagers []*alertmanagerSet
|
||||
cancelDiscovery func()
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// Options are the configurable parameters of a Handler.
|
||||
|
@ -204,7 +205,7 @@ func newAlertMetrics(r prometheus.Registerer, queueCap int, queueLen, alertmanag
|
|||
}
|
||||
|
||||
// New constructs a new Notifier.
|
||||
func New(o *Options) *Notifier {
|
||||
func New(o *Options, logger log.Logger) *Notifier {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
if o.Do == nil {
|
||||
|
@ -217,6 +218,7 @@ func New(o *Options) *Notifier {
|
|||
cancel: cancel,
|
||||
more: make(chan struct{}, 1),
|
||||
opts: o,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
queueLenFunc := func() float64 { return float64(n.queueLen()) }
|
||||
|
@ -237,7 +239,7 @@ func (n *Notifier) ApplyConfig(conf *config.Config) error {
|
|||
ctx, cancel := context.WithCancel(n.ctx)
|
||||
|
||||
for _, cfg := range conf.AlertingConfig.AlertmanagerConfigs {
|
||||
ams, err := newAlertmanagerSet(cfg)
|
||||
ams, err := newAlertmanagerSet(cfg, n.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -251,7 +253,7 @@ func (n *Notifier) ApplyConfig(conf *config.Config) error {
|
|||
// old ones.
|
||||
for _, ams := range amSets {
|
||||
go ams.ts.Run(ctx)
|
||||
ams.ts.UpdateProviders(discovery.ProvidersFromConfig(ams.cfg.ServiceDiscoveryConfig))
|
||||
ams.ts.UpdateProviders(discovery.ProvidersFromConfig(ams.cfg.ServiceDiscoveryConfig, n.logger))
|
||||
}
|
||||
if n.cancelDiscovery != nil {
|
||||
n.cancelDiscovery()
|
||||
|
@ -335,7 +337,7 @@ func (n *Notifier) Send(alerts ...*Alert) {
|
|||
if d := len(alerts) - n.opts.QueueCapacity; d > 0 {
|
||||
alerts = alerts[d:]
|
||||
|
||||
log.Warnf("Alert batch larger than queue capacity, dropping %d alerts", d)
|
||||
n.logger.Warnf("Alert batch larger than queue capacity, dropping %d alerts", d)
|
||||
n.metrics.dropped.Add(float64(d))
|
||||
}
|
||||
|
||||
|
@ -344,7 +346,7 @@ func (n *Notifier) Send(alerts ...*Alert) {
|
|||
if d := (len(n.queue) + len(alerts)) - n.opts.QueueCapacity; d > 0 {
|
||||
n.queue = n.queue[d:]
|
||||
|
||||
log.Warnf("Alert notification queue full, dropping %d alerts", d)
|
||||
n.logger.Warnf("Alert notification queue full, dropping %d alerts", d)
|
||||
n.metrics.dropped.Add(float64(d))
|
||||
}
|
||||
n.queue = append(n.queue, alerts...)
|
||||
|
@ -402,7 +404,7 @@ func (n *Notifier) sendAll(alerts ...*Alert) bool {
|
|||
|
||||
b, err := json.Marshal(alerts)
|
||||
if err != nil {
|
||||
log.Errorf("Encoding alerts failed: %s", err)
|
||||
n.logger.Errorf("Encoding alerts failed: %s", err)
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -427,7 +429,7 @@ func (n *Notifier) sendAll(alerts ...*Alert) bool {
|
|||
u := am.url().String()
|
||||
|
||||
if err := n.sendOne(ctx, ams.client, u, b); err != nil {
|
||||
log.With("alertmanager", u).With("count", len(alerts)).Errorf("Error sending alerts: %s", err)
|
||||
n.logger.With("alertmanager", u).With("count", len(alerts)).Errorf("Error sending alerts: %s", err)
|
||||
n.metrics.errors.WithLabelValues(u).Inc()
|
||||
} else {
|
||||
atomic.AddUint64(&numSuccess, 1)
|
||||
|
@ -466,7 +468,7 @@ func (n *Notifier) sendOne(ctx context.Context, c *http.Client, url string, b []
|
|||
|
||||
// Stop shuts down the notification handler.
|
||||
func (n *Notifier) Stop() {
|
||||
log.Info("Stopping notification handler...")
|
||||
n.logger.Info("Stopping notification handler...")
|
||||
n.cancel()
|
||||
}
|
||||
|
||||
|
@ -496,11 +498,12 @@ type alertmanagerSet struct {
|
|||
|
||||
metrics *alertMetrics
|
||||
|
||||
mtx sync.RWMutex
|
||||
ams []alertmanager
|
||||
mtx sync.RWMutex
|
||||
ams []alertmanager
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
func newAlertmanagerSet(cfg *config.AlertmanagerConfig) (*alertmanagerSet, error) {
|
||||
func newAlertmanagerSet(cfg *config.AlertmanagerConfig, logger log.Logger) (*alertmanagerSet, error) {
|
||||
client, err := httputil.NewClientFromConfig(cfg.HTTPClientConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -508,6 +511,7 @@ func newAlertmanagerSet(cfg *config.AlertmanagerConfig) (*alertmanagerSet, error
|
|||
s := &alertmanagerSet{
|
||||
client: client,
|
||||
cfg: cfg,
|
||||
logger: logger,
|
||||
}
|
||||
s.ts = discovery.NewTargetSet(s)
|
||||
|
||||
|
@ -522,7 +526,7 @@ func (s *alertmanagerSet) Sync(tgs []*config.TargetGroup) {
|
|||
for _, tg := range tgs {
|
||||
ams, err := alertmanagerFromGroup(tg, s.cfg)
|
||||
if err != nil {
|
||||
log.With("err", err).Error("generating discovered Alertmanagers failed")
|
||||
s.logger.With("err", err).Error("generating discovered Alertmanagers failed")
|
||||
continue
|
||||
}
|
||||
all = append(all, ams...)
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/prometheus/prometheus/config"
|
||||
"github.com/prometheus/prometheus/pkg/labels"
|
||||
|
@ -64,7 +65,7 @@ func TestPostPath(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestHandlerNextBatch(t *testing.T) {
|
||||
h := New(&Options{})
|
||||
h := New(&Options{}, log.Base())
|
||||
|
||||
for i := range make([]struct{}, 2*maxBatchSize+1) {
|
||||
h.queue = append(h.queue, &Alert{
|
||||
|
@ -151,7 +152,7 @@ func TestHandlerSendAll(t *testing.T) {
|
|||
defer server1.Close()
|
||||
defer server2.Close()
|
||||
|
||||
h := New(&Options{})
|
||||
h := New(&Options{}, log.Base())
|
||||
h.alertmanagers = append(h.alertmanagers, &alertmanagerSet{
|
||||
ams: []alertmanager{
|
||||
alertmanagerMock{
|
||||
|
@ -214,7 +215,7 @@ func TestCustomDo(t *testing.T) {
|
|||
Body: ioutil.NopCloser(nil),
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
}, log.Base())
|
||||
|
||||
h.sendOne(context.Background(), nil, testURL, []byte(testBody))
|
||||
|
||||
|
@ -236,7 +237,7 @@ func TestExternalLabels(t *testing.T) {
|
|||
Replacement: "c",
|
||||
},
|
||||
},
|
||||
})
|
||||
}, log.Base())
|
||||
|
||||
// This alert should get the external label attached.
|
||||
h.Send(&Alert{
|
||||
|
@ -276,7 +277,7 @@ func TestHandlerRelabel(t *testing.T) {
|
|||
Replacement: "renamed",
|
||||
},
|
||||
},
|
||||
})
|
||||
}, log.Base())
|
||||
|
||||
// This alert should be dropped due to the configuration
|
||||
h.Send(&Alert{
|
||||
|
@ -322,7 +323,9 @@ func TestHandlerQueueing(t *testing.T) {
|
|||
|
||||
h := New(&Options{
|
||||
QueueCapacity: 3 * maxBatchSize,
|
||||
})
|
||||
},
|
||||
log.Base(),
|
||||
)
|
||||
h.alertmanagers = append(h.alertmanagers, &alertmanagerSet{
|
||||
ams: []alertmanager{
|
||||
alertmanagerMock{
|
||||
|
|
|
@ -115,7 +115,7 @@ type scrapePool struct {
|
|||
}
|
||||
|
||||
func newScrapePool(ctx context.Context, cfg *config.ScrapeConfig, app Appendable) *scrapePool {
|
||||
client, err := NewHTTPClient(cfg.HTTPClientConfig)
|
||||
client, err := httputil.NewClientFromConfig(cfg.HTTPClientConfig)
|
||||
if err != nil {
|
||||
// Any errors that could occur here should be caught during config validation.
|
||||
log.Errorf("Error creating HTTP client for job %q: %s", cfg.JobName, err)
|
||||
|
|
|
@ -17,9 +17,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -32,7 +30,6 @@ import (
|
|||
"github.com/prometheus/prometheus/pkg/relabel"
|
||||
"github.com/prometheus/prometheus/pkg/value"
|
||||
"github.com/prometheus/prometheus/storage"
|
||||
"github.com/prometheus/prometheus/util/httputil"
|
||||
)
|
||||
|
||||
// TargetHealth describes the health state of a target.
|
||||
|
@ -70,44 +67,6 @@ func NewTarget(labels, discoveredLabels labels.Labels, params url.Values) *Targe
|
|||
}
|
||||
}
|
||||
|
||||
// NewHTTPClient returns a new HTTP client configured for the given scrape configuration.
|
||||
func NewHTTPClient(cfg config.HTTPClientConfig) (*http.Client, error) {
|
||||
tlsConfig, err := httputil.NewTLSConfig(cfg.TLSConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// The only timeout we care about is the configured scrape timeout.
|
||||
// It is applied on request. So we leave out any timings here.
|
||||
var rt http.RoundTripper = &http.Transport{
|
||||
Proxy: http.ProxyURL(cfg.ProxyURL.URL),
|
||||
MaxIdleConns: 10000,
|
||||
TLSClientConfig: tlsConfig,
|
||||
DisableCompression: true,
|
||||
}
|
||||
|
||||
// If a bearer token is provided, create a round tripper that will set the
|
||||
// Authorization header correctly on each request.
|
||||
bearerToken := cfg.BearerToken
|
||||
if len(bearerToken) == 0 && len(cfg.BearerTokenFile) > 0 {
|
||||
b, err := ioutil.ReadFile(cfg.BearerTokenFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read bearer token file %s: %s", cfg.BearerTokenFile, err)
|
||||
}
|
||||
bearerToken = strings.TrimSpace(string(b))
|
||||
}
|
||||
|
||||
if len(bearerToken) > 0 {
|
||||
rt = httputil.NewBearerAuthRoundTripper(bearerToken, rt)
|
||||
}
|
||||
|
||||
if cfg.BasicAuth != nil {
|
||||
rt = httputil.NewBasicAuthRoundTripper(cfg.BasicAuth.Username, cfg.BasicAuth.Password, rt)
|
||||
}
|
||||
|
||||
// Return a new client with the configured round tripper.
|
||||
return httputil.NewClient(rt), nil
|
||||
}
|
||||
|
||||
func (t *Target) String() string {
|
||||
return t.URL().String()
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ type TargetManager struct {
|
|||
|
||||
// Set of unqiue targets by scrape configuration.
|
||||
targetSets map[string]*targetSet
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
type targetSet struct {
|
||||
|
@ -53,16 +54,17 @@ type Appendable interface {
|
|||
}
|
||||
|
||||
// NewTargetManager creates a new TargetManager.
|
||||
func NewTargetManager(app Appendable) *TargetManager {
|
||||
func NewTargetManager(app Appendable, logger log.Logger) *TargetManager {
|
||||
return &TargetManager{
|
||||
append: app,
|
||||
targetSets: map[string]*targetSet{},
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Run starts background processing to handle target updates.
|
||||
func (tm *TargetManager) Run() {
|
||||
log.Info("Starting target manager...")
|
||||
tm.logger.Info("Starting target manager...")
|
||||
|
||||
tm.mtx.Lock()
|
||||
|
||||
|
@ -76,7 +78,7 @@ func (tm *TargetManager) Run() {
|
|||
|
||||
// Stop all background processing.
|
||||
func (tm *TargetManager) Stop() {
|
||||
log.Infoln("Stopping target manager...")
|
||||
tm.logger.Infoln("Stopping target manager...")
|
||||
|
||||
tm.mtx.Lock()
|
||||
// Cancel the base context, this will cause all target providers to shut down
|
||||
|
@ -88,7 +90,7 @@ func (tm *TargetManager) Stop() {
|
|||
// Wait for all scrape inserts to complete.
|
||||
tm.wg.Wait()
|
||||
|
||||
log.Debugln("Target manager stopped")
|
||||
tm.logger.Debugln("Target manager stopped")
|
||||
}
|
||||
|
||||
func (tm *TargetManager) reload() {
|
||||
|
@ -122,7 +124,7 @@ func (tm *TargetManager) reload() {
|
|||
} else {
|
||||
ts.sp.reload(scfg)
|
||||
}
|
||||
ts.ts.UpdateProviders(discovery.ProvidersFromConfig(scfg.ServiceDiscoveryConfig))
|
||||
ts.ts.UpdateProviders(discovery.ProvidersFromConfig(scfg.ServiceDiscoveryConfig, tm.logger))
|
||||
}
|
||||
|
||||
// Remove old target sets. Waiting for scrape pools to complete pending
|
||||
|
|
|
@ -42,14 +42,16 @@ func NewClientFromConfig(cfg config.HTTPClientConfig) (*http.Client, error) {
|
|||
// The only timeout we care about is the configured scrape timeout.
|
||||
// It is applied on request. So we leave out any timings here.
|
||||
var rt http.RoundTripper = &http.Transport{
|
||||
Proxy: http.ProxyURL(cfg.ProxyURL.URL),
|
||||
DisableKeepAlives: true,
|
||||
TLSClientConfig: tlsConfig,
|
||||
Proxy: http.ProxyURL(cfg.ProxyURL.URL),
|
||||
MaxIdleConns: 10000,
|
||||
DisableKeepAlives: false,
|
||||
TLSClientConfig: tlsConfig,
|
||||
DisableCompression: true,
|
||||
}
|
||||
|
||||
// If a bearer token is provided, create a round tripper that will set the
|
||||
// Authorization header correctly on each request.
|
||||
bearerToken := cfg.BearerToken
|
||||
bearerToken := string(cfg.BearerToken)
|
||||
if len(bearerToken) == 0 && len(cfg.BearerTokenFile) > 0 {
|
||||
b, err := ioutil.ReadFile(cfg.BearerTokenFile)
|
||||
if err != nil {
|
||||
|
@ -63,7 +65,7 @@ func NewClientFromConfig(cfg config.HTTPClientConfig) (*http.Client, error) {
|
|||
}
|
||||
|
||||
if cfg.BasicAuth != nil {
|
||||
rt = NewBasicAuthRoundTripper(cfg.BasicAuth.Username, cfg.BasicAuth.Password, rt)
|
||||
rt = NewBasicAuthRoundTripper(cfg.BasicAuth.Username, string(cfg.BasicAuth.Password), rt)
|
||||
}
|
||||
|
||||
// Return a new client with the configured round tripper.
|
||||
|
|
0
vendor/github.com/gophercloud/gophercloud/CHANGELOG.md
generated
vendored
Normal file
0
vendor/github.com/gophercloud/gophercloud/CHANGELOG.md
generated
vendored
Normal file
148
vendor/github.com/gophercloud/gophercloud/FAQ.md
generated
vendored
Normal file
148
vendor/github.com/gophercloud/gophercloud/FAQ.md
generated
vendored
Normal file
|
@ -0,0 +1,148 @@
|
|||
# Tips
|
||||
|
||||
## Implementing default logging and re-authentication attempts
|
||||
|
||||
You can implement custom logging and/or limit re-auth attempts by creating a custom HTTP client
|
||||
like the following and setting it as the provider client's HTTP Client (via the
|
||||
`gophercloud.ProviderClient.HTTPClient` field):
|
||||
|
||||
```go
|
||||
//...
|
||||
|
||||
// LogRoundTripper satisfies the http.RoundTripper interface and is used to
|
||||
// customize the default Gophercloud RoundTripper to allow for logging.
|
||||
type LogRoundTripper struct {
|
||||
rt http.RoundTripper
|
||||
numReauthAttempts int
|
||||
}
|
||||
|
||||
// newHTTPClient return a custom HTTP client that allows for logging relevant
|
||||
// information before and after the HTTP request.
|
||||
func newHTTPClient() http.Client {
|
||||
return http.Client{
|
||||
Transport: &LogRoundTripper{
|
||||
rt: http.DefaultTransport,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// RoundTrip performs a round-trip HTTP request and logs relevant information about it.
|
||||
func (lrt *LogRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
|
||||
glog.Infof("Request URL: %s\n", request.URL)
|
||||
|
||||
response, err := lrt.rt.RoundTrip(request)
|
||||
if response == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if response.StatusCode == http.StatusUnauthorized {
|
||||
if lrt.numReauthAttempts == 3 {
|
||||
return response, fmt.Errorf("Tried to re-authenticate 3 times with no success.")
|
||||
}
|
||||
lrt.numReauthAttempts++
|
||||
}
|
||||
|
||||
glog.Debugf("Response Status: %s\n", response.Status)
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
endpoint := "https://127.0.0.1/auth"
|
||||
pc := openstack.NewClient(endpoint)
|
||||
pc.HTTPClient = newHTTPClient()
|
||||
|
||||
//...
|
||||
```
|
||||
|
||||
|
||||
## Implementing custom objects
|
||||
|
||||
OpenStack request/response objects may differ among variable names or types.
|
||||
|
||||
### Custom request objects
|
||||
|
||||
To pass custom options to a request, implement the desired `<ACTION>OptsBuilder` interface. For
|
||||
example, to pass in
|
||||
|
||||
```go
|
||||
type MyCreateServerOpts struct {
|
||||
Name string
|
||||
Size int
|
||||
}
|
||||
```
|
||||
|
||||
to `servers.Create`, simply implement the `servers.CreateOptsBuilder` interface:
|
||||
|
||||
```go
|
||||
func (o MyCreateServeropts) ToServerCreateMap() (map[string]interface{}, error) {
|
||||
return map[string]interface{}{
|
||||
"name": o.Name,
|
||||
"size": o.Size,
|
||||
}, nil
|
||||
}
|
||||
```
|
||||
|
||||
create an instance of your custom options object, and pass it to `servers.Create`:
|
||||
|
||||
```go
|
||||
// ...
|
||||
myOpts := MyCreateServerOpts{
|
||||
Name: "s1",
|
||||
Size: "100",
|
||||
}
|
||||
server, err := servers.Create(computeClient, myOpts).Extract()
|
||||
// ...
|
||||
```
|
||||
|
||||
### Custom response objects
|
||||
|
||||
Some OpenStack services have extensions. Extensions that are supported in Gophercloud can be
|
||||
combined to create a custom object:
|
||||
|
||||
```go
|
||||
// ...
|
||||
type MyVolume struct {
|
||||
volumes.Volume
|
||||
tenantattr.VolumeExt
|
||||
}
|
||||
|
||||
var v struct {
|
||||
MyVolume `json:"volume"`
|
||||
}
|
||||
|
||||
err := volumes.Get(client, volID).ExtractInto(&v)
|
||||
// ...
|
||||
```
|
||||
|
||||
## Overriding default `UnmarshalJSON` method
|
||||
|
||||
For some response objects, a field may be a custom type or may be allowed to take on
|
||||
different types. In these cases, overriding the default `UnmarshalJSON` method may be
|
||||
necessary. To do this, declare the JSON `struct` field tag as "-" and create an `UnmarshalJSON`
|
||||
method on the type:
|
||||
|
||||
```go
|
||||
// ...
|
||||
type MyVolume struct {
|
||||
ID string `json: "id"`
|
||||
TimeCreated time.Time `json: "-"`
|
||||
}
|
||||
|
||||
func (r *MyVolume) UnmarshalJSON(b []byte) error {
|
||||
type tmp MyVolume
|
||||
var s struct {
|
||||
tmp
|
||||
TimeCreated gophercloud.JSONRFC3339MilliNoZ `json:"created_at"`
|
||||
}
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*r = Volume(s.tmp)
|
||||
|
||||
r.TimeCreated = time.Time(s.CreatedAt)
|
||||
|
||||
return err
|
||||
}
|
||||
// ...
|
||||
```
|
191
vendor/github.com/gophercloud/gophercloud/LICENSE
generated
vendored
Normal file
191
vendor/github.com/gophercloud/gophercloud/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,191 @@
|
|||
Copyright 2012-2013 Rackspace, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||
this file except in compliance with the License. You may obtain a copy of the
|
||||
License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations under the License.
|
||||
|
||||
------
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
32
vendor/github.com/gophercloud/gophercloud/MIGRATING.md
generated
vendored
Normal file
32
vendor/github.com/gophercloud/gophercloud/MIGRATING.md
generated
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
# 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`
|
143
vendor/github.com/gophercloud/gophercloud/README.md
generated
vendored
Normal file
143
vendor/github.com/gophercloud/gophercloud/README.md
generated
vendored
Normal file
|
@ -0,0 +1,143 @@
|
|||
# Gophercloud: an OpenStack SDK for Go
|
||||
[![Build Status](https://travis-ci.org/gophercloud/gophercloud.svg?branch=master)](https://travis-ci.org/gophercloud/gophercloud)
|
||||
[![Coverage Status](https://coveralls.io/repos/github/gophercloud/gophercloud/badge.svg?branch=master)](https://coveralls.io/github/gophercloud/gophercloud?branch=master)
|
||||
|
||||
Gophercloud is an OpenStack Go SDK.
|
||||
|
||||
## Useful links
|
||||
|
||||
* [Reference documentation](http://godoc.org/github.com/gophercloud/gophercloud)
|
||||
* [Effective Go](https://golang.org/doc/effective_go.html)
|
||||
|
||||
## How to install
|
||||
|
||||
Before installing, you need to ensure that your [GOPATH environment variable](https://golang.org/doc/code.html#GOPATH)
|
||||
is pointing to an appropriate directory where you want to install Gophercloud:
|
||||
|
||||
```bash
|
||||
mkdir $HOME/go
|
||||
export GOPATH=$HOME/go
|
||||
```
|
||||
|
||||
To protect yourself against changes in your dependencies, we highly recommend choosing a
|
||||
[dependency management solution](https://github.com/golang/go/wiki/PackageManagementTools) for
|
||||
your projects, such as [godep](https://github.com/tools/godep). Once this is set up, you can install
|
||||
Gophercloud as a dependency like so:
|
||||
|
||||
```bash
|
||||
go get github.com/gophercloud/gophercloud
|
||||
|
||||
# Edit your code to import relevant packages from "github.com/gophercloud/gophercloud"
|
||||
|
||||
godep save ./...
|
||||
```
|
||||
|
||||
This will install all the source files you need into a `Godeps/_workspace` directory, which is
|
||||
referenceable from your own source files when you use the `godep go` command.
|
||||
|
||||
## Getting started
|
||||
|
||||
### Credentials
|
||||
|
||||
Because you'll be hitting an API, you will need to retrieve your OpenStack
|
||||
credentials and either store them as environment variables or in your local Go
|
||||
files. The first method is recommended because it decouples credential
|
||||
information from source code, allowing you to push the latter to your version
|
||||
control system without any security risk.
|
||||
|
||||
You will need to retrieve the following:
|
||||
|
||||
* username
|
||||
* password
|
||||
* a valid Keystone identity URL
|
||||
|
||||
For users that have the OpenStack dashboard installed, there's a shortcut. If
|
||||
you visit the `project/access_and_security` path in Horizon and click on the
|
||||
"Download OpenStack RC File" button at the top right hand corner, you will
|
||||
download a bash file that exports all of your access details to environment
|
||||
variables. To execute the file, run `source admin-openrc.sh` and you will be
|
||||
prompted for your password.
|
||||
|
||||
### Authentication
|
||||
|
||||
Once you have access to your credentials, you can begin plugging them into
|
||||
Gophercloud. The next step is authentication, and this is handled by a base
|
||||
"Provider" struct. To get one, you can either pass in your credentials
|
||||
explicitly, or tell Gophercloud to use environment variables:
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack"
|
||||
"github.com/gophercloud/gophercloud/openstack/utils"
|
||||
)
|
||||
|
||||
// Option 1: Pass in the values yourself
|
||||
opts := gophercloud.AuthOptions{
|
||||
IdentityEndpoint: "https://openstack.example.com:5000/v2.0",
|
||||
Username: "{username}",
|
||||
Password: "{password}",
|
||||
}
|
||||
|
||||
// Option 2: Use a utility function to retrieve all your environment variables
|
||||
opts, err := openstack.AuthOptionsFromEnv()
|
||||
```
|
||||
|
||||
Once you have the `opts` variable, you can pass it in and get back a
|
||||
`ProviderClient` struct:
|
||||
|
||||
```go
|
||||
provider, err := openstack.AuthenticatedClient(opts)
|
||||
```
|
||||
|
||||
The `ProviderClient` is the top-level client that all of your OpenStack services
|
||||
derive from. The provider contains all of the authentication details that allow
|
||||
your Go code to access the API - such as the base URL and token ID.
|
||||
|
||||
### Provision a server
|
||||
|
||||
Once we have a base Provider, we inject it as a dependency into each OpenStack
|
||||
service. In order to work with the Compute API, we need a Compute service
|
||||
client; which can be created like so:
|
||||
|
||||
```go
|
||||
client, err := openstack.NewComputeV2(provider, gophercloud.EndpointOpts{
|
||||
Region: os.Getenv("OS_REGION_NAME"),
|
||||
})
|
||||
```
|
||||
|
||||
We then use this `client` for any Compute API operation we want. In our case,
|
||||
we want to provision a new server - so we invoke the `Create` method and pass
|
||||
in the flavor ID (hardware specification) and image ID (operating system) we're
|
||||
interested in:
|
||||
|
||||
```go
|
||||
import "github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
|
||||
|
||||
server, err := servers.Create(client, servers.CreateOpts{
|
||||
Name: "My new server!",
|
||||
FlavorRef: "flavor_id",
|
||||
ImageRef: "image_id",
|
||||
}).Extract()
|
||||
```
|
||||
|
||||
The above code sample creates a new server with the parameters, and embodies the
|
||||
new resource in the `server` variable (a
|
||||
[`servers.Server`](http://godoc.org/github.com/gophercloud/gophercloud) struct).
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
Have a look at the [FAQ](./FAQ.md) for some tips on customizing the way Gophercloud works.
|
||||
|
||||
## Backwards-Compatibility Guarantees
|
||||
|
||||
None. Vendor it and write tests covering the parts you use.
|
||||
|
||||
## Contributing
|
||||
|
||||
See the [contributing guide](./.github/CONTRIBUTING.md).
|
||||
|
||||
## Help and feedback
|
||||
|
||||
If you're struggling with something or have spotted a potential bug, feel free
|
||||
to submit an issue to our [bug tracker](/issues).
|
74
vendor/github.com/gophercloud/gophercloud/STYLEGUIDE.md
generated
vendored
Normal file
74
vendor/github.com/gophercloud/gophercloud/STYLEGUIDE.md
generated
vendored
Normal file
|
@ -0,0 +1,74 @@
|
|||
|
||||
## On Pull Requests
|
||||
|
||||
- Before you start a PR there needs to be a Github issue and a discussion about it
|
||||
on that issue with a core contributor, even if it's just a 'SGTM'.
|
||||
|
||||
- A PR's description must reference the issue it closes with a `For <ISSUE NUMBER>` (e.g. For #293).
|
||||
|
||||
- A PR's description must contain link(s) to the line(s) in the OpenStack
|
||||
source code (on Github) that prove(s) the PR code to be valid. Links to documentation
|
||||
are not good enough. The link(s) should be to a non-`master` branch. For example,
|
||||
a pull request implementing the creation of a Neutron v2 subnet might put the
|
||||
following link in the description:
|
||||
|
||||
https://github.com/openstack/neutron/blob/stable/mitaka/neutron/api/v2/attributes.py#L749
|
||||
|
||||
From that link, a reviewer (or user) can verify the fields in the request/response
|
||||
objects in the PR.
|
||||
|
||||
- A PR that is in-progress should have `[wip]` in front of the PR's title. When
|
||||
ready for review, remove the `[wip]` and ping a core contributor with an `@`.
|
||||
|
||||
- Forcing PRs to be small can have the effect of users submitting PRs in a hierarchical chain, with
|
||||
one depending on the next. If a PR depends on another one, it should have a [Pending #PRNUM]
|
||||
prefix in the PR title. In addition, it will be the PR submitter's responsibility to remove the
|
||||
[Pending #PRNUM] tag once the PR has been updated with the merged, dependent PR. That will
|
||||
let reviewers know it is ready to review.
|
||||
|
||||
- A PR should be small. Even if you intend on implementing an entire
|
||||
service, a PR should only be one route of that service
|
||||
(e.g. create server or get server, but not both).
|
||||
|
||||
- Unless explicitly asked, do not squash commits in the middle of a review; only
|
||||
append. It makes it difficult for the reviewer to see what's changed from one
|
||||
review to the next.
|
||||
|
||||
## On Code
|
||||
|
||||
- In re design: follow as closely as is reasonable the code already in the library.
|
||||
Most operations (e.g. create, delete) admit the same design.
|
||||
|
||||
- Unit tests and acceptance (integration) tests must be written to cover each PR.
|
||||
Tests for operations with several options (e.g. list, create) should include all
|
||||
the options in the tests. This will allow users to verify an operation on their
|
||||
own infrastructure and see an example of usage.
|
||||
|
||||
- If in doubt, ask in-line on the PR.
|
||||
|
||||
### File Structure
|
||||
|
||||
- The following should be used in most cases:
|
||||
|
||||
- `requests.go`: contains all the functions that make HTTP requests and the
|
||||
types associated with the HTTP request (parameters for URL, body, etc)
|
||||
- `results.go`: contains all the response objects and their methods
|
||||
- `urls.go`: contains the endpoints to which the requests are made
|
||||
|
||||
### Naming
|
||||
|
||||
- For methods on a type in `response.go`, the receiver should be named `r` and the
|
||||
variable into which it will be unmarshalled `s`.
|
||||
|
||||
- Functions in `requests.go`, with the exception of functions that return a
|
||||
`pagination.Pager`, should be named returns of the name `r`.
|
||||
|
||||
- Functions in `requests.go` that accept request bodies should accept as their
|
||||
last parameter an `interface` named `<Action>OptsBuilder` (eg `CreateOptsBuilder`).
|
||||
This `interface` should have at the least a method named `To<Resource><Action>Map`
|
||||
(eg `ToPortCreateMap`).
|
||||
|
||||
- Functions in `requests.go` that accept query strings should accept as their
|
||||
last parameter an `interface` named `<Action>OptsBuilder` (eg `ListOptsBuilder`).
|
||||
This `interface` should have at the least a method named `To<Resource><Action>Query`
|
||||
(eg `ToServerListQuery`).
|
327
vendor/github.com/gophercloud/gophercloud/auth_options.go
generated
vendored
Normal file
327
vendor/github.com/gophercloud/gophercloud/auth_options.go
generated
vendored
Normal file
|
@ -0,0 +1,327 @@
|
|||
package gophercloud
|
||||
|
||||
/*
|
||||
AuthOptions stores information needed to authenticate to an OpenStack Cloud.
|
||||
You can populate one manually, or use a provider's AuthOptionsFromEnv() function
|
||||
to read relevant information from the standard environment variables. Pass one
|
||||
to a provider's AuthenticatedClient function to authenticate and obtain a
|
||||
ProviderClient representing an active session on that provider.
|
||||
|
||||
Its fields are the union of those recognized by each identity implementation and
|
||||
provider.
|
||||
*/
|
||||
type AuthOptions struct {
|
||||
// IdentityEndpoint specifies the HTTP endpoint that is required to work with
|
||||
// the Identity API of the appropriate version. While it's ultimately needed by
|
||||
// all of the identity services, it will often be populated by a provider-level
|
||||
// function.
|
||||
IdentityEndpoint string `json:"-"`
|
||||
|
||||
// Username is required if using Identity V2 API. Consult with your provider's
|
||||
// control panel to discover your account's username. In Identity V3, either
|
||||
// UserID or a combination of Username and DomainID or DomainName are needed.
|
||||
Username string `json:"username,omitempty"`
|
||||
UserID string `json:"id,omitempty"`
|
||||
|
||||
Password string `json:"password,omitempty"`
|
||||
|
||||
// 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"`
|
||||
DomainName string `json:"name,omitempty"`
|
||||
|
||||
// The TenantID and TenantName fields are optional for the Identity V2 API.
|
||||
// The same fields are known as project_id and project_name in the Identity
|
||||
// V3 API, but are collected as TenantID and TenantName here in both cases.
|
||||
// Some providers allow you to specify a TenantName instead of the TenantId.
|
||||
// Some require both. Your provider's authentication policies will determine
|
||||
// how these fields influence authentication.
|
||||
// If DomainID or DomainName are provided, they will also apply to TenantName.
|
||||
// It is not currently possible to authenticate with Username and a Domain
|
||||
// and scope to a Project in a different Domain by using TenantName. To
|
||||
// accomplish that, the ProjectID will need to be provided to the TenantID
|
||||
// option.
|
||||
TenantID string `json:"tenantId,omitempty"`
|
||||
TenantName string `json:"tenantName,omitempty"`
|
||||
|
||||
// AllowReauth should be set to true if you grant permission for Gophercloud to
|
||||
// cache your credentials in memory, and to allow Gophercloud to attempt to
|
||||
// re-authenticate automatically if/when your token expires. If you set it to
|
||||
// false, it will not cache these settings, but re-authentication will not be
|
||||
// possible. This setting defaults to false.
|
||||
//
|
||||
// NOTE: The reauth function will try to re-authenticate endlessly if left unchecked.
|
||||
// The way to limit the number of attempts is to provide a custom HTTP client to the provider client
|
||||
// and provide a transport that implements the RoundTripper interface and stores the number of failed retries.
|
||||
// For an example of this, see here: https://github.com/rackspace/rack/blob/1.0.0/auth/clients.go#L311
|
||||
AllowReauth bool `json:"-"`
|
||||
|
||||
// TokenID allows users to authenticate (possibly as another user) with an
|
||||
// authentication token ID.
|
||||
TokenID string `json:"-"`
|
||||
}
|
||||
|
||||
// ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder
|
||||
// interface in the v2 tokens package
|
||||
func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) {
|
||||
// Populate the request map.
|
||||
authMap := make(map[string]interface{})
|
||||
|
||||
if opts.Username != "" {
|
||||
if opts.Password != "" {
|
||||
authMap["passwordCredentials"] = map[string]interface{}{
|
||||
"username": opts.Username,
|
||||
"password": opts.Password,
|
||||
}
|
||||
} else {
|
||||
return nil, ErrMissingInput{Argument: "Password"}
|
||||
}
|
||||
} else if opts.TokenID != "" {
|
||||
authMap["token"] = map[string]interface{}{
|
||||
"id": opts.TokenID,
|
||||
}
|
||||
} else {
|
||||
return nil, ErrMissingInput{Argument: "Username"}
|
||||
}
|
||||
|
||||
if opts.TenantID != "" {
|
||||
authMap["tenantId"] = opts.TenantID
|
||||
}
|
||||
if opts.TenantName != "" {
|
||||
authMap["tenantName"] = opts.TenantName
|
||||
}
|
||||
|
||||
return map[string]interface{}{"auth": authMap}, nil
|
||||
}
|
||||
|
||||
func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) {
|
||||
type domainReq struct {
|
||||
ID *string `json:"id,omitempty"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
type projectReq struct {
|
||||
Domain *domainReq `json:"domain,omitempty"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
ID *string `json:"id,omitempty"`
|
||||
}
|
||||
|
||||
type userReq struct {
|
||||
ID *string `json:"id,omitempty"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
Password string `json:"password"`
|
||||
Domain *domainReq `json:"domain,omitempty"`
|
||||
}
|
||||
|
||||
type passwordReq struct {
|
||||
User userReq `json:"user"`
|
||||
}
|
||||
|
||||
type tokenReq struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
type identityReq struct {
|
||||
Methods []string `json:"methods"`
|
||||
Password *passwordReq `json:"password,omitempty"`
|
||||
Token *tokenReq `json:"token,omitempty"`
|
||||
}
|
||||
|
||||
type authReq struct {
|
||||
Identity identityReq `json:"identity"`
|
||||
}
|
||||
|
||||
type request struct {
|
||||
Auth authReq `json:"auth"`
|
||||
}
|
||||
|
||||
// Populate the request structure based on the provided arguments. Create and return an error
|
||||
// if insufficient or incompatible information is present.
|
||||
var req request
|
||||
|
||||
if opts.Password == "" {
|
||||
if opts.TokenID != "" {
|
||||
// Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
|
||||
// parameters.
|
||||
if opts.Username != "" {
|
||||
return nil, ErrUsernameWithToken{}
|
||||
}
|
||||
if opts.UserID != "" {
|
||||
return nil, ErrUserIDWithToken{}
|
||||
}
|
||||
if opts.DomainID != "" {
|
||||
return nil, ErrDomainIDWithToken{}
|
||||
}
|
||||
if opts.DomainName != "" {
|
||||
return nil, ErrDomainNameWithToken{}
|
||||
}
|
||||
|
||||
// Configure the request for Token authentication.
|
||||
req.Auth.Identity.Methods = []string{"token"}
|
||||
req.Auth.Identity.Token = &tokenReq{
|
||||
ID: opts.TokenID,
|
||||
}
|
||||
} else {
|
||||
// If no password or token ID are available, authentication can't continue.
|
||||
return nil, ErrMissingPassword{}
|
||||
}
|
||||
} else {
|
||||
// Password authentication.
|
||||
req.Auth.Identity.Methods = []string{"password"}
|
||||
|
||||
// At least one of Username and UserID must be specified.
|
||||
if opts.Username == "" && opts.UserID == "" {
|
||||
return nil, ErrUsernameOrUserID{}
|
||||
}
|
||||
|
||||
if opts.Username != "" {
|
||||
// If Username is provided, UserID may not be provided.
|
||||
if opts.UserID != "" {
|
||||
return nil, ErrUsernameOrUserID{}
|
||||
}
|
||||
|
||||
// Either DomainID or DomainName must also be specified.
|
||||
if opts.DomainID == "" && opts.DomainName == "" {
|
||||
return nil, ErrDomainIDOrDomainName{}
|
||||
}
|
||||
|
||||
if opts.DomainID != "" {
|
||||
if opts.DomainName != "" {
|
||||
return nil, ErrDomainIDOrDomainName{}
|
||||
}
|
||||
|
||||
// Configure the request for Username and Password authentication with a DomainID.
|
||||
req.Auth.Identity.Password = &passwordReq{
|
||||
User: userReq{
|
||||
Name: &opts.Username,
|
||||
Password: opts.Password,
|
||||
Domain: &domainReq{ID: &opts.DomainID},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if opts.DomainName != "" {
|
||||
// Configure the request for Username and Password authentication with a DomainName.
|
||||
req.Auth.Identity.Password = &passwordReq{
|
||||
User: userReq{
|
||||
Name: &opts.Username,
|
||||
Password: opts.Password,
|
||||
Domain: &domainReq{Name: &opts.DomainName},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opts.UserID != "" {
|
||||
// If UserID is specified, neither DomainID nor DomainName may be.
|
||||
if opts.DomainID != "" {
|
||||
return nil, ErrDomainIDWithUserID{}
|
||||
}
|
||||
if opts.DomainName != "" {
|
||||
return nil, ErrDomainNameWithUserID{}
|
||||
}
|
||||
|
||||
// Configure the request for UserID and Password authentication.
|
||||
req.Auth.Identity.Password = &passwordReq{
|
||||
User: userReq{ID: &opts.UserID, Password: opts.Password},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b, err := BuildRequestBody(req, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(scope) != 0 {
|
||||
b["auth"].(map[string]interface{})["scope"] = scope
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
if scope.ProjectName != "" {
|
||||
// ProjectName provided: either DomainID or DomainName must also be supplied.
|
||||
// ProjectID may not be supplied.
|
||||
if scope.DomainID == "" && scope.DomainName == "" {
|
||||
return nil, ErrScopeDomainIDOrDomainName{}
|
||||
}
|
||||
if scope.ProjectID != "" {
|
||||
return nil, ErrScopeProjectIDOrProjectName{}
|
||||
}
|
||||
|
||||
if scope.DomainID != "" {
|
||||
// ProjectName + DomainID
|
||||
return map[string]interface{}{
|
||||
"project": map[string]interface{}{
|
||||
"name": &scope.ProjectName,
|
||||
"domain": map[string]interface{}{"id": &scope.DomainID},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
if scope.DomainName != "" {
|
||||
// ProjectName + DomainName
|
||||
return map[string]interface{}{
|
||||
"project": map[string]interface{}{
|
||||
"name": &scope.ProjectName,
|
||||
"domain": map[string]interface{}{"name": &scope.DomainName},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
} else if scope.ProjectID != "" {
|
||||
// ProjectID provided. ProjectName, DomainID, and DomainName may not be provided.
|
||||
if scope.DomainID != "" {
|
||||
return nil, ErrScopeProjectIDAlone{}
|
||||
}
|
||||
if scope.DomainName != "" {
|
||||
return nil, ErrScopeProjectIDAlone{}
|
||||
}
|
||||
|
||||
// ProjectID
|
||||
return map[string]interface{}{
|
||||
"project": map[string]interface{}{
|
||||
"id": &scope.ProjectID,
|
||||
},
|
||||
}, nil
|
||||
} else if scope.DomainID != "" {
|
||||
// DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
|
||||
if scope.DomainName != "" {
|
||||
return nil, ErrScopeDomainIDOrDomainName{}
|
||||
}
|
||||
|
||||
// DomainID
|
||||
return map[string]interface{}{
|
||||
"domain": map[string]interface{}{
|
||||
"id": &scope.DomainID,
|
||||
},
|
||||
}, nil
|
||||
} else if scope.DomainName != "" {
|
||||
return nil, ErrScopeDomainName{}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (opts AuthOptions) CanReauth() bool {
|
||||
return opts.AllowReauth
|
||||
}
|
69
vendor/github.com/gophercloud/gophercloud/doc.go
generated
vendored
Normal file
69
vendor/github.com/gophercloud/gophercloud/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
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:
|
||||
|
||||
opts := gophercloud.AuthOptions{
|
||||
IdentityEndpoint: "https://openstack.example.com:5000/v2.0",
|
||||
Username: "{username}",
|
||||
Password: "{password}",
|
||||
TenantID: "{tenant_id}",
|
||||
}
|
||||
|
||||
provider, err := openstack.AuthenticatedClient(opts)
|
||||
|
||||
Service structs are specific to a provider and handle all of the logic and
|
||||
operations for a particular OpenStack service. Examples of services include:
|
||||
Compute, Object Storage, Block Storage. In order to define one, you need to
|
||||
pass in the parent provider, like so:
|
||||
|
||||
opts := gophercloud.EndpointOpts{Region: "RegionOne"}
|
||||
|
||||
client := openstack.NewComputeV2(provider, opts)
|
||||
|
||||
Resource structs are the domain models that services make use of in order
|
||||
to work with and represent the state of API resources:
|
||||
|
||||
server, err := servers.Get(client, "{serverId}").Extract()
|
||||
|
||||
Intermediate Result structs are returned for API operations, which allow
|
||||
generic access to the HTTP headers, response body, and any errors associated
|
||||
with the network transaction. To turn a result into a usable resource struct,
|
||||
you must call the Extract method which is chained to the response, or an
|
||||
Extract function from an applicable extension:
|
||||
|
||||
result := servers.Get(client, "{serverId}")
|
||||
|
||||
// Attempt to extract the disk configuration from the OS-DCF disk config
|
||||
// extension:
|
||||
config, err := diskconfig.ExtractGet(result)
|
||||
|
||||
All requests that enumerate a collection return a Pager struct that is used to
|
||||
iterate through the results one page at a time. Use the EachPage method on that
|
||||
Pager to handle each successive Page in a closure, then use the appropriate
|
||||
extraction method from that request's package to interpret that Page as a slice
|
||||
of results:
|
||||
|
||||
err := servers.List(client, nil).EachPage(func (page pagination.Page) (bool, error) {
|
||||
s, err := servers.ExtractServers(page)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Handle the []servers.Server slice.
|
||||
|
||||
// Return "false" or an error to prematurely stop fetching new pages.
|
||||
return true, nil
|
||||
})
|
||||
|
||||
This top-level package contains utility functions and data types that are used
|
||||
throughout the provider and service packages. Of particular note for end users
|
||||
are the AuthOptions and EndpointOpts structs.
|
||||
*/
|
||||
package gophercloud
|
76
vendor/github.com/gophercloud/gophercloud/endpoint_search.go
generated
vendored
Normal file
76
vendor/github.com/gophercloud/gophercloud/endpoint_search.go
generated
vendored
Normal file
|
@ -0,0 +1,76 @@
|
|||
package gophercloud
|
||||
|
||||
// Availability indicates to whom a specific service endpoint is accessible:
|
||||
// the internet at large, internal networks only, or only to administrators.
|
||||
// Different identity services use different terminology for these. Identity v2
|
||||
// lists them as different kinds of URLs within the service catalog ("adminURL",
|
||||
// "internalURL", and "publicURL"), while v3 lists them as "Interfaces" in an
|
||||
// endpoint's response.
|
||||
type Availability string
|
||||
|
||||
const (
|
||||
// AvailabilityAdmin indicates that an endpoint is only available to
|
||||
// administrators.
|
||||
AvailabilityAdmin Availability = "admin"
|
||||
|
||||
// AvailabilityPublic indicates that an endpoint is available to everyone on
|
||||
// the internet.
|
||||
AvailabilityPublic Availability = "public"
|
||||
|
||||
// AvailabilityInternal indicates that an endpoint is only available within
|
||||
// the cluster's internal network.
|
||||
AvailabilityInternal Availability = "internal"
|
||||
)
|
||||
|
||||
// EndpointOpts specifies search criteria used by queries against an
|
||||
// OpenStack service catalog. The options must contain enough information to
|
||||
// unambiguously identify one, and only one, endpoint within the catalog.
|
||||
//
|
||||
// Usually, these are passed to service client factory functions in a provider
|
||||
// package, like "rackspace.NewComputeV2()".
|
||||
type EndpointOpts struct {
|
||||
// Type [required] is the service type for the client (e.g., "compute",
|
||||
// "object-store"). Generally, this will be supplied by the service client
|
||||
// function, but a user-given value will be honored if provided.
|
||||
Type string
|
||||
|
||||
// Name [optional] is the service name for the client (e.g., "nova") as it
|
||||
// appears in the service catalog. Services can have the same Type but a
|
||||
// different Name, which is why both Type and Name are sometimes needed.
|
||||
Name string
|
||||
|
||||
// Region [required] is the geographic region in which the endpoint resides,
|
||||
// generally specifying which datacenter should house your resources.
|
||||
// Required only for services that span multiple regions.
|
||||
Region string
|
||||
|
||||
// Availability [optional] is the visibility of the endpoint to be returned.
|
||||
// Valid types include the constants AvailabilityPublic, AvailabilityInternal,
|
||||
// or AvailabilityAdmin from this package.
|
||||
//
|
||||
// Availability is not required, and defaults to AvailabilityPublic. Not all
|
||||
// providers or services offer all Availability options.
|
||||
Availability Availability
|
||||
}
|
||||
|
||||
/*
|
||||
EndpointLocator is an internal function to be used by provider implementations.
|
||||
|
||||
It provides an implementation that locates a single endpoint from a service
|
||||
catalog for a specific ProviderClient based on user-provided EndpointOpts. The
|
||||
provider then uses it to discover related ServiceClients.
|
||||
*/
|
||||
type EndpointLocator func(EndpointOpts) (string, error)
|
||||
|
||||
// ApplyDefaults is an internal method to be used by provider implementations.
|
||||
//
|
||||
// It sets EndpointOpts fields if not already set, including a default type.
|
||||
// Currently, EndpointOpts.Availability defaults to the public endpoint.
|
||||
func (eo *EndpointOpts) ApplyDefaults(t string) {
|
||||
if eo.Type == "" {
|
||||
eo.Type = t
|
||||
}
|
||||
if eo.Availability == "" {
|
||||
eo.Availability = AvailabilityPublic
|
||||
}
|
||||
}
|
408
vendor/github.com/gophercloud/gophercloud/errors.go
generated
vendored
Normal file
408
vendor/github.com/gophercloud/gophercloud/errors.go
generated
vendored
Normal file
|
@ -0,0 +1,408 @@
|
|||
package gophercloud
|
||||
|
||||
import "fmt"
|
||||
|
||||
// BaseError is an error type that all other error types embed.
|
||||
type BaseError struct {
|
||||
DefaultErrString string
|
||||
Info string
|
||||
}
|
||||
|
||||
func (e BaseError) Error() string {
|
||||
e.DefaultErrString = "An error occurred while executing a Gophercloud request."
|
||||
return e.choseErrString()
|
||||
}
|
||||
|
||||
func (e BaseError) choseErrString() string {
|
||||
if e.Info != "" {
|
||||
return e.Info
|
||||
}
|
||||
return e.DefaultErrString
|
||||
}
|
||||
|
||||
// ErrMissingInput is the error when input is required in a particular
|
||||
// situation but not provided by the user
|
||||
type ErrMissingInput struct {
|
||||
BaseError
|
||||
Argument string
|
||||
}
|
||||
|
||||
func (e ErrMissingInput) Error() string {
|
||||
e.DefaultErrString = fmt.Sprintf("Missing input for argument [%s]", e.Argument)
|
||||
return e.choseErrString()
|
||||
}
|
||||
|
||||
// ErrInvalidInput is an error type used for most non-HTTP Gophercloud errors.
|
||||
type ErrInvalidInput struct {
|
||||
ErrMissingInput
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
func (e ErrInvalidInput) Error() string {
|
||||
e.DefaultErrString = fmt.Sprintf("Invalid input provided for argument [%s]: [%+v]", e.Argument, e.Value)
|
||||
return e.choseErrString()
|
||||
}
|
||||
|
||||
// ErrUnexpectedResponseCode is returned by the Request method when a response code other than
|
||||
// those listed in OkCodes is encountered.
|
||||
type ErrUnexpectedResponseCode struct {
|
||||
BaseError
|
||||
URL string
|
||||
Method string
|
||||
Expected []int
|
||||
Actual int
|
||||
Body []byte
|
||||
}
|
||||
|
||||
func (e ErrUnexpectedResponseCode) Error() string {
|
||||
e.DefaultErrString = fmt.Sprintf(
|
||||
"Expected HTTP response code %v when accessing [%s %s], but got %d instead\n%s",
|
||||
e.Expected, e.Method, e.URL, e.Actual, e.Body,
|
||||
)
|
||||
return e.choseErrString()
|
||||
}
|
||||
|
||||
// ErrDefault400 is the default error type returned on a 400 HTTP response code.
|
||||
type ErrDefault400 struct {
|
||||
ErrUnexpectedResponseCode
|
||||
}
|
||||
|
||||
// ErrDefault401 is the default error type returned on a 401 HTTP response code.
|
||||
type ErrDefault401 struct {
|
||||
ErrUnexpectedResponseCode
|
||||
}
|
||||
|
||||
// ErrDefault404 is the default error type returned on a 404 HTTP response code.
|
||||
type ErrDefault404 struct {
|
||||
ErrUnexpectedResponseCode
|
||||
}
|
||||
|
||||
// ErrDefault405 is the default error type returned on a 405 HTTP response code.
|
||||
type ErrDefault405 struct {
|
||||
ErrUnexpectedResponseCode
|
||||
}
|
||||
|
||||
// ErrDefault408 is the default error type returned on a 408 HTTP response code.
|
||||
type ErrDefault408 struct {
|
||||
ErrUnexpectedResponseCode
|
||||
}
|
||||
|
||||
// ErrDefault429 is the default error type returned on a 429 HTTP response code.
|
||||
type ErrDefault429 struct {
|
||||
ErrUnexpectedResponseCode
|
||||
}
|
||||
|
||||
// ErrDefault500 is the default error type returned on a 500 HTTP response code.
|
||||
type ErrDefault500 struct {
|
||||
ErrUnexpectedResponseCode
|
||||
}
|
||||
|
||||
// ErrDefault503 is the default error type returned on a 503 HTTP response code.
|
||||
type ErrDefault503 struct {
|
||||
ErrUnexpectedResponseCode
|
||||
}
|
||||
|
||||
func (e ErrDefault400) Error() string {
|
||||
return "Invalid request due to incorrect syntax or missing required parameters."
|
||||
}
|
||||
func (e ErrDefault401) Error() string {
|
||||
return "Authentication failed"
|
||||
}
|
||||
func (e ErrDefault404) Error() string {
|
||||
return "Resource not found"
|
||||
}
|
||||
func (e ErrDefault405) Error() string {
|
||||
return "Method not allowed"
|
||||
}
|
||||
func (e ErrDefault408) Error() string {
|
||||
return "The server timed out waiting for the request"
|
||||
}
|
||||
func (e ErrDefault429) Error() string {
|
||||
return "Too many requests have been sent in a given amount of time. Pause" +
|
||||
" requests, wait up to one minute, and try again."
|
||||
}
|
||||
func (e ErrDefault500) Error() string {
|
||||
return "Internal Server Error"
|
||||
}
|
||||
func (e ErrDefault503) Error() string {
|
||||
return "The service is currently unable to handle the request due to a temporary" +
|
||||
" overloading or maintenance. This is a temporary condition. Try again later."
|
||||
}
|
||||
|
||||
// Err400er is the interface resource error types implement to override the error message
|
||||
// from a 400 error.
|
||||
type Err400er interface {
|
||||
Error400(ErrUnexpectedResponseCode) error
|
||||
}
|
||||
|
||||
// Err401er is the interface resource error types implement to override the error message
|
||||
// from a 401 error.
|
||||
type Err401er interface {
|
||||
Error401(ErrUnexpectedResponseCode) error
|
||||
}
|
||||
|
||||
// Err404er is the interface resource error types implement to override the error message
|
||||
// from a 404 error.
|
||||
type Err404er interface {
|
||||
Error404(ErrUnexpectedResponseCode) error
|
||||
}
|
||||
|
||||
// Err405er is the interface resource error types implement to override the error message
|
||||
// from a 405 error.
|
||||
type Err405er interface {
|
||||
Error405(ErrUnexpectedResponseCode) error
|
||||
}
|
||||
|
||||
// Err408er is the interface resource error types implement to override the error message
|
||||
// from a 408 error.
|
||||
type Err408er interface {
|
||||
Error408(ErrUnexpectedResponseCode) error
|
||||
}
|
||||
|
||||
// Err429er is the interface resource error types implement to override the error message
|
||||
// from a 429 error.
|
||||
type Err429er interface {
|
||||
Error429(ErrUnexpectedResponseCode) error
|
||||
}
|
||||
|
||||
// Err500er is the interface resource error types implement to override the error message
|
||||
// from a 500 error.
|
||||
type Err500er interface {
|
||||
Error500(ErrUnexpectedResponseCode) error
|
||||
}
|
||||
|
||||
// Err503er is the interface resource error types implement to override the error message
|
||||
// from a 503 error.
|
||||
type Err503er interface {
|
||||
Error503(ErrUnexpectedResponseCode) error
|
||||
}
|
||||
|
||||
// ErrTimeOut is the error type returned when an operations times out.
|
||||
type ErrTimeOut struct {
|
||||
BaseError
|
||||
}
|
||||
|
||||
func (e ErrTimeOut) Error() string {
|
||||
e.DefaultErrString = "A time out occurred"
|
||||
return e.choseErrString()
|
||||
}
|
||||
|
||||
// ErrUnableToReauthenticate is the error type returned when reauthentication fails.
|
||||
type ErrUnableToReauthenticate struct {
|
||||
BaseError
|
||||
ErrOriginal error
|
||||
}
|
||||
|
||||
func (e ErrUnableToReauthenticate) Error() string {
|
||||
e.DefaultErrString = fmt.Sprintf("Unable to re-authenticate: %s", e.ErrOriginal)
|
||||
return e.choseErrString()
|
||||
}
|
||||
|
||||
// ErrErrorAfterReauthentication is the error type returned when reauthentication
|
||||
// succeeds, but an error occurs afterword (usually an HTTP error).
|
||||
type ErrErrorAfterReauthentication struct {
|
||||
BaseError
|
||||
ErrOriginal error
|
||||
}
|
||||
|
||||
func (e ErrErrorAfterReauthentication) Error() string {
|
||||
e.DefaultErrString = fmt.Sprintf("Successfully re-authenticated, but got error executing request: %s", e.ErrOriginal)
|
||||
return e.choseErrString()
|
||||
}
|
||||
|
||||
// ErrServiceNotFound is returned when no service in a service catalog matches
|
||||
// the provided EndpointOpts. This is generally returned by provider service
|
||||
// factory methods like "NewComputeV2()" and can mean that a service is not
|
||||
// enabled for your account.
|
||||
type ErrServiceNotFound struct {
|
||||
BaseError
|
||||
}
|
||||
|
||||
func (e ErrServiceNotFound) Error() string {
|
||||
e.DefaultErrString = "No suitable service could be found in the service catalog."
|
||||
return e.choseErrString()
|
||||
}
|
||||
|
||||
// ErrEndpointNotFound is returned when no available endpoints match the
|
||||
// provided EndpointOpts. This is also generally returned by provider service
|
||||
// factory methods, and usually indicates that a region was specified
|
||||
// incorrectly.
|
||||
type ErrEndpointNotFound struct {
|
||||
BaseError
|
||||
}
|
||||
|
||||
func (e ErrEndpointNotFound) Error() string {
|
||||
e.DefaultErrString = "No suitable endpoint could be found in the service catalog."
|
||||
return e.choseErrString()
|
||||
}
|
||||
|
||||
// ErrResourceNotFound is the error when trying to retrieve a resource's
|
||||
// ID by name and the resource doesn't exist.
|
||||
type ErrResourceNotFound struct {
|
||||
BaseError
|
||||
Name string
|
||||
ResourceType string
|
||||
}
|
||||
|
||||
func (e ErrResourceNotFound) Error() string {
|
||||
e.DefaultErrString = fmt.Sprintf("Unable to find %s with name %s", e.ResourceType, e.Name)
|
||||
return e.choseErrString()
|
||||
}
|
||||
|
||||
// ErrMultipleResourcesFound is the error when trying to retrieve a resource's
|
||||
// ID by name and multiple resources have the user-provided name.
|
||||
type ErrMultipleResourcesFound struct {
|
||||
BaseError
|
||||
Name string
|
||||
Count int
|
||||
ResourceType string
|
||||
}
|
||||
|
||||
func (e ErrMultipleResourcesFound) Error() string {
|
||||
e.DefaultErrString = fmt.Sprintf("Found %d %ss matching %s", e.Count, e.ResourceType, e.Name)
|
||||
return e.choseErrString()
|
||||
}
|
||||
|
||||
// ErrUnexpectedType is the error when an unexpected type is encountered
|
||||
type ErrUnexpectedType struct {
|
||||
BaseError
|
||||
Expected string
|
||||
Actual string
|
||||
}
|
||||
|
||||
func (e ErrUnexpectedType) Error() string {
|
||||
e.DefaultErrString = fmt.Sprintf("Expected %s but got %s", e.Expected, e.Actual)
|
||||
return e.choseErrString()
|
||||
}
|
||||
|
||||
func unacceptedAttributeErr(attribute string) string {
|
||||
return fmt.Sprintf("The base Identity V3 API does not accept authentication by %s", attribute)
|
||||
}
|
||||
|
||||
func redundantWithTokenErr(attribute string) string {
|
||||
return fmt.Sprintf("%s may not be provided when authenticating with a TokenID", attribute)
|
||||
}
|
||||
|
||||
func redundantWithUserID(attribute string) string {
|
||||
return fmt.Sprintf("%s may not be provided when authenticating with a UserID", attribute)
|
||||
}
|
||||
|
||||
// ErrAPIKeyProvided indicates that an APIKey was provided but can't be used.
|
||||
type ErrAPIKeyProvided struct{ BaseError }
|
||||
|
||||
func (e ErrAPIKeyProvided) Error() string {
|
||||
return unacceptedAttributeErr("APIKey")
|
||||
}
|
||||
|
||||
// ErrTenantIDProvided indicates that a TenantID was provided but can't be used.
|
||||
type ErrTenantIDProvided struct{ BaseError }
|
||||
|
||||
func (e ErrTenantIDProvided) Error() string {
|
||||
return unacceptedAttributeErr("TenantID")
|
||||
}
|
||||
|
||||
// ErrTenantNameProvided indicates that a TenantName was provided but can't be used.
|
||||
type ErrTenantNameProvided struct{ BaseError }
|
||||
|
||||
func (e ErrTenantNameProvided) Error() string {
|
||||
return unacceptedAttributeErr("TenantName")
|
||||
}
|
||||
|
||||
// ErrUsernameWithToken indicates that a Username was provided, but token authentication is being used instead.
|
||||
type ErrUsernameWithToken struct{ BaseError }
|
||||
|
||||
func (e ErrUsernameWithToken) Error() string {
|
||||
return redundantWithTokenErr("Username")
|
||||
}
|
||||
|
||||
// ErrUserIDWithToken indicates that a UserID was provided, but token authentication is being used instead.
|
||||
type ErrUserIDWithToken struct{ BaseError }
|
||||
|
||||
func (e ErrUserIDWithToken) Error() string {
|
||||
return redundantWithTokenErr("UserID")
|
||||
}
|
||||
|
||||
// ErrDomainIDWithToken indicates that a DomainID was provided, but token authentication is being used instead.
|
||||
type ErrDomainIDWithToken struct{ BaseError }
|
||||
|
||||
func (e ErrDomainIDWithToken) Error() string {
|
||||
return redundantWithTokenErr("DomainID")
|
||||
}
|
||||
|
||||
// ErrDomainNameWithToken indicates that a DomainName was provided, but token authentication is being used instead.s
|
||||
type ErrDomainNameWithToken struct{ BaseError }
|
||||
|
||||
func (e ErrDomainNameWithToken) Error() string {
|
||||
return redundantWithTokenErr("DomainName")
|
||||
}
|
||||
|
||||
// ErrUsernameOrUserID indicates that neither username nor userID are specified, or both are at once.
|
||||
type ErrUsernameOrUserID struct{ BaseError }
|
||||
|
||||
func (e ErrUsernameOrUserID) Error() string {
|
||||
return "Exactly one of Username and UserID must be provided for password authentication"
|
||||
}
|
||||
|
||||
// ErrDomainIDWithUserID indicates that a DomainID was provided, but unnecessary because a UserID is being used.
|
||||
type ErrDomainIDWithUserID struct{ BaseError }
|
||||
|
||||
func (e ErrDomainIDWithUserID) Error() string {
|
||||
return redundantWithUserID("DomainID")
|
||||
}
|
||||
|
||||
// ErrDomainNameWithUserID indicates that a DomainName was provided, but unnecessary because a UserID is being used.
|
||||
type ErrDomainNameWithUserID struct{ BaseError }
|
||||
|
||||
func (e ErrDomainNameWithUserID) Error() string {
|
||||
return redundantWithUserID("DomainName")
|
||||
}
|
||||
|
||||
// ErrDomainIDOrDomainName indicates that a username was provided, but no domain to scope it.
|
||||
// It may also indicate that both a DomainID and a DomainName were provided at once.
|
||||
type ErrDomainIDOrDomainName struct{ BaseError }
|
||||
|
||||
func (e ErrDomainIDOrDomainName) Error() string {
|
||||
return "You must provide exactly one of DomainID or DomainName to authenticate by Username"
|
||||
}
|
||||
|
||||
// ErrMissingPassword indicates that no password was provided and no token is available.
|
||||
type ErrMissingPassword struct{ BaseError }
|
||||
|
||||
func (e ErrMissingPassword) Error() string {
|
||||
return "You must provide a password to authenticate"
|
||||
}
|
||||
|
||||
// ErrScopeDomainIDOrDomainName indicates that a domain ID or Name was required in a Scope, but not present.
|
||||
type ErrScopeDomainIDOrDomainName struct{ BaseError }
|
||||
|
||||
func (e ErrScopeDomainIDOrDomainName) Error() string {
|
||||
return "You must provide exactly one of DomainID or DomainName in a Scope with ProjectName"
|
||||
}
|
||||
|
||||
// ErrScopeProjectIDOrProjectName indicates that both a ProjectID and a ProjectName were provided in a Scope.
|
||||
type ErrScopeProjectIDOrProjectName struct{ BaseError }
|
||||
|
||||
func (e ErrScopeProjectIDOrProjectName) Error() string {
|
||||
return "You must provide at most one of ProjectID or ProjectName in a Scope"
|
||||
}
|
||||
|
||||
// ErrScopeProjectIDAlone indicates that a ProjectID was provided with other constraints in a Scope.
|
||||
type ErrScopeProjectIDAlone struct{ BaseError }
|
||||
|
||||
func (e ErrScopeProjectIDAlone) Error() string {
|
||||
return "ProjectID must be supplied alone in a Scope"
|
||||
}
|
||||
|
||||
// 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"
|
||||
}
|
52
vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go
generated
vendored
Normal file
52
vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go
generated
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
package openstack
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
)
|
||||
|
||||
var nilOptions = gophercloud.AuthOptions{}
|
||||
|
||||
// AuthOptionsFromEnv fills out an identity.AuthOptions structure with the settings found on the various OpenStack
|
||||
// OS_* environment variables. The following variables provide sources of truth: OS_AUTH_URL, OS_USERNAME,
|
||||
// OS_PASSWORD, 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.
|
||||
func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
|
||||
authURL := os.Getenv("OS_AUTH_URL")
|
||||
username := os.Getenv("OS_USERNAME")
|
||||
userID := os.Getenv("OS_USERID")
|
||||
password := os.Getenv("OS_PASSWORD")
|
||||
tenantID := os.Getenv("OS_TENANT_ID")
|
||||
tenantName := os.Getenv("OS_TENANT_NAME")
|
||||
domainID := os.Getenv("OS_DOMAIN_ID")
|
||||
domainName := os.Getenv("OS_DOMAIN_NAME")
|
||||
|
||||
if authURL == "" {
|
||||
err := gophercloud.ErrMissingInput{Argument: "authURL"}
|
||||
return nilOptions, err
|
||||
}
|
||||
|
||||
if username == "" && userID == "" {
|
||||
err := gophercloud.ErrMissingInput{Argument: "username"}
|
||||
return nilOptions, err
|
||||
}
|
||||
|
||||
if password == "" {
|
||||
err := gophercloud.ErrMissingInput{Argument: "password"}
|
||||
return nilOptions, err
|
||||
}
|
||||
|
||||
ao := gophercloud.AuthOptions{
|
||||
IdentityEndpoint: authURL,
|
||||
UserID: userID,
|
||||
Username: username,
|
||||
Password: password,
|
||||
TenantID: tenantID,
|
||||
TenantName: tenantName,
|
||||
DomainID: domainID,
|
||||
DomainName: domainName,
|
||||
}
|
||||
|
||||
return ao, nil
|
||||
}
|
336
vendor/github.com/gophercloud/gophercloud/openstack/client.go
generated
vendored
Normal file
336
vendor/github.com/gophercloud/gophercloud/openstack/client.go
generated
vendored
Normal file
|
@ -0,0 +1,336 @@
|
|||
package openstack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
|
||||
tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
|
||||
"github.com/gophercloud/gophercloud/openstack/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
v20 = "v2.0"
|
||||
v30 = "v3.0"
|
||||
)
|
||||
|
||||
// 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.
|
||||
func NewClient(endpoint string) (*gophercloud.ProviderClient, error) {
|
||||
u, err := url.Parse(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
|
||||
}
|
||||
|
||||
return &gophercloud.ProviderClient{
|
||||
IdentityBase: base,
|
||||
IdentityEndpoint: "",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AuthenticatedClient logs in to an OpenStack cloud found at the identity endpoint specified by options, acquires a token, and
|
||||
// returns a Client instance that's ready to operate.
|
||||
// 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.
|
||||
func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) {
|
||||
client, err := NewClient(options.IdentityEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = Authenticate(client, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// Authenticate or re-authenticate against the most recent identity service supported at the provided endpoint.
|
||||
func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
|
||||
versions := []*utils.Version{
|
||||
{ID: v20, Priority: 20, Suffix: "/v2.0/"},
|
||||
{ID: v30, Priority: 30, Suffix: "/v3/"},
|
||||
}
|
||||
|
||||
chosen, endpoint, err := utils.ChooseVersion(client, versions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch chosen.ID {
|
||||
case v20:
|
||||
return v2auth(client, endpoint, options, gophercloud.EndpointOpts{})
|
||||
case v30:
|
||||
return v3auth(client, endpoint, &options, gophercloud.EndpointOpts{})
|
||||
default:
|
||||
// The switch statement must be out of date from the versions list.
|
||||
return fmt.Errorf("Unrecognized identity version: %s", chosen.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// AuthenticateV2 explicitly authenticates against the identity v2 endpoint.
|
||||
func AuthenticateV2(client *gophercloud.ProviderClient, options gophercloud.AuthOptions, eo gophercloud.EndpointOpts) error {
|
||||
return v2auth(client, "", options, eo)
|
||||
}
|
||||
|
||||
func v2auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions, eo gophercloud.EndpointOpts) error {
|
||||
v2Client, err := NewIdentityV2(client, eo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if endpoint != "" {
|
||||
v2Client.Endpoint = endpoint
|
||||
}
|
||||
|
||||
v2Opts := tokens2.AuthOptions{
|
||||
IdentityEndpoint: options.IdentityEndpoint,
|
||||
Username: options.Username,
|
||||
Password: options.Password,
|
||||
TenantID: options.TenantID,
|
||||
TenantName: options.TenantName,
|
||||
AllowReauth: options.AllowReauth,
|
||||
TokenID: options.TokenID,
|
||||
}
|
||||
|
||||
result := tokens2.Create(v2Client, v2Opts)
|
||||
|
||||
token, err := result.ExtractToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
catalog, err := result.ExtractServiceCatalog()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if options.AllowReauth {
|
||||
client.ReauthFunc = func() error {
|
||||
client.TokenID = ""
|
||||
return v2auth(client, endpoint, options, eo)
|
||||
}
|
||||
}
|
||||
client.TokenID = token.ID
|
||||
client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
|
||||
return V2EndpointURL(catalog, opts)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AuthenticateV3 explicitly authenticates against the identity v3 service.
|
||||
func AuthenticateV3(client *gophercloud.ProviderClient, options tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error {
|
||||
return v3auth(client, "", options, eo)
|
||||
}
|
||||
|
||||
func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error {
|
||||
// Override the generated service endpoint with the one returned by the version endpoint.
|
||||
v3Client, err := NewIdentityV3(client, eo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if endpoint != "" {
|
||||
v3Client.Endpoint = endpoint
|
||||
}
|
||||
|
||||
result := tokens3.Create(v3Client, opts)
|
||||
|
||||
token, err := result.ExtractToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
catalog, err := result.ExtractServiceCatalog()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client.TokenID = token.ID
|
||||
|
||||
if opts.CanReauth() {
|
||||
client.ReauthFunc = func() error {
|
||||
client.TokenID = ""
|
||||
return v3auth(client, endpoint, opts, eo)
|
||||
}
|
||||
}
|
||||
client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
|
||||
return V3EndpointURL(catalog, opts)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewIdentityV2 creates a ServiceClient that may be used to interact with the v2 identity service.
|
||||
func NewIdentityV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||
endpoint := client.IdentityBase + "v2.0/"
|
||||
var err error
|
||||
if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) {
|
||||
eo.ApplyDefaults("identity")
|
||||
endpoint, err = client.EndpointLocator(eo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &gophercloud.ServiceClient{
|
||||
ProviderClient: client,
|
||||
Endpoint: endpoint,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewIdentityV3 creates a ServiceClient that may be used to access the v3 identity service.
|
||||
func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||
endpoint := client.IdentityBase + "v3/"
|
||||
var err error
|
||||
if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) {
|
||||
eo.ApplyDefaults("identity")
|
||||
endpoint, err = client.EndpointLocator(eo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &gophercloud.ServiceClient{
|
||||
ProviderClient: client,
|
||||
Endpoint: endpoint,
|
||||
}, 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
23
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/delegate.go
generated
vendored
Normal file
23
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/delegate.go
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
package extensions
|
||||
|
||||
import (
|
||||
"github.com/gophercloud/gophercloud"
|
||||
common "github.com/gophercloud/gophercloud/openstack/common/extensions"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// ExtractExtensions interprets a Page as a slice of Extensions.
|
||||
func ExtractExtensions(page pagination.Page) ([]common.Extension, error) {
|
||||
return common.ExtractExtensions(page)
|
||||
}
|
||||
|
||||
// Get retrieves information for a specific extension using its alias.
|
||||
func Get(c *gophercloud.ServiceClient, alias string) common.GetResult {
|
||||
return common.Get(c, alias)
|
||||
}
|
||||
|
||||
// List returns a Pager which allows you to iterate over the full collection of extensions.
|
||||
// It does not accept query parameters.
|
||||
func List(c *gophercloud.ServiceClient) pagination.Pager {
|
||||
return common.List(c)
|
||||
}
|
3
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/doc.go
generated
vendored
Normal file
3
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
// Package extensions provides information and interaction with the
|
||||
// different extensions available for the OpenStack Compute service.
|
||||
package extensions
|
3
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/doc.go
generated
vendored
Normal file
3
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
// Package floatingips provides the ability to manage floating ips through
|
||||
// nova-network
|
||||
package floatingips
|
112
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/requests.go
generated
vendored
Normal file
112
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/requests.go
generated
vendored
Normal file
|
@ -0,0 +1,112 @@
|
|||
package floatingips
|
||||
|
||||
import (
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// List returns a Pager that allows you to iterate over a collection of FloatingIPs.
|
||||
func List(client *gophercloud.ServiceClient) pagination.Pager {
|
||||
return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page {
|
||||
return FloatingIPPage{pagination.SinglePageBase(r)}
|
||||
})
|
||||
}
|
||||
|
||||
// CreateOptsBuilder describes struct types that can be accepted by the Create call. Notable, the
|
||||
// CreateOpts struct in this package does.
|
||||
type CreateOptsBuilder interface {
|
||||
ToFloatingIPCreateMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// CreateOpts specifies a Floating IP allocation request
|
||||
type CreateOpts struct {
|
||||
// Pool is the pool of floating IPs to allocate one from
|
||||
Pool string `json:"pool" required:"true"`
|
||||
}
|
||||
|
||||
// ToFloatingIPCreateMap constructs a request body from CreateOpts.
|
||||
func (opts CreateOpts) ToFloatingIPCreateMap() (map[string]interface{}, error) {
|
||||
return gophercloud.BuildRequestBody(opts, "")
|
||||
}
|
||||
|
||||
// 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 {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get returns data about a previously created FloatingIP.
|
||||
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.
|
||||
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
|
||||
type AssociateOptsBuilder interface {
|
||||
ToFloatingIPAssociateMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// 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 string `json:"address" required:"true"`
|
||||
// FixedIP is an optional fixed IP address of the server
|
||||
FixedIP string `json:"fixed_address,omitempty"`
|
||||
}
|
||||
|
||||
// ToFloatingIPAssociateMap constructs a request body from AssociateOpts.
|
||||
func (opts AssociateOpts) ToFloatingIPAssociateMap() (map[string]interface{}, error) {
|
||||
return gophercloud.BuildRequestBody(opts, "addFloatingIp")
|
||||
}
|
||||
|
||||
// AssociateInstance pairs an allocated floating IP with an instance.
|
||||
func AssociateInstance(client *gophercloud.ServiceClient, serverID string, opts AssociateOptsBuilder) (r AssociateResult) {
|
||||
b, err := opts.ToFloatingIPAssociateMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(associateURL(client, serverID), b, nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// DisassociateOptsBuilder is the interface types must satfisfy to be used as
|
||||
// Disassociate options
|
||||
type DisassociateOptsBuilder interface {
|
||||
ToFloatingIPDisassociateMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// DisassociateOpts specifies the required information to disassociate a floating IP with an instance
|
||||
type DisassociateOpts struct {
|
||||
FloatingIP string `json:"address" required:"true"`
|
||||
}
|
||||
|
||||
// ToFloatingIPDisassociateMap constructs a request body from AssociateOpts.
|
||||
func (opts DisassociateOpts) ToFloatingIPDisassociateMap() (map[string]interface{}, error) {
|
||||
return gophercloud.BuildRequestBody(opts, "removeFloatingIp")
|
||||
}
|
||||
|
||||
// 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 {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(disassociateURL(client, serverID), b, nil, nil)
|
||||
return
|
||||
}
|
117
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/results.go
generated
vendored
Normal file
117
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/results.go
generated
vendored
Normal file
|
@ -0,0 +1,117 @@
|
|||
package floatingips
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// A FloatingIP is an IP that can be associated with an instance
|
||||
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 string `json:"fixed_ip,omitempty"`
|
||||
|
||||
// InstanceID is the ID of the instance that is using the Floating IP
|
||||
InstanceID string `json:"instance_id"`
|
||||
|
||||
// IP is the actual Floating IP
|
||||
IP string `json:"ip"`
|
||||
|
||||
// Pool is the pool of floating IPs that this floating IP belongs to
|
||||
Pool string `json:"pool"`
|
||||
}
|
||||
|
||||
func (r *FloatingIP) UnmarshalJSON(b []byte) error {
|
||||
type tmp FloatingIP
|
||||
var s struct {
|
||||
tmp
|
||||
ID interface{} `json:"id"`
|
||||
}
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*r = FloatingIP(s.tmp)
|
||||
|
||||
switch t := s.ID.(type) {
|
||||
case float64:
|
||||
r.ID = strconv.FormatFloat(t, 'f', -1, 64)
|
||||
case string:
|
||||
r.ID = t
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// FloatingIPPage stores a single, only page of FloatingIPs
|
||||
// results from a List call.
|
||||
type FloatingIPPage struct {
|
||||
pagination.SinglePageBase
|
||||
}
|
||||
|
||||
// IsEmpty determines whether or not a FloatingIPsPage is empty.
|
||||
func (page FloatingIPPage) IsEmpty() (bool, error) {
|
||||
va, err := ExtractFloatingIPs(page)
|
||||
return len(va) == 0, err
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
err := (r.(FloatingIPPage)).ExtractInto(&s)
|
||||
return s.FloatingIPs, err
|
||||
}
|
||||
|
||||
// FloatingIPResult is the raw result from a FloatingIP request.
|
||||
type FloatingIPResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// Extract is a method that attempts to interpret any FloatingIP resource
|
||||
// response as a FloatingIP struct.
|
||||
func (r FloatingIPResult) Extract() (*FloatingIP, error) {
|
||||
var s struct {
|
||||
FloatingIP *FloatingIP `json:"floating_ip"`
|
||||
}
|
||||
err := r.ExtractInto(&s)
|
||||
return s.FloatingIP, err
|
||||
}
|
||||
|
||||
// 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.
|
||||
type GetResult struct {
|
||||
FloatingIPResult
|
||||
}
|
||||
|
||||
// DeleteResult is the response from a Delete operation. Call its Extract method to determine if
|
||||
// the call succeeded or failed.
|
||||
type DeleteResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
||||
|
||||
// AssociateResult is the response from a Delete operation. Call its Extract 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.
|
||||
type DisassociateResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
37
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/urls.go
generated
vendored
Normal file
37
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/urls.go
generated
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
package floatingips
|
||||
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
const resourcePath = "os-floating-ips"
|
||||
|
||||
func resourceURL(c *gophercloud.ServiceClient) string {
|
||||
return c.ServiceURL(resourcePath)
|
||||
}
|
||||
|
||||
func listURL(c *gophercloud.ServiceClient) string {
|
||||
return resourceURL(c)
|
||||
}
|
||||
|
||||
func createURL(c *gophercloud.ServiceClient) string {
|
||||
return resourceURL(c)
|
||||
}
|
||||
|
||||
func getURL(c *gophercloud.ServiceClient, id string) string {
|
||||
return c.ServiceURL(resourcePath, id)
|
||||
}
|
||||
|
||||
func deleteURL(c *gophercloud.ServiceClient, id string) string {
|
||||
return getURL(c, id)
|
||||
}
|
||||
|
||||
func serverURL(c *gophercloud.ServiceClient, serverID string) string {
|
||||
return c.ServiceURL("servers/" + serverID + "/action")
|
||||
}
|
||||
|
||||
func associateURL(c *gophercloud.ServiceClient, serverID string) string {
|
||||
return serverURL(c, serverID)
|
||||
}
|
||||
|
||||
func disassociateURL(c *gophercloud.ServiceClient, serverID string) string {
|
||||
return serverURL(c, serverID)
|
||||
}
|
7
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/doc.go
generated
vendored
Normal file
7
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
// 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
|
141
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/requests.go
generated
vendored
Normal file
141
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/requests.go
generated
vendored
Normal file
|
@ -0,0 +1,141 @@
|
|||
package flavors
|
||||
|
||||
import (
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// ListOptsBuilder allows extensions to add additional parameters to the
|
||||
// List request.
|
||||
type ListOptsBuilder interface {
|
||||
ToFlavorListQuery() (string, error)
|
||||
}
|
||||
|
||||
// ListOpts helps control the results returned by the List() function.
|
||||
// For example, a flavor with a minDisk field of 10 will not be returned if you specify MinDisk set to 20.
|
||||
// Typically, software will use the last ID of the previous call to List to set the Marker for the current call.
|
||||
type ListOpts struct {
|
||||
|
||||
// ChangesSince, if provided, instructs List to return only those things which have changed since the timestamp provided.
|
||||
ChangesSince string `q:"changes-since"`
|
||||
|
||||
// MinDisk and MinRAM, if provided, elides flavors which do not meet your criteria.
|
||||
MinDisk int `q:"minDisk"`
|
||||
MinRAM int `q:"minRam"`
|
||||
|
||||
// Marker and Limit control paging.
|
||||
// Marker instructs List where to start listing from.
|
||||
Marker string `q:"marker"`
|
||||
|
||||
// Limit instructs List to refrain from sending excessively large lists of flavors.
|
||||
Limit int `q:"limit"`
|
||||
}
|
||||
|
||||
// ToFlavorListQuery formats a ListOpts into a query string.
|
||||
func (opts ListOpts) ToFlavorListQuery() (string, error) {
|
||||
q, err := gophercloud.BuildQueryString(opts)
|
||||
return q.String(), err
|
||||
}
|
||||
|
||||
// ListDetail instructs OpenStack to provide a list of flavors.
|
||||
// You may provide criteria by which List curtails its results for easier processing.
|
||||
// See ListOpts for more details.
|
||||
func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
|
||||
url := listURL(client)
|
||||
if opts != nil {
|
||||
query, err := opts.ToFlavorListQuery()
|
||||
if err != nil {
|
||||
return pagination.Pager{Err: err}
|
||||
}
|
||||
url += query
|
||||
}
|
||||
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
|
||||
return FlavorPage{pagination.LinkedPageBase{PageResult: r}}
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
type CreateOpts struct {
|
||||
Name string `json:"name" required:"true"`
|
||||
// memory size, in MBs
|
||||
RAM int `json:"ram" required:"true"`
|
||||
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"`
|
||||
RxTxFactor float64 `json:"rxtx_factor,omitempty"`
|
||||
IsPublic *bool `json:"os-flavor-access:is_public,omitempty"`
|
||||
// ephemeral disk size, in GBs, non-zero, positive
|
||||
Ephemeral *int `json:"OS-FLV-EXT-DATA:ephemeral,omitempty"`
|
||||
}
|
||||
|
||||
// ToFlavorCreateMap satisfies the CreateOptsBuilder interface
|
||||
func (opts *CreateOpts) ToFlavorCreateMap() (map[string]interface{}, error) {
|
||||
return gophercloud.BuildRequestBody(opts, "flavor")
|
||||
}
|
||||
|
||||
// Create a flavor
|
||||
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
|
||||
b, err := opts.ToFlavorCreateMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200, 201},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get instructs OpenStack to provide details on a single flavor, identified by its ID.
|
||||
// Use ExtractFlavor to convert its result into a Flavor.
|
||||
func Get(client *gophercloud.ServiceClient, id string) (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.
|
||||
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
|
||||
count := 0
|
||||
id := ""
|
||||
allPages, err := ListDetail(client, nil).AllPages()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
all, err := ExtractFlavors(allPages)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, f := range all {
|
||||
if f.Name == name {
|
||||
count++
|
||||
id = f.ID
|
||||
}
|
||||
}
|
||||
|
||||
switch count {
|
||||
case 0:
|
||||
err := &gophercloud.ErrResourceNotFound{}
|
||||
err.ResourceType = "flavor"
|
||||
err.Name = name
|
||||
return "", err
|
||||
case 1:
|
||||
return id, nil
|
||||
default:
|
||||
err := &gophercloud.ErrMultipleResourcesFound{}
|
||||
err.ResourceType = "flavor"
|
||||
err.Name = name
|
||||
err.Count = count
|
||||
return "", err
|
||||
}
|
||||
}
|
113
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/results.go
generated
vendored
Normal file
113
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/results.go
generated
vendored
Normal file
|
@ -0,0 +1,113 @@
|
|||
package flavors
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
type commonResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
type CreateResult struct {
|
||||
commonResult
|
||||
}
|
||||
|
||||
// GetResult temporarily holds the response from a Get call.
|
||||
type GetResult struct {
|
||||
commonResult
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
err := r.ExtractInto(&s)
|
||||
return s.Flavor, err
|
||||
}
|
||||
|
||||
// Flavor records represent (virtual) hardware configurations for server resources in a region.
|
||||
type Flavor struct {
|
||||
// The Id field contains the flavor's unique identifier.
|
||||
// For example, this identifier will be useful when specifying which hardware configuration to use for a new server instance.
|
||||
ID string `json:"id"`
|
||||
// The Disk and RA< fields provide a measure of storage space offered by the flavor, in GB and MB, respectively.
|
||||
Disk int `json:"disk"`
|
||||
RAM int `json:"ram"`
|
||||
// The Name field provides a human-readable moniker for the flavor.
|
||||
Name string `json:"name"`
|
||||
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"`
|
||||
// VCPUs indicates how many (virtual) CPUs are available for this flavor.
|
||||
VCPUs int `json:"vcpus"`
|
||||
}
|
||||
|
||||
func (r *Flavor) UnmarshalJSON(b []byte) error {
|
||||
type tmp Flavor
|
||||
var s struct {
|
||||
tmp
|
||||
Swap interface{} `json:"swap"`
|
||||
}
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*r = Flavor(s.tmp)
|
||||
|
||||
switch t := s.Swap.(type) {
|
||||
case float64:
|
||||
r.Swap = int(t)
|
||||
case string:
|
||||
switch t {
|
||||
case "":
|
||||
r.Swap = 0
|
||||
default:
|
||||
swap, err := strconv.ParseFloat(t, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Swap = int(swap)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FlavorPage contains a single page of the response from a List call.
|
||||
type FlavorPage struct {
|
||||
pagination.LinkedPageBase
|
||||
}
|
||||
|
||||
// IsEmpty determines if a page contains any results.
|
||||
func (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.
|
||||
func (page FlavorPage) NextPageURL() (string, error) {
|
||||
var s struct {
|
||||
Links []gophercloud.Link `json:"flavors_links"`
|
||||
}
|
||||
err := page.ExtractInto(&s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return gophercloud.ExtractNextURL(s.Links)
|
||||
}
|
||||
|
||||
// ExtractFlavors provides access to the list of flavors in a page acquired from the List operation.
|
||||
func ExtractFlavors(r pagination.Page) ([]Flavor, error) {
|
||||
var s struct {
|
||||
Flavors []Flavor `json:"flavors"`
|
||||
}
|
||||
err := (r.(FlavorPage)).ExtractInto(&s)
|
||||
return s.Flavors, err
|
||||
}
|
17
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/urls.go
generated
vendored
Normal file
17
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/urls.go
generated
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
package flavors
|
||||
|
||||
import (
|
||||
"github.com/gophercloud/gophercloud"
|
||||
)
|
||||
|
||||
func getURL(client *gophercloud.ServiceClient, id string) string {
|
||||
return client.ServiceURL("flavors", id)
|
||||
}
|
||||
|
||||
func listURL(client *gophercloud.ServiceClient) string {
|
||||
return client.ServiceURL("flavors", "detail")
|
||||
}
|
||||
|
||||
func createURL(client *gophercloud.ServiceClient) string {
|
||||
return client.ServiceURL("flavors")
|
||||
}
|
7
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/doc.go
generated
vendored
Normal file
7
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
// 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
|
102
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/requests.go
generated
vendored
Normal file
102
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/requests.go
generated
vendored
Normal file
|
@ -0,0 +1,102 @@
|
|||
package images
|
||||
|
||||
import (
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// ListOptsBuilder allows extensions to add additional parameters to the
|
||||
// List request.
|
||||
type ListOptsBuilder interface {
|
||||
ToImageListQuery() (string, error)
|
||||
}
|
||||
|
||||
// ListOpts contain options for limiting the number of Images returned from a call to ListDetail.
|
||||
type ListOpts struct {
|
||||
// When the image last changed status (in date-time format).
|
||||
ChangesSince string `q:"changes-since"`
|
||||
// The number of Images to return.
|
||||
Limit int `q:"limit"`
|
||||
// UUID of the Image at which to set a marker.
|
||||
Marker string `q:"marker"`
|
||||
// The name of the Image.
|
||||
Name string `q:"name"`
|
||||
// The name of the Server (in URL format).
|
||||
Server string `q:"server"`
|
||||
// The current status of the Image.
|
||||
Status string `q:"status"`
|
||||
// The value of the type of image (e.g. BASE, SERVER, ALL)
|
||||
Type string `q:"type"`
|
||||
}
|
||||
|
||||
// ToImageListQuery formats a ListOpts into a query string.
|
||||
func (opts ListOpts) ToImageListQuery() (string, error) {
|
||||
q, err := gophercloud.BuildQueryString(opts)
|
||||
return q.String(), err
|
||||
}
|
||||
|
||||
// ListDetail enumerates the available images.
|
||||
func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
|
||||
url := listDetailURL(client)
|
||||
if opts != nil {
|
||||
query, err := opts.ToImageListQuery()
|
||||
if err != nil {
|
||||
return pagination.Pager{Err: err}
|
||||
}
|
||||
url += query
|
||||
}
|
||||
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
|
||||
return ImagePage{pagination.LinkedPageBase{PageResult: r}}
|
||||
})
|
||||
}
|
||||
|
||||
// Get acquires additional detail about a specific image by ID.
|
||||
// Use ExtractImage() to interpret the result as an openstack Image.
|
||||
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
|
||||
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete deletes the specified image ID.
|
||||
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
|
||||
_, r.Err = client.Delete(deleteURL(client, id), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 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 := ""
|
||||
allPages, err := ListDetail(client, nil).AllPages()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
all, err := ExtractImages(allPages)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, f := range all {
|
||||
if f.Name == name {
|
||||
count++
|
||||
id = f.ID
|
||||
}
|
||||
}
|
||||
|
||||
switch count {
|
||||
case 0:
|
||||
err := &gophercloud.ErrResourceNotFound{}
|
||||
err.ResourceType = "image"
|
||||
err.Name = name
|
||||
return "", err
|
||||
case 1:
|
||||
return id, nil
|
||||
default:
|
||||
err := &gophercloud.ErrMultipleResourcesFound{}
|
||||
err.ResourceType = "image"
|
||||
err.Name = name
|
||||
err.Count = count
|
||||
return "", err
|
||||
}
|
||||
}
|
83
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/results.go
generated
vendored
Normal file
83
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/results.go
generated
vendored
Normal file
|
@ -0,0 +1,83 @@
|
|||
package images
|
||||
|
||||
import (
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// GetResult temporarily stores a Get response.
|
||||
type GetResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// DeleteResult represents the result of an image.Delete operation.
|
||||
type DeleteResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
||||
|
||||
// Extract interprets a GetResult as an Image.
|
||||
func (r GetResult) Extract() (*Image, error) {
|
||||
var s struct {
|
||||
Image *Image `json:"image"`
|
||||
}
|
||||
err := r.ExtractInto(&s)
|
||||
return s.Image, err
|
||||
}
|
||||
|
||||
// Image is used for JSON (un)marshalling.
|
||||
// It provides a description of an OS image.
|
||||
type Image struct {
|
||||
// ID contains the image's unique identifier.
|
||||
ID string
|
||||
|
||||
Created string
|
||||
|
||||
// MinDisk and MinRAM specify the minimum resources a server must provide to be able to install the image.
|
||||
MinDisk int
|
||||
MinRAM int
|
||||
|
||||
// Name provides a human-readable moniker for the OS image.
|
||||
Name string
|
||||
|
||||
// The Progress and Status fields indicate image-creation status.
|
||||
// Any usable image will have 100% progress.
|
||||
Progress int
|
||||
Status string
|
||||
|
||||
Updated string
|
||||
|
||||
Metadata map[string]interface{}
|
||||
}
|
||||
|
||||
// ImagePage contains a single page of results from a List operation.
|
||||
// Use ExtractImages to convert it into a slice of usable structs.
|
||||
type ImagePage struct {
|
||||
pagination.LinkedPageBase
|
||||
}
|
||||
|
||||
// IsEmpty returns true if a page contains no Image results.
|
||||
func (page ImagePage) IsEmpty() (bool, error) {
|
||||
images, err := ExtractImages(page)
|
||||
return len(images) == 0, err
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
err := page.ExtractInto(&s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return gophercloud.ExtractNextURL(s.Links)
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
err := (r.(ImagePage)).ExtractInto(&s)
|
||||
return s.Images, err
|
||||
}
|
15
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/urls.go
generated
vendored
Normal file
15
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/urls.go
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
package images
|
||||
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
func listDetailURL(client *gophercloud.ServiceClient) string {
|
||||
return client.ServiceURL("images", "detail")
|
||||
}
|
||||
|
||||
func getURL(client *gophercloud.ServiceClient, id string) string {
|
||||
return client.ServiceURL("images", id)
|
||||
}
|
||||
|
||||
func deleteURL(client *gophercloud.ServiceClient, id string) string {
|
||||
return client.ServiceURL("images", id)
|
||||
}
|
6
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/doc.go
generated
vendored
Normal file
6
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
// 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
|
71
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/errors.go
generated
vendored
Normal file
71
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/errors.go
generated
vendored
Normal file
|
@ -0,0 +1,71 @@
|
|||
package servers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
)
|
||||
|
||||
// ErrNeitherImageIDNorImageNameProvided is the error when neither the image
|
||||
// ID nor the image name is provided for a server operation
|
||||
type ErrNeitherImageIDNorImageNameProvided struct{ gophercloud.ErrMissingInput }
|
||||
|
||||
func (e ErrNeitherImageIDNorImageNameProvided) Error() string {
|
||||
return "One and only one of the image ID and the image name must be provided."
|
||||
}
|
||||
|
||||
// ErrNeitherFlavorIDNorFlavorNameProvided is the error when neither the flavor
|
||||
// ID nor the flavor name is provided for a server operation
|
||||
type ErrNeitherFlavorIDNorFlavorNameProvided struct{ gophercloud.ErrMissingInput }
|
||||
|
||||
func (e ErrNeitherFlavorIDNorFlavorNameProvided) Error() string {
|
||||
return "One and only one of the flavor ID and the flavor name must be provided."
|
||||
}
|
||||
|
||||
type ErrNoClientProvidedForIDByName struct{ gophercloud.ErrMissingInput }
|
||||
|
||||
func (e ErrNoClientProvidedForIDByName) Error() string {
|
||||
return "A service client must be provided to find a resource ID by name."
|
||||
}
|
||||
|
||||
// ErrInvalidHowParameterProvided is the error when an unknown value is given
|
||||
// for the `how` argument
|
||||
type ErrInvalidHowParameterProvided struct{ gophercloud.ErrInvalidInput }
|
||||
|
||||
// ErrNoAdminPassProvided is the error when an administrative password isn't
|
||||
// provided for a server operation
|
||||
type ErrNoAdminPassProvided struct{ gophercloud.ErrMissingInput }
|
||||
|
||||
// ErrNoImageIDProvided is the error when an image ID isn't provided for a server
|
||||
// operation
|
||||
type ErrNoImageIDProvided struct{ gophercloud.ErrMissingInput }
|
||||
|
||||
// ErrNoIDProvided is the error when a server ID isn't provided for a server
|
||||
// operation
|
||||
type ErrNoIDProvided struct{ gophercloud.ErrMissingInput }
|
||||
|
||||
// ErrServer is a generic error type for servers HTTP operations.
|
||||
type ErrServer struct {
|
||||
gophercloud.ErrUnexpectedResponseCode
|
||||
ID string
|
||||
}
|
||||
|
||||
func (se ErrServer) Error() string {
|
||||
return fmt.Sprintf("Error while executing HTTP request for server [%s]", se.ID)
|
||||
}
|
||||
|
||||
// Error404 overrides the generic 404 error message.
|
||||
func (se ErrServer) Error404(e gophercloud.ErrUnexpectedResponseCode) error {
|
||||
se.ErrUnexpectedResponseCode = e
|
||||
return &ErrServerNotFound{se}
|
||||
}
|
||||
|
||||
// ErrServerNotFound is the error when a 404 is received during server HTTP
|
||||
// operations.
|
||||
type ErrServerNotFound struct {
|
||||
ErrServer
|
||||
}
|
||||
|
||||
func (e ErrServerNotFound) Error() string {
|
||||
return fmt.Sprintf("I couldn't find server [%s]", e.ID)
|
||||
}
|
741
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go
generated
vendored
Normal file
741
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go
generated
vendored
Normal file
|
@ -0,0 +1,741 @@
|
|||
package servers
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/images"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// ListOptsBuilder allows extensions to add additional parameters to the
|
||||
// List request.
|
||||
type ListOptsBuilder interface {
|
||||
ToServerListQuery() (string, error)
|
||||
}
|
||||
|
||||
// ListOpts allows the filtering and sorting of paginated collections through
|
||||
// the API. Filtering is achieved by passing in struct field values that map to
|
||||
// the server attributes you want to see returned. Marker and Limit are used
|
||||
// for pagination.
|
||||
type ListOpts struct {
|
||||
// A time/date stamp for when the server last changed status.
|
||||
ChangesSince string `q:"changes-since"`
|
||||
|
||||
// Name of the image in URL format.
|
||||
Image string `q:"image"`
|
||||
|
||||
// Name of the flavor in URL format.
|
||||
Flavor string `q:"flavor"`
|
||||
|
||||
// Name of the server as a string; can be queried with regular expressions.
|
||||
// Realize that ?name=bob returns both bob and bobb. If you need to match bob
|
||||
// only, you can use a regular expression matching the syntax of the
|
||||
// underlying database server implemented for Compute.
|
||||
Name string `q:"name"`
|
||||
|
||||
// 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 string `q:"host"`
|
||||
|
||||
// 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 int `q:"limit"`
|
||||
|
||||
// Bool to show all tenants
|
||||
AllTenants bool `q:"all_tenants"`
|
||||
}
|
||||
|
||||
// ToServerListQuery formats a ListOpts into a query string.
|
||||
func (opts ListOpts) ToServerListQuery() (string, error) {
|
||||
q, err := gophercloud.BuildQueryString(opts)
|
||||
return q.String(), err
|
||||
}
|
||||
|
||||
// List makes a request against the API to list servers accessible to you.
|
||||
func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
|
||||
url := listDetailURL(client)
|
||||
if opts != nil {
|
||||
query, err := opts.ToServerListQuery()
|
||||
if err != nil {
|
||||
return pagination.Pager{Err: err}
|
||||
}
|
||||
url += query
|
||||
}
|
||||
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
|
||||
return ServerPage{pagination.LinkedPageBase{PageResult: r}}
|
||||
})
|
||||
}
|
||||
|
||||
// CreateOptsBuilder describes struct types that can be accepted by the Create call.
|
||||
// The CreateOpts struct in this package does.
|
||||
type CreateOptsBuilder interface {
|
||||
ToServerCreateMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// Network is used within CreateOpts to control a new server's network attachments.
|
||||
type Network struct {
|
||||
// UUID of a nova-network to attach to the newly provisioned server.
|
||||
// Required unless Port is provided.
|
||||
UUID string
|
||||
|
||||
// Port of a neutron network to attach to the newly provisioned server.
|
||||
// Required unless UUID is provided.
|
||||
Port string
|
||||
|
||||
// FixedIP [optional] specifies a fixed IPv4 address to be used on this network.
|
||||
FixedIP string
|
||||
}
|
||||
|
||||
// Personality is an array of files that are injected into the server at launch.
|
||||
type Personality []*File
|
||||
|
||||
// File is used within CreateOpts and RebuildOpts to inject a file into the server at launch.
|
||||
// File implements the json.Marshaler interface, so when a Create or Rebuild operation is requested,
|
||||
// json.Marshal will call File's MarshalJSON method.
|
||||
type File struct {
|
||||
// Path of the file
|
||||
Path string
|
||||
// Contents of the file. Maximum content size is 255 bytes.
|
||||
Contents []byte
|
||||
}
|
||||
|
||||
// MarshalJSON marshals the escaped file, base64 encoding the contents.
|
||||
func (f *File) MarshalJSON() ([]byte, error) {
|
||||
file := struct {
|
||||
Path string `json:"path"`
|
||||
Contents string `json:"contents"`
|
||||
}{
|
||||
Path: f.Path,
|
||||
Contents: base64.StdEncoding.EncodeToString(f.Contents),
|
||||
}
|
||||
return json.Marshal(file)
|
||||
}
|
||||
|
||||
// CreateOpts specifies server creation parameters.
|
||||
type CreateOpts struct {
|
||||
// Name is the name to assign to the newly launched server.
|
||||
Name string `json:"name" required:"true"`
|
||||
|
||||
// ImageRef [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.
|
||||
// Also optional if using the boot-from-volume extension.
|
||||
ImageName string `json:"-"`
|
||||
|
||||
// FlavorRef [optional; required if FlavorName is not provided] is the ID or
|
||||
// full URL to the flavor that describes the server's specs.
|
||||
FlavorRef string `json:"flavorRef"`
|
||||
|
||||
// FlavorName [optional; required if FlavorRef is not provided] is the name of
|
||||
// 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 []string `json:"-"`
|
||||
|
||||
// UserData contains configuration information or scripts to use upon launch.
|
||||
// Create will base64-encode it for you, if it isn't already.
|
||||
UserData []byte `json:"-"`
|
||||
|
||||
// AvailabilityZone in which to launch the server.
|
||||
AvailabilityZone string `json:"availability_zone,omitempty"`
|
||||
|
||||
// Networks dictates how this server will be attached to available networks.
|
||||
// By default, the server will be attached to all isolated networks for the tenant.
|
||||
Networks []Network `json:"-"`
|
||||
|
||||
// Metadata contains key-value pairs (up to 255 bytes each) to attach to the server.
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
|
||||
// Personality includes files to inject into the server at launch.
|
||||
// Create will base64-encode file contents for you.
|
||||
Personality Personality `json:"personality,omitempty"`
|
||||
|
||||
// ConfigDrive enables metadata injection through a configuration drive.
|
||||
ConfigDrive *bool `json:"config_drive,omitempty"`
|
||||
|
||||
// AdminPass sets the root user password. If not set, a randomly-generated
|
||||
// password will be created and returned in the rponse.
|
||||
AdminPass string `json:"adminPass,omitempty"`
|
||||
|
||||
// AccessIPv4 specifies an IPv4 address for the instance.
|
||||
AccessIPv4 string `json:"accessIPv4,omitempty"`
|
||||
|
||||
// AccessIPv6 pecifies an IPv6 address for the instance.
|
||||
AccessIPv6 string `json:"accessIPv6,omitempty"`
|
||||
|
||||
// ServiceClient will allow calls to be made to retrieve an image or
|
||||
// flavor ID by name.
|
||||
ServiceClient *gophercloud.ServiceClient `json:"-"`
|
||||
}
|
||||
|
||||
// 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
|
||||
b, err := gophercloud.BuildRequestBody(opts, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if opts.UserData != nil {
|
||||
var userData string
|
||||
if _, err := base64.StdEncoding.DecodeString(string(opts.UserData)); err != nil {
|
||||
userData = base64.StdEncoding.EncodeToString(opts.UserData)
|
||||
} else {
|
||||
userData = string(opts.UserData)
|
||||
}
|
||||
b["user_data"] = &userData
|
||||
}
|
||||
|
||||
if len(opts.SecurityGroups) > 0 {
|
||||
securityGroups := make([]map[string]interface{}, len(opts.SecurityGroups))
|
||||
for i, groupName := range opts.SecurityGroups {
|
||||
securityGroups[i] = map[string]interface{}{"name": groupName}
|
||||
}
|
||||
b["security_groups"] = securityGroups
|
||||
}
|
||||
|
||||
if len(opts.Networks) > 0 {
|
||||
networks := make([]map[string]interface{}, len(opts.Networks))
|
||||
for i, net := range opts.Networks {
|
||||
networks[i] = make(map[string]interface{})
|
||||
if net.UUID != "" {
|
||||
networks[i]["uuid"] = net.UUID
|
||||
}
|
||||
if net.Port != "" {
|
||||
networks[i]["port"] = net.Port
|
||||
}
|
||||
if net.FixedIP != "" {
|
||||
networks[i]["fixed_ip"] = net.FixedIP
|
||||
}
|
||||
}
|
||||
b["networks"] = networks
|
||||
}
|
||||
|
||||
// If ImageRef isn't provided, check if ImageName was provided to ascertain
|
||||
// the image ID.
|
||||
if opts.ImageRef == "" {
|
||||
if opts.ImageName != "" {
|
||||
if sc == nil {
|
||||
err := ErrNoClientProvidedForIDByName{}
|
||||
err.Argument = "ServiceClient"
|
||||
return nil, err
|
||||
}
|
||||
imageID, err := images.IDFromName(sc, opts.ImageName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b["imageRef"] = imageID
|
||||
}
|
||||
}
|
||||
|
||||
// If FlavorRef isn't provided, use FlavorName to ascertain the flavor ID.
|
||||
if opts.FlavorRef == "" {
|
||||
if opts.FlavorName == "" {
|
||||
err := ErrNeitherFlavorIDNorFlavorNameProvided{}
|
||||
err.Argument = "FlavorRef/FlavorName"
|
||||
return nil, err
|
||||
}
|
||||
if sc == nil {
|
||||
err := ErrNoClientProvidedForIDByName{}
|
||||
err.Argument = "ServiceClient"
|
||||
return nil, err
|
||||
}
|
||||
flavorID, err := flavors.IDFromName(sc, opts.FlavorName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b["flavorRef"] = flavorID
|
||||
}
|
||||
|
||||
return map[string]interface{}{"server": b}, nil
|
||||
}
|
||||
|
||||
// Create requests a server to be provisioned to the user in the current tenant.
|
||||
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
|
||||
reqBody, err := opts.ToServerCreateMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(listURL(client), reqBody, &r.Body, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
func ForceDelete(client *gophercloud.ServiceClient, id string) (r ActionResult) {
|
||||
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"forceDelete": ""}, nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Get requests details on a single server, by ID.
|
||||
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
|
||||
_, r.Err = client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200, 203},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateOptsBuilder allows extensions to add additional attributes to the Update request.
|
||||
type UpdateOptsBuilder interface {
|
||||
ToServerUpdateMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// UpdateOpts specifies the base attributes that may be updated on an existing server.
|
||||
type UpdateOpts struct {
|
||||
// Name changes the displayed name of the server.
|
||||
// The server host name will *not* change.
|
||||
// Server names are not constrained to be unique, even within the same tenant.
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// AccessIPv4 provides a new IPv4 address for the instance.
|
||||
AccessIPv4 string `json:"accessIPv4,omitempty"`
|
||||
|
||||
// AccessIPv6 provides a new IPv6 address for the instance.
|
||||
AccessIPv6 string `json:"accessIPv6,omitempty"`
|
||||
}
|
||||
|
||||
// ToServerUpdateMap formats an UpdateOpts structure into a request body.
|
||||
func (opts UpdateOpts) ToServerUpdateMap() (map[string]interface{}, error) {
|
||||
return gophercloud.BuildRequestBody(opts, "server")
|
||||
}
|
||||
|
||||
// Update requests that various attributes of the indicated server be changed.
|
||||
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
|
||||
b, err := opts.ToServerUpdateMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// ChangeAdminPassword alters the administrator or root password for a specified server.
|
||||
func ChangeAdminPassword(client *gophercloud.ServiceClient, id, newPassword string) (r ActionResult) {
|
||||
b := map[string]interface{}{
|
||||
"changePassword": map[string]string{
|
||||
"adminPass": newPassword,
|
||||
},
|
||||
}
|
||||
_, r.Err = client.Post(actionURL(client, id), b, nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// RebootMethod describes the mechanisms by which a server reboot can be requested.
|
||||
type RebootMethod string
|
||||
|
||||
// These constants determine how a server should be rebooted.
|
||||
// See the Reboot() function for further details.
|
||||
const (
|
||||
SoftReboot RebootMethod = "SOFT"
|
||||
HardReboot RebootMethod = "HARD"
|
||||
OSReboot = SoftReboot
|
||||
PowerCycle = HardReboot
|
||||
)
|
||||
|
||||
// RebootOptsBuilder is an interface that options must satisfy in order to be
|
||||
// used when rebooting a server instance
|
||||
type RebootOptsBuilder interface {
|
||||
ToServerRebootMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// RebootOpts satisfies the RebootOptsBuilder interface
|
||||
type RebootOpts struct {
|
||||
Type RebootMethod `json:"type" required:"true"`
|
||||
}
|
||||
|
||||
// ToServerRebootMap allows RebootOpts to satisfiy the RebootOptsBuilder
|
||||
// interface
|
||||
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.
|
||||
func Reboot(client *gophercloud.ServiceClient, id string, opts RebootOptsBuilder) (r ActionResult) {
|
||||
b, err := opts.ToServerRebootMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(actionURL(client, id), b, nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// RebuildOptsBuilder is an interface that allows extensions to override the
|
||||
// default behaviour of rebuild options
|
||||
type RebuildOptsBuilder interface {
|
||||
ToServerRebuildMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// RebuildOpts represents the configuration options used in a server rebuild
|
||||
// operation
|
||||
type RebuildOpts struct {
|
||||
// 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"`
|
||||
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 map[string]string `json:"metadata,omitempty"`
|
||||
// Personality [optional] includes files to inject into the server at launch.
|
||||
// Rebuild will base64-encode file contents for you.
|
||||
Personality Personality `json:"personality,omitempty"`
|
||||
ServiceClient *gophercloud.ServiceClient `json:"-"`
|
||||
}
|
||||
|
||||
// ToServerRebuildMap formats a RebuildOpts struct into a map for use in JSON
|
||||
func (opts RebuildOpts) ToServerRebuildMap() (map[string]interface{}, error) {
|
||||
b, err := gophercloud.BuildRequestBody(opts, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If ImageRef isn't provided, check if ImageName was provided to ascertain
|
||||
// the image ID.
|
||||
if opts.ImageID == "" {
|
||||
if opts.ImageName != "" {
|
||||
if opts.ServiceClient == nil {
|
||||
err := ErrNoClientProvidedForIDByName{}
|
||||
err.Argument = "ServiceClient"
|
||||
return nil, err
|
||||
}
|
||||
imageID, err := images.IDFromName(opts.ServiceClient, opts.ImageName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b["imageRef"] = imageID
|
||||
}
|
||||
}
|
||||
|
||||
return map[string]interface{}{"rebuild": b}, nil
|
||||
}
|
||||
|
||||
// Rebuild will reprovision the server according to the configuration options
|
||||
// provided in the RebuildOpts struct.
|
||||
func Rebuild(client *gophercloud.ServiceClient, id string, opts RebuildOptsBuilder) (r RebuildResult) {
|
||||
b, err := opts.ToServerRebuildMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(actionURL(client, id), b, &r.Body, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// ResizeOptsBuilder is an interface that allows extensions to override the default structure of
|
||||
// a Resize request.
|
||||
type ResizeOptsBuilder interface {
|
||||
ToServerResizeMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// ResizeOpts represents the configuration options used to control a Resize operation.
|
||||
type ResizeOpts struct {
|
||||
// FlavorRef is the ID of the flavor you wish your server to become.
|
||||
FlavorRef string `json:"flavorRef" required:"true"`
|
||||
}
|
||||
|
||||
// ToServerResizeMap formats a ResizeOpts as a map that can be used as a JSON request body for the
|
||||
// Resize request.
|
||||
func (opts ResizeOpts) ToServerResizeMap() (map[string]interface{}, error) {
|
||||
return gophercloud.BuildRequestBody(opts, "resize")
|
||||
}
|
||||
|
||||
// Resize instructs the provider to change the flavor of the server.
|
||||
// Note that this implies rebuilding it.
|
||||
// Unfortunately, one cannot pass rebuild parameters to the resize function.
|
||||
// When the resize completes, the server will be in 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.
|
||||
func Resize(client *gophercloud.ServiceClient, id string, opts ResizeOptsBuilder) (r ActionResult) {
|
||||
b, err := opts.ToServerResizeMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(actionURL(client, id), b, nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// ConfirmResize confirms a previous resize operation on a server.
|
||||
// See Resize() for more details.
|
||||
func ConfirmResize(client *gophercloud.ServiceClient, id string) (r ActionResult) {
|
||||
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"confirmResize": nil}, nil, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{201, 202, 204},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// RevertResize cancels a previous resize operation on a server.
|
||||
// See Resize() for more details.
|
||||
func RevertResize(client *gophercloud.ServiceClient, id string) (r ActionResult) {
|
||||
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"revertResize": nil}, nil, nil)
|
||||
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.
|
||||
type ResetMetadataOptsBuilder interface {
|
||||
ToMetadataResetMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// MetadataOpts is a map that contains key-value pairs.
|
||||
type MetadataOpts map[string]string
|
||||
|
||||
// ToMetadataResetMap assembles a body for a Reset request based on the contents of a MetadataOpts.
|
||||
func (opts MetadataOpts) ToMetadataResetMap() (map[string]interface{}, error) {
|
||||
return map[string]interface{}{"metadata": opts}, nil
|
||||
}
|
||||
|
||||
// ToMetadataUpdateMap assembles a body for an Update request based on the contents of a MetadataOpts.
|
||||
func (opts MetadataOpts) ToMetadataUpdateMap() (map[string]interface{}, error) {
|
||||
return map[string]interface{}{"metadata": opts}, nil
|
||||
}
|
||||
|
||||
// ResetMetadata will create multiple new key-value pairs for the given server ID.
|
||||
// Note: Using this operation will erase any already-existing metadata and create
|
||||
// the new metadata provided. To keep any already-existing metadata, use the
|
||||
// UpdateMetadatas or UpdateMetadata function.
|
||||
func ResetMetadata(client *gophercloud.ServiceClient, id string, opts ResetMetadataOptsBuilder) (r ResetMetadataResult) {
|
||||
b, err := opts.ToMetadataResetMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Put(metadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Metadata requests all the metadata for the given server ID.
|
||||
func Metadata(client *gophercloud.ServiceClient, id string) (r GetMetadataResult) {
|
||||
_, r.Err = client.Get(metadataURL(client, id), &r.Body, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateMetadataOptsBuilder allows extensions to add additional parameters to the
|
||||
// Create request.
|
||||
type UpdateMetadataOptsBuilder interface {
|
||||
ToMetadataUpdateMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// UpdateMetadata updates (or creates) all the metadata specified by opts for the given server ID.
|
||||
// This operation does not affect already-existing metadata that is not specified
|
||||
// by opts.
|
||||
func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMetadataOptsBuilder) (r UpdateMetadataResult) {
|
||||
b, err := opts.ToMetadataUpdateMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(metadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// MetadatumOptsBuilder allows extensions to add additional parameters to the
|
||||
// Create request.
|
||||
type MetadatumOptsBuilder interface {
|
||||
ToMetadatumCreateMap() (map[string]interface{}, string, error)
|
||||
}
|
||||
|
||||
// MetadatumOpts is a map of length one that contains a key-value pair.
|
||||
type MetadatumOpts map[string]string
|
||||
|
||||
// ToMetadatumCreateMap assembles a body for a Create request based on the contents of a MetadataumOpts.
|
||||
func (opts MetadatumOpts) ToMetadatumCreateMap() (map[string]interface{}, string, error) {
|
||||
if len(opts) != 1 {
|
||||
err := gophercloud.ErrInvalidInput{}
|
||||
err.Argument = "servers.MetadatumOpts"
|
||||
err.Info = "Must have 1 and only 1 key-value pair"
|
||||
return nil, "", err
|
||||
}
|
||||
metadatum := map[string]interface{}{"meta": opts}
|
||||
var key string
|
||||
for k := range metadatum["meta"].(MetadatumOpts) {
|
||||
key = k
|
||||
}
|
||||
return metadatum, key, nil
|
||||
}
|
||||
|
||||
// CreateMetadatum will create or update the key-value pair with the given key for the given server ID.
|
||||
func CreateMetadatum(client *gophercloud.ServiceClient, id string, opts MetadatumOptsBuilder) (r CreateMetadatumResult) {
|
||||
b, key, err := opts.ToMetadatumCreateMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Put(metadatumURL(client, id, key), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Metadatum requests the key-value pair with the given key for the given server ID.
|
||||
func Metadatum(client *gophercloud.ServiceClient, id, key string) (r GetMetadatumResult) {
|
||||
_, 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.
|
||||
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.
|
||||
func ListAddresses(client *gophercloud.ServiceClient, id string) pagination.Pager {
|
||||
return pagination.NewPager(client, listAddressesURL(client, id), func(r pagination.PageResult) pagination.Page {
|
||||
return AddressPage{pagination.SinglePageBase(r)}
|
||||
})
|
||||
}
|
||||
|
||||
// ListAddressesByNetwork makes a request against the API to list the servers IP addresses
|
||||
// for the given network.
|
||||
func ListAddressesByNetwork(client *gophercloud.ServiceClient, id, network string) pagination.Pager {
|
||||
return pagination.NewPager(client, listAddressesByNetworkURL(client, id, network), func(r pagination.PageResult) pagination.Page {
|
||||
return NetworkAddressPage{pagination.SinglePageBase(r)}
|
||||
})
|
||||
}
|
||||
|
||||
// CreateImageOptsBuilder is the interface types must satisfy in order to be
|
||||
// used as CreateImage options
|
||||
type CreateImageOptsBuilder interface {
|
||||
ToServerCreateImageMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// CreateImageOpts satisfies the CreateImageOptsBuilder
|
||||
type CreateImageOpts struct {
|
||||
// Name of the image/snapshot
|
||||
Name string `json:"name" required:"true"`
|
||||
// Metadata contains key-value pairs (up to 255 bytes each) to attach to the created image.
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// ToServerCreateImageMap formats a CreateImageOpts structure into a request body.
|
||||
func (opts CreateImageOpts) ToServerCreateImageMap() (map[string]interface{}, error) {
|
||||
return gophercloud.BuildRequestBody(opts, "createImage")
|
||||
}
|
||||
|
||||
// CreateImage makes a request against the nova API to schedule an image to be created of the server
|
||||
func CreateImage(client *gophercloud.ServiceClient, id string, opts CreateImageOptsBuilder) (r CreateImageResult) {
|
||||
b, err := opts.ToServerCreateImageMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
resp, err := client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
r.Err = err
|
||||
r.Header = resp.Header
|
||||
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 := ""
|
||||
allPages, err := List(client, nil).AllPages()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
all, err := ExtractServers(allPages)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, f := range all {
|
||||
if f.Name == name {
|
||||
count++
|
||||
id = f.ID
|
||||
}
|
||||
}
|
||||
|
||||
switch count {
|
||||
case 0:
|
||||
return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "server"}
|
||||
case 1:
|
||||
return id, nil
|
||||
default:
|
||||
return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "server"}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
350
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go
generated
vendored
Normal file
350
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go
generated
vendored
Normal file
|
@ -0,0 +1,350 @@
|
|||
package servers
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
type serverResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// Extract interprets any serverResult as a Server, if possible.
|
||||
func (r serverResult) Extract() (*Server, error) {
|
||||
var s Server
|
||||
err := r.ExtractInto(&s)
|
||||
return &s, err
|
||||
}
|
||||
|
||||
func (r serverResult) ExtractInto(v interface{}) error {
|
||||
return r.Result.ExtractIntoStructPtr(v, "server")
|
||||
}
|
||||
|
||||
func ExtractServersInto(r pagination.Page, v interface{}) error {
|
||||
return r.(ServerPage).Result.ExtractIntoSlicePtr(v, "servers")
|
||||
}
|
||||
|
||||
// CreateResult temporarily contains the response from a Create call.
|
||||
type CreateResult struct {
|
||||
serverResult
|
||||
}
|
||||
|
||||
// GetResult temporarily contains the response from a Get call.
|
||||
type GetResult struct {
|
||||
serverResult
|
||||
}
|
||||
|
||||
// UpdateResult temporarily contains the response from an Update call.
|
||||
type UpdateResult struct {
|
||||
serverResult
|
||||
}
|
||||
|
||||
// DeleteResult temporarily contains the response from a Delete call.
|
||||
type DeleteResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
||||
|
||||
// RebuildResult temporarily contains the response from a Rebuild call.
|
||||
type RebuildResult struct {
|
||||
serverResult
|
||||
}
|
||||
|
||||
// ActionResult represents the result of server action operations, like reboot
|
||||
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
|
||||
type CreateImageResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// GetPasswordResult represent the result of a get os-server-password operation.
|
||||
type GetPasswordResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// ExtractPassword gets the encrypted password.
|
||||
// If privateKey != nil the password is decrypted with the private key.
|
||||
// If privateKey == nil the encrypted password is returned and can be decrypted with:
|
||||
// echo '<pwd>' | base64 -D | openssl rsautl -decrypt -inkey <private_key>
|
||||
func (r GetPasswordResult) ExtractPassword(privateKey *rsa.PrivateKey) (string, error) {
|
||||
var s struct {
|
||||
Password string `json:"password"`
|
||||
}
|
||||
err := r.ExtractInto(&s)
|
||||
if err == nil && privateKey != nil && s.Password != "" {
|
||||
return decryptPassword(s.Password, privateKey)
|
||||
}
|
||||
return s.Password, err
|
||||
}
|
||||
|
||||
func decryptPassword(encryptedPassword string, privateKey *rsa.PrivateKey) (string, error) {
|
||||
b64EncryptedPassword := make([]byte, base64.StdEncoding.DecodedLen(len(encryptedPassword)))
|
||||
|
||||
n, err := base64.StdEncoding.Decode(b64EncryptedPassword, []byte(encryptedPassword))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to base64 decode encrypted password: %s", err)
|
||||
}
|
||||
password, err := rsa.DecryptPKCS1v15(nil, privateKey, b64EncryptedPassword[0:n])
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to decrypt password: %s", err)
|
||||
}
|
||||
|
||||
return string(password), nil
|
||||
}
|
||||
|
||||
// ExtractImageID gets the ID of the newly created server image from the header
|
||||
func (r CreateImageResult) ExtractImageID() (string, error) {
|
||||
if r.Err != nil {
|
||||
return "", r.Err
|
||||
}
|
||||
// Get the image id from the header
|
||||
u, err := url.ParseRequestURI(r.Header.Get("Location"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
imageID := path.Base(u.Path)
|
||||
if imageID == "." || imageID == "/" {
|
||||
return "", fmt.Errorf("Failed to parse the ID of newly created image: %s", u)
|
||||
}
|
||||
return imageID, nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
type Server struct {
|
||||
// ID uniquely identifies this server amongst all other servers, including those not accessible to the current tenant.
|
||||
ID string `json:"id"`
|
||||
// TenantID identifies the tenant owning this server resource.
|
||||
TenantID string `json:"tenant_id"`
|
||||
// UserID uniquely identifies the user account owning the tenant.
|
||||
UserID string `json:"user_id"`
|
||||
// Name contains the human-readable name for the server.
|
||||
Name string `json:"name"`
|
||||
// Updated and Created contain ISO-8601 timestamps of when the state of the server last changed, and when it was created.
|
||||
Updated time.Time `json:"updated"`
|
||||
Created time.Time `json:"created"`
|
||||
HostID string `json:"hostid"`
|
||||
// Status contains the current operational status of the server, such as IN_PROGRESS or ACTIVE.
|
||||
Status string `json:"status"`
|
||||
// Progress ranges from 0..100.
|
||||
// A request made against the server completes only once Progress reaches 100.
|
||||
Progress int `json:"progress"`
|
||||
// AccessIPv4 and AccessIPv6 contain the IP addresses of the server, suitable for remote access for administration.
|
||||
AccessIPv4 string `json:"accessIPv4"`
|
||||
AccessIPv6 string `json:"accessIPv6"`
|
||||
// Image refers to a JSON object, which itself indicates the OS image used to deploy the server.
|
||||
Image map[string]interface{} `json:"-"`
|
||||
// Flavor refers to a JSON object, which itself indicates the hardware configuration of the deployed server.
|
||||
Flavor map[string]interface{} `json:"flavor"`
|
||||
// Addresses includes a list of all IP addresses assigned to the server, keyed by pool.
|
||||
Addresses map[string]interface{} `json:"addresses"`
|
||||
// Metadata includes a list of all user-specified key-value pairs attached to the server.
|
||||
Metadata map[string]string `json:"metadata"`
|
||||
// Links includes HTTP references to the itself, useful for passing along to other APIs that might want a server reference.
|
||||
Links []interface{} `json:"links"`
|
||||
// KeyName indicates which public key was injected into the server on launch.
|
||||
KeyName string `json:"key_name"`
|
||||
// AdminPass will generally be empty (""). However, it will contain the administrative password chosen when provisioning a new server without a set AdminPass setting in the first place.
|
||||
// Note that this is the ONLY time this field will be valid.
|
||||
AdminPass string `json:"adminPass"`
|
||||
// SecurityGroups includes the security groups that this instance has applied to it
|
||||
SecurityGroups []map[string]interface{} `json:"security_groups"`
|
||||
}
|
||||
|
||||
func (r *Server) UnmarshalJSON(b []byte) error {
|
||||
type tmp Server
|
||||
var s struct {
|
||||
tmp
|
||||
Image interface{} `json:"image"`
|
||||
}
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*r = Server(s.tmp)
|
||||
|
||||
switch t := s.Image.(type) {
|
||||
case map[string]interface{}:
|
||||
r.Image = t
|
||||
case string:
|
||||
switch t {
|
||||
case "":
|
||||
r.Image = nil
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// ServerPage abstracts the raw results of making a List() request against the API.
|
||||
// As OpenStack extensions may freely alter the response bodies of structures returned to the client, you may only safely access the
|
||||
// data provided through the ExtractServers call.
|
||||
type ServerPage struct {
|
||||
pagination.LinkedPageBase
|
||||
}
|
||||
|
||||
// IsEmpty returns true if a page contains no Server results.
|
||||
func (r ServerPage) IsEmpty() (bool, error) {
|
||||
s, err := ExtractServers(r)
|
||||
return len(s) == 0, err
|
||||
}
|
||||
|
||||
// NextPageURL uses the response's embedded link reference to navigate to the next page of results.
|
||||
func (r ServerPage) NextPageURL() (string, error) {
|
||||
var s struct {
|
||||
Links []gophercloud.Link `json:"servers_links"`
|
||||
}
|
||||
err := r.ExtractInto(&s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return gophercloud.ExtractNextURL(s.Links)
|
||||
}
|
||||
|
||||
// ExtractServers interprets the results of a single page from a List() call, producing a slice of Server entities.
|
||||
func ExtractServers(r pagination.Page) ([]Server, error) {
|
||||
var s []Server
|
||||
err := ExtractServersInto(r, &s)
|
||||
return s, err
|
||||
}
|
||||
|
||||
// MetadataResult contains the result of a call for (potentially) multiple key-value pairs.
|
||||
type MetadataResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// GetMetadataResult temporarily contains the response from a metadata Get call.
|
||||
type GetMetadataResult struct {
|
||||
MetadataResult
|
||||
}
|
||||
|
||||
// ResetMetadataResult temporarily contains the response from a metadata Reset call.
|
||||
type ResetMetadataResult struct {
|
||||
MetadataResult
|
||||
}
|
||||
|
||||
// UpdateMetadataResult temporarily contains the response from a metadata Update call.
|
||||
type UpdateMetadataResult struct {
|
||||
MetadataResult
|
||||
}
|
||||
|
||||
// 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.
|
||||
type GetMetadatumResult struct {
|
||||
MetadatumResult
|
||||
}
|
||||
|
||||
// CreateMetadatumResult temporarily contains the response from a metadatum Create call.
|
||||
type CreateMetadatumResult struct {
|
||||
MetadatumResult
|
||||
}
|
||||
|
||||
// DeleteMetadatumResult temporarily contains the response from a metadatum Delete call.
|
||||
type DeleteMetadatumResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
||||
|
||||
// Extract interprets any MetadataResult as a Metadata, if possible.
|
||||
func (r MetadataResult) Extract() (map[string]string, error) {
|
||||
var s struct {
|
||||
Metadata map[string]string `json:"metadata"`
|
||||
}
|
||||
err := r.ExtractInto(&s)
|
||||
return s.Metadata, err
|
||||
}
|
||||
|
||||
// Extract interprets any MetadatumResult as a Metadatum, if possible.
|
||||
func (r MetadatumResult) Extract() (map[string]string, error) {
|
||||
var s struct {
|
||||
Metadatum map[string]string `json:"meta"`
|
||||
}
|
||||
err := r.ExtractInto(&s)
|
||||
return s.Metadatum, err
|
||||
}
|
||||
|
||||
// Address represents an IP address.
|
||||
type Address struct {
|
||||
Version int `json:"version"`
|
||||
Address string `json:"addr"`
|
||||
}
|
||||
|
||||
// AddressPage abstracts the raw results of making a ListAddresses() request against the API.
|
||||
// As OpenStack extensions may freely alter the response bodies of structures returned
|
||||
// to the client, you may only safely access the data provided through the ExtractAddresses call.
|
||||
type AddressPage struct {
|
||||
pagination.SinglePageBase
|
||||
}
|
||||
|
||||
// IsEmpty returns true if an AddressPage contains no networks.
|
||||
func (r AddressPage) IsEmpty() (bool, error) {
|
||||
addresses, err := ExtractAddresses(r)
|
||||
return len(addresses) == 0, err
|
||||
}
|
||||
|
||||
// ExtractAddresses interprets the results of a single page from a ListAddresses() call,
|
||||
// producing a map of addresses.
|
||||
func ExtractAddresses(r pagination.Page) (map[string][]Address, error) {
|
||||
var s struct {
|
||||
Addresses map[string][]Address `json:"addresses"`
|
||||
}
|
||||
err := (r.(AddressPage)).ExtractInto(&s)
|
||||
return s.Addresses, err
|
||||
}
|
||||
|
||||
// NetworkAddressPage abstracts the raw results of making a ListAddressesByNetwork() request against the API.
|
||||
// As OpenStack extensions may freely alter the response bodies of structures returned
|
||||
// to the client, you may only safely access the data provided through the ExtractAddresses call.
|
||||
type NetworkAddressPage struct {
|
||||
pagination.SinglePageBase
|
||||
}
|
||||
|
||||
// IsEmpty returns true if a NetworkAddressPage contains no addresses.
|
||||
func (r NetworkAddressPage) IsEmpty() (bool, error) {
|
||||
addresses, err := ExtractNetworkAddresses(r)
|
||||
return len(addresses) == 0, err
|
||||
}
|
||||
|
||||
// ExtractNetworkAddresses interprets the results of a single page from a ListAddressesByNetwork() call,
|
||||
// producing a slice of addresses.
|
||||
func ExtractNetworkAddresses(r pagination.Page) ([]Address, error) {
|
||||
var s map[string][]Address
|
||||
err := (r.(NetworkAddressPage)).ExtractInto(&s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var key string
|
||||
for k := range s {
|
||||
key = k
|
||||
}
|
||||
|
||||
return s[key], err
|
||||
}
|
51
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/urls.go
generated
vendored
Normal file
51
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/urls.go
generated
vendored
Normal file
|
@ -0,0 +1,51 @@
|
|||
package servers
|
||||
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
func createURL(client *gophercloud.ServiceClient) string {
|
||||
return client.ServiceURL("servers")
|
||||
}
|
||||
|
||||
func listURL(client *gophercloud.ServiceClient) string {
|
||||
return createURL(client)
|
||||
}
|
||||
|
||||
func listDetailURL(client *gophercloud.ServiceClient) string {
|
||||
return client.ServiceURL("servers", "detail")
|
||||
}
|
||||
|
||||
func deleteURL(client *gophercloud.ServiceClient, id string) string {
|
||||
return client.ServiceURL("servers", id)
|
||||
}
|
||||
|
||||
func getURL(client *gophercloud.ServiceClient, id string) string {
|
||||
return deleteURL(client, id)
|
||||
}
|
||||
|
||||
func updateURL(client *gophercloud.ServiceClient, id string) string {
|
||||
return deleteURL(client, id)
|
||||
}
|
||||
|
||||
func actionURL(client *gophercloud.ServiceClient, id string) string {
|
||||
return client.ServiceURL("servers", id, "action")
|
||||
}
|
||||
|
||||
func metadatumURL(client *gophercloud.ServiceClient, id, key string) string {
|
||||
return client.ServiceURL("servers", id, "metadata", key)
|
||||
}
|
||||
|
||||
func metadataURL(client *gophercloud.ServiceClient, id string) string {
|
||||
return client.ServiceURL("servers", id, "metadata")
|
||||
}
|
||||
|
||||
func listAddressesURL(client *gophercloud.ServiceClient, id string) string {
|
||||
return client.ServiceURL("servers", id, "ips")
|
||||
}
|
||||
|
||||
func listAddressesByNetworkURL(client *gophercloud.ServiceClient, id, network string) string {
|
||||
return client.ServiceURL("servers", id, "ips", network)
|
||||
}
|
||||
|
||||
func passwordURL(client *gophercloud.ServiceClient, id string) string {
|
||||
return client.ServiceURL("servers", id, "os-server-password")
|
||||
}
|
20
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/util.go
generated
vendored
Normal file
20
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/util.go
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
package servers
|
||||
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
// WaitForStatus will continually poll a server until it successfully transitions to a specified
|
||||
// status. It will do this for at most the number of seconds specified.
|
||||
func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error {
|
||||
return gophercloud.WaitFor(secs, func() (bool, error) {
|
||||
current, err := Get(c, id).Extract()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if current.Status == status {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
})
|
||||
}
|
99
vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go
generated
vendored
Normal file
99
vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go
generated
vendored
Normal file
|
@ -0,0 +1,99 @@
|
|||
package openstack
|
||||
|
||||
import (
|
||||
"github.com/gophercloud/gophercloud"
|
||||
tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
|
||||
tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
|
||||
)
|
||||
|
||||
// V2EndpointURL discovers the endpoint URL for a specific service from a ServiceCatalog acquired
|
||||
// during the v2 identity service. The specified EndpointOpts are used to identify a unique,
|
||||
// unambiguous endpoint to return. It's an error both when multiple endpoints match the provided
|
||||
// criteria and when none do. The minimum that can be specified is a Type, but you will also often
|
||||
// need to specify a Name and/or a Region depending on what's available on your OpenStack
|
||||
// deployment.
|
||||
func V2EndpointURL(catalog *tokens2.ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) {
|
||||
// Extract Endpoints from the catalog entries that match the requested Type, Name if provided, and Region if provided.
|
||||
var endpoints = make([]tokens2.Endpoint, 0, 1)
|
||||
for _, entry := range catalog.Entries {
|
||||
if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) {
|
||||
for _, endpoint := range entry.Endpoints {
|
||||
if opts.Region == "" || endpoint.Region == opts.Region {
|
||||
endpoints = append(endpoints, endpoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Report an error if the options were ambiguous.
|
||||
if len(endpoints) > 1 {
|
||||
err := &ErrMultipleMatchingEndpointsV2{}
|
||||
err.Endpoints = endpoints
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Extract the appropriate URL from the matching Endpoint.
|
||||
for _, endpoint := range endpoints {
|
||||
switch opts.Availability {
|
||||
case gophercloud.AvailabilityPublic:
|
||||
return gophercloud.NormalizeURL(endpoint.PublicURL), nil
|
||||
case gophercloud.AvailabilityInternal:
|
||||
return gophercloud.NormalizeURL(endpoint.InternalURL), nil
|
||||
case gophercloud.AvailabilityAdmin:
|
||||
return gophercloud.NormalizeURL(endpoint.AdminURL), nil
|
||||
default:
|
||||
err := &ErrInvalidAvailabilityProvided{}
|
||||
err.Argument = "Availability"
|
||||
err.Value = opts.Availability
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// Report an error if there were no matching endpoints.
|
||||
err := &gophercloud.ErrEndpointNotFound{}
|
||||
return "", err
|
||||
}
|
||||
|
||||
// V3EndpointURL discovers the endpoint URL for a specific service from a Catalog acquired
|
||||
// during the v3 identity service. The specified EndpointOpts are used to identify a unique,
|
||||
// unambiguous endpoint to return. It's an error both when multiple endpoints match the provided
|
||||
// criteria and when none do. The minimum that can be specified is a Type, but you will also often
|
||||
// need to specify a Name and/or a Region depending on what's available on your OpenStack
|
||||
// deployment.
|
||||
func V3EndpointURL(catalog *tokens3.ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) {
|
||||
// Extract Endpoints from the catalog entries that match the requested Type, Interface,
|
||||
// Name if provided, and Region if provided.
|
||||
var endpoints = make([]tokens3.Endpoint, 0, 1)
|
||||
for _, entry := range catalog.Entries {
|
||||
if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) {
|
||||
for _, endpoint := range entry.Endpoints {
|
||||
if opts.Availability != gophercloud.AvailabilityAdmin &&
|
||||
opts.Availability != gophercloud.AvailabilityPublic &&
|
||||
opts.Availability != gophercloud.AvailabilityInternal {
|
||||
err := &ErrInvalidAvailabilityProvided{}
|
||||
err.Argument = "Availability"
|
||||
err.Value = opts.Availability
|
||||
return "", err
|
||||
}
|
||||
if (opts.Availability == gophercloud.Availability(endpoint.Interface)) &&
|
||||
(opts.Region == "" || endpoint.Region == opts.Region) {
|
||||
endpoints = append(endpoints, endpoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Report an error if the options were ambiguous.
|
||||
if len(endpoints) > 1 {
|
||||
return "", ErrMultipleMatchingEndpointsV3{Endpoints: endpoints}
|
||||
}
|
||||
|
||||
// Extract the URL from the matching Endpoint.
|
||||
for _, endpoint := range endpoints {
|
||||
return gophercloud.NormalizeURL(endpoint.URL), nil
|
||||
}
|
||||
|
||||
// Report an error if there were no matching endpoints.
|
||||
err := &gophercloud.ErrEndpointNotFound{}
|
||||
return "", err
|
||||
}
|
71
vendor/github.com/gophercloud/gophercloud/openstack/errors.go
generated
vendored
Normal file
71
vendor/github.com/gophercloud/gophercloud/openstack/errors.go
generated
vendored
Normal file
|
@ -0,0 +1,71 @@
|
|||
package openstack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
|
||||
tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
|
||||
)
|
||||
|
||||
// ErrEndpointNotFound is the error when no suitable endpoint can be found
|
||||
// in the user's catalog
|
||||
type ErrEndpointNotFound struct{ gophercloud.BaseError }
|
||||
|
||||
func (e ErrEndpointNotFound) Error() string {
|
||||
return "No suitable endpoint could be found in the service catalog."
|
||||
}
|
||||
|
||||
// ErrInvalidAvailabilityProvided is the error when an invalid endpoint
|
||||
// availability is provided
|
||||
type ErrInvalidAvailabilityProvided struct{ gophercloud.ErrInvalidInput }
|
||||
|
||||
func (e ErrInvalidAvailabilityProvided) Error() string {
|
||||
return fmt.Sprintf("Unexpected availability in endpoint query: %s", e.Value)
|
||||
}
|
||||
|
||||
// ErrMultipleMatchingEndpointsV2 is the error when more than one endpoint
|
||||
// for the given options is found in the v2 catalog
|
||||
type ErrMultipleMatchingEndpointsV2 struct {
|
||||
gophercloud.BaseError
|
||||
Endpoints []tokens2.Endpoint
|
||||
}
|
||||
|
||||
func (e ErrMultipleMatchingEndpointsV2) Error() string {
|
||||
return fmt.Sprintf("Discovered %d matching endpoints: %#v", len(e.Endpoints), e.Endpoints)
|
||||
}
|
||||
|
||||
// ErrMultipleMatchingEndpointsV3 is the error when more than one endpoint
|
||||
// for the given options is found in the v3 catalog
|
||||
type ErrMultipleMatchingEndpointsV3 struct {
|
||||
gophercloud.BaseError
|
||||
Endpoints []tokens3.Endpoint
|
||||
}
|
||||
|
||||
func (e ErrMultipleMatchingEndpointsV3) Error() string {
|
||||
return fmt.Sprintf("Discovered %d matching endpoints: %#v", len(e.Endpoints), e.Endpoints)
|
||||
}
|
||||
|
||||
// ErrNoAuthURL is the error when the OS_AUTH_URL environment variable is not
|
||||
// found
|
||||
type ErrNoAuthURL struct{ gophercloud.ErrInvalidInput }
|
||||
|
||||
func (e ErrNoAuthURL) Error() string {
|
||||
return "Environment variable OS_AUTH_URL needs to be set."
|
||||
}
|
||||
|
||||
// ErrNoUsername is the error when the OS_USERNAME environment variable is not
|
||||
// found
|
||||
type ErrNoUsername struct{ gophercloud.ErrInvalidInput }
|
||||
|
||||
func (e ErrNoUsername) Error() string {
|
||||
return "Environment variable OS_USERNAME needs to be set."
|
||||
}
|
||||
|
||||
// ErrNoPassword is the error when the OS_PASSWORD environment variable is not
|
||||
// found
|
||||
type ErrNoPassword struct{ gophercloud.ErrInvalidInput }
|
||||
|
||||
func (e ErrNoPassword) Error() string {
|
||||
return "Environment variable OS_PASSWORD needs to be set."
|
||||
}
|
7
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/doc.go
generated
vendored
Normal file
7
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
// 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
|
29
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go
generated
vendored
Normal file
29
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
package tenants
|
||||
|
||||
import (
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// ListOpts filters the Tenants that are returned by the List call.
|
||||
type ListOpts struct {
|
||||
// Marker is the ID of the last Tenant on the previous page.
|
||||
Marker string `q:"marker"`
|
||||
// Limit specifies the page size.
|
||||
Limit int `q:"limit"`
|
||||
}
|
||||
|
||||
// List enumerates the Tenants to which the current token has access.
|
||||
func List(client *gophercloud.ServiceClient, opts *ListOpts) pagination.Pager {
|
||||
url := listURL(client)
|
||||
if opts != nil {
|
||||
q, err := gophercloud.BuildQueryString(opts)
|
||||
if err != nil {
|
||||
return pagination.Pager{Err: err}
|
||||
}
|
||||
url += q.String()
|
||||
}
|
||||
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
|
||||
return TenantPage{pagination.LinkedPageBase{PageResult: r}}
|
||||
})
|
||||
}
|
53
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/results.go
generated
vendored
Normal file
53
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/results.go
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
package tenants
|
||||
|
||||
import (
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// Tenant is a grouping of users in the identity service.
|
||||
type Tenant struct {
|
||||
// ID is a unique identifier for this tenant.
|
||||
ID string `json:"id"`
|
||||
|
||||
// Name is a friendlier user-facing name for this tenant.
|
||||
Name string `json:"name"`
|
||||
|
||||
// Description is a human-readable explanation of this Tenant's purpose.
|
||||
Description string `json:"description"`
|
||||
|
||||
// Enabled indicates whether or not a tenant is active.
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
// TenantPage is a single page of Tenant results.
|
||||
type TenantPage struct {
|
||||
pagination.LinkedPageBase
|
||||
}
|
||||
|
||||
// IsEmpty determines whether or not a page of Tenants contains any results.
|
||||
func (r TenantPage) IsEmpty() (bool, error) {
|
||||
tenants, err := ExtractTenants(r)
|
||||
return len(tenants) == 0, err
|
||||
}
|
||||
|
||||
// NextPageURL extracts the "next" link from the tenants_links section of the result.
|
||||
func (r TenantPage) NextPageURL() (string, error) {
|
||||
var s struct {
|
||||
Links []gophercloud.Link `json:"tenants_links"`
|
||||
}
|
||||
err := r.ExtractInto(&s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return gophercloud.ExtractNextURL(s.Links)
|
||||
}
|
||||
|
||||
// ExtractTenants returns a slice of Tenants contained in a single page of results.
|
||||
func ExtractTenants(r pagination.Page) ([]Tenant, error) {
|
||||
var s struct {
|
||||
Tenants []Tenant `json:"tenants"`
|
||||
}
|
||||
err := (r.(TenantPage)).ExtractInto(&s)
|
||||
return s.Tenants, err
|
||||
}
|
7
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/urls.go
generated
vendored
Normal file
7
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/urls.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
package tenants
|
||||
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
func listURL(client *gophercloud.ServiceClient) string {
|
||||
return client.ServiceURL("tenants")
|
||||
}
|
5
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/doc.go
generated
vendored
Normal file
5
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
// 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
|
99
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go
generated
vendored
Normal file
99
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go
generated
vendored
Normal file
|
@ -0,0 +1,99 @@
|
|||
package tokens
|
||||
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
type PasswordCredentialsV2 struct {
|
||||
Username string `json:"username" required:"true"`
|
||||
Password string `json:"password" required:"true"`
|
||||
}
|
||||
|
||||
type TokenCredentialsV2 struct {
|
||||
ID string `json:"id,omitempty" required:"true"`
|
||||
}
|
||||
|
||||
// AuthOptionsV2 wraps a gophercloud AuthOptions in order to adhere to the AuthOptionsBuilder
|
||||
// interface.
|
||||
type AuthOptionsV2 struct {
|
||||
PasswordCredentials *PasswordCredentialsV2 `json:"passwordCredentials,omitempty" xor:"TokenCredentials"`
|
||||
|
||||
// The TenantID and TenantName fields are optional for the Identity V2 API.
|
||||
// Some providers allow you to specify a TenantName instead of the TenantId.
|
||||
// Some require both. Your provider's authentication policies will determine
|
||||
// how these fields influence authentication.
|
||||
TenantID string `json:"tenantId,omitempty"`
|
||||
TenantName string `json:"tenantName,omitempty"`
|
||||
|
||||
// TokenCredentials allows users to authenticate (possibly as another user) with an
|
||||
// authentication token ID.
|
||||
TokenCredentials *TokenCredentialsV2 `json:"token,omitempty" xor:"PasswordCredentials"`
|
||||
}
|
||||
|
||||
// AuthOptionsBuilder describes any argument that may be passed to the Create call.
|
||||
type AuthOptionsBuilder interface {
|
||||
// ToTokenCreateMap assembles the Create request body, returning an error if parameters are
|
||||
// missing or inconsistent.
|
||||
ToTokenV2CreateMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// AuthOptions are the valid options for Openstack Identity v2 authentication.
|
||||
// For field descriptions, see gophercloud.AuthOptions.
|
||||
type AuthOptions struct {
|
||||
IdentityEndpoint string `json:"-"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
TenantID string `json:"tenantId,omitempty"`
|
||||
TenantName string `json:"tenantName,omitempty"`
|
||||
AllowReauth bool `json:"-"`
|
||||
TokenID string
|
||||
}
|
||||
|
||||
// ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder
|
||||
// interface in the v2 tokens package
|
||||
func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) {
|
||||
v2Opts := AuthOptionsV2{
|
||||
TenantID: opts.TenantID,
|
||||
TenantName: opts.TenantName,
|
||||
}
|
||||
|
||||
if opts.Password != "" {
|
||||
v2Opts.PasswordCredentials = &PasswordCredentialsV2{
|
||||
Username: opts.Username,
|
||||
Password: opts.Password,
|
||||
}
|
||||
} else {
|
||||
v2Opts.TokenCredentials = &TokenCredentialsV2{
|
||||
ID: opts.TokenID,
|
||||
}
|
||||
}
|
||||
|
||||
b, err := gophercloud.BuildRequestBody(v2Opts, "auth")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Create authenticates to the identity service and attempts to acquire a Token.
|
||||
// If successful, the CreateResult
|
||||
// Generally, rather than interact with this call directly, end users should call openstack.AuthenticatedClient(),
|
||||
// which abstracts all of the gory details about navigating service catalogs and such.
|
||||
func Create(client *gophercloud.ServiceClient, auth AuthOptionsBuilder) (r CreateResult) {
|
||||
b, err := auth.ToTokenV2CreateMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(CreateURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200, 203},
|
||||
MoreHeaders: map[string]string{"X-Auth-Token": ""},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get validates and retrieves information for user's token.
|
||||
func Get(client *gophercloud.ServiceClient, token string) (r GetResult) {
|
||||
_, r.Err = client.Get(GetURL(client, token), &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200, 203},
|
||||
})
|
||||
return
|
||||
}
|
144
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go
generated
vendored
Normal file
144
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go
generated
vendored
Normal file
|
@ -0,0 +1,144 @@
|
|||
package tokens
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack/identity/v2/tenants"
|
||||
)
|
||||
|
||||
// Token provides only the most basic information related to an authentication token.
|
||||
type Token struct {
|
||||
// ID provides the primary means of identifying a user to the OpenStack API.
|
||||
// OpenStack defines this field as an opaque value, so do not depend on its content.
|
||||
// It is safe, however, to compare for equality.
|
||||
ID string
|
||||
|
||||
// ExpiresAt provides a timestamp in ISO 8601 format, indicating when the authentication token becomes invalid.
|
||||
// After this point in time, future API requests made using this authentication token will respond with errors.
|
||||
// Either the caller will need to reauthenticate manually, or more preferably, the caller should exploit automatic re-authentication.
|
||||
// See the AuthOptions structure for more details.
|
||||
ExpiresAt time.Time
|
||||
|
||||
// Tenant provides information about the tenant to which this token grants access.
|
||||
Tenant tenants.Tenant
|
||||
}
|
||||
|
||||
// Role is a role for a user.
|
||||
type Role struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// User is an OpenStack user.
|
||||
type User struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
UserName string `json:"username"`
|
||||
Roles []Role `json:"roles"`
|
||||
}
|
||||
|
||||
// Endpoint represents a single API endpoint offered by a service.
|
||||
// It provides the public and internal URLs, if supported, along with a region specifier, again if provided.
|
||||
// The significance of the Region field will depend upon your provider.
|
||||
//
|
||||
// In addition, the interface offered by the service will have version information associated with it
|
||||
// through the VersionId, VersionInfo, and VersionList fields, if provided or supported.
|
||||
//
|
||||
// In all cases, fields which aren't supported by the provider and service combined will assume a zero-value ("").
|
||||
type Endpoint struct {
|
||||
TenantID string `json:"tenantId"`
|
||||
PublicURL string `json:"publicURL"`
|
||||
InternalURL string `json:"internalURL"`
|
||||
AdminURL string `json:"adminURL"`
|
||||
Region string `json:"region"`
|
||||
VersionID string `json:"versionId"`
|
||||
VersionInfo string `json:"versionInfo"`
|
||||
VersionList string `json:"versionList"`
|
||||
}
|
||||
|
||||
// CatalogEntry provides a type-safe interface to an Identity API V2 service catalog listing.
|
||||
// Each class of service, such as cloud DNS or block storage services, will have a single
|
||||
// CatalogEntry representing it.
|
||||
//
|
||||
// Note: when looking for the desired service, try, whenever possible, to key off the type field.
|
||||
// Otherwise, you'll tie the representation of the service to a specific provider.
|
||||
type CatalogEntry struct {
|
||||
// Name will contain the provider-specified name for the service.
|
||||
Name string `json:"name"`
|
||||
|
||||
// Type will contain a type string if OpenStack defines a type for the service.
|
||||
// Otherwise, for provider-specific services, the provider may assign their own type strings.
|
||||
Type string `json:"type"`
|
||||
|
||||
// Endpoints will let the caller iterate over all the different endpoints that may exist for
|
||||
// the service.
|
||||
Endpoints []Endpoint `json:"endpoints"`
|
||||
}
|
||||
|
||||
// ServiceCatalog provides a view into the service catalog from a previous, successful authentication.
|
||||
type ServiceCatalog struct {
|
||||
Entries []CatalogEntry
|
||||
}
|
||||
|
||||
// CreateResult defers the interpretation of a created token.
|
||||
// Use ExtractToken() to interpret it as a Token, or ExtractServiceCatalog() to interpret it as a service catalog.
|
||||
type CreateResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// GetResult is the deferred response from a Get call, which is the same with a Created token.
|
||||
// Use ExtractUser() to interpret it as a User.
|
||||
type GetResult struct {
|
||||
CreateResult
|
||||
}
|
||||
|
||||
// ExtractToken returns the just-created Token from a CreateResult.
|
||||
func (r CreateResult) ExtractToken() (*Token, error) {
|
||||
var s struct {
|
||||
Access struct {
|
||||
Token struct {
|
||||
Expires string `json:"expires"`
|
||||
ID string `json:"id"`
|
||||
Tenant tenants.Tenant `json:"tenant"`
|
||||
} `json:"token"`
|
||||
} `json:"access"`
|
||||
}
|
||||
|
||||
err := r.ExtractInto(&s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
expiresTs, err := time.Parse(gophercloud.RFC3339Milli, s.Access.Token.Expires)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Token{
|
||||
ID: s.Access.Token.ID,
|
||||
ExpiresAt: expiresTs,
|
||||
Tenant: s.Access.Token.Tenant,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ExtractServiceCatalog returns the ServiceCatalog that was generated along with the user's Token.
|
||||
func (r CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) {
|
||||
var s struct {
|
||||
Access struct {
|
||||
Entries []CatalogEntry `json:"serviceCatalog"`
|
||||
} `json:"access"`
|
||||
}
|
||||
err := r.ExtractInto(&s)
|
||||
return &ServiceCatalog{Entries: s.Access.Entries}, err
|
||||
}
|
||||
|
||||
// ExtractUser returns the User from a GetResult.
|
||||
func (r GetResult) ExtractUser() (*User, error) {
|
||||
var s struct {
|
||||
Access struct {
|
||||
User User `json:"user"`
|
||||
} `json:"access"`
|
||||
}
|
||||
err := r.ExtractInto(&s)
|
||||
return &s.Access.User, err
|
||||
}
|
13
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/urls.go
generated
vendored
Normal file
13
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/urls.go
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
package tokens
|
||||
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
// CreateURL generates the URL used to create new Tokens.
|
||||
func CreateURL(client *gophercloud.ServiceClient) string {
|
||||
return client.ServiceURL("tokens")
|
||||
}
|
||||
|
||||
// GetURL generates the URL used to Validate Tokens.
|
||||
func GetURL(client *gophercloud.ServiceClient, token string) string {
|
||||
return client.ServiceURL("tokens", token)
|
||||
}
|
6
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/doc.go
generated
vendored
Normal file
6
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
// 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
|
200
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go
generated
vendored
Normal file
200
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go
generated
vendored
Normal file
|
@ -0,0 +1,200 @@
|
|||
package tokens
|
||||
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
// Scope allows a created token to be limited to a specific domain or project.
|
||||
type Scope struct {
|
||||
ProjectID string `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"`
|
||||
}
|
||||
|
||||
// AuthOptionsBuilder describes any argument that may be passed to the Create call.
|
||||
type AuthOptionsBuilder interface {
|
||||
// ToTokenV3CreateMap assembles the Create request body, returning an error if parameters are
|
||||
// missing or inconsistent.
|
||||
ToTokenV3CreateMap(map[string]interface{}) (map[string]interface{}, error)
|
||||
ToTokenV3ScopeMap() (map[string]interface{}, error)
|
||||
CanReauth() bool
|
||||
}
|
||||
|
||||
type AuthOptions struct {
|
||||
// IdentityEndpoint specifies the HTTP endpoint that is required to work with
|
||||
// the Identity API of the appropriate version. While it's ultimately needed by
|
||||
// all of the identity services, it will often be populated by a provider-level
|
||||
// function.
|
||||
IdentityEndpoint string `json:"-"`
|
||||
|
||||
// Username is required if using Identity V2 API. Consult with your provider's
|
||||
// control panel to discover your account's username. In Identity V3, either
|
||||
// UserID or a combination of Username and DomainID or DomainName are needed.
|
||||
Username string `json:"username,omitempty"`
|
||||
UserID string `json:"id,omitempty"`
|
||||
|
||||
Password string `json:"password,omitempty"`
|
||||
|
||||
// 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"`
|
||||
DomainName string `json:"name,omitempty"`
|
||||
|
||||
// AllowReauth should be set to true if you grant permission for Gophercloud to
|
||||
// cache your credentials in memory, and to allow Gophercloud to attempt to
|
||||
// re-authenticate automatically if/when your token expires. If you set it to
|
||||
// false, it will not cache these settings, but re-authentication will not be
|
||||
// possible. This setting defaults to false.
|
||||
AllowReauth bool `json:"-"`
|
||||
|
||||
// TokenID allows users to authenticate (possibly as another user) with an
|
||||
// authentication token ID.
|
||||
TokenID string `json:"-"`
|
||||
|
||||
Scope Scope `json:"-"`
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
return gophercloudAuthOpts.ToTokenV3CreateMap(scope)
|
||||
}
|
||||
|
||||
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{}
|
||||
}
|
||||
|
||||
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{}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (opts *AuthOptions) CanReauth() bool {
|
||||
return opts.AllowReauth
|
||||
}
|
||||
|
||||
func subjectTokenHeaders(c *gophercloud.ServiceClient, subjectToken string) map[string]string {
|
||||
return map[string]string{
|
||||
"X-Subject-Token": subjectToken,
|
||||
}
|
||||
}
|
||||
|
||||
// Create authenticates and either generates a new token, or changes the Scope of an existing token.
|
||||
func Create(c *gophercloud.ServiceClient, opts AuthOptionsBuilder) (r CreateResult) {
|
||||
scope, err := opts.ToTokenV3ScopeMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
b, err := opts.ToTokenV3CreateMap(scope)
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := c.Post(tokenURL(c), b, &r.Body, &gophercloud.RequestOpts{
|
||||
MoreHeaders: map[string]string{"X-Auth-Token": ""},
|
||||
})
|
||||
r.Err = err
|
||||
if resp != nil {
|
||||
r.Header = resp.Header
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get validates and retrieves information about another token.
|
||||
func Get(c *gophercloud.ServiceClient, token string) (r GetResult) {
|
||||
resp, err := c.Get(tokenURL(c), &r.Body, &gophercloud.RequestOpts{
|
||||
MoreHeaders: subjectTokenHeaders(c, token),
|
||||
OkCodes: []int{200, 203},
|
||||
})
|
||||
if resp != nil {
|
||||
r.Err = err
|
||||
r.Header = resp.Header
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 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{
|
||||
MoreHeaders: subjectTokenHeaders(c, token),
|
||||
OkCodes: []int{204, 404},
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return resp.StatusCode == 204, nil
|
||||
}
|
||||
|
||||
// Revoke immediately makes specified token invalid.
|
||||
func Revoke(c *gophercloud.ServiceClient, token string) (r RevokeResult) {
|
||||
_, r.Err = c.Delete(tokenURL(c), &gophercloud.RequestOpts{
|
||||
MoreHeaders: subjectTokenHeaders(c, token),
|
||||
})
|
||||
return
|
||||
}
|
103
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go
generated
vendored
Normal file
103
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go
generated
vendored
Normal file
|
@ -0,0 +1,103 @@
|
|||
package tokens
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
)
|
||||
|
||||
// Endpoint represents a single API endpoint offered by a service.
|
||||
// It matches either a public, internal or admin URL.
|
||||
// If supported, it contains a region specifier, again if provided.
|
||||
// The significance of the Region field will depend upon your provider.
|
||||
type Endpoint struct {
|
||||
ID string `json:"id"`
|
||||
Region string `json:"region"`
|
||||
Interface string `json:"interface"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// CatalogEntry provides a type-safe interface to an Identity API V3 service catalog listing.
|
||||
// Each class of service, such as cloud DNS or block storage services, could have multiple
|
||||
// CatalogEntry representing it (one by interface type, e.g public, admin or internal).
|
||||
//
|
||||
// Note: when looking for the desired service, try, whenever possible, to key off the type field.
|
||||
// Otherwise, you'll tie the representation of the service to a specific provider.
|
||||
type CatalogEntry struct {
|
||||
// Service ID
|
||||
ID string `json:"id"`
|
||||
// Name will contain the provider-specified name for the service.
|
||||
Name string `json:"name"`
|
||||
// Type will contain a type string if OpenStack defines a type for the service.
|
||||
// Otherwise, for provider-specific services, the provider may assign their own type strings.
|
||||
Type string `json:"type"`
|
||||
// Endpoints will let the caller iterate over all the different endpoints that may exist for
|
||||
// the service.
|
||||
Endpoints []Endpoint `json:"endpoints"`
|
||||
}
|
||||
|
||||
// ServiceCatalog provides a view into the service catalog from a previous, successful authentication.
|
||||
type ServiceCatalog struct {
|
||||
Entries []CatalogEntry `json:"catalog"`
|
||||
}
|
||||
|
||||
// commonResult is the deferred result of a Create or a Get call.
|
||||
type commonResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// Extract is a shortcut for ExtractToken.
|
||||
// This function is deprecated and still present for backward compatibility.
|
||||
func (r commonResult) Extract() (*Token, error) {
|
||||
return r.ExtractToken()
|
||||
}
|
||||
|
||||
// ExtractToken interprets a commonResult as a Token.
|
||||
func (r commonResult) ExtractToken() (*Token, error) {
|
||||
var s Token
|
||||
err := r.ExtractInto(&s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Parse the token itself from the stored headers.
|
||||
s.ID = r.Header.Get("X-Subject-Token")
|
||||
|
||||
return &s, err
|
||||
}
|
||||
|
||||
// ExtractServiceCatalog returns the ServiceCatalog that was generated along with the user's Token.
|
||||
func (r CreateResult) 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.
|
||||
type CreateResult struct {
|
||||
commonResult
|
||||
}
|
||||
|
||||
// GetResult is the deferred response from a Get call.
|
||||
type GetResult struct {
|
||||
commonResult
|
||||
}
|
||||
|
||||
// RevokeResult is the deferred response from a Revoke call.
|
||||
type RevokeResult struct {
|
||||
commonResult
|
||||
}
|
||||
|
||||
// Token is a string that grants a user access to a controlled set of services in an OpenStack provider.
|
||||
// Each Token is valid for a set length of time.
|
||||
type Token struct {
|
||||
// ID is the issued token.
|
||||
ID string `json:"id"`
|
||||
// ExpiresAt is the timestamp at which this token will no longer be accepted.
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
}
|
||||
|
||||
func (r commonResult) ExtractInto(v interface{}) error {
|
||||
return r.ExtractIntoStructPtr(v, "token")
|
||||
}
|
7
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/urls.go
generated
vendored
Normal file
7
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/urls.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
package tokens
|
||||
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
func tokenURL(c *gophercloud.ServiceClient) string {
|
||||
return c.ServiceURL("auth", "tokens")
|
||||
}
|
114
vendor/github.com/gophercloud/gophercloud/openstack/utils/choose_version.go
generated
vendored
Normal file
114
vendor/github.com/gophercloud/gophercloud/openstack/utils/choose_version.go
generated
vendored
Normal file
|
@ -0,0 +1,114 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
)
|
||||
|
||||
// Version is a supported API version, corresponding to a vN package within the appropriate service.
|
||||
type Version struct {
|
||||
ID string
|
||||
Suffix string
|
||||
Priority int
|
||||
}
|
||||
|
||||
var goodStatus = map[string]bool{
|
||||
"current": true,
|
||||
"supported": true,
|
||||
"stable": true,
|
||||
}
|
||||
|
||||
// ChooseVersion queries the base endpoint of an API to choose the most recent non-experimental alternative from a service's
|
||||
// published versions.
|
||||
// It returns the highest-Priority Version among the alternatives that are provided, as well as its corresponding endpoint.
|
||||
func ChooseVersion(client *gophercloud.ProviderClient, recognized []*Version) (*Version, string, error) {
|
||||
type linkResp struct {
|
||||
Href string `json:"href"`
|
||||
Rel string `json:"rel"`
|
||||
}
|
||||
|
||||
type valueResp struct {
|
||||
ID string `json:"id"`
|
||||
Status string `json:"status"`
|
||||
Links []linkResp `json:"links"`
|
||||
}
|
||||
|
||||
type versionsResp struct {
|
||||
Values []valueResp `json:"values"`
|
||||
}
|
||||
|
||||
type response struct {
|
||||
Versions versionsResp `json:"versions"`
|
||||
}
|
||||
|
||||
normalize := func(endpoint string) string {
|
||||
if !strings.HasSuffix(endpoint, "/") {
|
||||
return endpoint + "/"
|
||||
}
|
||||
return endpoint
|
||||
}
|
||||
identityEndpoint := normalize(client.IdentityEndpoint)
|
||||
|
||||
// If a full endpoint is specified, check version suffixes for a match first.
|
||||
for _, v := range recognized {
|
||||
if strings.HasSuffix(identityEndpoint, v.Suffix) {
|
||||
return v, identityEndpoint, nil
|
||||
}
|
||||
}
|
||||
|
||||
var resp response
|
||||
_, err := client.Request("GET", client.IdentityBase, &gophercloud.RequestOpts{
|
||||
JSONResponse: &resp,
|
||||
OkCodes: []int{200, 300},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
byID := make(map[string]*Version)
|
||||
for _, version := range recognized {
|
||||
byID[version.ID] = version
|
||||
}
|
||||
|
||||
var highest *Version
|
||||
var endpoint string
|
||||
|
||||
for _, value := range resp.Versions.Values {
|
||||
href := ""
|
||||
for _, link := range value.Links {
|
||||
if link.Rel == "self" {
|
||||
href = normalize(link.Href)
|
||||
}
|
||||
}
|
||||
|
||||
if matching, ok := byID[value.ID]; ok {
|
||||
// Prefer a version that exactly matches the provided endpoint.
|
||||
if href == identityEndpoint {
|
||||
if href == "" {
|
||||
return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", value.ID, client.IdentityBase)
|
||||
}
|
||||
return matching, href, nil
|
||||
}
|
||||
|
||||
// Otherwise, find the highest-priority version with a whitelisted status.
|
||||
if goodStatus[strings.ToLower(value.Status)] {
|
||||
if highest == nil || matching.Priority > highest.Priority {
|
||||
highest = matching
|
||||
endpoint = href
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if highest == nil {
|
||||
return nil, "", fmt.Errorf("No supported version available from endpoint %s", client.IdentityBase)
|
||||
}
|
||||
if endpoint == "" {
|
||||
return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", highest.ID, client.IdentityBase)
|
||||
}
|
||||
|
||||
return highest, endpoint, nil
|
||||
}
|
60
vendor/github.com/gophercloud/gophercloud/pagination/http.go
generated
vendored
Normal file
60
vendor/github.com/gophercloud/gophercloud/pagination/http.go
generated
vendored
Normal file
|
@ -0,0 +1,60 @@
|
|||
package pagination
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
)
|
||||
|
||||
// PageResult stores the HTTP response that returned the current page of results.
|
||||
type PageResult struct {
|
||||
gophercloud.Result
|
||||
url.URL
|
||||
}
|
||||
|
||||
// PageResultFrom parses an HTTP response as JSON and returns a PageResult containing the
|
||||
// results, interpreting it as JSON if the content type indicates.
|
||||
func PageResultFrom(resp *http.Response) (PageResult, error) {
|
||||
var parsedBody interface{}
|
||||
|
||||
defer resp.Body.Close()
|
||||
rawBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return PageResult{}, err
|
||||
}
|
||||
|
||||
if strings.HasPrefix(resp.Header.Get("Content-Type"), "application/json") {
|
||||
err = json.Unmarshal(rawBody, &parsedBody)
|
||||
if err != nil {
|
||||
return PageResult{}, err
|
||||
}
|
||||
} else {
|
||||
parsedBody = rawBody
|
||||
}
|
||||
|
||||
return PageResultFromParsed(resp, parsedBody), err
|
||||
}
|
||||
|
||||
// PageResultFromParsed constructs a PageResult from an HTTP response that has already had its
|
||||
// body parsed as JSON (and closed).
|
||||
func PageResultFromParsed(resp *http.Response, body interface{}) PageResult {
|
||||
return PageResult{
|
||||
Result: gophercloud.Result{
|
||||
Body: body,
|
||||
Header: resp.Header,
|
||||
},
|
||||
URL: *resp.Request.URL,
|
||||
}
|
||||
}
|
||||
|
||||
// Request performs an HTTP request and extracts the http.Response from the result.
|
||||
func Request(client *gophercloud.ServiceClient, headers map[string]string, url string) (*http.Response, error) {
|
||||
return client.Get(url, nil, &gophercloud.RequestOpts{
|
||||
MoreHeaders: headers,
|
||||
OkCodes: []int{200, 204},
|
||||
})
|
||||
}
|
92
vendor/github.com/gophercloud/gophercloud/pagination/linked.go
generated
vendored
Normal file
92
vendor/github.com/gophercloud/gophercloud/pagination/linked.go
generated
vendored
Normal file
|
@ -0,0 +1,92 @@
|
|||
package pagination
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
)
|
||||
|
||||
// LinkedPageBase may be embedded to implement a page that provides navigational "Next" and "Previous" links within its result.
|
||||
type LinkedPageBase struct {
|
||||
PageResult
|
||||
|
||||
// LinkPath lists the keys that should be traversed within a response to arrive at the "next" pointer.
|
||||
// If any link along the path is missing, an empty URL will be returned.
|
||||
// If any link results in an unexpected value type, an error will be returned.
|
||||
// When left as "nil", []string{"links", "next"} will be used as a default.
|
||||
LinkPath []string
|
||||
}
|
||||
|
||||
// NextPageURL extracts the pagination structure from a JSON response and returns the "next" link, if one is present.
|
||||
// It assumes that the links are available in a "links" element of the top-level response object.
|
||||
// If this is not the case, override NextPageURL on your result type.
|
||||
func (current LinkedPageBase) NextPageURL() (string, error) {
|
||||
var path []string
|
||||
var key string
|
||||
|
||||
if current.LinkPath == nil {
|
||||
path = []string{"links", "next"}
|
||||
} else {
|
||||
path = current.LinkPath
|
||||
}
|
||||
|
||||
submap, ok := current.Body.(map[string]interface{})
|
||||
if !ok {
|
||||
err := gophercloud.ErrUnexpectedType{}
|
||||
err.Expected = "map[string]interface{}"
|
||||
err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body))
|
||||
return "", err
|
||||
}
|
||||
|
||||
for {
|
||||
key, path = path[0], path[1:len(path)]
|
||||
|
||||
value, ok := submap[key]
|
||||
if !ok {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if len(path) > 0 {
|
||||
submap, ok = value.(map[string]interface{})
|
||||
if !ok {
|
||||
err := gophercloud.ErrUnexpectedType{}
|
||||
err.Expected = "map[string]interface{}"
|
||||
err.Actual = fmt.Sprintf("%v", reflect.TypeOf(value))
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
if value == nil {
|
||||
// Actual null element.
|
||||
return "", nil
|
||||
}
|
||||
|
||||
url, ok := value.(string)
|
||||
if !ok {
|
||||
err := gophercloud.ErrUnexpectedType{}
|
||||
err.Expected = "string"
|
||||
err.Actual = fmt.Sprintf("%v", reflect.TypeOf(value))
|
||||
return "", err
|
||||
}
|
||||
|
||||
return url, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IsEmpty satisifies the IsEmpty method of the Page interface
|
||||
func (current LinkedPageBase) IsEmpty() (bool, error) {
|
||||
if b, ok := current.Body.([]interface{}); ok {
|
||||
return len(b) == 0, nil
|
||||
}
|
||||
err := gophercloud.ErrUnexpectedType{}
|
||||
err.Expected = "[]interface{}"
|
||||
err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body))
|
||||
return true, err
|
||||
}
|
||||
|
||||
// GetBody returns the linked page's body. This method is needed to satisfy the
|
||||
// Page interface.
|
||||
func (current LinkedPageBase) GetBody() interface{} {
|
||||
return current.Body
|
||||
}
|
58
vendor/github.com/gophercloud/gophercloud/pagination/marker.go
generated
vendored
Normal file
58
vendor/github.com/gophercloud/gophercloud/pagination/marker.go
generated
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
package pagination
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
)
|
||||
|
||||
// MarkerPage is a stricter Page interface that describes additional functionality required for use with NewMarkerPager.
|
||||
// For convenience, embed the MarkedPageBase struct.
|
||||
type MarkerPage interface {
|
||||
Page
|
||||
|
||||
// LastMarker returns the last "marker" value on this page.
|
||||
LastMarker() (string, error)
|
||||
}
|
||||
|
||||
// MarkerPageBase is a page in a collection that's paginated by "limit" and "marker" query parameters.
|
||||
type MarkerPageBase struct {
|
||||
PageResult
|
||||
|
||||
// Owner is a reference to the embedding struct.
|
||||
Owner MarkerPage
|
||||
}
|
||||
|
||||
// NextPageURL generates the URL for the page of results after this one.
|
||||
func (current MarkerPageBase) NextPageURL() (string, error) {
|
||||
currentURL := current.URL
|
||||
|
||||
mark, err := current.Owner.LastMarker()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
q := currentURL.Query()
|
||||
q.Set("marker", mark)
|
||||
currentURL.RawQuery = q.Encode()
|
||||
|
||||
return currentURL.String(), nil
|
||||
}
|
||||
|
||||
// IsEmpty satisifies the IsEmpty method of the Page interface
|
||||
func (current MarkerPageBase) IsEmpty() (bool, error) {
|
||||
if b, ok := current.Body.([]interface{}); ok {
|
||||
return len(b) == 0, nil
|
||||
}
|
||||
err := gophercloud.ErrUnexpectedType{}
|
||||
err.Expected = "[]interface{}"
|
||||
err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body))
|
||||
return true, err
|
||||
}
|
||||
|
||||
// GetBody returns the linked page's body. This method is needed to satisfy the
|
||||
// Page interface.
|
||||
func (current MarkerPageBase) GetBody() interface{} {
|
||||
return current.Body
|
||||
}
|
238
vendor/github.com/gophercloud/gophercloud/pagination/pager.go
generated
vendored
Normal file
238
vendor/github.com/gophercloud/gophercloud/pagination/pager.go
generated
vendored
Normal file
|
@ -0,0 +1,238 @@
|
|||
package pagination
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrPageNotAvailable is returned from a Pager when a next or previous page is requested, but does not exist.
|
||||
ErrPageNotAvailable = errors.New("The requested page does not exist.")
|
||||
)
|
||||
|
||||
// Page must be satisfied by the result type of any resource collection.
|
||||
// It allows clients to interact with the resource uniformly, regardless of whether or not or how it's paginated.
|
||||
// Generally, rather than implementing this interface directly, implementors should embed one of the concrete PageBase structs,
|
||||
// instead.
|
||||
// Depending on the pagination strategy of a particular resource, there may be an additional subinterface that the result type
|
||||
// will need to implement.
|
||||
type Page interface {
|
||||
|
||||
// NextPageURL generates the URL for the page of data that follows this collection.
|
||||
// Return "" if no such page exists.
|
||||
NextPageURL() (string, error)
|
||||
|
||||
// IsEmpty returns true if this Page has no items in it.
|
||||
IsEmpty() (bool, error)
|
||||
|
||||
// GetBody returns the Page Body. This is used in the `AllPages` method.
|
||||
GetBody() interface{}
|
||||
}
|
||||
|
||||
// Pager knows how to advance through a specific resource collection, one page at a time.
|
||||
type Pager struct {
|
||||
client *gophercloud.ServiceClient
|
||||
|
||||
initialURL string
|
||||
|
||||
createPage func(r PageResult) Page
|
||||
|
||||
Err error
|
||||
|
||||
// Headers supplies additional HTTP headers to populate on each paged request.
|
||||
Headers map[string]string
|
||||
}
|
||||
|
||||
// NewPager constructs a manually-configured pager.
|
||||
// Supply the URL for the first page, a function that requests a specific page given a URL, and a function that counts a page.
|
||||
func NewPager(client *gophercloud.ServiceClient, initialURL string, createPage func(r PageResult) Page) Pager {
|
||||
return Pager{
|
||||
client: client,
|
||||
initialURL: initialURL,
|
||||
createPage: createPage,
|
||||
}
|
||||
}
|
||||
|
||||
// WithPageCreator returns a new Pager that substitutes a different page creation function. This is
|
||||
// useful for overriding List functions in delegation.
|
||||
func (p Pager) WithPageCreator(createPage func(r PageResult) Page) Pager {
|
||||
return Pager{
|
||||
client: p.client,
|
||||
initialURL: p.initialURL,
|
||||
createPage: createPage,
|
||||
}
|
||||
}
|
||||
|
||||
func (p Pager) fetchNextPage(url string) (Page, error) {
|
||||
resp, err := Request(p.client, p.Headers, url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
remembered, err := PageResultFrom(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p.createPage(remembered), nil
|
||||
}
|
||||
|
||||
// EachPage iterates over each page returned by a Pager, yielding one at a time to a handler function.
|
||||
// Return "false" from the handler to prematurely stop iterating.
|
||||
func (p Pager) EachPage(handler func(Page) (bool, error)) error {
|
||||
if p.Err != nil {
|
||||
return p.Err
|
||||
}
|
||||
currentURL := p.initialURL
|
||||
for {
|
||||
currentPage, err := p.fetchNextPage(currentURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
empty, err := currentPage.IsEmpty()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if empty {
|
||||
return nil
|
||||
}
|
||||
|
||||
ok, err := handler(currentPage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
currentURL, err = currentPage.NextPageURL()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if currentURL == "" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AllPages returns all the pages from a `List` operation in a single page,
|
||||
// allowing the user to retrieve all the pages at once.
|
||||
func (p Pager) AllPages() (Page, error) {
|
||||
// pagesSlice holds all the pages until they get converted into as Page Body.
|
||||
var pagesSlice []interface{}
|
||||
// body will contain the final concatenated Page body.
|
||||
var body reflect.Value
|
||||
|
||||
// Grab a test page to ascertain the page body type.
|
||||
testPage, 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)
|
||||
|
||||
// if it's a single page, just return the testPage (first page)
|
||||
if _, found := pageType.FieldByName("SinglePageBase"); found {
|
||||
return testPage, nil
|
||||
}
|
||||
|
||||
// Switch on the page body type. Recognized types are `map[string]interface{}`,
|
||||
// `[]byte`, and `[]interface{}`.
|
||||
switch pb := testPage.GetBody().(type) {
|
||||
case map[string]interface{}:
|
||||
// key is the map key for the page body if the body type is `map[string]interface{}`.
|
||||
var key string
|
||||
// Iterate over the pages to concatenate the bodies.
|
||||
err = p.EachPage(func(page Page) (bool, error) {
|
||||
b := page.GetBody().(map[string]interface{})
|
||||
for k, v := range b {
|
||||
// If it's a linked page, we don't want the `links`, we want the other one.
|
||||
if !strings.HasSuffix(k, "links") {
|
||||
// check the field's type. we only want []interface{} (which is really []map[string]interface{})
|
||||
switch vt := v.(type) {
|
||||
case []interface{}:
|
||||
key = k
|
||||
pagesSlice = append(pagesSlice, vt...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Set body to value of type `map[string]interface{}`
|
||||
body = reflect.MakeMap(reflect.MapOf(reflect.TypeOf(key), reflect.TypeOf(pagesSlice)))
|
||||
body.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(pagesSlice))
|
||||
case []byte:
|
||||
// Iterate over the pages to concatenate the bodies.
|
||||
err = p.EachPage(func(page Page) (bool, error) {
|
||||
b := page.GetBody().([]byte)
|
||||
pagesSlice = append(pagesSlice, b)
|
||||
// seperate pages with a comma
|
||||
pagesSlice = append(pagesSlice, []byte{10})
|
||||
return true, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(pagesSlice) > 0 {
|
||||
// Remove the trailing comma.
|
||||
pagesSlice = pagesSlice[:len(pagesSlice)-1]
|
||||
}
|
||||
var b []byte
|
||||
// Combine the slice of slices in to a single slice.
|
||||
for _, slice := range pagesSlice {
|
||||
b = append(b, slice.([]byte)...)
|
||||
}
|
||||
// Set body to value of type `bytes`.
|
||||
body = reflect.New(reflect.TypeOf(b)).Elem()
|
||||
body.SetBytes(b)
|
||||
case []interface{}:
|
||||
// Iterate over the pages to concatenate the bodies.
|
||||
err = p.EachPage(func(page Page) (bool, error) {
|
||||
b := page.GetBody().([]interface{})
|
||||
pagesSlice = append(pagesSlice, b...)
|
||||
return true, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Set body to value of type `[]interface{}`
|
||||
body = reflect.MakeSlice(reflect.TypeOf(pagesSlice), len(pagesSlice), len(pagesSlice))
|
||||
for i, s := range pagesSlice {
|
||||
body.Index(i).Set(reflect.ValueOf(s))
|
||||
}
|
||||
default:
|
||||
err := gophercloud.ErrUnexpectedType{}
|
||||
err.Expected = "map[string]interface{}/[]byte/[]interface{}"
|
||||
err.Actual = fmt.Sprintf("%T", pb)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Each `Extract*` function is expecting a specific type of page coming back,
|
||||
// otherwise the type assertion in those functions will fail. pageType is needed
|
||||
// to create a type in this method that has the same type that the `Extract*`
|
||||
// function is expecting and set the Body of that object to the concatenated
|
||||
// pages.
|
||||
page := reflect.New(pageType)
|
||||
// Set the page body to be the concatenated pages.
|
||||
page.Elem().FieldByName("Body").Set(body)
|
||||
// Set any additional headers that were pass along. The `objectstorage` pacakge,
|
||||
// for example, passes a Content-Type header.
|
||||
h := make(http.Header)
|
||||
for k, v := range p.Headers {
|
||||
h.Add(k, v)
|
||||
}
|
||||
page.Elem().FieldByName("Header").Set(reflect.ValueOf(h))
|
||||
// Type assert the page to a Page interface so that the type assertion in the
|
||||
// `Extract*` methods will work.
|
||||
return page.Elem().Interface().(Page), err
|
||||
}
|
4
vendor/github.com/gophercloud/gophercloud/pagination/pkg.go
generated
vendored
Normal file
4
vendor/github.com/gophercloud/gophercloud/pagination/pkg.go
generated
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
/*
|
||||
Package pagination contains utilities and convenience structs that implement common pagination idioms within OpenStack APIs.
|
||||
*/
|
||||
package pagination
|
33
vendor/github.com/gophercloud/gophercloud/pagination/single.go
generated
vendored
Normal file
33
vendor/github.com/gophercloud/gophercloud/pagination/single.go
generated
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
package pagination
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
)
|
||||
|
||||
// SinglePageBase may be embedded in a Page that contains all of the results from an operation at once.
|
||||
type SinglePageBase PageResult
|
||||
|
||||
// NextPageURL always returns "" to indicate that there are no more pages to return.
|
||||
func (current SinglePageBase) NextPageURL() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// IsEmpty satisifies the IsEmpty method of the Page interface
|
||||
func (current SinglePageBase) IsEmpty() (bool, error) {
|
||||
if b, ok := current.Body.([]interface{}); ok {
|
||||
return len(b) == 0, nil
|
||||
}
|
||||
err := gophercloud.ErrUnexpectedType{}
|
||||
err.Expected = "[]interface{}"
|
||||
err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body))
|
||||
return true, err
|
||||
}
|
||||
|
||||
// GetBody returns the single page's body. This method is needed to satisfy the
|
||||
// Page interface.
|
||||
func (current SinglePageBase) GetBody() interface{} {
|
||||
return current.Body
|
||||
}
|
445
vendor/github.com/gophercloud/gophercloud/params.go
generated
vendored
Normal file
445
vendor/github.com/gophercloud/gophercloud/params.go
generated
vendored
Normal file
|
@ -0,0 +1,445 @@
|
|||
package gophercloud
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// BuildRequestBody builds a map[string]interface from the given `struct`. If
|
||||
// parent is not the empty string, the final map[string]interface returned will
|
||||
// encapsulate the built one
|
||||
//
|
||||
func BuildRequestBody(opts interface{}, parent string) (map[string]interface{}, error) {
|
||||
optsValue := reflect.ValueOf(opts)
|
||||
if optsValue.Kind() == reflect.Ptr {
|
||||
optsValue = optsValue.Elem()
|
||||
}
|
||||
|
||||
optsType := reflect.TypeOf(opts)
|
||||
if optsType.Kind() == reflect.Ptr {
|
||||
optsType = optsType.Elem()
|
||||
}
|
||||
|
||||
optsMap := make(map[string]interface{})
|
||||
if optsValue.Kind() == reflect.Struct {
|
||||
//fmt.Printf("optsValue.Kind() is a reflect.Struct: %+v\n", optsValue.Kind())
|
||||
for i := 0; i < optsValue.NumField(); i++ {
|
||||
v := optsValue.Field(i)
|
||||
f := optsType.Field(i)
|
||||
|
||||
if f.Name != strings.Title(f.Name) {
|
||||
//fmt.Printf("Skipping field: %s...\n", f.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
//fmt.Printf("Starting on field: %s...\n", f.Name)
|
||||
|
||||
zero := isZero(v)
|
||||
//fmt.Printf("v is zero?: %v\n", zero)
|
||||
|
||||
// if the field has a required tag that's set to "true"
|
||||
if requiredTag := f.Tag.Get("required"); requiredTag == "true" {
|
||||
//fmt.Printf("Checking required field [%s]:\n\tv: %+v\n\tisZero:%v\n", f.Name, v.Interface(), zero)
|
||||
// if the field's value is zero, return a missing-argument error
|
||||
if zero {
|
||||
// if the field has a 'required' tag, it can't have a zero-value
|
||||
err := ErrMissingInput{}
|
||||
err.Argument = f.Name
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if xorTag := f.Tag.Get("xor"); xorTag != "" {
|
||||
//fmt.Printf("Checking `xor` tag for field [%s] with value %+v:\n\txorTag: %s\n", f.Name, v, xorTag)
|
||||
xorField := optsValue.FieldByName(xorTag)
|
||||
var xorFieldIsZero bool
|
||||
if reflect.ValueOf(xorField.Interface()) == reflect.Zero(xorField.Type()) {
|
||||
xorFieldIsZero = true
|
||||
} else {
|
||||
if xorField.Kind() == reflect.Ptr {
|
||||
xorField = xorField.Elem()
|
||||
}
|
||||
xorFieldIsZero = isZero(xorField)
|
||||
}
|
||||
if !(zero != xorFieldIsZero) {
|
||||
err := ErrMissingInput{}
|
||||
err.Argument = fmt.Sprintf("%s/%s", f.Name, xorTag)
|
||||
err.Info = fmt.Sprintf("Exactly one of %s and %s must be provided", f.Name, xorTag)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if orTag := f.Tag.Get("or"); orTag != "" {
|
||||
//fmt.Printf("Checking `or` tag for field with:\n\tname: %+v\n\torTag:%s\n", f.Name, orTag)
|
||||
//fmt.Printf("field is zero?: %v\n", zero)
|
||||
if zero {
|
||||
orField := optsValue.FieldByName(orTag)
|
||||
var orFieldIsZero bool
|
||||
if reflect.ValueOf(orField.Interface()) == reflect.Zero(orField.Type()) {
|
||||
orFieldIsZero = true
|
||||
} else {
|
||||
if orField.Kind() == reflect.Ptr {
|
||||
orField = orField.Elem()
|
||||
}
|
||||
orFieldIsZero = isZero(orField)
|
||||
}
|
||||
if orFieldIsZero {
|
||||
err := ErrMissingInput{}
|
||||
err.Argument = fmt.Sprintf("%s/%s", f.Name, orTag)
|
||||
err.Info = fmt.Sprintf("At least one of %s and %s must be provided", f.Name, orTag)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 != "" {
|
||||
jsonTagPieces := strings.Split(jsonTag, ",")
|
||||
if len(jsonTagPieces) > 1 && jsonTagPieces[1] == "omitempty" {
|
||||
if v.CanSet() {
|
||||
if !v.IsNil() {
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v.Set(reflect.Zero(v.Type()))
|
||||
}
|
||||
}
|
||||
//fmt.Printf("value after change: %+v\n", optsValue.Field(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
//fmt.Printf("Calling BuildRequestBody with:\n\tv: %+v\n\tf.Name:%s\n", v.Interface(), f.Name)
|
||||
_, err := BuildRequestBody(v.Interface(), f.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//fmt.Printf("opts: %+v \n", opts)
|
||||
|
||||
b, err := json.Marshal(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//fmt.Printf("string(b): %s\n", string(b))
|
||||
|
||||
err = json.Unmarshal(b, &optsMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//fmt.Printf("optsMap: %+v\n", optsMap)
|
||||
|
||||
if parent != "" {
|
||||
optsMap = map[string]interface{}{parent: optsMap}
|
||||
}
|
||||
//fmt.Printf("optsMap after parent added: %+v\n", optsMap)
|
||||
return optsMap, nil
|
||||
}
|
||||
// Return an error if the underlying type of 'opts' isn't a struct.
|
||||
return nil, fmt.Errorf("Options type is not a struct.")
|
||||
}
|
||||
|
||||
// EnabledState is a convenience type, mostly used in Create and Update
|
||||
// operations. Because the zero value of a bool is FALSE, we need to use a
|
||||
// pointer instead to indicate zero-ness.
|
||||
type EnabledState *bool
|
||||
|
||||
// Convenience vars for EnabledState values.
|
||||
var (
|
||||
iTrue = true
|
||||
iFalse = false
|
||||
|
||||
Enabled EnabledState = &iTrue
|
||||
Disabled EnabledState = &iFalse
|
||||
)
|
||||
|
||||
// IPVersion is a type for the possible IP address versions. Valid instances
|
||||
// are IPv4 and IPv6
|
||||
type IPVersion int
|
||||
|
||||
const (
|
||||
// IPv4 is used for IP version 4 addresses
|
||||
IPv4 IPVersion = 4
|
||||
// IPv6 is used for IP version 6 addresses
|
||||
IPv6 IPVersion = 6
|
||||
)
|
||||
|
||||
// IntToPointer is a function for converting integers into integer pointers.
|
||||
// This is useful when passing in options to operations.
|
||||
func IntToPointer(i int) *int {
|
||||
return &i
|
||||
}
|
||||
|
||||
/*
|
||||
MaybeString is an internal function to be used by request methods in individual
|
||||
resource packages.
|
||||
|
||||
It takes a string that might be a zero value and returns either a pointer to its
|
||||
address or nil. This is useful for allowing users to conveniently omit values
|
||||
from an options struct by leaving them zeroed, but still pass nil to the JSON
|
||||
serializer so they'll be omitted from the request body.
|
||||
*/
|
||||
func MaybeString(original string) *string {
|
||||
if original != "" {
|
||||
return &original
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
MaybeInt is an internal function to be used by request methods in individual
|
||||
resource packages.
|
||||
|
||||
Like MaybeString, it accepts an int that may or may not be a zero value, and
|
||||
returns either a pointer to its address or nil. It's intended to hint that the
|
||||
JSON serializer should omit its field.
|
||||
*/
|
||||
func MaybeInt(original int) *int {
|
||||
if original != 0 {
|
||||
return &original
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
func isUnderlyingStructZero(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Ptr:
|
||||
return isUnderlyingStructZero(v.Elem())
|
||||
default:
|
||||
return isZero(v)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
var t time.Time
|
||||
|
||||
func isZero(v reflect.Value) bool {
|
||||
//fmt.Printf("\n\nchecking isZero for value: %+v\n", v)
|
||||
switch v.Kind() {
|
||||
case reflect.Ptr:
|
||||
if v.IsNil() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
case reflect.Func, reflect.Map, reflect.Slice:
|
||||
return v.IsNil()
|
||||
case reflect.Array:
|
||||
z := true
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
z = z && isZero(v.Index(i))
|
||||
}
|
||||
return z
|
||||
case reflect.Struct:
|
||||
if v.Type() == reflect.TypeOf(t) {
|
||||
if v.Interface().(time.Time).IsZero() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
z := true
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
z = z && isZero(v.Field(i))
|
||||
}
|
||||
return z
|
||||
}
|
||||
// Compare other types directly:
|
||||
z := reflect.Zero(v.Type())
|
||||
//fmt.Printf("zero type for value: %+v\n\n\n", z)
|
||||
return v.Interface() == z.Interface()
|
||||
}
|
||||
|
||||
/*
|
||||
BuildQueryString is an internal function to be used by request methods in
|
||||
individual resource packages.
|
||||
|
||||
It accepts a tagged structure and expands it into a URL struct. Field names are
|
||||
converted into query parameters based on a "q" tag. For example:
|
||||
|
||||
type struct Something {
|
||||
Bar string `q:"x_bar"`
|
||||
Baz int `q:"lorem_ipsum"`
|
||||
}
|
||||
|
||||
instance := Something{
|
||||
Bar: "AAA",
|
||||
Baz: "BBB",
|
||||
}
|
||||
|
||||
will be converted into "?x_bar=AAA&lorem_ipsum=BBB".
|
||||
|
||||
The struct's fields may be strings, integers, or boolean values. Fields left at
|
||||
their type's zero value will be omitted from the query.
|
||||
*/
|
||||
func BuildQueryString(opts interface{}) (*url.URL, error) {
|
||||
optsValue := reflect.ValueOf(opts)
|
||||
if optsValue.Kind() == reflect.Ptr {
|
||||
optsValue = optsValue.Elem()
|
||||
}
|
||||
|
||||
optsType := reflect.TypeOf(opts)
|
||||
if optsType.Kind() == reflect.Ptr {
|
||||
optsType = optsType.Elem()
|
||||
}
|
||||
|
||||
params := url.Values{}
|
||||
|
||||
if optsValue.Kind() == reflect.Struct {
|
||||
for i := 0; i < optsValue.NumField(); i++ {
|
||||
v := optsValue.Field(i)
|
||||
f := optsType.Field(i)
|
||||
qTag := f.Tag.Get("q")
|
||||
|
||||
// if the field has a 'q' tag, it goes in the query string
|
||||
if qTag != "" {
|
||||
tags := strings.Split(qTag, ",")
|
||||
|
||||
// if the field is set, add it to the slice of query pieces
|
||||
if !isZero(v) {
|
||||
loop:
|
||||
switch v.Kind() {
|
||||
case reflect.Ptr:
|
||||
v = v.Elem()
|
||||
goto loop
|
||||
case reflect.String:
|
||||
params.Add(tags[0], v.String())
|
||||
case reflect.Int:
|
||||
params.Add(tags[0], strconv.FormatInt(v.Int(), 10))
|
||||
case reflect.Bool:
|
||||
params.Add(tags[0], strconv.FormatBool(v.Bool()))
|
||||
case reflect.Slice:
|
||||
switch v.Type().Elem() {
|
||||
case reflect.TypeOf(0):
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
params.Add(tags[0], strconv.FormatInt(v.Index(i).Int(), 10))
|
||||
}
|
||||
default:
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
params.Add(tags[0], v.Index(i).String())
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &url.URL{RawQuery: params.Encode()}, nil
|
||||
}
|
||||
// Return an error if the underlying type of 'opts' isn't a struct.
|
||||
return nil, fmt.Errorf("Options type is not a struct.")
|
||||
}
|
||||
|
||||
/*
|
||||
BuildHeaders is an internal function to be used by request methods in
|
||||
individual resource packages.
|
||||
|
||||
It accepts an arbitrary tagged structure and produces a string map that's
|
||||
suitable for use as the HTTP headers of an outgoing request. Field names are
|
||||
mapped to header names based in "h" tags.
|
||||
|
||||
type struct Something {
|
||||
Bar string `h:"x_bar"`
|
||||
Baz int `h:"lorem_ipsum"`
|
||||
}
|
||||
|
||||
instance := Something{
|
||||
Bar: "AAA",
|
||||
Baz: "BBB",
|
||||
}
|
||||
|
||||
will be converted into:
|
||||
|
||||
map[string]string{
|
||||
"x_bar": "AAA",
|
||||
"lorem_ipsum": "BBB",
|
||||
}
|
||||
|
||||
Untagged fields and fields left at their zero values are skipped. Integers,
|
||||
booleans and string values are supported.
|
||||
*/
|
||||
func BuildHeaders(opts interface{}) (map[string]string, error) {
|
||||
optsValue := reflect.ValueOf(opts)
|
||||
if optsValue.Kind() == reflect.Ptr {
|
||||
optsValue = optsValue.Elem()
|
||||
}
|
||||
|
||||
optsType := reflect.TypeOf(opts)
|
||||
if optsType.Kind() == reflect.Ptr {
|
||||
optsType = optsType.Elem()
|
||||
}
|
||||
|
||||
optsMap := make(map[string]string)
|
||||
if optsValue.Kind() == reflect.Struct {
|
||||
for i := 0; i < optsValue.NumField(); i++ {
|
||||
v := optsValue.Field(i)
|
||||
f := optsType.Field(i)
|
||||
hTag := f.Tag.Get("h")
|
||||
|
||||
// if the field has a 'h' tag, it goes in the header
|
||||
if hTag != "" {
|
||||
tags := strings.Split(hTag, ",")
|
||||
|
||||
// if the field is set, add it to the slice of query pieces
|
||||
if !isZero(v) {
|
||||
switch v.Kind() {
|
||||
case reflect.String:
|
||||
optsMap[tags[0]] = v.String()
|
||||
case reflect.Int:
|
||||
optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10)
|
||||
case reflect.Bool:
|
||||
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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return optsMap, nil
|
||||
}
|
||||
// Return an error if the underlying type of 'opts' isn't a struct.
|
||||
return optsMap, fmt.Errorf("Options type is not a struct.")
|
||||
}
|
||||
|
||||
// IDSliceToQueryString takes a slice of elements and converts them into a query
|
||||
// string. For example, if name=foo and slice=[]int{20, 40, 60}, then the
|
||||
// result would be `?name=20&name=40&name=60'
|
||||
func IDSliceToQueryString(name string, ids []int) string {
|
||||
str := ""
|
||||
for k, v := range ids {
|
||||
if k == 0 {
|
||||
str += "?"
|
||||
} else {
|
||||
str += "&"
|
||||
}
|
||||
str += fmt.Sprintf("%s=%s", name, strconv.Itoa(v))
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// IntWithinRange returns TRUE if an integer falls within a defined range, and
|
||||
// FALSE if not.
|
||||
func IntWithinRange(val, min, max int) bool {
|
||||
return val > min && val < max
|
||||
}
|
307
vendor/github.com/gophercloud/gophercloud/provider_client.go
generated
vendored
Normal file
307
vendor/github.com/gophercloud/gophercloud/provider_client.go
generated
vendored
Normal file
|
@ -0,0 +1,307 @@
|
|||
package gophercloud
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DefaultUserAgent is the default User-Agent string set in the request header.
|
||||
const DefaultUserAgent = "gophercloud/2.0.0"
|
||||
|
||||
// UserAgent represents a User-Agent header.
|
||||
type UserAgent struct {
|
||||
// prepend is the slice of User-Agent strings to prepend to DefaultUserAgent.
|
||||
// All the strings to prepend are accumulated and prepended in the Join method.
|
||||
prepend []string
|
||||
}
|
||||
|
||||
// Prepend prepends a user-defined string to the default User-Agent string. Users
|
||||
// may pass in one or more strings to prepend.
|
||||
func (ua *UserAgent) Prepend(s ...string) {
|
||||
ua.prepend = append(s, ua.prepend...)
|
||||
}
|
||||
|
||||
// Join concatenates all the user-defined User-Agend strings with the default
|
||||
// Gophercloud User-Agent string.
|
||||
func (ua *UserAgent) Join() string {
|
||||
uaSlice := append(ua.prepend, DefaultUserAgent)
|
||||
return strings.Join(uaSlice, " ")
|
||||
}
|
||||
|
||||
// ProviderClient stores details that are required to interact with any
|
||||
// services within a specific provider's API.
|
||||
//
|
||||
// Generally, you acquire a ProviderClient by calling the NewClient method in
|
||||
// the appropriate provider's child package, providing whatever authentication
|
||||
// credentials are required.
|
||||
type ProviderClient struct {
|
||||
// IdentityBase is the base URL used for a particular provider's identity
|
||||
// service - it will be used when issuing authenticatation requests. It
|
||||
// should point to the root resource of the identity service, not a specific
|
||||
// identity version.
|
||||
IdentityBase string
|
||||
|
||||
// IdentityEndpoint is the identity endpoint. This may be a specific version
|
||||
// of the identity service. If this is the case, this endpoint is used rather
|
||||
// than querying versions first.
|
||||
IdentityEndpoint string
|
||||
|
||||
// TokenID is the ID of the most recently issued valid token.
|
||||
TokenID string
|
||||
|
||||
// EndpointLocator describes how this provider discovers the endpoints for
|
||||
// its constituent services.
|
||||
EndpointLocator EndpointLocator
|
||||
|
||||
// HTTPClient allows users to interject arbitrary http, https, or other transit behaviors.
|
||||
HTTPClient http.Client
|
||||
|
||||
// UserAgent represents the User-Agent header in the HTTP request.
|
||||
UserAgent UserAgent
|
||||
|
||||
// ReauthFunc is the function used to re-authenticate the user if the request
|
||||
// fails with a 401 HTTP response code. This a needed because there may be multiple
|
||||
// authentication functions for different Identity service versions.
|
||||
ReauthFunc func() error
|
||||
|
||||
Debug bool
|
||||
}
|
||||
|
||||
// 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{}
|
||||
}
|
||||
return map[string]string{"X-Auth-Token": client.TokenID}
|
||||
}
|
||||
|
||||
// RequestOpts customizes the behavior of the provider.Request() method.
|
||||
type RequestOpts struct {
|
||||
// JSONBody, if provided, will be encoded as JSON and used as the body of the HTTP request. The
|
||||
// content type of the request will default to "application/json" unless overridden by MoreHeaders.
|
||||
// It's an error to specify both a JSONBody and a RawBody.
|
||||
JSONBody interface{}
|
||||
// RawBody contains an io.Reader that will be consumed by the request directly. No content-type
|
||||
// will be set unless one is provided explicitly by MoreHeaders.
|
||||
RawBody io.Reader
|
||||
// JSONResponse, if provided, will be populated with the contents of the response body parsed as
|
||||
// JSON.
|
||||
JSONResponse interface{}
|
||||
// OkCodes contains a list of numeric HTTP status codes that should be interpreted as success. If
|
||||
// the response has a different code, an error will be returned.
|
||||
OkCodes []int
|
||||
// MoreHeaders specifies additional HTTP headers to be provide on the request. If a header is
|
||||
// provided with a blank value (""), that header will be *omitted* instead: use this to suppress
|
||||
// the default Accept header or an inferred Content-Type, for example.
|
||||
MoreHeaders map[string]string
|
||||
// ErrorContext specifies the resource error type to return if an error is encountered.
|
||||
// This lets resources override default error messages based on the response status code.
|
||||
ErrorContext error
|
||||
}
|
||||
|
||||
var applicationJSON = "application/json"
|
||||
|
||||
// Request performs an HTTP request using the ProviderClient's current HTTPClient. An authentication
|
||||
// header will automatically be provided.
|
||||
func (client *ProviderClient) Request(method, url string, options *RequestOpts) (*http.Response, error) {
|
||||
var body io.Reader
|
||||
var contentType *string
|
||||
|
||||
// Derive the content body by either encoding an arbitrary object as JSON, or by taking a provided
|
||||
// io.ReadSeeker as-is. Default the content-type to application/json.
|
||||
if options.JSONBody != nil {
|
||||
if options.RawBody != nil {
|
||||
panic("Please provide only one of JSONBody or RawBody to gophercloud.Request().")
|
||||
}
|
||||
|
||||
rendered, err := json.Marshal(options.JSONBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body = bytes.NewReader(rendered)
|
||||
contentType = &applicationJSON
|
||||
}
|
||||
|
||||
if options.RawBody != nil {
|
||||
body = options.RawBody
|
||||
}
|
||||
|
||||
// Construct the http.Request.
|
||||
req, err := http.NewRequest(method, url, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Populate the request headers. Apply options.MoreHeaders last, to give the caller the chance to
|
||||
// modify or omit any header.
|
||||
if contentType != nil {
|
||||
req.Header.Set("Content-Type", *contentType)
|
||||
}
|
||||
req.Header.Set("Accept", applicationJSON)
|
||||
|
||||
for k, v := range client.AuthenticatedHeaders() {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
|
||||
// Set the User-Agent header
|
||||
req.Header.Set("User-Agent", client.UserAgent.Join())
|
||||
|
||||
if options.MoreHeaders != nil {
|
||||
for k, v := range options.MoreHeaders {
|
||||
if v != "" {
|
||||
req.Header.Set(k, v)
|
||||
} else {
|
||||
req.Header.Del(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set connection parameter to close the connection immediately when we've got the response
|
||||
req.Close = true
|
||||
|
||||
// Issue the request.
|
||||
resp, err := client.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Allow default OkCodes if none explicitly set
|
||||
if options.OkCodes == nil {
|
||||
options.OkCodes = defaultOkCodes(method)
|
||||
}
|
||||
|
||||
// Validate the HTTP response status.
|
||||
var ok bool
|
||||
for _, code := range options.OkCodes {
|
||||
if resp.StatusCode == code {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
Expected: options.OkCodes,
|
||||
Actual: resp.StatusCode,
|
||||
Body: body,
|
||||
}
|
||||
//respErr.Function = "gophercloud.ProviderClient.Request"
|
||||
|
||||
errType := options.ErrorContext
|
||||
switch resp.StatusCode {
|
||||
case http.StatusBadRequest:
|
||||
err = ErrDefault400{respErr}
|
||||
if error400er, ok := errType.(Err400er); ok {
|
||||
err = error400er.Error400(respErr)
|
||||
}
|
||||
case http.StatusUnauthorized:
|
||||
if client.ReauthFunc != nil {
|
||||
err = client.ReauthFunc()
|
||||
if err != nil {
|
||||
e := &ErrUnableToReauthenticate{}
|
||||
e.ErrOriginal = respErr
|
||||
return nil, e
|
||||
}
|
||||
if options.RawBody != nil {
|
||||
if seeker, ok := options.RawBody.(io.Seeker); ok {
|
||||
seeker.Seek(0, 0)
|
||||
}
|
||||
}
|
||||
resp, err = client.Request(method, url, options)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *ErrUnexpectedResponseCode:
|
||||
e := &ErrErrorAfterReauthentication{}
|
||||
e.ErrOriginal = err.(*ErrUnexpectedResponseCode)
|
||||
return nil, e
|
||||
default:
|
||||
e := &ErrErrorAfterReauthentication{}
|
||||
e.ErrOriginal = err
|
||||
return nil, e
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
err = ErrDefault401{respErr}
|
||||
if error401er, ok := errType.(Err401er); ok {
|
||||
err = error401er.Error401(respErr)
|
||||
}
|
||||
case http.StatusNotFound:
|
||||
err = ErrDefault404{respErr}
|
||||
if error404er, ok := errType.(Err404er); ok {
|
||||
err = error404er.Error404(respErr)
|
||||
}
|
||||
case http.StatusMethodNotAllowed:
|
||||
err = ErrDefault405{respErr}
|
||||
if error405er, ok := errType.(Err405er); ok {
|
||||
err = error405er.Error405(respErr)
|
||||
}
|
||||
case http.StatusRequestTimeout:
|
||||
err = ErrDefault408{respErr}
|
||||
if error408er, ok := errType.(Err408er); ok {
|
||||
err = error408er.Error408(respErr)
|
||||
}
|
||||
case 429:
|
||||
err = ErrDefault429{respErr}
|
||||
if error429er, ok := errType.(Err429er); ok {
|
||||
err = error429er.Error429(respErr)
|
||||
}
|
||||
case http.StatusInternalServerError:
|
||||
err = ErrDefault500{respErr}
|
||||
if error500er, ok := errType.(Err500er); ok {
|
||||
err = error500er.Error500(respErr)
|
||||
}
|
||||
case http.StatusServiceUnavailable:
|
||||
err = ErrDefault503{respErr}
|
||||
if error503er, ok := errType.(Err503er); ok {
|
||||
err = error503er.Error503(respErr)
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
err = respErr
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// Parse the response body as JSON, if requested to do so.
|
||||
if options.JSONResponse != nil {
|
||||
defer resp.Body.Close()
|
||||
if err := json.NewDecoder(resp.Body).Decode(options.JSONResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func defaultOkCodes(method string) []int {
|
||||
switch {
|
||||
case method == "GET":
|
||||
return []int{200}
|
||||
case method == "POST":
|
||||
return []int{201, 202}
|
||||
case method == "PUT":
|
||||
return []int{201, 202}
|
||||
case method == "PATCH":
|
||||
return []int{200, 204}
|
||||
case method == "DELETE":
|
||||
return []int{202, 204}
|
||||
}
|
||||
|
||||
return []int{}
|
||||
}
|
336
vendor/github.com/gophercloud/gophercloud/results.go
generated
vendored
Normal file
336
vendor/github.com/gophercloud/gophercloud/results.go
generated
vendored
Normal file
|
@ -0,0 +1,336 @@
|
|||
package gophercloud
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
Result is an internal type to be used by individual resource packages, but its
|
||||
methods will be available on a wide variety of user-facing embedding types.
|
||||
|
||||
It acts as a base struct that other Result types, returned from request
|
||||
functions, can embed for convenience. All Results capture basic information
|
||||
from the HTTP transaction that was performed, including the response body,
|
||||
HTTP headers, and any errors that happened.
|
||||
|
||||
Generally, each Result type will have an Extract method that can be used to
|
||||
further interpret the result's payload in a specific context. Extensions or
|
||||
providers can then provide additional extraction functions to pull out
|
||||
provider- or extension-specific information as well.
|
||||
*/
|
||||
type Result struct {
|
||||
// Body is the payload of the HTTP response from the server. In most cases,
|
||||
// this will be the deserialized JSON structure.
|
||||
Body interface{}
|
||||
|
||||
// Header contains the HTTP header structure from the original response.
|
||||
Header http.Header
|
||||
|
||||
// Err is an error that occurred during the operation. It's deferred until
|
||||
// extraction to make it easier to chain the Extract call.
|
||||
Err error
|
||||
}
|
||||
|
||||
// ExtractInto allows users to provide an object into which `Extract` will extract
|
||||
// the `Result.Body`. This would be useful for OpenStack providers that have
|
||||
// different fields in the response object than OpenStack proper.
|
||||
func (r Result) ExtractInto(to interface{}) error {
|
||||
if r.Err != nil {
|
||||
return r.Err
|
||||
}
|
||||
|
||||
if reader, ok := r.Body.(io.Reader); ok {
|
||||
if readCloser, ok := reader.(io.Closer); ok {
|
||||
defer readCloser.Close()
|
||||
}
|
||||
return json.NewDecoder(reader).Decode(to)
|
||||
}
|
||||
|
||||
b, err := json.Marshal(r.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.Unmarshal(b, to)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (r Result) extractIntoPtr(to interface{}, label string) error {
|
||||
if label == "" {
|
||||
return r.ExtractInto(&to)
|
||||
}
|
||||
|
||||
var m map[string]interface{}
|
||||
err := r.ExtractInto(&m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := json.Marshal(m[label])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(b, &to)
|
||||
return err
|
||||
}
|
||||
|
||||
// ExtractIntoStructPtr will unmarshal the Result (r) into the provided
|
||||
// interface{} (to).
|
||||
//
|
||||
// NOTE: For internal use only
|
||||
//
|
||||
// `to` must be a pointer to an underlying struct type
|
||||
//
|
||||
// If provided, `label` will be filtered out of the response
|
||||
// body prior to `r` being unmarshalled into `to`.
|
||||
func (r Result) ExtractIntoStructPtr(to interface{}, label string) error {
|
||||
if r.Err != nil {
|
||||
return r.Err
|
||||
}
|
||||
|
||||
t := reflect.TypeOf(to)
|
||||
if k := t.Kind(); k != reflect.Ptr {
|
||||
return fmt.Errorf("Expected pointer, got %v", k)
|
||||
}
|
||||
switch t.Elem().Kind() {
|
||||
case reflect.Struct:
|
||||
return r.extractIntoPtr(to, label)
|
||||
default:
|
||||
return fmt.Errorf("Expected pointer to struct, got: %v", t)
|
||||
}
|
||||
}
|
||||
|
||||
// ExtractIntoSlicePtr will unmarshal the Result (r) into the provided
|
||||
// interface{} (to).
|
||||
//
|
||||
// NOTE: For internal use only
|
||||
//
|
||||
// `to` must be a pointer to an underlying slice type
|
||||
//
|
||||
// If provided, `label` will be filtered out of the response
|
||||
// body prior to `r` being unmarshalled into `to`.
|
||||
func (r Result) ExtractIntoSlicePtr(to interface{}, label string) error {
|
||||
if r.Err != nil {
|
||||
return r.Err
|
||||
}
|
||||
|
||||
t := reflect.TypeOf(to)
|
||||
if k := t.Kind(); k != reflect.Ptr {
|
||||
return fmt.Errorf("Expected pointer, got %v", k)
|
||||
}
|
||||
switch t.Elem().Kind() {
|
||||
case reflect.Slice:
|
||||
return r.extractIntoPtr(to, label)
|
||||
default:
|
||||
return fmt.Errorf("Expected pointer to slice, got: %v", t)
|
||||
}
|
||||
}
|
||||
|
||||
// PrettyPrintJSON creates a string containing the full response body as
|
||||
// pretty-printed JSON. It's useful for capturing test fixtures and for
|
||||
// debugging extraction bugs. If you include its output in an issue related to
|
||||
// a buggy extraction function, we will all love you forever.
|
||||
func (r Result) PrettyPrintJSON() string {
|
||||
pretty, err := json.MarshalIndent(r.Body, "", " ")
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
return string(pretty)
|
||||
}
|
||||
|
||||
// ErrResult is an internal type to be used by individual resource packages, but
|
||||
// its methods will be available on a wide variety of user-facing embedding
|
||||
// types.
|
||||
//
|
||||
// It represents results that only contain a potential error and
|
||||
// nothing else. Usually, if the operation executed successfully, the Err field
|
||||
// will be nil; otherwise it will be stocked with a relevant error. Use the
|
||||
// ExtractErr method
|
||||
// to cleanly pull it out.
|
||||
type ErrResult struct {
|
||||
Result
|
||||
}
|
||||
|
||||
// ExtractErr is a function that extracts error information, or nil, from a result.
|
||||
func (r ErrResult) ExtractErr() error {
|
||||
return r.Err
|
||||
}
|
||||
|
||||
/*
|
||||
HeaderResult is an internal type to be used by individual resource packages, but
|
||||
its methods will be available on a wide variety of user-facing embedding types.
|
||||
|
||||
It represents a result that only contains an error (possibly nil) and an
|
||||
http.Header. This is used, for example, by the objectstorage packages in
|
||||
openstack, because most of the operations don't return response bodies, but do
|
||||
have relevant information in headers.
|
||||
*/
|
||||
type HeaderResult struct {
|
||||
Result
|
||||
}
|
||||
|
||||
// ExtractHeader will return the http.Header and error from the HeaderResult.
|
||||
//
|
||||
// header, err := objects.Create(client, "my_container", objects.CreateOpts{}).ExtractHeader()
|
||||
func (r HeaderResult) ExtractInto(to interface{}) error {
|
||||
if r.Err != nil {
|
||||
return r.Err
|
||||
}
|
||||
|
||||
tmpHeaderMap := map[string]string{}
|
||||
for k, v := range r.Header {
|
||||
if len(v) > 0 {
|
||||
tmpHeaderMap[k] = v[0]
|
||||
}
|
||||
}
|
||||
|
||||
b, err := json.Marshal(tmpHeaderMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.Unmarshal(b, to)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// RFC3339Milli describes a common time format used by some API responses.
|
||||
const RFC3339Milli = "2006-01-02T15:04:05.999999Z"
|
||||
|
||||
type JSONRFC3339Milli time.Time
|
||||
|
||||
func (jt *JSONRFC3339Milli) UnmarshalJSON(data []byte) error {
|
||||
b := bytes.NewBuffer(data)
|
||||
dec := json.NewDecoder(b)
|
||||
var s string
|
||||
if err := dec.Decode(&s); err != nil {
|
||||
return err
|
||||
}
|
||||
t, err := time.Parse(RFC3339Milli, s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*jt = JSONRFC3339Milli(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
const RFC3339MilliNoZ = "2006-01-02T15:04:05.999999"
|
||||
|
||||
type JSONRFC3339MilliNoZ time.Time
|
||||
|
||||
func (jt *JSONRFC3339MilliNoZ) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
t, err := time.Parse(RFC3339MilliNoZ, s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*jt = JSONRFC3339MilliNoZ(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
type JSONRFC1123 time.Time
|
||||
|
||||
func (jt *JSONRFC1123) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
t, err := time.Parse(time.RFC1123, s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*jt = JSONRFC1123(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
type JSONUnix time.Time
|
||||
|
||||
func (jt *JSONUnix) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
unix, err := strconv.ParseInt(s, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t = time.Unix(unix, 0)
|
||||
*jt = JSONUnix(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RFC3339NoZ is the time format used in Heat (Orchestration).
|
||||
const RFC3339NoZ = "2006-01-02T15:04:05"
|
||||
|
||||
type JSONRFC3339NoZ time.Time
|
||||
|
||||
func (jt *JSONRFC3339NoZ) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
t, err := time.Parse(RFC3339NoZ, s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*jt = JSONRFC3339NoZ(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
Link is an internal type to be used in packages of collection resources that are
|
||||
paginated in a certain way.
|
||||
|
||||
It's a response substructure common to many paginated collection results that is
|
||||
used to point to related pages. Usually, the one we care about is the one with
|
||||
Rel field set to "next".
|
||||
*/
|
||||
type Link struct {
|
||||
Href string `json:"href"`
|
||||
Rel string `json:"rel"`
|
||||
}
|
||||
|
||||
/*
|
||||
ExtractNextURL is an internal function useful for packages of collection
|
||||
resources that are paginated in a certain way.
|
||||
|
||||
It attempts to extract the "next" URL from slice of Link structs, or
|
||||
"" if no such URL is present.
|
||||
*/
|
||||
func ExtractNextURL(links []Link) (string, error) {
|
||||
var url string
|
||||
|
||||
for _, l := range links {
|
||||
if l.Rel == "next" {
|
||||
url = l.Href
|
||||
}
|
||||
}
|
||||
|
||||
if url == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return url, nil
|
||||
}
|
141
vendor/github.com/gophercloud/gophercloud/service_client.go
generated
vendored
Normal file
141
vendor/github.com/gophercloud/gophercloud/service_client.go
generated
vendored
Normal file
|
@ -0,0 +1,141 @@
|
|||
package gophercloud
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ServiceClient stores details required to interact with a specific service API implemented by a provider.
|
||||
// Generally, you'll acquire these by calling the appropriate `New` method on a ProviderClient.
|
||||
type ServiceClient struct {
|
||||
// ProviderClient is a reference to the provider that implements this service.
|
||||
*ProviderClient
|
||||
|
||||
// Endpoint is the base URL of the service's API, acquired from a service catalog.
|
||||
// It MUST end with a /.
|
||||
Endpoint string
|
||||
|
||||
// ResourceBase is the base URL shared by the resources within a service's API. It should include
|
||||
// the API version and, like Endpoint, MUST end with a / if set. If not set, the Endpoint is used
|
||||
// as-is, instead.
|
||||
ResourceBase string
|
||||
|
||||
Microversion string
|
||||
}
|
||||
|
||||
// ResourceBaseURL returns the base URL of any resources used by this service. It MUST end with a /.
|
||||
func (client *ServiceClient) ResourceBaseURL() string {
|
||||
if client.ResourceBase != "" {
|
||||
return client.ResourceBase
|
||||
}
|
||||
return client.Endpoint
|
||||
}
|
||||
|
||||
// ServiceURL constructs a URL for a resource belonging to this provider.
|
||||
func (client *ServiceClient) ServiceURL(parts ...string) string {
|
||||
return client.ResourceBaseURL() + strings.Join(parts, "/")
|
||||
}
|
||||
|
||||
// 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{}
|
||||
}
|
||||
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
|
||||
|
||||
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{}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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{}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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{}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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{}
|
||||
}
|
||||
|
||||
if opts.MoreHeaders == nil {
|
||||
opts.MoreHeaders = make(map[string]string)
|
||||
}
|
||||
opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion
|
||||
|
||||
return client.Request("DELETE", url, opts)
|
||||
}
|
102
vendor/github.com/gophercloud/gophercloud/util.go
generated
vendored
Normal file
102
vendor/github.com/gophercloud/gophercloud/util.go
generated
vendored
Normal file
|
@ -0,0 +1,102 @@
|
|||
package gophercloud
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// WaitFor polls a predicate function, once per second, up to a timeout limit.
|
||||
// This is useful to wait for a resource to transition to a certain state.
|
||||
// To handle situations when the predicate might hang indefinitely, the
|
||||
// predicate will be prematurely cancelled after the timeout.
|
||||
// Resource packages will wrap this in a more convenient function that's
|
||||
// specific to a certain resource, but it can also be useful on its own.
|
||||
func WaitFor(timeout int, predicate func() (bool, error)) error {
|
||||
type WaitForResult struct {
|
||||
Success bool
|
||||
Error error
|
||||
}
|
||||
|
||||
start := time.Now().Unix()
|
||||
|
||||
for {
|
||||
// If a timeout is set, and that's been exceeded, shut it down.
|
||||
if timeout >= 0 && time.Now().Unix()-start >= int64(timeout) {
|
||||
return fmt.Errorf("A timeout occurred")
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
var result WaitForResult
|
||||
ch := make(chan bool, 1)
|
||||
go func() {
|
||||
defer close(ch)
|
||||
satisfied, err := predicate()
|
||||
result.Success = satisfied
|
||||
result.Error = err
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ch:
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.Success {
|
||||
return nil
|
||||
}
|
||||
// If the predicate has not finished by the timeout, cancel it.
|
||||
case <-time.After(time.Duration(timeout) * time.Second):
|
||||
return fmt.Errorf("A timeout occurred")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NormalizeURL is an internal function to be used by provider clients.
|
||||
//
|
||||
// It ensures that each endpoint URL has a closing `/`, as expected by
|
||||
// ServiceClient's methods.
|
||||
func NormalizeURL(url string) string {
|
||||
if !strings.HasSuffix(url, "/") {
|
||||
return url + "/"
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
// NormalizePathURL is used to convert rawPath to a fqdn, using basePath as
|
||||
// a reference in the filesystem, if necessary. basePath is assumed to contain
|
||||
// either '.' when first used, or the file:// type fqdn of the parent resource.
|
||||
// e.g. myFavScript.yaml => file://opt/lib/myFavScript.yaml
|
||||
func NormalizePathURL(basePath, rawPath string) (string, error) {
|
||||
u, err := url.Parse(rawPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// if a scheme is defined, it must be a fqdn already
|
||||
if u.Scheme != "" {
|
||||
return u.String(), nil
|
||||
}
|
||||
// if basePath is a url, then child resources are assumed to be relative to it
|
||||
bu, err := url.Parse(basePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var basePathSys, absPathSys string
|
||||
if bu.Scheme != "" {
|
||||
basePathSys = filepath.FromSlash(bu.Path)
|
||||
absPathSys = filepath.Join(basePathSys, rawPath)
|
||||
bu.Path = filepath.ToSlash(absPathSys)
|
||||
return bu.String(), nil
|
||||
}
|
||||
|
||||
absPathSys = filepath.Join(basePath, rawPath)
|
||||
u.Path = filepath.ToSlash(absPathSys)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
u.Scheme = "file"
|
||||
return u.String(), nil
|
||||
|
||||
}
|
454
vendor/github.com/samuel/go-zookeeper/zk/conn.go
generated
vendored
454
vendor/github.com/samuel/go-zookeeper/zk/conn.go
generated
vendored
|
@ -44,9 +44,9 @@ const (
|
|||
type watchType int
|
||||
|
||||
const (
|
||||
watchTypeData = iota
|
||||
watchTypeExist = iota
|
||||
watchTypeChild = iota
|
||||
watchTypeData = iota
|
||||
watchTypeExist
|
||||
watchTypeChild
|
||||
)
|
||||
|
||||
type watchPathType struct {
|
||||
|
@ -61,37 +61,52 @@ type Logger interface {
|
|||
Printf(string, ...interface{})
|
||||
}
|
||||
|
||||
type Conn struct {
|
||||
lastZxid int64
|
||||
sessionID int64
|
||||
state State // must be 32-bit aligned
|
||||
xid uint32
|
||||
timeout int32 // session timeout in milliseconds
|
||||
passwd []byte
|
||||
type authCreds struct {
|
||||
scheme string
|
||||
auth []byte
|
||||
}
|
||||
|
||||
dialer Dialer
|
||||
servers []string
|
||||
serverIndex int // remember last server that was tried during connect to round-robin attempts to servers
|
||||
lastServerIndex int // index of the last server that was successfully connected to and authenticated with
|
||||
conn net.Conn
|
||||
eventChan chan Event
|
||||
shouldQuit chan struct{}
|
||||
pingInterval time.Duration
|
||||
recvTimeout time.Duration
|
||||
connectTimeout time.Duration
|
||||
type Conn struct {
|
||||
lastZxid int64
|
||||
sessionID int64
|
||||
state State // must be 32-bit aligned
|
||||
xid uint32
|
||||
sessionTimeoutMs int32 // session timeout in milliseconds
|
||||
passwd []byte
|
||||
|
||||
dialer Dialer
|
||||
hostProvider HostProvider
|
||||
serverMu sync.Mutex // protects server
|
||||
server string // remember the address/port of the current server
|
||||
conn net.Conn
|
||||
eventChan chan Event
|
||||
eventCallback EventCallback // may be nil
|
||||
shouldQuit chan struct{}
|
||||
pingInterval time.Duration
|
||||
recvTimeout time.Duration
|
||||
connectTimeout time.Duration
|
||||
|
||||
creds []authCreds
|
||||
credsMu sync.Mutex // protects server
|
||||
|
||||
sendChan chan *request
|
||||
requests map[int32]*request // Xid -> pending request
|
||||
requestsLock sync.Mutex
|
||||
watchers map[watchPathType][]chan Event
|
||||
watchersLock sync.Mutex
|
||||
closeChan chan struct{} // channel to tell send loop stop
|
||||
|
||||
// Debug (used by unit tests)
|
||||
reconnectDelay time.Duration
|
||||
|
||||
logger Logger
|
||||
|
||||
buf []byte
|
||||
}
|
||||
|
||||
// connOption represents a connection option.
|
||||
type connOption func(c *Conn)
|
||||
|
||||
type request struct {
|
||||
xid int32
|
||||
opcode int32
|
||||
|
@ -122,26 +137,39 @@ type Event struct {
|
|||
Server string // For connection events
|
||||
}
|
||||
|
||||
// Connect establishes a new connection to a pool of zookeeper servers
|
||||
// using the default net.Dialer. See ConnectWithDialer for further
|
||||
// information about session timeout.
|
||||
func Connect(servers []string, sessionTimeout time.Duration) (*Conn, <-chan Event, error) {
|
||||
return ConnectWithDialer(servers, sessionTimeout, nil)
|
||||
// HostProvider is used to represent a set of hosts a ZooKeeper client should connect to.
|
||||
// It is an analog of the Java equivalent:
|
||||
// http://svn.apache.org/viewvc/zookeeper/trunk/src/java/main/org/apache/zookeeper/client/HostProvider.java?view=markup
|
||||
type HostProvider interface {
|
||||
// Init is called first, with the servers specified in the connection string.
|
||||
Init(servers []string) error
|
||||
// Len returns the number of servers.
|
||||
Len() int
|
||||
// Next returns the next server to connect to. retryStart will be true if we've looped through
|
||||
// all known servers without Connected() being called.
|
||||
Next() (server string, retryStart bool)
|
||||
// Notify the HostProvider of a successful connection.
|
||||
Connected()
|
||||
}
|
||||
|
||||
// ConnectWithDialer establishes a new connection to a pool of zookeeper
|
||||
// ConnectWithDialer establishes a new connection to a pool of zookeeper servers
|
||||
// using a custom Dialer. See Connect for further information about session timeout.
|
||||
// This method is deprecated and provided for compatibility: use the WithDialer option instead.
|
||||
func ConnectWithDialer(servers []string, sessionTimeout time.Duration, dialer Dialer) (*Conn, <-chan Event, error) {
|
||||
return Connect(servers, sessionTimeout, WithDialer(dialer))
|
||||
}
|
||||
|
||||
// Connect establishes a new connection to a pool of zookeeper
|
||||
// servers. The provided session timeout sets the amount of time for which
|
||||
// a session is considered valid after losing connection to a server. Within
|
||||
// the session timeout it's possible to reestablish a connection to a different
|
||||
// server and keep the same session. This is means any ephemeral nodes and
|
||||
// watches are maintained.
|
||||
func ConnectWithDialer(servers []string, sessionTimeout time.Duration, dialer Dialer) (*Conn, <-chan Event, error) {
|
||||
func Connect(servers []string, sessionTimeout time.Duration, options ...connOption) (*Conn, <-chan Event, error) {
|
||||
if len(servers) == 0 {
|
||||
return nil, nil, errors.New("zk: server list must not be empty")
|
||||
}
|
||||
|
||||
recvTimeout := sessionTimeout * 2 / 3
|
||||
|
||||
srvs := make([]string, len(servers))
|
||||
|
||||
for i, addr := range servers {
|
||||
|
@ -156,38 +184,69 @@ func ConnectWithDialer(servers []string, sessionTimeout time.Duration, dialer Di
|
|||
stringShuffle(srvs)
|
||||
|
||||
ec := make(chan Event, eventChanSize)
|
||||
if dialer == nil {
|
||||
dialer = net.DialTimeout
|
||||
}
|
||||
conn := Conn{
|
||||
dialer: dialer,
|
||||
servers: srvs,
|
||||
serverIndex: 0,
|
||||
lastServerIndex: -1,
|
||||
conn: nil,
|
||||
state: StateDisconnected,
|
||||
eventChan: ec,
|
||||
shouldQuit: make(chan struct{}),
|
||||
recvTimeout: recvTimeout,
|
||||
pingInterval: recvTimeout / 2,
|
||||
connectTimeout: 1 * time.Second,
|
||||
sendChan: make(chan *request, sendChanSize),
|
||||
requests: make(map[int32]*request),
|
||||
watchers: make(map[watchPathType][]chan Event),
|
||||
passwd: emptyPassword,
|
||||
timeout: int32(sessionTimeout.Nanoseconds() / 1e6),
|
||||
logger: DefaultLogger,
|
||||
conn := &Conn{
|
||||
dialer: net.DialTimeout,
|
||||
hostProvider: &DNSHostProvider{},
|
||||
conn: nil,
|
||||
state: StateDisconnected,
|
||||
eventChan: ec,
|
||||
shouldQuit: make(chan struct{}),
|
||||
connectTimeout: 1 * time.Second,
|
||||
sendChan: make(chan *request, sendChanSize),
|
||||
requests: make(map[int32]*request),
|
||||
watchers: make(map[watchPathType][]chan Event),
|
||||
passwd: emptyPassword,
|
||||
logger: DefaultLogger,
|
||||
buf: make([]byte, bufferSize),
|
||||
|
||||
// Debug
|
||||
reconnectDelay: 0,
|
||||
}
|
||||
|
||||
// Set provided options.
|
||||
for _, option := range options {
|
||||
option(conn)
|
||||
}
|
||||
|
||||
if err := conn.hostProvider.Init(srvs); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
conn.setTimeouts(int32(sessionTimeout / time.Millisecond))
|
||||
|
||||
go func() {
|
||||
conn.loop()
|
||||
conn.flushRequests(ErrClosing)
|
||||
conn.invalidateWatches(ErrClosing)
|
||||
close(conn.eventChan)
|
||||
}()
|
||||
return &conn, ec, nil
|
||||
return conn, ec, nil
|
||||
}
|
||||
|
||||
// WithDialer returns a connection option specifying a non-default Dialer.
|
||||
func WithDialer(dialer Dialer) connOption {
|
||||
return func(c *Conn) {
|
||||
c.dialer = dialer
|
||||
}
|
||||
}
|
||||
|
||||
// WithHostProvider returns a connection option specifying a non-default HostProvider.
|
||||
func WithHostProvider(hostProvider HostProvider) connOption {
|
||||
return func(c *Conn) {
|
||||
c.hostProvider = hostProvider
|
||||
}
|
||||
}
|
||||
|
||||
// EventCallback is a function that is called when an Event occurs.
|
||||
type EventCallback func(Event)
|
||||
|
||||
// WithEventCallback returns a connection option that specifies an event
|
||||
// callback.
|
||||
// The callback must not block - doing so would delay the ZK go routines.
|
||||
func WithEventCallback(cb EventCallback) connOption {
|
||||
return func(c *Conn) {
|
||||
c.eventCallback = cb
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) Close() {
|
||||
|
@ -199,31 +258,54 @@ func (c *Conn) Close() {
|
|||
}
|
||||
}
|
||||
|
||||
// States returns the current state of the connection.
|
||||
// State returns the current state of the connection.
|
||||
func (c *Conn) State() State {
|
||||
return State(atomic.LoadInt32((*int32)(&c.state)))
|
||||
}
|
||||
|
||||
// SessionID returns the current session id of the connection.
|
||||
func (c *Conn) SessionID() int64 {
|
||||
return atomic.LoadInt64(&c.sessionID)
|
||||
}
|
||||
|
||||
// SetLogger sets the logger to be used for printing errors.
|
||||
// Logger is an interface provided by this package.
|
||||
func (c *Conn) SetLogger(l Logger) {
|
||||
c.logger = l
|
||||
}
|
||||
|
||||
func (c *Conn) setTimeouts(sessionTimeoutMs int32) {
|
||||
c.sessionTimeoutMs = sessionTimeoutMs
|
||||
sessionTimeout := time.Duration(sessionTimeoutMs) * time.Millisecond
|
||||
c.recvTimeout = sessionTimeout * 2 / 3
|
||||
c.pingInterval = c.recvTimeout / 2
|
||||
}
|
||||
|
||||
func (c *Conn) setState(state State) {
|
||||
atomic.StoreInt32((*int32)(&c.state), int32(state))
|
||||
c.sendEvent(Event{Type: EventSession, State: state, Server: c.Server()})
|
||||
}
|
||||
|
||||
func (c *Conn) sendEvent(evt Event) {
|
||||
if c.eventCallback != nil {
|
||||
c.eventCallback(evt)
|
||||
}
|
||||
|
||||
select {
|
||||
case c.eventChan <- Event{Type: EventSession, State: state, Server: c.servers[c.serverIndex]}:
|
||||
case c.eventChan <- evt:
|
||||
default:
|
||||
// panic("zk: event channel full - it must be monitored and never allowed to be full")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) connect() error {
|
||||
c.setState(StateConnecting)
|
||||
var retryStart bool
|
||||
for {
|
||||
c.serverIndex = (c.serverIndex + 1) % len(c.servers)
|
||||
if c.serverIndex == c.lastServerIndex {
|
||||
c.serverMu.Lock()
|
||||
c.server, retryStart = c.hostProvider.Next()
|
||||
c.serverMu.Unlock()
|
||||
c.setState(StateConnecting)
|
||||
if retryStart {
|
||||
c.flushUnsentRequests(ErrNoServer)
|
||||
select {
|
||||
case <-time.After(time.Second):
|
||||
|
@ -233,22 +315,79 @@ func (c *Conn) connect() error {
|
|||
c.flushUnsentRequests(ErrClosing)
|
||||
return ErrClosing
|
||||
}
|
||||
} else if c.lastServerIndex < 0 {
|
||||
// lastServerIndex defaults to -1 to avoid a delay on the initial connect
|
||||
c.lastServerIndex = 0
|
||||
}
|
||||
|
||||
zkConn, err := c.dialer("tcp", c.servers[c.serverIndex], c.connectTimeout)
|
||||
zkConn, err := c.dialer("tcp", c.Server(), c.connectTimeout)
|
||||
if err == nil {
|
||||
c.conn = zkConn
|
||||
c.setState(StateConnected)
|
||||
c.logger.Printf("Connected to %s", c.Server())
|
||||
return nil
|
||||
}
|
||||
|
||||
c.logger.Printf("Failed to connect to %s: %+v", c.servers[c.serverIndex], err)
|
||||
c.logger.Printf("Failed to connect to %s: %+v", c.Server(), err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) resendZkAuth(reauthReadyChan chan struct{}) {
|
||||
c.credsMu.Lock()
|
||||
defer c.credsMu.Unlock()
|
||||
|
||||
defer close(reauthReadyChan)
|
||||
|
||||
c.logger.Printf("Re-submitting `%d` credentials after reconnect",
|
||||
len(c.creds))
|
||||
|
||||
for _, cred := range c.creds {
|
||||
resChan, err := c.sendRequest(
|
||||
opSetAuth,
|
||||
&setAuthRequest{Type: 0,
|
||||
Scheme: cred.scheme,
|
||||
Auth: cred.auth,
|
||||
},
|
||||
&setAuthResponse{},
|
||||
nil)
|
||||
|
||||
if err != nil {
|
||||
c.logger.Printf("Call to sendRequest failed during credential resubmit: %s", err)
|
||||
// FIXME(prozlach): lets ignore errors for now
|
||||
continue
|
||||
}
|
||||
|
||||
res := <-resChan
|
||||
if res.err != nil {
|
||||
c.logger.Printf("Credential re-submit failed: %s", res.err)
|
||||
// FIXME(prozlach): lets ignore errors for now
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) sendRequest(
|
||||
opcode int32,
|
||||
req interface{},
|
||||
res interface{},
|
||||
recvFunc func(*request, *responseHeader, error),
|
||||
) (
|
||||
<-chan response,
|
||||
error,
|
||||
) {
|
||||
rq := &request{
|
||||
xid: c.nextXid(),
|
||||
opcode: opcode,
|
||||
pkt: req,
|
||||
recvStruct: res,
|
||||
recvChan: make(chan response, 1),
|
||||
recvFunc: recvFunc,
|
||||
}
|
||||
|
||||
if err := c.sendData(rq); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rq.recvChan, nil
|
||||
}
|
||||
|
||||
func (c *Conn) loop() {
|
||||
for {
|
||||
if err := c.connect(); err != nil {
|
||||
|
@ -259,41 +398,46 @@ func (c *Conn) loop() {
|
|||
err := c.authenticate()
|
||||
switch {
|
||||
case err == ErrSessionExpired:
|
||||
c.logger.Printf("Authentication failed: %s", err)
|
||||
c.invalidateWatches(err)
|
||||
case err != nil && c.conn != nil:
|
||||
c.logger.Printf("Authentication failed: %s", err)
|
||||
c.conn.Close()
|
||||
case err == nil:
|
||||
c.lastServerIndex = c.serverIndex
|
||||
closeChan := make(chan struct{}) // channel to tell send loop stop
|
||||
var wg sync.WaitGroup
|
||||
c.logger.Printf("Authenticated: id=%d, timeout=%d", c.SessionID(), c.sessionTimeoutMs)
|
||||
c.hostProvider.Connected() // mark success
|
||||
c.closeChan = make(chan struct{}) // channel to tell send loop stop
|
||||
reauthChan := make(chan struct{}) // channel to tell send loop that authdata has been resubmitted
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
c.sendLoop(c.conn, closeChan)
|
||||
<-reauthChan
|
||||
err := c.sendLoop()
|
||||
c.logger.Printf("Send loop terminated: err=%v", err)
|
||||
c.conn.Close() // causes recv loop to EOF/exit
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
err = c.recvLoop(c.conn)
|
||||
err := c.recvLoop(c.conn)
|
||||
c.logger.Printf("Recv loop terminated: err=%v", err)
|
||||
if err == nil {
|
||||
panic("zk: recvLoop should never return nil error")
|
||||
}
|
||||
close(closeChan) // tell send loop to exit
|
||||
close(c.closeChan) // tell send loop to exit
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
c.resendZkAuth(reauthChan)
|
||||
|
||||
c.sendSetWatches()
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
c.setState(StateDisconnected)
|
||||
|
||||
// Yeesh
|
||||
if err != io.EOF && err != ErrSessionExpired && !strings.Contains(err.Error(), "use of closed network connection") {
|
||||
c.logger.Printf(err.Error())
|
||||
}
|
||||
|
||||
select {
|
||||
case <-c.shouldQuit:
|
||||
c.flushRequests(ErrClosing)
|
||||
|
@ -399,13 +543,12 @@ func (c *Conn) sendSetWatches() {
|
|||
func (c *Conn) authenticate() error {
|
||||
buf := make([]byte, 256)
|
||||
|
||||
// connect request
|
||||
|
||||
// Encode and send a connect request.
|
||||
n, err := encodePacket(buf[4:], &connectRequest{
|
||||
ProtocolVersion: protocolVersion,
|
||||
LastZxidSeen: c.lastZxid,
|
||||
TimeOut: c.timeout,
|
||||
SessionID: c.sessionID,
|
||||
TimeOut: c.sessionTimeoutMs,
|
||||
SessionID: c.SessionID(),
|
||||
Passwd: c.passwd,
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -421,23 +564,12 @@ func (c *Conn) authenticate() error {
|
|||
return err
|
||||
}
|
||||
|
||||
c.sendSetWatches()
|
||||
|
||||
// connect response
|
||||
|
||||
// package length
|
||||
// Receive and decode a connect response.
|
||||
c.conn.SetReadDeadline(time.Now().Add(c.recvTimeout * 10))
|
||||
_, err = io.ReadFull(c.conn, buf[:4])
|
||||
c.conn.SetReadDeadline(time.Time{})
|
||||
if err != nil {
|
||||
// Sometimes zookeeper just drops connection on invalid session data,
|
||||
// we prefer to drop session and start from scratch when that event
|
||||
// occurs instead of dropping into loop of connect/disconnect attempts
|
||||
c.sessionID = 0
|
||||
c.passwd = emptyPassword
|
||||
c.lastZxid = 0
|
||||
c.setState(StateExpired)
|
||||
return ErrSessionExpired
|
||||
return err
|
||||
}
|
||||
|
||||
blen := int(binary.BigEndian.Uint32(buf[:4]))
|
||||
|
@ -456,81 +588,88 @@ func (c *Conn) authenticate() error {
|
|||
return err
|
||||
}
|
||||
if r.SessionID == 0 {
|
||||
c.sessionID = 0
|
||||
atomic.StoreInt64(&c.sessionID, int64(0))
|
||||
c.passwd = emptyPassword
|
||||
c.lastZxid = 0
|
||||
c.setState(StateExpired)
|
||||
return ErrSessionExpired
|
||||
}
|
||||
|
||||
c.timeout = r.TimeOut
|
||||
c.sessionID = r.SessionID
|
||||
atomic.StoreInt64(&c.sessionID, r.SessionID)
|
||||
c.setTimeouts(r.TimeOut)
|
||||
c.passwd = r.Passwd
|
||||
c.setState(StateHasSession)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) sendLoop(conn net.Conn, closeChan <-chan struct{}) error {
|
||||
func (c *Conn) sendData(req *request) error {
|
||||
header := &requestHeader{req.xid, req.opcode}
|
||||
n, err := encodePacket(c.buf[4:], header)
|
||||
if err != nil {
|
||||
req.recvChan <- response{-1, err}
|
||||
return nil
|
||||
}
|
||||
|
||||
n2, err := encodePacket(c.buf[4+n:], req.pkt)
|
||||
if err != nil {
|
||||
req.recvChan <- response{-1, err}
|
||||
return nil
|
||||
}
|
||||
|
||||
n += n2
|
||||
|
||||
binary.BigEndian.PutUint32(c.buf[:4], uint32(n))
|
||||
|
||||
c.requestsLock.Lock()
|
||||
select {
|
||||
case <-c.closeChan:
|
||||
req.recvChan <- response{-1, ErrConnectionClosed}
|
||||
c.requestsLock.Unlock()
|
||||
return ErrConnectionClosed
|
||||
default:
|
||||
}
|
||||
c.requests[req.xid] = req
|
||||
c.requestsLock.Unlock()
|
||||
|
||||
c.conn.SetWriteDeadline(time.Now().Add(c.recvTimeout))
|
||||
_, err = c.conn.Write(c.buf[:n+4])
|
||||
c.conn.SetWriteDeadline(time.Time{})
|
||||
if err != nil {
|
||||
req.recvChan <- response{-1, err}
|
||||
c.conn.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) sendLoop() error {
|
||||
pingTicker := time.NewTicker(c.pingInterval)
|
||||
defer pingTicker.Stop()
|
||||
|
||||
buf := make([]byte, bufferSize)
|
||||
for {
|
||||
select {
|
||||
case req := <-c.sendChan:
|
||||
header := &requestHeader{req.xid, req.opcode}
|
||||
n, err := encodePacket(buf[4:], header)
|
||||
if err != nil {
|
||||
req.recvChan <- response{-1, err}
|
||||
continue
|
||||
}
|
||||
|
||||
n2, err := encodePacket(buf[4+n:], req.pkt)
|
||||
if err != nil {
|
||||
req.recvChan <- response{-1, err}
|
||||
continue
|
||||
}
|
||||
|
||||
n += n2
|
||||
|
||||
binary.BigEndian.PutUint32(buf[:4], uint32(n))
|
||||
|
||||
c.requestsLock.Lock()
|
||||
select {
|
||||
case <-closeChan:
|
||||
req.recvChan <- response{-1, ErrConnectionClosed}
|
||||
c.requestsLock.Unlock()
|
||||
return ErrConnectionClosed
|
||||
default:
|
||||
}
|
||||
c.requests[req.xid] = req
|
||||
c.requestsLock.Unlock()
|
||||
|
||||
conn.SetWriteDeadline(time.Now().Add(c.recvTimeout))
|
||||
_, err = conn.Write(buf[:n+4])
|
||||
conn.SetWriteDeadline(time.Time{})
|
||||
if err != nil {
|
||||
req.recvChan <- response{-1, err}
|
||||
conn.Close()
|
||||
if err := c.sendData(req); err != nil {
|
||||
return err
|
||||
}
|
||||
case <-pingTicker.C:
|
||||
n, err := encodePacket(buf[4:], &requestHeader{Xid: -2, Opcode: opPing})
|
||||
n, err := encodePacket(c.buf[4:], &requestHeader{Xid: -2, Opcode: opPing})
|
||||
if err != nil {
|
||||
panic("zk: opPing should never fail to serialize")
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint32(buf[:4], uint32(n))
|
||||
binary.BigEndian.PutUint32(c.buf[:4], uint32(n))
|
||||
|
||||
conn.SetWriteDeadline(time.Now().Add(c.recvTimeout))
|
||||
_, err = conn.Write(buf[:n+4])
|
||||
conn.SetWriteDeadline(time.Time{})
|
||||
c.conn.SetWriteDeadline(time.Now().Add(c.recvTimeout))
|
||||
_, err = c.conn.Write(c.buf[:n+4])
|
||||
c.conn.SetWriteDeadline(time.Time{})
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
c.conn.Close()
|
||||
return err
|
||||
}
|
||||
case <-closeChan:
|
||||
case <-c.closeChan:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -565,7 +704,7 @@ func (c *Conn) recvLoop(conn net.Conn) error {
|
|||
|
||||
if res.Xid == -1 {
|
||||
res := &watcherEvent{}
|
||||
_, err := decodePacket(buf[16:16+blen], res)
|
||||
_, err := decodePacket(buf[16:blen], res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -575,10 +714,7 @@ func (c *Conn) recvLoop(conn net.Conn) error {
|
|||
Path: res.Path,
|
||||
Err: nil,
|
||||
}
|
||||
select {
|
||||
case c.eventChan <- ev:
|
||||
default:
|
||||
}
|
||||
c.sendEvent(ev)
|
||||
wTypes := make([]watchType, 0, 2)
|
||||
switch res.Type {
|
||||
case EventNodeCreated:
|
||||
|
@ -622,7 +758,7 @@ func (c *Conn) recvLoop(conn net.Conn) error {
|
|||
if res.Err != 0 {
|
||||
err = res.Err.toError()
|
||||
} else {
|
||||
_, err = decodePacket(buf[16:16+blen], req.recvStruct)
|
||||
_, err = decodePacket(buf[16:blen], req.recvStruct)
|
||||
}
|
||||
if req.recvFunc != nil {
|
||||
req.recvFunc(req, &res, err)
|
||||
|
@ -670,7 +806,28 @@ func (c *Conn) request(opcode int32, req interface{}, res interface{}, recvFunc
|
|||
|
||||
func (c *Conn) AddAuth(scheme string, auth []byte) error {
|
||||
_, err := c.request(opSetAuth, &setAuthRequest{Type: 0, Scheme: scheme, Auth: auth}, &setAuthResponse{}, nil)
|
||||
return err
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remember authdata so that it can be re-submitted on reconnect
|
||||
//
|
||||
// FIXME(prozlach): For now we treat "userfoo:passbar" and "userfoo:passbar2"
|
||||
// as two different entries, which will be re-submitted on reconnet. Some
|
||||
// research is needed on how ZK treats these cases and
|
||||
// then maybe switch to something like "map[username] = password" to allow
|
||||
// only single password for given user with users being unique.
|
||||
obj := authCreds{
|
||||
scheme: scheme,
|
||||
auth: auth,
|
||||
}
|
||||
|
||||
c.credsMu.Lock()
|
||||
c.creds = append(c.creds, obj)
|
||||
c.credsMu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) Children(path string) ([]string, *Stat, error) {
|
||||
|
@ -816,7 +973,6 @@ func (c *Conn) GetACL(path string) ([]ACL, *Stat, error) {
|
|||
_, err := c.request(opGetAcl, &getAclRequest{Path: path}, res, nil)
|
||||
return res.Acl, &res.Stat, err
|
||||
}
|
||||
|
||||
func (c *Conn) SetACL(path string, acl []ACL, version int32) (*Stat, error) {
|
||||
res := &setAclResponse{}
|
||||
_, err := c.request(opSetAcl, &setAclRequest{Path: path, Acl: acl, Version: version}, res, nil)
|
||||
|
@ -832,6 +988,7 @@ func (c *Conn) Sync(path string) (string, error) {
|
|||
type MultiResponse struct {
|
||||
Stat *Stat
|
||||
String string
|
||||
Error error
|
||||
}
|
||||
|
||||
// Multi executes multiple ZooKeeper operations or none of them. The provided
|
||||
|
@ -854,7 +1011,7 @@ func (c *Conn) Multi(ops ...interface{}) ([]MultiResponse, error) {
|
|||
case *CheckVersionRequest:
|
||||
opCode = opCheck
|
||||
default:
|
||||
return nil, fmt.Errorf("uknown operation type %T", op)
|
||||
return nil, fmt.Errorf("unknown operation type %T", op)
|
||||
}
|
||||
req.Ops = append(req.Ops, multiRequestOp{multiHeader{opCode, false, -1}, op})
|
||||
}
|
||||
|
@ -862,7 +1019,14 @@ func (c *Conn) Multi(ops ...interface{}) ([]MultiResponse, error) {
|
|||
_, err := c.request(opMulti, req, res, nil)
|
||||
mr := make([]MultiResponse, len(res.Ops))
|
||||
for i, op := range res.Ops {
|
||||
mr[i] = MultiResponse{Stat: op.Stat, String: op.String}
|
||||
mr[i] = MultiResponse{Stat: op.Stat, String: op.String, Error: op.Err.toError()}
|
||||
}
|
||||
return mr, err
|
||||
}
|
||||
|
||||
// Server returns the current or last-connected server name.
|
||||
func (c *Conn) Server() string {
|
||||
c.serverMu.Lock()
|
||||
defer c.serverMu.Unlock()
|
||||
return c.server
|
||||
}
|
||||
|
|
56
vendor/github.com/samuel/go-zookeeper/zk/constants.go
generated
vendored
56
vendor/github.com/samuel/go-zookeeper/zk/constants.go
generated
vendored
|
@ -28,18 +28,19 @@ const (
|
|||
opClose = -11
|
||||
opSetAuth = 100
|
||||
opSetWatches = 101
|
||||
opError = -1
|
||||
// Not in protocol, used internally
|
||||
opWatcherEvent = -2
|
||||
)
|
||||
|
||||
const (
|
||||
EventNodeCreated = EventType(1)
|
||||
EventNodeDeleted = EventType(2)
|
||||
EventNodeDataChanged = EventType(3)
|
||||
EventNodeChildrenChanged = EventType(4)
|
||||
EventNodeCreated EventType = 1
|
||||
EventNodeDeleted EventType = 2
|
||||
EventNodeDataChanged EventType = 3
|
||||
EventNodeChildrenChanged EventType = 4
|
||||
|
||||
EventSession = EventType(-1)
|
||||
EventNotWatching = EventType(-2)
|
||||
EventSession EventType = -1
|
||||
EventNotWatching EventType = -2
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -54,14 +55,13 @@ var (
|
|||
)
|
||||
|
||||
const (
|
||||
StateUnknown = State(-1)
|
||||
StateDisconnected = State(0)
|
||||
StateConnecting = State(1)
|
||||
StateAuthFailed = State(4)
|
||||
StateConnectedReadOnly = State(5)
|
||||
StateSaslAuthenticated = State(6)
|
||||
StateExpired = State(-112)
|
||||
// StateAuthFailed = State(-113)
|
||||
StateUnknown State = -1
|
||||
StateDisconnected State = 0
|
||||
StateConnecting State = 1
|
||||
StateAuthFailed State = 4
|
||||
StateConnectedReadOnly State = 5
|
||||
StateSaslAuthenticated State = 6
|
||||
StateExpired State = -112
|
||||
|
||||
StateConnected = State(100)
|
||||
StateHasSession = State(101)
|
||||
|
@ -154,20 +154,20 @@ const (
|
|||
errBadArguments = -8
|
||||
errInvalidState = -9
|
||||
// API errors
|
||||
errAPIError = ErrCode(-100)
|
||||
errNoNode = ErrCode(-101) // *
|
||||
errNoAuth = ErrCode(-102)
|
||||
errBadVersion = ErrCode(-103) // *
|
||||
errNoChildrenForEphemerals = ErrCode(-108)
|
||||
errNodeExists = ErrCode(-110) // *
|
||||
errNotEmpty = ErrCode(-111)
|
||||
errSessionExpired = ErrCode(-112)
|
||||
errInvalidCallback = ErrCode(-113)
|
||||
errInvalidAcl = ErrCode(-114)
|
||||
errAuthFailed = ErrCode(-115)
|
||||
errClosing = ErrCode(-116)
|
||||
errNothing = ErrCode(-117)
|
||||
errSessionMoved = ErrCode(-118)
|
||||
errAPIError ErrCode = -100
|
||||
errNoNode ErrCode = -101 // *
|
||||
errNoAuth ErrCode = -102
|
||||
errBadVersion ErrCode = -103 // *
|
||||
errNoChildrenForEphemerals ErrCode = -108
|
||||
errNodeExists ErrCode = -110 // *
|
||||
errNotEmpty ErrCode = -111
|
||||
errSessionExpired ErrCode = -112
|
||||
errInvalidCallback ErrCode = -113
|
||||
errInvalidAcl ErrCode = -114
|
||||
errAuthFailed ErrCode = -115
|
||||
errClosing ErrCode = -116
|
||||
errNothing ErrCode = -117
|
||||
errSessionMoved ErrCode = -118
|
||||
)
|
||||
|
||||
// Constants for ACL permissions
|
||||
|
|
88
vendor/github.com/samuel/go-zookeeper/zk/dnshostprovider.go
generated
vendored
Normal file
88
vendor/github.com/samuel/go-zookeeper/zk/dnshostprovider.go
generated
vendored
Normal file
|
@ -0,0 +1,88 @@
|
|||
package zk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// DNSHostProvider is the default HostProvider. It currently matches
|
||||
// the Java StaticHostProvider, resolving hosts from DNS once during
|
||||
// the call to Init. It could be easily extended to re-query DNS
|
||||
// periodically or if there is trouble connecting.
|
||||
type DNSHostProvider struct {
|
||||
mu sync.Mutex // Protects everything, so we can add asynchronous updates later.
|
||||
servers []string
|
||||
curr int
|
||||
last int
|
||||
lookupHost func(string) ([]string, error) // Override of net.LookupHost, for testing.
|
||||
}
|
||||
|
||||
// Init is called first, with the servers specified in the connection
|
||||
// string. It uses DNS to look up addresses for each server, then
|
||||
// shuffles them all together.
|
||||
func (hp *DNSHostProvider) Init(servers []string) error {
|
||||
hp.mu.Lock()
|
||||
defer hp.mu.Unlock()
|
||||
|
||||
lookupHost := hp.lookupHost
|
||||
if lookupHost == nil {
|
||||
lookupHost = net.LookupHost
|
||||
}
|
||||
|
||||
found := []string{}
|
||||
for _, server := range servers {
|
||||
host, port, err := net.SplitHostPort(server)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
addrs, err := lookupHost(host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
found = append(found, net.JoinHostPort(addr, port))
|
||||
}
|
||||
}
|
||||
|
||||
if len(found) == 0 {
|
||||
return fmt.Errorf("No hosts found for addresses %q", servers)
|
||||
}
|
||||
|
||||
// Randomize the order of the servers to avoid creating hotspots
|
||||
stringShuffle(found)
|
||||
|
||||
hp.servers = found
|
||||
hp.curr = -1
|
||||
hp.last = -1
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Len returns the number of servers available
|
||||
func (hp *DNSHostProvider) Len() int {
|
||||
hp.mu.Lock()
|
||||
defer hp.mu.Unlock()
|
||||
return len(hp.servers)
|
||||
}
|
||||
|
||||
// Next returns the next server to connect to. retryStart will be true
|
||||
// if we've looped through all known servers without Connected() being
|
||||
// called.
|
||||
func (hp *DNSHostProvider) Next() (server string, retryStart bool) {
|
||||
hp.mu.Lock()
|
||||
defer hp.mu.Unlock()
|
||||
hp.curr = (hp.curr + 1) % len(hp.servers)
|
||||
retryStart = hp.curr == hp.last
|
||||
if hp.last == -1 {
|
||||
hp.last = 0
|
||||
}
|
||||
return hp.servers[hp.curr], retryStart
|
||||
}
|
||||
|
||||
// Connected notifies the HostProvider of a successful connection.
|
||||
func (hp *DNSHostProvider) Connected() {
|
||||
hp.mu.Lock()
|
||||
defer hp.mu.Unlock()
|
||||
hp.last = hp.curr
|
||||
}
|
58
vendor/github.com/samuel/go-zookeeper/zk/flw.go
generated
vendored
58
vendor/github.com/samuel/go-zookeeper/zk/flw.go
generated
vendored
|
@ -5,10 +5,10 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -22,7 +22,7 @@ import (
|
|||
// which server had the issue.
|
||||
func FLWSrvr(servers []string, timeout time.Duration) ([]*ServerStats, bool) {
|
||||
// different parts of the regular expression that are required to parse the srvr output
|
||||
var (
|
||||
const (
|
||||
zrVer = `^Zookeeper version: ([A-Za-z0-9\.\-]+), built on (\d\d/\d\d/\d\d\d\d \d\d:\d\d [A-Za-z0-9:\+\-]+)`
|
||||
zrLat = `^Latency min/avg/max: (\d+)/(\d+)/(\d+)`
|
||||
zrNet = `^Received: (\d+).*\n^Sent: (\d+).*\n^Connections: (\d+).*\n^Outstanding: (\d+)`
|
||||
|
@ -31,7 +31,6 @@ func FLWSrvr(servers []string, timeout time.Duration) ([]*ServerStats, bool) {
|
|||
|
||||
// build the regex from the pieces above
|
||||
re, err := regexp.Compile(fmt.Sprintf(`(?m:\A%v.*\n%v.*\n%v.*\n%v)`, zrVer, zrLat, zrNet, zrState))
|
||||
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
@ -152,14 +151,13 @@ func FLWRuok(servers []string, timeout time.Duration) []bool {
|
|||
// As with FLWSrvr, the boolean value indicates whether one of the requests had
|
||||
// an issue. The Clients struct has an Error value that can be checked.
|
||||
func FLWCons(servers []string, timeout time.Duration) ([]*ServerClients, bool) {
|
||||
var (
|
||||
const (
|
||||
zrAddr = `^ /((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?):(?:\d+))\[\d+\]`
|
||||
zrPac = `\(queued=(\d+),recved=(\d+),sent=(\d+),sid=(0x[A-Za-z0-9]+),lop=(\w+),est=(\d+),to=(\d+),`
|
||||
zrSesh = `lcxid=(0x[A-Za-z0-9]+),lzxid=(0x[A-Za-z0-9]+),lresp=(\d+),llat=(\d+),minlat=(\d+),avglat=(\d+),maxlat=(\d+)\)`
|
||||
)
|
||||
|
||||
re, err := regexp.Compile(fmt.Sprintf("%v%v%v", zrAddr, zrPac, zrSesh))
|
||||
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
@ -205,41 +203,21 @@ func FLWCons(servers []string, timeout time.Duration) ([]*ServerClients, bool) {
|
|||
sid, _ := strconv.ParseInt(match[4], 0, 64)
|
||||
est, _ := strconv.ParseInt(match[6], 0, 64)
|
||||
timeout, _ := strconv.ParseInt(match[7], 0, 32)
|
||||
lcxid, _ := parseInt64(match[8])
|
||||
lzxid, _ := parseInt64(match[9])
|
||||
lresp, _ := strconv.ParseInt(match[10], 0, 64)
|
||||
llat, _ := strconv.ParseInt(match[11], 0, 32)
|
||||
minlat, _ := strconv.ParseInt(match[12], 0, 32)
|
||||
avglat, _ := strconv.ParseInt(match[13], 0, 32)
|
||||
maxlat, _ := strconv.ParseInt(match[14], 0, 32)
|
||||
|
||||
// zookeeper returns a value, '0xffffffffffffffff', as the
|
||||
// Lzxid for PING requests in the 'cons' output.
|
||||
// unfortunately, in Go that is an invalid int64 and is not represented
|
||||
// as -1.
|
||||
// However, converting the string value to a big.Int and then back to
|
||||
// and int64 properly sets the value to -1
|
||||
lzxid, ok := new(big.Int).SetString(match[9], 0)
|
||||
|
||||
var errVal error
|
||||
|
||||
if !ok {
|
||||
errVal = fmt.Errorf("failed to convert lzxid value to big.Int")
|
||||
imOk = false
|
||||
}
|
||||
|
||||
lcxid, ok := new(big.Int).SetString(match[8], 0)
|
||||
|
||||
if !ok && errVal == nil {
|
||||
errVal = fmt.Errorf("failed to convert lcxid value to big.Int")
|
||||
imOk = false
|
||||
}
|
||||
|
||||
clients = append(clients, &ServerClient{
|
||||
Queued: queued,
|
||||
Received: recvd,
|
||||
Sent: sent,
|
||||
SessionID: sid,
|
||||
Lcxid: lcxid.Int64(),
|
||||
Lzxid: lzxid.Int64(),
|
||||
Lcxid: int64(lcxid),
|
||||
Lzxid: int64(lzxid),
|
||||
Timeout: int32(timeout),
|
||||
LastLatency: int32(llat),
|
||||
MinLatency: int32(minlat),
|
||||
|
@ -249,7 +227,6 @@ func FLWCons(servers []string, timeout time.Duration) ([]*ServerClients, bool) {
|
|||
LastResponse: time.Unix(lresp, 0),
|
||||
Addr: match[0],
|
||||
LastOperation: match[5],
|
||||
Error: errVal,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -259,9 +236,17 @@ func FLWCons(servers []string, timeout time.Duration) ([]*ServerClients, bool) {
|
|||
return sc, imOk
|
||||
}
|
||||
|
||||
// parseInt64 is similar to strconv.ParseInt, but it also handles hex values that represent negative numbers
|
||||
func parseInt64(s string) (int64, error) {
|
||||
if strings.HasPrefix(s, "0x") {
|
||||
i, err := strconv.ParseUint(s, 0, 64)
|
||||
return int64(i), err
|
||||
}
|
||||
return strconv.ParseInt(s, 0, 64)
|
||||
}
|
||||
|
||||
func fourLetterWord(server, command string, timeout time.Duration) ([]byte, error) {
|
||||
conn, err := net.DialTimeout("tcp", server, timeout)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -271,20 +256,11 @@ func fourLetterWord(server, command string, timeout time.Duration) ([]byte, erro
|
|||
defer conn.Close()
|
||||
|
||||
conn.SetWriteDeadline(time.Now().Add(timeout))
|
||||
|
||||
_, err = conn.Write([]byte(command))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn.SetReadDeadline(time.Now().Add(timeout))
|
||||
|
||||
resp, err := ioutil.ReadAll(conn)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
return ioutil.ReadAll(conn)
|
||||
}
|
||||
|
|
12
vendor/github.com/samuel/go-zookeeper/zk/lock.go
generated
vendored
12
vendor/github.com/samuel/go-zookeeper/zk/lock.go
generated
vendored
|
@ -58,8 +58,16 @@ func (l *Lock) Lock() error {
|
|||
parts := strings.Split(l.path, "/")
|
||||
pth := ""
|
||||
for _, p := range parts[1:] {
|
||||
var exists bool
|
||||
pth += "/" + p
|
||||
_, err := l.c.Create(pth, []byte{}, 0, l.acl)
|
||||
exists, _, err = l.c.Exists(pth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists == true {
|
||||
continue
|
||||
}
|
||||
_, err = l.c.Create(pth, []byte{}, 0, l.acl)
|
||||
if err != nil && err != ErrNodeExists {
|
||||
return err
|
||||
}
|
||||
|
@ -86,7 +94,7 @@ func (l *Lock) Lock() error {
|
|||
}
|
||||
|
||||
lowestSeq := seq
|
||||
prevSeq := 0
|
||||
prevSeq := -1
|
||||
prevSeqPath := ""
|
||||
for _, p := range children {
|
||||
s, err := parseSeq(p)
|
||||
|
|
121
vendor/github.com/samuel/go-zookeeper/zk/server_help.go
generated
vendored
121
vendor/github.com/samuel/go-zookeeper/zk/server_help.go
generated
vendored
|
@ -7,9 +7,14 @@ import (
|
|||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
type TestServer struct {
|
||||
Port int
|
||||
Path string
|
||||
|
@ -87,33 +92,125 @@ func StartTestCluster(size int, stdout, stderr io.Writer) (*TestCluster, error)
|
|||
Srv: srv,
|
||||
})
|
||||
}
|
||||
if err := cluster.waitForStart(10, time.Second); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
success = true
|
||||
time.Sleep(time.Second) // Give the server time to become active. Should probably actually attempt to connect to verify.
|
||||
return cluster, nil
|
||||
}
|
||||
|
||||
func (ts *TestCluster) Connect(idx int) (*Conn, error) {
|
||||
zk, _, err := Connect([]string{fmt.Sprintf("127.0.0.1:%d", ts.Servers[idx].Port)}, time.Second*15)
|
||||
func (tc *TestCluster) Connect(idx int) (*Conn, error) {
|
||||
zk, _, err := Connect([]string{fmt.Sprintf("127.0.0.1:%d", tc.Servers[idx].Port)}, time.Second*15)
|
||||
return zk, err
|
||||
}
|
||||
|
||||
func (ts *TestCluster) ConnectAll() (*Conn, <-chan Event, error) {
|
||||
return ts.ConnectAllTimeout(time.Second * 15)
|
||||
func (tc *TestCluster) ConnectAll() (*Conn, <-chan Event, error) {
|
||||
return tc.ConnectAllTimeout(time.Second * 15)
|
||||
}
|
||||
|
||||
func (ts *TestCluster) ConnectAllTimeout(sessionTimeout time.Duration) (*Conn, <-chan Event, error) {
|
||||
hosts := make([]string, len(ts.Servers))
|
||||
for i, srv := range ts.Servers {
|
||||
func (tc *TestCluster) ConnectAllTimeout(sessionTimeout time.Duration) (*Conn, <-chan Event, error) {
|
||||
return tc.ConnectWithOptions(sessionTimeout)
|
||||
}
|
||||
|
||||
func (tc *TestCluster) ConnectWithOptions(sessionTimeout time.Duration, options ...connOption) (*Conn, <-chan Event, error) {
|
||||
hosts := make([]string, len(tc.Servers))
|
||||
for i, srv := range tc.Servers {
|
||||
hosts[i] = fmt.Sprintf("127.0.0.1:%d", srv.Port)
|
||||
}
|
||||
zk, ch, err := Connect(hosts, sessionTimeout)
|
||||
zk, ch, err := Connect(hosts, sessionTimeout, options...)
|
||||
return zk, ch, err
|
||||
}
|
||||
|
||||
func (ts *TestCluster) Stop() error {
|
||||
for _, srv := range ts.Servers {
|
||||
func (tc *TestCluster) Stop() error {
|
||||
for _, srv := range tc.Servers {
|
||||
srv.Srv.Stop()
|
||||
}
|
||||
defer os.RemoveAll(ts.Path)
|
||||
defer os.RemoveAll(tc.Path)
|
||||
return tc.waitForStop(5, time.Second)
|
||||
}
|
||||
|
||||
// waitForStart blocks until the cluster is up
|
||||
func (tc *TestCluster) waitForStart(maxRetry int, interval time.Duration) error {
|
||||
// verify that the servers are up with SRVR
|
||||
serverAddrs := make([]string, len(tc.Servers))
|
||||
for i, s := range tc.Servers {
|
||||
serverAddrs[i] = fmt.Sprintf("127.0.0.1:%d", s.Port)
|
||||
}
|
||||
|
||||
for i := 0; i < maxRetry; i++ {
|
||||
_, ok := FLWSrvr(serverAddrs, time.Second)
|
||||
if ok {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(interval)
|
||||
}
|
||||
return fmt.Errorf("unable to verify health of servers")
|
||||
}
|
||||
|
||||
// waitForStop blocks until the cluster is down
|
||||
func (tc *TestCluster) waitForStop(maxRetry int, interval time.Duration) error {
|
||||
// verify that the servers are up with RUOK
|
||||
serverAddrs := make([]string, len(tc.Servers))
|
||||
for i, s := range tc.Servers {
|
||||
serverAddrs[i] = fmt.Sprintf("127.0.0.1:%d", s.Port)
|
||||
}
|
||||
|
||||
var success bool
|
||||
for i := 0; i < maxRetry && !success; i++ {
|
||||
success = true
|
||||
for _, ok := range FLWRuok(serverAddrs, time.Second) {
|
||||
if ok {
|
||||
success = false
|
||||
}
|
||||
}
|
||||
if !success {
|
||||
time.Sleep(interval)
|
||||
}
|
||||
}
|
||||
if !success {
|
||||
return fmt.Errorf("unable to verify servers are down")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tc *TestCluster) StartServer(server string) {
|
||||
for _, s := range tc.Servers {
|
||||
if strings.HasSuffix(server, fmt.Sprintf(":%d", s.Port)) {
|
||||
s.Srv.Start()
|
||||
return
|
||||
}
|
||||
}
|
||||
panic(fmt.Sprintf("Unknown server: %s", server))
|
||||
}
|
||||
|
||||
func (tc *TestCluster) StopServer(server string) {
|
||||
for _, s := range tc.Servers {
|
||||
if strings.HasSuffix(server, fmt.Sprintf(":%d", s.Port)) {
|
||||
s.Srv.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
panic(fmt.Sprintf("Unknown server: %s", server))
|
||||
}
|
||||
|
||||
func (tc *TestCluster) StartAllServers() error {
|
||||
for _, s := range tc.Servers {
|
||||
if err := s.Srv.Start(); err != nil {
|
||||
return fmt.Errorf(
|
||||
"Failed to start server listening on port `%d` : %+v", s.Port, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tc *TestCluster) StopAllServers() error {
|
||||
for _, s := range tc.Servers {
|
||||
if err := s.Srv.Stop(); err != nil {
|
||||
return fmt.Errorf(
|
||||
"Failed to stop server listening on port `%d` : %+v", s.Port, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
51
vendor/github.com/samuel/go-zookeeper/zk/structs.go
generated
vendored
51
vendor/github.com/samuel/go-zookeeper/zk/structs.go
generated
vendored
|
@ -270,6 +270,7 @@ type multiResponseOp struct {
|
|||
Header multiHeader
|
||||
String string
|
||||
Stat *Stat
|
||||
Err ErrCode
|
||||
}
|
||||
type multiResponse struct {
|
||||
Ops []multiResponseOp
|
||||
|
@ -327,6 +328,8 @@ func (r *multiRequest) Decode(buf []byte) (int, error) {
|
|||
}
|
||||
|
||||
func (r *multiResponse) Decode(buf []byte) (int, error) {
|
||||
var multiErr error
|
||||
|
||||
r.Ops = make([]multiResponseOp, 0)
|
||||
r.DoneHeader = multiHeader{-1, true, -1}
|
||||
total := 0
|
||||
|
@ -347,6 +350,8 @@ func (r *multiResponse) Decode(buf []byte) (int, error) {
|
|||
switch header.Type {
|
||||
default:
|
||||
return total, ErrAPIError
|
||||
case opError:
|
||||
w = reflect.ValueOf(&res.Err)
|
||||
case opCreate:
|
||||
w = reflect.ValueOf(&res.String)
|
||||
case opSetData:
|
||||
|
@ -362,8 +367,12 @@ func (r *multiResponse) Decode(buf []byte) (int, error) {
|
|||
total += n
|
||||
}
|
||||
r.Ops = append(r.Ops, res)
|
||||
if multiErr == nil && res.Err != errOk {
|
||||
// Use the first error as the error returned from Multi().
|
||||
multiErr = res.Err.toError()
|
||||
}
|
||||
}
|
||||
return total, nil
|
||||
return total, multiErr
|
||||
}
|
||||
|
||||
type watcherEvent struct {
|
||||
|
@ -598,43 +607,3 @@ func requestStructForOp(op int32) interface{} {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func responseStructForOp(op int32) interface{} {
|
||||
switch op {
|
||||
case opClose:
|
||||
return &closeResponse{}
|
||||
case opCreate:
|
||||
return &createResponse{}
|
||||
case opDelete:
|
||||
return &deleteResponse{}
|
||||
case opExists:
|
||||
return &existsResponse{}
|
||||
case opGetAcl:
|
||||
return &getAclResponse{}
|
||||
case opGetChildren:
|
||||
return &getChildrenResponse{}
|
||||
case opGetChildren2:
|
||||
return &getChildren2Response{}
|
||||
case opGetData:
|
||||
return &getDataResponse{}
|
||||
case opPing:
|
||||
return &pingResponse{}
|
||||
case opSetAcl:
|
||||
return &setAclResponse{}
|
||||
case opSetData:
|
||||
return &setDataResponse{}
|
||||
case opSetWatches:
|
||||
return &setWatchesResponse{}
|
||||
case opSync:
|
||||
return &syncResponse{}
|
||||
case opWatcherEvent:
|
||||
return &watcherEvent{}
|
||||
case opSetAuth:
|
||||
return &setAuthResponse{}
|
||||
// case opCheck:
|
||||
// return &checkVersionResponse{}
|
||||
case opMulti:
|
||||
return &multiResponse{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
148
vendor/github.com/samuel/go-zookeeper/zk/tracer.go
generated
vendored
148
vendor/github.com/samuel/go-zookeeper/zk/tracer.go
generated
vendored
|
@ -1,148 +0,0 @@
|
|||
package zk
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
requests = make(map[int32]int32) // Map of Xid -> Opcode
|
||||
requestsLock = &sync.Mutex{}
|
||||
)
|
||||
|
||||
func trace(conn1, conn2 net.Conn, client bool) {
|
||||
defer conn1.Close()
|
||||
defer conn2.Close()
|
||||
buf := make([]byte, 10*1024)
|
||||
init := true
|
||||
for {
|
||||
_, err := io.ReadFull(conn1, buf[:4])
|
||||
if err != nil {
|
||||
fmt.Println("1>", client, err)
|
||||
return
|
||||
}
|
||||
|
||||
blen := int(binary.BigEndian.Uint32(buf[:4]))
|
||||
|
||||
_, err = io.ReadFull(conn1, buf[4:4+blen])
|
||||
if err != nil {
|
||||
fmt.Println("2>", client, err)
|
||||
return
|
||||
}
|
||||
|
||||
var cr interface{}
|
||||
opcode := int32(-1)
|
||||
readHeader := true
|
||||
if client {
|
||||
if init {
|
||||
cr = &connectRequest{}
|
||||
readHeader = false
|
||||
} else {
|
||||
xid := int32(binary.BigEndian.Uint32(buf[4:8]))
|
||||
opcode = int32(binary.BigEndian.Uint32(buf[8:12]))
|
||||
requestsLock.Lock()
|
||||
requests[xid] = opcode
|
||||
requestsLock.Unlock()
|
||||
cr = requestStructForOp(opcode)
|
||||
if cr == nil {
|
||||
fmt.Printf("Unknown opcode %d\n", opcode)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if init {
|
||||
cr = &connectResponse{}
|
||||
readHeader = false
|
||||
} else {
|
||||
xid := int32(binary.BigEndian.Uint32(buf[4:8]))
|
||||
zxid := int64(binary.BigEndian.Uint64(buf[8:16]))
|
||||
errnum := int32(binary.BigEndian.Uint32(buf[16:20]))
|
||||
if xid != -1 || zxid != -1 {
|
||||
requestsLock.Lock()
|
||||
found := false
|
||||
opcode, found = requests[xid]
|
||||
if !found {
|
||||
opcode = 0
|
||||
}
|
||||
delete(requests, xid)
|
||||
requestsLock.Unlock()
|
||||
} else {
|
||||
opcode = opWatcherEvent
|
||||
}
|
||||
cr = responseStructForOp(opcode)
|
||||
if cr == nil {
|
||||
fmt.Printf("Unknown opcode %d\n", opcode)
|
||||
}
|
||||
if errnum != 0 {
|
||||
cr = &struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
opname := "."
|
||||
if opcode != -1 {
|
||||
opname = opNames[opcode]
|
||||
}
|
||||
if cr == nil {
|
||||
fmt.Printf("%+v %s %+v\n", client, opname, buf[4:4+blen])
|
||||
} else {
|
||||
n := 4
|
||||
hdrStr := ""
|
||||
if readHeader {
|
||||
var hdr interface{}
|
||||
if client {
|
||||
hdr = &requestHeader{}
|
||||
} else {
|
||||
hdr = &responseHeader{}
|
||||
}
|
||||
if n2, err := decodePacket(buf[n:n+blen], hdr); err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
n += n2
|
||||
}
|
||||
hdrStr = fmt.Sprintf(" %+v", hdr)
|
||||
}
|
||||
if _, err := decodePacket(buf[n:n+blen], cr); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fmt.Printf("%+v %s%s %+v\n", client, opname, hdrStr, cr)
|
||||
}
|
||||
|
||||
init = false
|
||||
|
||||
written, err := conn2.Write(buf[:4+blen])
|
||||
if err != nil {
|
||||
fmt.Println("3>", client, err)
|
||||
return
|
||||
} else if written != 4+blen {
|
||||
fmt.Printf("Written != read: %d != %d\n", written, blen)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleConnection(addr string, conn net.Conn) {
|
||||
zkConn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
go trace(conn, zkConn, true)
|
||||
trace(zkConn, conn, false)
|
||||
}
|
||||
|
||||
func StartTracer(listenAddr, serverAddr string) {
|
||||
ln, err := net.Listen("tcp", listenAddr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
continue
|
||||
}
|
||||
go handleConnection(serverAddr, conn)
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue