mirror of https://github.com/hashicorp/consul
Vendoring update for go-discover. (#4412)
* New Providers added and updated vendoring for go-discover * Vendor.json formatted using make vendorfmt * Docs/Agent/auto-join: Added documentation for the new providers introduced in this PR * Updated the golang.org/x/sys/unix in the vendor directory * Agent: TestGoDiscoverRegistration updated to reflect the addition of new providers * Deleted terraform.tfstate from vendor. * Deleted terraform.tfstate.backup Deleted terraform state file artifacts from unknown runs. * Updated x/sys/windows vendor for Windows binary compilationpull/4469/head
parent
3959a91e43
commit
f4a1c381a5
|
@ -13,7 +13,7 @@ func TestGoDiscoverRegistration(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
got := d.Names()
|
||||
want := []string{"aliyun", "aws", "azure", "digitalocean", "gce", "os", "scaleway", "softlayer", "triton"}
|
||||
want := []string{"aliyun", "aws", "azure", "digitalocean", "gce", "os", "packet", "scaleway", "softlayer", "triton", "vsphere"}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("got go-discover providers %v want %v", got, want)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
# 1.0.5
|
||||
|
||||
* Fix hooks race (#707)
|
||||
* Fix panic deadlock (#695)
|
||||
|
||||
# 1.0.4
|
||||
|
||||
* Fix race when adding hooks (#612)
|
||||
* Fix terminal check in AppEngine (#635)
|
||||
|
||||
# 1.0.3
|
||||
|
||||
* Replace example files with testable examples
|
||||
|
||||
# 1.0.2
|
||||
|
||||
* bug: quote non-string values in text formatter (#583)
|
||||
* Make (*Logger) SetLevel a public method
|
||||
|
||||
# 1.0.1
|
||||
|
||||
* bug: fix escaping in text formatter (#575)
|
||||
|
||||
# 1.0.0
|
||||
|
||||
* Officially changed name to lower-case
|
||||
* bug: colors on Windows 10 (#541)
|
||||
* bug: fix race in accessing level (#512)
|
||||
|
||||
# 0.11.5
|
||||
|
||||
* feature: add writer and writerlevel to entry (#372)
|
||||
|
||||
# 0.11.4
|
||||
|
||||
* bug: fix undefined variable on solaris (#493)
|
||||
|
||||
# 0.11.3
|
||||
|
||||
* formatter: configure quoting of empty values (#484)
|
||||
* formatter: configure quoting character (default is `"`) (#484)
|
||||
* bug: fix not importing io correctly in non-linux environments (#481)
|
||||
|
||||
# 0.11.2
|
||||
|
||||
* bug: fix windows terminal detection (#476)
|
||||
|
||||
# 0.11.1
|
||||
|
||||
* bug: fix tty detection with custom out (#471)
|
||||
|
||||
# 0.11.0
|
||||
|
||||
* performance: Use bufferpool to allocate (#370)
|
||||
* terminal: terminal detection for app-engine (#343)
|
||||
* feature: exit handler (#375)
|
||||
|
||||
# 0.10.0
|
||||
|
||||
* feature: Add a test hook (#180)
|
||||
* feature: `ParseLevel` is now case-insensitive (#326)
|
||||
* feature: `FieldLogger` interface that generalizes `Logger` and `Entry` (#308)
|
||||
* performance: avoid re-allocations on `WithFields` (#335)
|
||||
|
||||
# 0.9.0
|
||||
|
||||
* logrus/text_formatter: don't emit empty msg
|
||||
* logrus/hooks/airbrake: move out of main repository
|
||||
* logrus/hooks/sentry: move out of main repository
|
||||
* logrus/hooks/papertrail: move out of main repository
|
||||
* logrus/hooks/bugsnag: move out of main repository
|
||||
* logrus/core: run tests with `-race`
|
||||
* logrus/core: detect TTY based on `stderr`
|
||||
* logrus/core: support `WithError` on logger
|
||||
* logrus/core: Solaris support
|
||||
|
||||
# 0.8.7
|
||||
|
||||
* logrus/core: fix possible race (#216)
|
||||
* logrus/doc: small typo fixes and doc improvements
|
||||
|
||||
|
||||
# 0.8.6
|
||||
|
||||
* hooks/raven: allow passing an initialized client
|
||||
|
||||
# 0.8.5
|
||||
|
||||
* logrus/core: revert #208
|
||||
|
||||
# 0.8.4
|
||||
|
||||
* formatter/text: fix data race (#218)
|
||||
|
||||
# 0.8.3
|
||||
|
||||
* logrus/core: fix entry log level (#208)
|
||||
* logrus/core: improve performance of text formatter by 40%
|
||||
* logrus/core: expose `LevelHooks` type
|
||||
* logrus/core: add support for DragonflyBSD and NetBSD
|
||||
* formatter/text: print structs more verbosely
|
||||
|
||||
# 0.8.2
|
||||
|
||||
* logrus: fix more Fatal family functions
|
||||
|
||||
# 0.8.1
|
||||
|
||||
* logrus: fix not exiting on `Fatalf` and `Fatalln`
|
||||
|
||||
# 0.8.0
|
||||
|
||||
* logrus: defaults to stderr instead of stdout
|
||||
* hooks/sentry: add special field for `*http.Request`
|
||||
* formatter/text: ignore Windows for colors
|
||||
|
||||
# 0.7.3
|
||||
|
||||
* formatter/\*: allow configuration of timestamp layout
|
||||
|
||||
# 0.7.2
|
||||
|
||||
* formatter/text: Add configuration option for time format (#158)
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Simon Eskildsen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,461 @@
|
|||
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [![Build Status](https://travis-ci.org/sirupsen/logrus.svg?branch=master)](https://travis-ci.org/sirupsen/logrus) [![GoDoc](https://godoc.org/github.com/sirupsen/logrus?status.svg)](https://godoc.org/github.com/sirupsen/logrus)
|
||||
|
||||
Logrus is a structured logger for Go (golang), completely API compatible with
|
||||
the standard library logger.
|
||||
|
||||
**Seeing weird case-sensitive problems?** It's in the past been possible to
|
||||
import Logrus as both upper- and lower-case. Due to the Go package environment,
|
||||
this caused issues in the community and we needed a standard. Some environments
|
||||
experienced problems with the upper-case variant, so the lower-case was decided.
|
||||
Everything using `logrus` will need to use the lower-case:
|
||||
`github.com/sirupsen/logrus`. Any package that isn't, should be changed.
|
||||
|
||||
To fix Glide, see [these
|
||||
comments](https://github.com/sirupsen/logrus/issues/553#issuecomment-306591437).
|
||||
For an in-depth explanation of the casing issue, see [this
|
||||
comment](https://github.com/sirupsen/logrus/issues/570#issuecomment-313933276).
|
||||
|
||||
**Are you interested in assisting in maintaining Logrus?** Currently I have a
|
||||
lot of obligations, and I am unable to provide Logrus with the maintainership it
|
||||
needs. If you'd like to help, please reach out to me at `simon at author's
|
||||
username dot com`.
|
||||
|
||||
Nicely color-coded in development (when a TTY is attached, otherwise just
|
||||
plain text):
|
||||
|
||||
![Colored](http://i.imgur.com/PY7qMwd.png)
|
||||
|
||||
With `log.SetFormatter(&log.JSONFormatter{})`, for easy parsing by logstash
|
||||
or Splunk:
|
||||
|
||||
```json
|
||||
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
|
||||
ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
|
||||
|
||||
{"level":"warning","msg":"The group's number increased tremendously!",
|
||||
"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
|
||||
|
||||
{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
|
||||
"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
|
||||
|
||||
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
|
||||
"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
|
||||
|
||||
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
|
||||
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
|
||||
```
|
||||
|
||||
With the default `log.SetFormatter(&log.TextFormatter{})` when a TTY is not
|
||||
attached, the output is compatible with the
|
||||
[logfmt](http://godoc.org/github.com/kr/logfmt) format:
|
||||
|
||||
```text
|
||||
time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
|
||||
time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
|
||||
time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true
|
||||
time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
|
||||
time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009
|
||||
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true
|
||||
exit status 1
|
||||
```
|
||||
|
||||
#### Case-sensitivity
|
||||
|
||||
The organization's name was changed to lower-case--and this will not be changed
|
||||
back. If you are getting import conflicts due to case sensitivity, please use
|
||||
the lower-case import: `github.com/sirupsen/logrus`.
|
||||
|
||||
#### Example
|
||||
|
||||
The simplest way to use Logrus is simply the package-level exported logger:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"animal": "walrus",
|
||||
}).Info("A walrus appears")
|
||||
}
|
||||
```
|
||||
|
||||
Note that it's completely api-compatible with the stdlib logger, so you can
|
||||
replace your `log` imports everywhere with `log "github.com/sirupsen/logrus"`
|
||||
and you'll now have the flexibility of Logrus. You can customize it all you
|
||||
want:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Log as JSON instead of the default ASCII formatter.
|
||||
log.SetFormatter(&log.JSONFormatter{})
|
||||
|
||||
// Output to stdout instead of the default stderr
|
||||
// Can be any io.Writer, see below for File example
|
||||
log.SetOutput(os.Stdout)
|
||||
|
||||
// Only log the warning severity or above.
|
||||
log.SetLevel(log.WarnLevel)
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"omg": true,
|
||||
"number": 122,
|
||||
}).Warn("The group's number increased tremendously!")
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"omg": true,
|
||||
"number": 100,
|
||||
}).Fatal("The ice breaks!")
|
||||
|
||||
// A common pattern is to re-use fields between logging statements by re-using
|
||||
// the logrus.Entry returned from WithFields()
|
||||
contextLogger := log.WithFields(log.Fields{
|
||||
"common": "this is a common field",
|
||||
"other": "I also should be logged always",
|
||||
})
|
||||
|
||||
contextLogger.Info("I'll be logged with common and other field")
|
||||
contextLogger.Info("Me too")
|
||||
}
|
||||
```
|
||||
|
||||
For more advanced usage such as logging to multiple locations from the same
|
||||
application, you can also create an instance of the `logrus` Logger:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Create a new instance of the logger. You can have any number of instances.
|
||||
var log = logrus.New()
|
||||
|
||||
func main() {
|
||||
// The API for setting attributes is a little different than the package level
|
||||
// exported logger. See Godoc.
|
||||
log.Out = os.Stdout
|
||||
|
||||
// You could set this to any `io.Writer` such as a file
|
||||
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY, 0666)
|
||||
// if err == nil {
|
||||
// log.Out = file
|
||||
// } else {
|
||||
// log.Info("Failed to log to file, using default stderr")
|
||||
// }
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
}
|
||||
```
|
||||
|
||||
#### Fields
|
||||
|
||||
Logrus encourages careful, structured logging through logging fields instead of
|
||||
long, unparseable error messages. For example, instead of: `log.Fatalf("Failed
|
||||
to send event %s to topic %s with key %d")`, you should log the much more
|
||||
discoverable:
|
||||
|
||||
```go
|
||||
log.WithFields(log.Fields{
|
||||
"event": event,
|
||||
"topic": topic,
|
||||
"key": key,
|
||||
}).Fatal("Failed to send event")
|
||||
```
|
||||
|
||||
We've found this API forces you to think about logging in a way that produces
|
||||
much more useful logging messages. We've been in countless situations where just
|
||||
a single added field to a log statement that was already there would've saved us
|
||||
hours. The `WithFields` call is optional.
|
||||
|
||||
In general, with Logrus using any of the `printf`-family functions should be
|
||||
seen as a hint you should add a field, however, you can still use the
|
||||
`printf`-family functions with Logrus.
|
||||
|
||||
#### Default Fields
|
||||
|
||||
Often it's helpful to have fields _always_ attached to log statements in an
|
||||
application or parts of one. For example, you may want to always log the
|
||||
`request_id` and `user_ip` in the context of a request. Instead of writing
|
||||
`log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})` on
|
||||
every line, you can create a `logrus.Entry` to pass around instead:
|
||||
|
||||
```go
|
||||
requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
|
||||
requestLogger.Info("something happened on that request") # will log request_id and user_ip
|
||||
requestLogger.Warn("something not great happened")
|
||||
```
|
||||
|
||||
#### Hooks
|
||||
|
||||
You can add hooks for logging levels. For example to send errors to an exception
|
||||
tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to
|
||||
multiple places simultaneously, e.g. syslog.
|
||||
|
||||
Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in
|
||||
`init`:
|
||||
|
||||
```go
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "airbrake"
|
||||
logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
|
||||
"log/syslog"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
// Use the Airbrake hook to report errors that have Error severity or above to
|
||||
// an exception tracker. You can create custom hooks, see the Hooks section.
|
||||
log.AddHook(airbrake.NewHook(123, "xyz", "production"))
|
||||
|
||||
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
||||
if err != nil {
|
||||
log.Error("Unable to connect to local syslog daemon")
|
||||
} else {
|
||||
log.AddHook(hook)
|
||||
}
|
||||
}
|
||||
```
|
||||
Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md).
|
||||
|
||||
A list of currently known of service hook can be found in this wiki [page](https://github.com/sirupsen/logrus/wiki/Hooks)
|
||||
|
||||
|
||||
#### Level logging
|
||||
|
||||
Logrus has six logging levels: Debug, Info, Warning, Error, Fatal and Panic.
|
||||
|
||||
```go
|
||||
log.Debug("Useful debugging information.")
|
||||
log.Info("Something noteworthy happened!")
|
||||
log.Warn("You should probably take a look at this.")
|
||||
log.Error("Something failed but I'm not quitting.")
|
||||
// Calls os.Exit(1) after logging
|
||||
log.Fatal("Bye.")
|
||||
// Calls panic() after logging
|
||||
log.Panic("I'm bailing.")
|
||||
```
|
||||
|
||||
You can set the logging level on a `Logger`, then it will only log entries with
|
||||
that severity or anything above it:
|
||||
|
||||
```go
|
||||
// Will log anything that is info or above (warn, error, fatal, panic). Default.
|
||||
log.SetLevel(log.InfoLevel)
|
||||
```
|
||||
|
||||
It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose
|
||||
environment if your application has that.
|
||||
|
||||
#### Entries
|
||||
|
||||
Besides the fields added with `WithField` or `WithFields` some fields are
|
||||
automatically added to all logging events:
|
||||
|
||||
1. `time`. The timestamp when the entry was created.
|
||||
2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after
|
||||
the `AddFields` call. E.g. `Failed to send event.`
|
||||
3. `level`. The logging level. E.g. `info`.
|
||||
|
||||
#### Environments
|
||||
|
||||
Logrus has no notion of environment.
|
||||
|
||||
If you wish for hooks and formatters to only be used in specific environments,
|
||||
you should handle that yourself. For example, if your application has a global
|
||||
variable `Environment`, which is a string representation of the environment you
|
||||
could do:
|
||||
|
||||
```go
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
init() {
|
||||
// do something here to set environment depending on an environment variable
|
||||
// or command-line flag
|
||||
if Environment == "production" {
|
||||
log.SetFormatter(&log.JSONFormatter{})
|
||||
} else {
|
||||
// The TextFormatter is default, you don't actually have to do this.
|
||||
log.SetFormatter(&log.TextFormatter{})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This configuration is how `logrus` was intended to be used, but JSON in
|
||||
production is mostly only useful if you do log aggregation with tools like
|
||||
Splunk or Logstash.
|
||||
|
||||
#### Formatters
|
||||
|
||||
The built-in logging formatters are:
|
||||
|
||||
* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise
|
||||
without colors.
|
||||
* *Note:* to force colored output when there is no TTY, set the `ForceColors`
|
||||
field to `true`. To force no colored output even if there is a TTY set the
|
||||
`DisableColors` field to `true`. For Windows, see
|
||||
[github.com/mattn/go-colorable](https://github.com/mattn/go-colorable).
|
||||
* When colors are enabled, levels are truncated to 4 characters by default. To disable
|
||||
truncation set the `DisableLevelTruncation` field to `true`.
|
||||
* All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#TextFormatter).
|
||||
* `logrus.JSONFormatter`. Logs fields as JSON.
|
||||
* All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#JSONFormatter).
|
||||
|
||||
Third party logging formatters:
|
||||
|
||||
* [`FluentdFormatter`](https://github.com/joonix/log). Formats entries that can be parsed by Kubernetes and Google Container Engine.
|
||||
* [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events.
|
||||
* [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout.
|
||||
* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
|
||||
|
||||
You can define your formatter by implementing the `Formatter` interface,
|
||||
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
|
||||
`Fields` type (`map[string]interface{}`) with all your fields as well as the
|
||||
default ones (see Entries section above):
|
||||
|
||||
```go
|
||||
type MyJSONFormatter struct {
|
||||
}
|
||||
|
||||
log.SetFormatter(new(MyJSONFormatter))
|
||||
|
||||
func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
// Note this doesn't include Time, Level and Message which are available on
|
||||
// the Entry. Consult `godoc` on information about those fields or read the
|
||||
// source of the official loggers.
|
||||
serialized, err := json.Marshal(entry.Data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
||||
}
|
||||
return append(serialized, '\n'), nil
|
||||
}
|
||||
```
|
||||
|
||||
#### Logger as an `io.Writer`
|
||||
|
||||
Logrus can be transformed into an `io.Writer`. That writer is the end of an `io.Pipe` and it is your responsibility to close it.
|
||||
|
||||
```go
|
||||
w := logger.Writer()
|
||||
defer w.Close()
|
||||
|
||||
srv := http.Server{
|
||||
// create a stdlib log.Logger that writes to
|
||||
// logrus.Logger.
|
||||
ErrorLog: log.New(w, "", 0),
|
||||
}
|
||||
```
|
||||
|
||||
Each line written to that writer will be printed the usual way, using formatters
|
||||
and hooks. The level for those entries is `info`.
|
||||
|
||||
This means that we can override the standard library logger easily:
|
||||
|
||||
```go
|
||||
logger := logrus.New()
|
||||
logger.Formatter = &logrus.JSONFormatter{}
|
||||
|
||||
// Use logrus for standard log output
|
||||
// Note that `log` here references stdlib's log
|
||||
// Not logrus imported under the name `log`.
|
||||
log.SetOutput(logger.Writer())
|
||||
```
|
||||
|
||||
#### Rotation
|
||||
|
||||
Log rotation is not provided with Logrus. Log rotation should be done by an
|
||||
external program (like `logrotate(8)`) that can compress and delete old log
|
||||
entries. It should not be a feature of the application-level logger.
|
||||
|
||||
#### Tools
|
||||
|
||||
| Tool | Description |
|
||||
| ---- | ----------- |
|
||||
|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will generated with different config at different environment.|
|
||||
|[Logrus Viper Helper](https://github.com/heirko/go-contrib/tree/master/logrusHelper)|An Helper around Logrus to wrap with spf13/Viper to load configuration with fangs! And to simplify Logrus configuration use some behavior of [Logrus Mate](https://github.com/gogap/logrus_mate). [sample](https://github.com/heirko/iris-contrib/blob/master/middleware/logrus-logger/example) |
|
||||
|
||||
#### Testing
|
||||
|
||||
Logrus has a built in facility for asserting the presence of log messages. This is implemented through the `test` hook and provides:
|
||||
|
||||
* decorators for existing logger (`test.NewLocal` and `test.NewGlobal`) which basically just add the `test` hook
|
||||
* a test logger (`test.NewNullLogger`) that just records log messages (and does not output any):
|
||||
|
||||
```go
|
||||
import(
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/sirupsen/logrus/hooks/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSomething(t*testing.T){
|
||||
logger, hook := test.NewNullLogger()
|
||||
logger.Error("Helloerror")
|
||||
|
||||
assert.Equal(t, 1, len(hook.Entries))
|
||||
assert.Equal(t, logrus.ErrorLevel, hook.LastEntry().Level)
|
||||
assert.Equal(t, "Helloerror", hook.LastEntry().Message)
|
||||
|
||||
hook.Reset()
|
||||
assert.Nil(t, hook.LastEntry())
|
||||
}
|
||||
```
|
||||
|
||||
#### Fatal handlers
|
||||
|
||||
Logrus can register one or more functions that will be called when any `fatal`
|
||||
level message is logged. The registered handlers will be executed before
|
||||
logrus performs a `os.Exit(1)`. This behavior may be helpful if callers need
|
||||
to gracefully shutdown. Unlike a `panic("Something went wrong...")` call which can be intercepted with a deferred `recover` a call to `os.Exit(1)` can not be intercepted.
|
||||
|
||||
```
|
||||
...
|
||||
handler := func() {
|
||||
// gracefully shutdown something...
|
||||
}
|
||||
logrus.RegisterExitHandler(handler)
|
||||
...
|
||||
```
|
||||
|
||||
#### Thread safety
|
||||
|
||||
By default, Logger is protected by a mutex for concurrent writes. The mutex is held when calling hooks and writing logs.
|
||||
If you are sure such locking is not needed, you can call logger.SetNoLock() to disable the locking.
|
||||
|
||||
Situation when locking is not needed includes:
|
||||
|
||||
* You have no hooks registered, or hooks calling is already thread-safe.
|
||||
|
||||
* Writing to logger.Out is already thread-safe, for example:
|
||||
|
||||
1) logger.Out is protected by locks.
|
||||
|
||||
2) logger.Out is a os.File handler opened with `O_APPEND` flag, and every write is smaller than 4k. (This allow multi-thread/multi-process writing)
|
||||
|
||||
(Refer to http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/)
|
|
@ -0,0 +1,64 @@
|
|||
package logrus
|
||||
|
||||
// The following code was sourced and modified from the
|
||||
// https://github.com/tebeka/atexit package governed by the following license:
|
||||
//
|
||||
// Copyright (c) 2012 Miki Tebeka <miki.tebeka@gmail.com>.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
var handlers = []func(){}
|
||||
|
||||
func runHandler(handler func()) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error: Logrus exit handler error:", err)
|
||||
}
|
||||
}()
|
||||
|
||||
handler()
|
||||
}
|
||||
|
||||
func runHandlers() {
|
||||
for _, handler := range handlers {
|
||||
runHandler(handler)
|
||||
}
|
||||
}
|
||||
|
||||
// Exit runs all the Logrus atexit handlers and then terminates the program using os.Exit(code)
|
||||
func Exit(code int) {
|
||||
runHandlers()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
// RegisterExitHandler adds a Logrus Exit handler, call logrus.Exit to invoke
|
||||
// all handlers. The handlers will also be invoked when any Fatal log entry is
|
||||
// made.
|
||||
//
|
||||
// This method is useful when a caller wishes to use logrus to log a fatal
|
||||
// message but also needs to gracefully shutdown. An example usecase could be
|
||||
// closing database connections, or sending a alert that the application is
|
||||
// closing.
|
||||
func RegisterExitHandler(handler func()) {
|
||||
handlers = append(handlers, handler)
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
version: "{build}"
|
||||
platform: x64
|
||||
clone_folder: c:\gopath\src\github.com\sirupsen\logrus
|
||||
environment:
|
||||
GOPATH: c:\gopath
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
install:
|
||||
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
|
||||
- go version
|
||||
build_script:
|
||||
- go get -t
|
||||
- go test
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
Package logrus is a structured logger for Go, completely API compatible with the standard library logger.
|
||||
|
||||
|
||||
The simplest way to use Logrus is simply the package-level exported logger:
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"animal": "walrus",
|
||||
"number": 1,
|
||||
"size": 10,
|
||||
}).Info("A walrus appears")
|
||||
}
|
||||
|
||||
Output:
|
||||
time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10
|
||||
|
||||
For a full guide visit https://github.com/sirupsen/logrus
|
||||
*/
|
||||
package logrus
|
|
@ -0,0 +1,302 @@
|
|||
package logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var bufferPool *sync.Pool
|
||||
|
||||
func init() {
|
||||
bufferPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return new(bytes.Buffer)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Defines the key when adding errors using WithError.
|
||||
var ErrorKey = "error"
|
||||
|
||||
// An entry is the final or intermediate Logrus logging entry. It contains all
|
||||
// the fields passed with WithField{,s}. It's finally logged when Debug, Info,
|
||||
// Warn, Error, Fatal or Panic is called on it. These objects can be reused and
|
||||
// passed around as much as you wish to avoid field duplication.
|
||||
type Entry struct {
|
||||
Logger *Logger
|
||||
|
||||
// Contains all the fields set by the user.
|
||||
Data Fields
|
||||
|
||||
// Time at which the log entry was created
|
||||
Time time.Time
|
||||
|
||||
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
|
||||
// This field will be set on entry firing and the value will be equal to the one in Logger struct field.
|
||||
Level Level
|
||||
|
||||
// Message passed to Debug, Info, Warn, Error, Fatal or Panic
|
||||
Message string
|
||||
|
||||
// When formatter is called in entry.log(), an Buffer may be set to entry
|
||||
Buffer *bytes.Buffer
|
||||
}
|
||||
|
||||
func NewEntry(logger *Logger) *Entry {
|
||||
return &Entry{
|
||||
Logger: logger,
|
||||
// Default is five fields, give a little extra room
|
||||
Data: make(Fields, 5),
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the string representation from the reader and ultimately the
|
||||
// formatter.
|
||||
func (entry *Entry) String() (string, error) {
|
||||
serialized, err := entry.Logger.Formatter.Format(entry)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
str := string(serialized)
|
||||
return str, nil
|
||||
}
|
||||
|
||||
// Add an error as single field (using the key defined in ErrorKey) to the Entry.
|
||||
func (entry *Entry) WithError(err error) *Entry {
|
||||
return entry.WithField(ErrorKey, err)
|
||||
}
|
||||
|
||||
// Add a single field to the Entry.
|
||||
func (entry *Entry) WithField(key string, value interface{}) *Entry {
|
||||
return entry.WithFields(Fields{key: value})
|
||||
}
|
||||
|
||||
// Add a map of fields to the Entry.
|
||||
func (entry *Entry) WithFields(fields Fields) *Entry {
|
||||
data := make(Fields, len(entry.Data)+len(fields))
|
||||
for k, v := range entry.Data {
|
||||
data[k] = v
|
||||
}
|
||||
for k, v := range fields {
|
||||
data[k] = v
|
||||
}
|
||||
return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time}
|
||||
}
|
||||
|
||||
// Overrides the time of the Entry.
|
||||
func (entry *Entry) WithTime(t time.Time) *Entry {
|
||||
return &Entry{Logger: entry.Logger, Data: entry.Data, Time: t}
|
||||
}
|
||||
|
||||
// This function is not declared with a pointer value because otherwise
|
||||
// race conditions will occur when using multiple goroutines
|
||||
func (entry Entry) log(level Level, msg string) {
|
||||
var buffer *bytes.Buffer
|
||||
|
||||
// Default to now, but allow users to override if they want.
|
||||
//
|
||||
// We don't have to worry about polluting future calls to Entry#log()
|
||||
// with this assignment because this function is declared with a
|
||||
// non-pointer receiver.
|
||||
if entry.Time.IsZero() {
|
||||
entry.Time = time.Now()
|
||||
}
|
||||
|
||||
entry.Level = level
|
||||
entry.Message = msg
|
||||
|
||||
entry.fireHooks()
|
||||
|
||||
buffer = bufferPool.Get().(*bytes.Buffer)
|
||||
buffer.Reset()
|
||||
defer bufferPool.Put(buffer)
|
||||
entry.Buffer = buffer
|
||||
|
||||
entry.write()
|
||||
|
||||
entry.Buffer = nil
|
||||
|
||||
// To avoid Entry#log() returning a value that only would make sense for
|
||||
// panic() to use in Entry#Panic(), we avoid the allocation by checking
|
||||
// directly here.
|
||||
if level <= PanicLevel {
|
||||
panic(&entry)
|
||||
}
|
||||
}
|
||||
|
||||
// This function is not declared with a pointer value because otherwise
|
||||
// race conditions will occur when using multiple goroutines
|
||||
func (entry Entry) fireHooks() {
|
||||
entry.Logger.mu.Lock()
|
||||
defer entry.Logger.mu.Unlock()
|
||||
err := entry.Logger.Hooks.Fire(entry.Level, &entry)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) write() {
|
||||
serialized, err := entry.Logger.Formatter.Format(entry)
|
||||
entry.Logger.mu.Lock()
|
||||
defer entry.Logger.mu.Unlock()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
|
||||
} else {
|
||||
_, err = entry.Logger.Out.Write(serialized)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Debug(args ...interface{}) {
|
||||
if entry.Logger.level() >= DebugLevel {
|
||||
entry.log(DebugLevel, fmt.Sprint(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Print(args ...interface{}) {
|
||||
entry.Info(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Info(args ...interface{}) {
|
||||
if entry.Logger.level() >= InfoLevel {
|
||||
entry.log(InfoLevel, fmt.Sprint(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Warn(args ...interface{}) {
|
||||
if entry.Logger.level() >= WarnLevel {
|
||||
entry.log(WarnLevel, fmt.Sprint(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Warning(args ...interface{}) {
|
||||
entry.Warn(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Error(args ...interface{}) {
|
||||
if entry.Logger.level() >= ErrorLevel {
|
||||
entry.log(ErrorLevel, fmt.Sprint(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Fatal(args ...interface{}) {
|
||||
if entry.Logger.level() >= FatalLevel {
|
||||
entry.log(FatalLevel, fmt.Sprint(args...))
|
||||
}
|
||||
Exit(1)
|
||||
}
|
||||
|
||||
func (entry *Entry) Panic(args ...interface{}) {
|
||||
if entry.Logger.level() >= PanicLevel {
|
||||
entry.log(PanicLevel, fmt.Sprint(args...))
|
||||
}
|
||||
panic(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
// Entry Printf family functions
|
||||
|
||||
func (entry *Entry) Debugf(format string, args ...interface{}) {
|
||||
if entry.Logger.level() >= DebugLevel {
|
||||
entry.Debug(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Infof(format string, args ...interface{}) {
|
||||
if entry.Logger.level() >= InfoLevel {
|
||||
entry.Info(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Printf(format string, args ...interface{}) {
|
||||
entry.Infof(format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warnf(format string, args ...interface{}) {
|
||||
if entry.Logger.level() >= WarnLevel {
|
||||
entry.Warn(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Warningf(format string, args ...interface{}) {
|
||||
entry.Warnf(format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Errorf(format string, args ...interface{}) {
|
||||
if entry.Logger.level() >= ErrorLevel {
|
||||
entry.Error(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Fatalf(format string, args ...interface{}) {
|
||||
if entry.Logger.level() >= FatalLevel {
|
||||
entry.Fatal(fmt.Sprintf(format, args...))
|
||||
}
|
||||
Exit(1)
|
||||
}
|
||||
|
||||
func (entry *Entry) Panicf(format string, args ...interface{}) {
|
||||
if entry.Logger.level() >= PanicLevel {
|
||||
entry.Panic(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
// Entry Println family functions
|
||||
|
||||
func (entry *Entry) Debugln(args ...interface{}) {
|
||||
if entry.Logger.level() >= DebugLevel {
|
||||
entry.Debug(entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Infoln(args ...interface{}) {
|
||||
if entry.Logger.level() >= InfoLevel {
|
||||
entry.Info(entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Println(args ...interface{}) {
|
||||
entry.Infoln(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warnln(args ...interface{}) {
|
||||
if entry.Logger.level() >= WarnLevel {
|
||||
entry.Warn(entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Warningln(args ...interface{}) {
|
||||
entry.Warnln(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Errorln(args ...interface{}) {
|
||||
if entry.Logger.level() >= ErrorLevel {
|
||||
entry.Error(entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Fatalln(args ...interface{}) {
|
||||
if entry.Logger.level() >= FatalLevel {
|
||||
entry.Fatal(entry.sprintlnn(args...))
|
||||
}
|
||||
Exit(1)
|
||||
}
|
||||
|
||||
func (entry *Entry) Panicln(args ...interface{}) {
|
||||
if entry.Logger.level() >= PanicLevel {
|
||||
entry.Panic(entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
// Sprintlnn => Sprint no newline. This is to get the behavior of how
|
||||
// fmt.Sprintln where spaces are always added between operands, regardless of
|
||||
// their type. Instead of vendoring the Sprintln implementation to spare a
|
||||
// string allocation, we do the simplest thing.
|
||||
func (entry *Entry) sprintlnn(args ...interface{}) string {
|
||||
msg := fmt.Sprintln(args...)
|
||||
return msg[:len(msg)-1]
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
package logrus
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// std is the name of the standard logger in stdlib `log`
|
||||
std = New()
|
||||
)
|
||||
|
||||
func StandardLogger() *Logger {
|
||||
return std
|
||||
}
|
||||
|
||||
// SetOutput sets the standard logger output.
|
||||
func SetOutput(out io.Writer) {
|
||||
std.SetOutput(out)
|
||||
}
|
||||
|
||||
// SetFormatter sets the standard logger formatter.
|
||||
func SetFormatter(formatter Formatter) {
|
||||
std.mu.Lock()
|
||||
defer std.mu.Unlock()
|
||||
std.Formatter = formatter
|
||||
}
|
||||
|
||||
// SetLevel sets the standard logger level.
|
||||
func SetLevel(level Level) {
|
||||
std.mu.Lock()
|
||||
defer std.mu.Unlock()
|
||||
std.SetLevel(level)
|
||||
}
|
||||
|
||||
// GetLevel returns the standard logger level.
|
||||
func GetLevel() Level {
|
||||
std.mu.Lock()
|
||||
defer std.mu.Unlock()
|
||||
return std.level()
|
||||
}
|
||||
|
||||
// AddHook adds a hook to the standard logger hooks.
|
||||
func AddHook(hook Hook) {
|
||||
std.mu.Lock()
|
||||
defer std.mu.Unlock()
|
||||
std.Hooks.Add(hook)
|
||||
}
|
||||
|
||||
// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key.
|
||||
func WithError(err error) *Entry {
|
||||
return std.WithField(ErrorKey, err)
|
||||
}
|
||||
|
||||
// WithField creates an entry from the standard logger and adds a field to
|
||||
// it. If you want multiple fields, use `WithFields`.
|
||||
//
|
||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||
// or Panic on the Entry it returns.
|
||||
func WithField(key string, value interface{}) *Entry {
|
||||
return std.WithField(key, value)
|
||||
}
|
||||
|
||||
// WithFields creates an entry from the standard logger and adds multiple
|
||||
// fields to it. This is simply a helper for `WithField`, invoking it
|
||||
// once for each field.
|
||||
//
|
||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||
// or Panic on the Entry it returns.
|
||||
func WithFields(fields Fields) *Entry {
|
||||
return std.WithFields(fields)
|
||||
}
|
||||
|
||||
// WithTime creats an entry from the standard logger and overrides the time of
|
||||
// logs generated with it.
|
||||
//
|
||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||
// or Panic on the Entry it returns.
|
||||
func WithTime(t time.Time) *Entry {
|
||||
return std.WithTime(t)
|
||||
}
|
||||
|
||||
// Debug logs a message at level Debug on the standard logger.
|
||||
func Debug(args ...interface{}) {
|
||||
std.Debug(args...)
|
||||
}
|
||||
|
||||
// Print logs a message at level Info on the standard logger.
|
||||
func Print(args ...interface{}) {
|
||||
std.Print(args...)
|
||||
}
|
||||
|
||||
// Info logs a message at level Info on the standard logger.
|
||||
func Info(args ...interface{}) {
|
||||
std.Info(args...)
|
||||
}
|
||||
|
||||
// Warn logs a message at level Warn on the standard logger.
|
||||
func Warn(args ...interface{}) {
|
||||
std.Warn(args...)
|
||||
}
|
||||
|
||||
// Warning logs a message at level Warn on the standard logger.
|
||||
func Warning(args ...interface{}) {
|
||||
std.Warning(args...)
|
||||
}
|
||||
|
||||
// Error logs a message at level Error on the standard logger.
|
||||
func Error(args ...interface{}) {
|
||||
std.Error(args...)
|
||||
}
|
||||
|
||||
// Panic logs a message at level Panic on the standard logger.
|
||||
func Panic(args ...interface{}) {
|
||||
std.Panic(args...)
|
||||
}
|
||||
|
||||
// Fatal logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
||||
func Fatal(args ...interface{}) {
|
||||
std.Fatal(args...)
|
||||
}
|
||||
|
||||
// Debugf logs a message at level Debug on the standard logger.
|
||||
func Debugf(format string, args ...interface{}) {
|
||||
std.Debugf(format, args...)
|
||||
}
|
||||
|
||||
// Printf logs a message at level Info on the standard logger.
|
||||
func Printf(format string, args ...interface{}) {
|
||||
std.Printf(format, args...)
|
||||
}
|
||||
|
||||
// Infof logs a message at level Info on the standard logger.
|
||||
func Infof(format string, args ...interface{}) {
|
||||
std.Infof(format, args...)
|
||||
}
|
||||
|
||||
// Warnf logs a message at level Warn on the standard logger.
|
||||
func Warnf(format string, args ...interface{}) {
|
||||
std.Warnf(format, args...)
|
||||
}
|
||||
|
||||
// Warningf logs a message at level Warn on the standard logger.
|
||||
func Warningf(format string, args ...interface{}) {
|
||||
std.Warningf(format, args...)
|
||||
}
|
||||
|
||||
// Errorf logs a message at level Error on the standard logger.
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
std.Errorf(format, args...)
|
||||
}
|
||||
|
||||
// Panicf logs a message at level Panic on the standard logger.
|
||||
func Panicf(format string, args ...interface{}) {
|
||||
std.Panicf(format, args...)
|
||||
}
|
||||
|
||||
// Fatalf logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
||||
func Fatalf(format string, args ...interface{}) {
|
||||
std.Fatalf(format, args...)
|
||||
}
|
||||
|
||||
// Debugln logs a message at level Debug on the standard logger.
|
||||
func Debugln(args ...interface{}) {
|
||||
std.Debugln(args...)
|
||||
}
|
||||
|
||||
// Println logs a message at level Info on the standard logger.
|
||||
func Println(args ...interface{}) {
|
||||
std.Println(args...)
|
||||
}
|
||||
|
||||
// Infoln logs a message at level Info on the standard logger.
|
||||
func Infoln(args ...interface{}) {
|
||||
std.Infoln(args...)
|
||||
}
|
||||
|
||||
// Warnln logs a message at level Warn on the standard logger.
|
||||
func Warnln(args ...interface{}) {
|
||||
std.Warnln(args...)
|
||||
}
|
||||
|
||||
// Warningln logs a message at level Warn on the standard logger.
|
||||
func Warningln(args ...interface{}) {
|
||||
std.Warningln(args...)
|
||||
}
|
||||
|
||||
// Errorln logs a message at level Error on the standard logger.
|
||||
func Errorln(args ...interface{}) {
|
||||
std.Errorln(args...)
|
||||
}
|
||||
|
||||
// Panicln logs a message at level Panic on the standard logger.
|
||||
func Panicln(args ...interface{}) {
|
||||
std.Panicln(args...)
|
||||
}
|
||||
|
||||
// Fatalln logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
||||
func Fatalln(args ...interface{}) {
|
||||
std.Fatalln(args...)
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package logrus
|
||||
|
||||
import "time"
|
||||
|
||||
const defaultTimestampFormat = time.RFC3339
|
||||
|
||||
// The Formatter interface is used to implement a custom Formatter. It takes an
|
||||
// `Entry`. It exposes all the fields, including the default ones:
|
||||
//
|
||||
// * `entry.Data["msg"]`. The message passed from Info, Warn, Error ..
|
||||
// * `entry.Data["time"]`. The timestamp.
|
||||
// * `entry.Data["level"]. The level the entry was logged at.
|
||||
//
|
||||
// Any additional fields added with `WithField` or `WithFields` are also in
|
||||
// `entry.Data`. Format is expected to return an array of bytes which are then
|
||||
// logged to `logger.Out`.
|
||||
type Formatter interface {
|
||||
Format(*Entry) ([]byte, error)
|
||||
}
|
||||
|
||||
// This is to not silently overwrite `time`, `msg` and `level` fields when
|
||||
// dumping it. If this code wasn't there doing:
|
||||
//
|
||||
// logrus.WithField("level", 1).Info("hello")
|
||||
//
|
||||
// Would just silently drop the user provided level. Instead with this code
|
||||
// it'll logged as:
|
||||
//
|
||||
// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
|
||||
//
|
||||
// It's not exported because it's still using Data in an opinionated way. It's to
|
||||
// avoid code duplication between the two default formatters.
|
||||
func prefixFieldClashes(data Fields, fieldMap FieldMap) {
|
||||
timeKey := fieldMap.resolve(FieldKeyTime)
|
||||
if t, ok := data[timeKey]; ok {
|
||||
data["fields."+timeKey] = t
|
||||
delete(data, timeKey)
|
||||
}
|
||||
|
||||
msgKey := fieldMap.resolve(FieldKeyMsg)
|
||||
if m, ok := data[msgKey]; ok {
|
||||
data["fields."+msgKey] = m
|
||||
delete(data, msgKey)
|
||||
}
|
||||
|
||||
levelKey := fieldMap.resolve(FieldKeyLevel)
|
||||
if l, ok := data[levelKey]; ok {
|
||||
data["fields."+levelKey] = l
|
||||
delete(data, levelKey)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package logrus
|
||||
|
||||
// A hook to be fired when logging on the logging levels returned from
|
||||
// `Levels()` on your implementation of the interface. Note that this is not
|
||||
// fired in a goroutine or a channel with workers, you should handle such
|
||||
// functionality yourself if your call is non-blocking and you don't wish for
|
||||
// the logging calls for levels returned from `Levels()` to block.
|
||||
type Hook interface {
|
||||
Levels() []Level
|
||||
Fire(*Entry) error
|
||||
}
|
||||
|
||||
// Internal type for storing the hooks on a logger instance.
|
||||
type LevelHooks map[Level][]Hook
|
||||
|
||||
// Add a hook to an instance of logger. This is called with
|
||||
// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface.
|
||||
func (hooks LevelHooks) Add(hook Hook) {
|
||||
for _, level := range hook.Levels() {
|
||||
hooks[level] = append(hooks[level], hook)
|
||||
}
|
||||
}
|
||||
|
||||
// Fire all the hooks for the passed level. Used by `entry.log` to fire
|
||||
// appropriate hooks for a log entry.
|
||||
func (hooks LevelHooks) Fire(level Level, entry *Entry) error {
|
||||
for _, hook := range hooks[level] {
|
||||
if err := hook.Fire(entry); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package logrus
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type fieldKey string
|
||||
|
||||
// FieldMap allows customization of the key names for default fields.
|
||||
type FieldMap map[fieldKey]string
|
||||
|
||||
// Default key names for the default fields
|
||||
const (
|
||||
FieldKeyMsg = "msg"
|
||||
FieldKeyLevel = "level"
|
||||
FieldKeyTime = "time"
|
||||
)
|
||||
|
||||
func (f FieldMap) resolve(key fieldKey) string {
|
||||
if k, ok := f[key]; ok {
|
||||
return k
|
||||
}
|
||||
|
||||
return string(key)
|
||||
}
|
||||
|
||||
// JSONFormatter formats logs into parsable json
|
||||
type JSONFormatter struct {
|
||||
// TimestampFormat sets the format used for marshaling timestamps.
|
||||
TimestampFormat string
|
||||
|
||||
// DisableTimestamp allows disabling automatic timestamps in output
|
||||
DisableTimestamp bool
|
||||
|
||||
// DataKey allows users to put all the log entry parameters into a nested dictionary at a given key.
|
||||
DataKey string
|
||||
|
||||
// FieldMap allows users to customize the names of keys for default fields.
|
||||
// As an example:
|
||||
// formatter := &JSONFormatter{
|
||||
// FieldMap: FieldMap{
|
||||
// FieldKeyTime: "@timestamp",
|
||||
// FieldKeyLevel: "@level",
|
||||
// FieldKeyMsg: "@message",
|
||||
// },
|
||||
// }
|
||||
FieldMap FieldMap
|
||||
}
|
||||
|
||||
// Format renders a single log entry
|
||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
data := make(Fields, len(entry.Data)+3)
|
||||
for k, v := range entry.Data {
|
||||
switch v := v.(type) {
|
||||
case error:
|
||||
// Otherwise errors are ignored by `encoding/json`
|
||||
// https://github.com/sirupsen/logrus/issues/137
|
||||
data[k] = v.Error()
|
||||
default:
|
||||
data[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if f.DataKey != "" {
|
||||
newData := make(Fields, 4)
|
||||
newData[f.DataKey] = data
|
||||
data = newData
|
||||
}
|
||||
|
||||
prefixFieldClashes(data, f.FieldMap)
|
||||
|
||||
timestampFormat := f.TimestampFormat
|
||||
if timestampFormat == "" {
|
||||
timestampFormat = defaultTimestampFormat
|
||||
}
|
||||
|
||||
if !f.DisableTimestamp {
|
||||
data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat)
|
||||
}
|
||||
data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message
|
||||
data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String()
|
||||
|
||||
serialized, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
||||
}
|
||||
return append(serialized, '\n'), nil
|
||||
}
|
|
@ -0,0 +1,337 @@
|
|||
package logrus
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Logger struct {
|
||||
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
|
||||
// file, or leave it default which is `os.Stderr`. You can also set this to
|
||||
// something more adventorous, such as logging to Kafka.
|
||||
Out io.Writer
|
||||
// Hooks for the logger instance. These allow firing events based on logging
|
||||
// levels and log entries. For example, to send errors to an error tracking
|
||||
// service, log to StatsD or dump the core on fatal errors.
|
||||
Hooks LevelHooks
|
||||
// All log entries pass through the formatter before logged to Out. The
|
||||
// included formatters are `TextFormatter` and `JSONFormatter` for which
|
||||
// TextFormatter is the default. In development (when a TTY is attached) it
|
||||
// logs with colors, but to a file it wouldn't. You can easily implement your
|
||||
// own that implements the `Formatter` interface, see the `README` or included
|
||||
// formatters for examples.
|
||||
Formatter Formatter
|
||||
// The logging level the logger should log at. This is typically (and defaults
|
||||
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
||||
// logged.
|
||||
Level Level
|
||||
// Used to sync writing to the log. Locking is enabled by Default
|
||||
mu MutexWrap
|
||||
// Reusable empty entry
|
||||
entryPool sync.Pool
|
||||
}
|
||||
|
||||
type MutexWrap struct {
|
||||
lock sync.Mutex
|
||||
disabled bool
|
||||
}
|
||||
|
||||
func (mw *MutexWrap) Lock() {
|
||||
if !mw.disabled {
|
||||
mw.lock.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
func (mw *MutexWrap) Unlock() {
|
||||
if !mw.disabled {
|
||||
mw.lock.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (mw *MutexWrap) Disable() {
|
||||
mw.disabled = true
|
||||
}
|
||||
|
||||
// Creates a new logger. Configuration should be set by changing `Formatter`,
|
||||
// `Out` and `Hooks` directly on the default logger instance. You can also just
|
||||
// instantiate your own:
|
||||
//
|
||||
// var log = &Logger{
|
||||
// Out: os.Stderr,
|
||||
// Formatter: new(JSONFormatter),
|
||||
// Hooks: make(LevelHooks),
|
||||
// Level: logrus.DebugLevel,
|
||||
// }
|
||||
//
|
||||
// It's recommended to make this a global instance called `log`.
|
||||
func New() *Logger {
|
||||
return &Logger{
|
||||
Out: os.Stderr,
|
||||
Formatter: new(TextFormatter),
|
||||
Hooks: make(LevelHooks),
|
||||
Level: InfoLevel,
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) newEntry() *Entry {
|
||||
entry, ok := logger.entryPool.Get().(*Entry)
|
||||
if ok {
|
||||
return entry
|
||||
}
|
||||
return NewEntry(logger)
|
||||
}
|
||||
|
||||
func (logger *Logger) releaseEntry(entry *Entry) {
|
||||
logger.entryPool.Put(entry)
|
||||
}
|
||||
|
||||
// Adds a field to the log entry, note that it doesn't log until you call
|
||||
// Debug, Print, Info, Warn, Error, Fatal or Panic. It only creates a log entry.
|
||||
// If you want multiple fields, use `WithFields`.
|
||||
func (logger *Logger) WithField(key string, value interface{}) *Entry {
|
||||
entry := logger.newEntry()
|
||||
defer logger.releaseEntry(entry)
|
||||
return entry.WithField(key, value)
|
||||
}
|
||||
|
||||
// Adds a struct of fields to the log entry. All it does is call `WithField` for
|
||||
// each `Field`.
|
||||
func (logger *Logger) WithFields(fields Fields) *Entry {
|
||||
entry := logger.newEntry()
|
||||
defer logger.releaseEntry(entry)
|
||||
return entry.WithFields(fields)
|
||||
}
|
||||
|
||||
// Add an error as single field to the log entry. All it does is call
|
||||
// `WithError` for the given `error`.
|
||||
func (logger *Logger) WithError(err error) *Entry {
|
||||
entry := logger.newEntry()
|
||||
defer logger.releaseEntry(entry)
|
||||
return entry.WithError(err)
|
||||
}
|
||||
|
||||
// Overrides the time of the log entry.
|
||||
func (logger *Logger) WithTime(t time.Time) *Entry {
|
||||
entry := logger.newEntry()
|
||||
defer logger.releaseEntry(entry)
|
||||
return entry.WithTime(t)
|
||||
}
|
||||
|
||||
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
||||
if logger.level() >= DebugLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Debugf(format, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Infof(format string, args ...interface{}) {
|
||||
if logger.level() >= InfoLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Infof(format, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Printf(format string, args ...interface{}) {
|
||||
entry := logger.newEntry()
|
||||
entry.Printf(format, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warnf(format string, args ...interface{}) {
|
||||
if logger.level() >= WarnLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Warnf(format, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
||||
if logger.level() >= WarnLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Warnf(format, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
||||
if logger.level() >= ErrorLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Errorf(format, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
||||
if logger.level() >= FatalLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Fatalf(format, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
Exit(1)
|
||||
}
|
||||
|
||||
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
||||
if logger.level() >= PanicLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Panicf(format, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Debug(args ...interface{}) {
|
||||
if logger.level() >= DebugLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Debug(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Info(args ...interface{}) {
|
||||
if logger.level() >= InfoLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Info(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Print(args ...interface{}) {
|
||||
entry := logger.newEntry()
|
||||
entry.Info(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warn(args ...interface{}) {
|
||||
if logger.level() >= WarnLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Warn(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Warning(args ...interface{}) {
|
||||
if logger.level() >= WarnLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Warn(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Error(args ...interface{}) {
|
||||
if logger.level() >= ErrorLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Error(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatal(args ...interface{}) {
|
||||
if logger.level() >= FatalLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Fatal(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
Exit(1)
|
||||
}
|
||||
|
||||
func (logger *Logger) Panic(args ...interface{}) {
|
||||
if logger.level() >= PanicLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Panic(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Debugln(args ...interface{}) {
|
||||
if logger.level() >= DebugLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Debugln(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Infoln(args ...interface{}) {
|
||||
if logger.level() >= InfoLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Infoln(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Println(args ...interface{}) {
|
||||
entry := logger.newEntry()
|
||||
entry.Println(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warnln(args ...interface{}) {
|
||||
if logger.level() >= WarnLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Warnln(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Warningln(args ...interface{}) {
|
||||
if logger.level() >= WarnLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Warnln(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Errorln(args ...interface{}) {
|
||||
if logger.level() >= ErrorLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Errorln(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatalln(args ...interface{}) {
|
||||
if logger.level() >= FatalLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Fatalln(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
Exit(1)
|
||||
}
|
||||
|
||||
func (logger *Logger) Panicln(args ...interface{}) {
|
||||
if logger.level() >= PanicLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Panicln(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
//When file is opened with appending mode, it's safe to
|
||||
//write concurrently to a file (within 4k message on Linux).
|
||||
//In these cases user can choose to disable the lock.
|
||||
func (logger *Logger) SetNoLock() {
|
||||
logger.mu.Disable()
|
||||
}
|
||||
|
||||
func (logger *Logger) level() Level {
|
||||
return Level(atomic.LoadUint32((*uint32)(&logger.Level)))
|
||||
}
|
||||
|
||||
func (logger *Logger) SetLevel(level Level) {
|
||||
atomic.StoreUint32((*uint32)(&logger.Level), uint32(level))
|
||||
}
|
||||
|
||||
func (logger *Logger) SetOutput(out io.Writer) {
|
||||
logger.mu.Lock()
|
||||
defer logger.mu.Unlock()
|
||||
logger.Out = out
|
||||
}
|
||||
|
||||
func (logger *Logger) AddHook(hook Hook) {
|
||||
logger.mu.Lock()
|
||||
defer logger.mu.Unlock()
|
||||
logger.Hooks.Add(hook)
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
package logrus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Fields type, used to pass to `WithFields`.
|
||||
type Fields map[string]interface{}
|
||||
|
||||
// Level type
|
||||
type Level uint32
|
||||
|
||||
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
|
||||
func (level Level) String() string {
|
||||
switch level {
|
||||
case DebugLevel:
|
||||
return "debug"
|
||||
case InfoLevel:
|
||||
return "info"
|
||||
case WarnLevel:
|
||||
return "warning"
|
||||
case ErrorLevel:
|
||||
return "error"
|
||||
case FatalLevel:
|
||||
return "fatal"
|
||||
case PanicLevel:
|
||||
return "panic"
|
||||
}
|
||||
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// ParseLevel takes a string level and returns the Logrus log level constant.
|
||||
func ParseLevel(lvl string) (Level, error) {
|
||||
switch strings.ToLower(lvl) {
|
||||
case "panic":
|
||||
return PanicLevel, nil
|
||||
case "fatal":
|
||||
return FatalLevel, nil
|
||||
case "error":
|
||||
return ErrorLevel, nil
|
||||
case "warn", "warning":
|
||||
return WarnLevel, nil
|
||||
case "info":
|
||||
return InfoLevel, nil
|
||||
case "debug":
|
||||
return DebugLevel, nil
|
||||
}
|
||||
|
||||
var l Level
|
||||
return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
|
||||
}
|
||||
|
||||
// A constant exposing all logging levels
|
||||
var AllLevels = []Level{
|
||||
PanicLevel,
|
||||
FatalLevel,
|
||||
ErrorLevel,
|
||||
WarnLevel,
|
||||
InfoLevel,
|
||||
DebugLevel,
|
||||
}
|
||||
|
||||
// These are the different logging levels. You can set the logging level to log
|
||||
// on your instance of logger, obtained with `logrus.New()`.
|
||||
const (
|
||||
// PanicLevel level, highest level of severity. Logs and then calls panic with the
|
||||
// message passed to Debug, Info, ...
|
||||
PanicLevel Level = iota
|
||||
// FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the
|
||||
// logging level is set to Panic.
|
||||
FatalLevel
|
||||
// ErrorLevel level. Logs. Used for errors that should definitely be noted.
|
||||
// Commonly used for hooks to send errors to an error tracking service.
|
||||
ErrorLevel
|
||||
// WarnLevel level. Non-critical entries that deserve eyes.
|
||||
WarnLevel
|
||||
// InfoLevel level. General operational entries about what's going on inside the
|
||||
// application.
|
||||
InfoLevel
|
||||
// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
|
||||
DebugLevel
|
||||
)
|
||||
|
||||
// Won't compile if StdLogger can't be realized by a log.Logger
|
||||
var (
|
||||
_ StdLogger = &log.Logger{}
|
||||
_ StdLogger = &Entry{}
|
||||
_ StdLogger = &Logger{}
|
||||
)
|
||||
|
||||
// StdLogger is what your logrus-enabled library should take, that way
|
||||
// it'll accept a stdlib logger and a logrus logger. There's no standard
|
||||
// interface, this is the closest we get, unfortunately.
|
||||
type StdLogger interface {
|
||||
Print(...interface{})
|
||||
Printf(string, ...interface{})
|
||||
Println(...interface{})
|
||||
|
||||
Fatal(...interface{})
|
||||
Fatalf(string, ...interface{})
|
||||
Fatalln(...interface{})
|
||||
|
||||
Panic(...interface{})
|
||||
Panicf(string, ...interface{})
|
||||
Panicln(...interface{})
|
||||
}
|
||||
|
||||
// The FieldLogger interface generalizes the Entry and Logger types
|
||||
type FieldLogger interface {
|
||||
WithField(key string, value interface{}) *Entry
|
||||
WithFields(fields Fields) *Entry
|
||||
WithError(err error) *Entry
|
||||
|
||||
Debugf(format string, args ...interface{})
|
||||
Infof(format string, args ...interface{})
|
||||
Printf(format string, args ...interface{})
|
||||
Warnf(format string, args ...interface{})
|
||||
Warningf(format string, args ...interface{})
|
||||
Errorf(format string, args ...interface{})
|
||||
Fatalf(format string, args ...interface{})
|
||||
Panicf(format string, args ...interface{})
|
||||
|
||||
Debug(args ...interface{})
|
||||
Info(args ...interface{})
|
||||
Print(args ...interface{})
|
||||
Warn(args ...interface{})
|
||||
Warning(args ...interface{})
|
||||
Error(args ...interface{})
|
||||
Fatal(args ...interface{})
|
||||
Panic(args ...interface{})
|
||||
|
||||
Debugln(args ...interface{})
|
||||
Infoln(args ...interface{})
|
||||
Println(args ...interface{})
|
||||
Warnln(args ...interface{})
|
||||
Warningln(args ...interface{})
|
||||
Errorln(args ...interface{})
|
||||
Fatalln(args ...interface{})
|
||||
Panicln(args ...interface{})
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
// +build darwin freebsd openbsd netbsd dragonfly
|
||||
// +build !appengine,!gopherjs
|
||||
|
||||
package logrus
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
const ioctlReadTermios = unix.TIOCGETA
|
||||
|
||||
type Termios unix.Termios
|
|
@ -0,0 +1,11 @@
|
|||
// +build appengine gopherjs
|
||||
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
func checkIfTerminal(w io.Writer) bool {
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
// +build !appengine,!gopherjs
|
||||
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
func checkIfTerminal(w io.Writer) bool {
|
||||
switch v := w.(type) {
|
||||
case *os.File:
|
||||
return terminal.IsTerminal(int(v.Fd()))
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
// Based on ssh/terminal:
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !appengine,!gopherjs
|
||||
|
||||
package logrus
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
const ioctlReadTermios = unix.TCGETS
|
||||
|
||||
type Termios unix.Termios
|
|
@ -0,0 +1,195 @@
|
|||
package logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
nocolor = 0
|
||||
red = 31
|
||||
green = 32
|
||||
yellow = 33
|
||||
blue = 36
|
||||
gray = 37
|
||||
)
|
||||
|
||||
var (
|
||||
baseTimestamp time.Time
|
||||
emptyFieldMap FieldMap
|
||||
)
|
||||
|
||||
func init() {
|
||||
baseTimestamp = time.Now()
|
||||
}
|
||||
|
||||
// TextFormatter formats logs into text
|
||||
type TextFormatter struct {
|
||||
// Set to true to bypass checking for a TTY before outputting colors.
|
||||
ForceColors bool
|
||||
|
||||
// Force disabling colors.
|
||||
DisableColors bool
|
||||
|
||||
// Disable timestamp logging. useful when output is redirected to logging
|
||||
// system that already adds timestamps.
|
||||
DisableTimestamp bool
|
||||
|
||||
// Enable logging the full timestamp when a TTY is attached instead of just
|
||||
// the time passed since beginning of execution.
|
||||
FullTimestamp bool
|
||||
|
||||
// TimestampFormat to use for display when a full timestamp is printed
|
||||
TimestampFormat string
|
||||
|
||||
// The fields are sorted by default for a consistent output. For applications
|
||||
// that log extremely frequently and don't use the JSON formatter this may not
|
||||
// be desired.
|
||||
DisableSorting bool
|
||||
|
||||
// Disables the truncation of the level text to 4 characters.
|
||||
DisableLevelTruncation bool
|
||||
|
||||
// QuoteEmptyFields will wrap empty fields in quotes if true
|
||||
QuoteEmptyFields bool
|
||||
|
||||
// Whether the logger's out is to a terminal
|
||||
isTerminal bool
|
||||
|
||||
// FieldMap allows users to customize the names of keys for default fields.
|
||||
// As an example:
|
||||
// formatter := &TextFormatter{
|
||||
// FieldMap: FieldMap{
|
||||
// FieldKeyTime: "@timestamp",
|
||||
// FieldKeyLevel: "@level",
|
||||
// FieldKeyMsg: "@message"}}
|
||||
FieldMap FieldMap
|
||||
|
||||
sync.Once
|
||||
}
|
||||
|
||||
func (f *TextFormatter) init(entry *Entry) {
|
||||
if entry.Logger != nil {
|
||||
f.isTerminal = checkIfTerminal(entry.Logger.Out)
|
||||
}
|
||||
}
|
||||
|
||||
// Format renders a single log entry
|
||||
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
prefixFieldClashes(entry.Data, f.FieldMap)
|
||||
|
||||
keys := make([]string, 0, len(entry.Data))
|
||||
for k := range entry.Data {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
if !f.DisableSorting {
|
||||
sort.Strings(keys)
|
||||
}
|
||||
|
||||
var b *bytes.Buffer
|
||||
if entry.Buffer != nil {
|
||||
b = entry.Buffer
|
||||
} else {
|
||||
b = &bytes.Buffer{}
|
||||
}
|
||||
|
||||
f.Do(func() { f.init(entry) })
|
||||
|
||||
isColored := (f.ForceColors || f.isTerminal) && !f.DisableColors
|
||||
|
||||
timestampFormat := f.TimestampFormat
|
||||
if timestampFormat == "" {
|
||||
timestampFormat = defaultTimestampFormat
|
||||
}
|
||||
if isColored {
|
||||
f.printColored(b, entry, keys, timestampFormat)
|
||||
} else {
|
||||
if !f.DisableTimestamp {
|
||||
f.appendKeyValue(b, f.FieldMap.resolve(FieldKeyTime), entry.Time.Format(timestampFormat))
|
||||
}
|
||||
f.appendKeyValue(b, f.FieldMap.resolve(FieldKeyLevel), entry.Level.String())
|
||||
if entry.Message != "" {
|
||||
f.appendKeyValue(b, f.FieldMap.resolve(FieldKeyMsg), entry.Message)
|
||||
}
|
||||
for _, key := range keys {
|
||||
f.appendKeyValue(b, key, entry.Data[key])
|
||||
}
|
||||
}
|
||||
|
||||
b.WriteByte('\n')
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, timestampFormat string) {
|
||||
var levelColor int
|
||||
switch entry.Level {
|
||||
case DebugLevel:
|
||||
levelColor = gray
|
||||
case WarnLevel:
|
||||
levelColor = yellow
|
||||
case ErrorLevel, FatalLevel, PanicLevel:
|
||||
levelColor = red
|
||||
default:
|
||||
levelColor = blue
|
||||
}
|
||||
|
||||
levelText := strings.ToUpper(entry.Level.String())
|
||||
if !f.DisableLevelTruncation {
|
||||
levelText = levelText[0:4]
|
||||
}
|
||||
|
||||
if f.DisableTimestamp {
|
||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m %-44s ", levelColor, levelText, entry.Message)
|
||||
} else if !f.FullTimestamp {
|
||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), entry.Message)
|
||||
} else {
|
||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message)
|
||||
}
|
||||
for _, k := range keys {
|
||||
v := entry.Data[k]
|
||||
fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k)
|
||||
f.appendValue(b, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *TextFormatter) needsQuoting(text string) bool {
|
||||
if f.QuoteEmptyFields && len(text) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, ch := range text {
|
||||
if !((ch >= 'a' && ch <= 'z') ||
|
||||
(ch >= 'A' && ch <= 'Z') ||
|
||||
(ch >= '0' && ch <= '9') ||
|
||||
ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
|
||||
if b.Len() > 0 {
|
||||
b.WriteByte(' ')
|
||||
}
|
||||
b.WriteString(key)
|
||||
b.WriteByte('=')
|
||||
f.appendValue(b, value)
|
||||
}
|
||||
|
||||
func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
|
||||
stringVal, ok := value.(string)
|
||||
if !ok {
|
||||
stringVal = fmt.Sprint(value)
|
||||
}
|
||||
|
||||
if !f.needsQuoting(stringVal) {
|
||||
b.WriteString(stringVal)
|
||||
} else {
|
||||
b.WriteString(fmt.Sprintf("%q", stringVal))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package logrus
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func (logger *Logger) Writer() *io.PipeWriter {
|
||||
return logger.WriterLevel(InfoLevel)
|
||||
}
|
||||
|
||||
func (logger *Logger) WriterLevel(level Level) *io.PipeWriter {
|
||||
return NewEntry(logger).WriterLevel(level)
|
||||
}
|
||||
|
||||
func (entry *Entry) Writer() *io.PipeWriter {
|
||||
return entry.WriterLevel(InfoLevel)
|
||||
}
|
||||
|
||||
func (entry *Entry) WriterLevel(level Level) *io.PipeWriter {
|
||||
reader, writer := io.Pipe()
|
||||
|
||||
var printFunc func(args ...interface{})
|
||||
|
||||
switch level {
|
||||
case DebugLevel:
|
||||
printFunc = entry.Debug
|
||||
case InfoLevel:
|
||||
printFunc = entry.Info
|
||||
case WarnLevel:
|
||||
printFunc = entry.Warn
|
||||
case ErrorLevel:
|
||||
printFunc = entry.Error
|
||||
case FatalLevel:
|
||||
printFunc = entry.Fatal
|
||||
case PanicLevel:
|
||||
printFunc = entry.Panic
|
||||
default:
|
||||
printFunc = entry.Print
|
||||
}
|
||||
|
||||
go entry.writerScanner(reader, printFunc)
|
||||
runtime.SetFinalizer(writer, writerFinalizer)
|
||||
|
||||
return writer
|
||||
}
|
||||
|
||||
func (entry *Entry) writerScanner(reader *io.PipeReader, printFunc func(args ...interface{})) {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
for scanner.Scan() {
|
||||
printFunc(scanner.Text())
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
entry.Errorf("Error while reading from Writer: %s", err)
|
||||
}
|
||||
reader.Close()
|
||||
}
|
||||
|
||||
func writerFinalizer(writer *io.PipeWriter) {
|
||||
writer.Close()
|
||||
}
|
|
@ -30,10 +30,12 @@ function.
|
|||
* Scaleway [Config options](https://github.com/hashicorp/go-discover/blob/master/provider/scaleway/scaleway_discover.go#L14-L22)
|
||||
* SoftLayer [Config options](https://github.com/hashicorp/go-discover/blob/master/provider/softlayer/softlayer_discover.go#L16-L25)
|
||||
* Triton [Config options](https://github.com/hashicorp/go-discover/blob/master/provider/triton/triton_discover.go#L17-L27)
|
||||
* vSphere [Config options](https://github.com/hashicorp/go-discover/blob/master/provider/vsphere/vsphere_discover.go#L148-L155)
|
||||
* Packet [Config options](https://github.com/hashicorp/go-discover/blob/master/provider/packet/packet_discover.go#L25-L35)
|
||||
|
||||
HashiCorp maintains acceptance tests that regularly allocate and run tests with
|
||||
real resources to verify the behavior of several of these providers. Those
|
||||
currently are: Amazon AWS, Microsoft Azure, Google Cloud, DigitalOcean, and Triton.
|
||||
currently are: Amazon AWS, Microsoft Azure, Google Cloud, DigitalOcean, Triton, Scaleway and AliBaba Cloud.
|
||||
|
||||
### Config Example
|
||||
|
||||
|
@ -65,6 +67,11 @@ provider=softlayer datacenter=dal06 tag_value=consul username=... api_key=...
|
|||
# Triton
|
||||
provider=triton account=testaccount url=https://us-sw-1.api.joyentcloud.com key_id=... tag_key=consul-role tag_value=server
|
||||
|
||||
# vSphere
|
||||
provider=vsphere category_name=consul-role tag_name=consul-server host=... user=... password=... insecure_ssl=[true|false]
|
||||
|
||||
# Packet
|
||||
provider=packet auth_token=token project=uuid url=... address_type=...
|
||||
```
|
||||
|
||||
## Command Line Tool Usage
|
||||
|
|
|
@ -15,9 +15,11 @@ import (
|
|||
"github.com/hashicorp/go-discover/provider/digitalocean"
|
||||
"github.com/hashicorp/go-discover/provider/gce"
|
||||
"github.com/hashicorp/go-discover/provider/os"
|
||||
"github.com/hashicorp/go-discover/provider/packet"
|
||||
"github.com/hashicorp/go-discover/provider/scaleway"
|
||||
"github.com/hashicorp/go-discover/provider/softlayer"
|
||||
"github.com/hashicorp/go-discover/provider/triton"
|
||||
"github.com/hashicorp/go-discover/provider/vsphere"
|
||||
)
|
||||
|
||||
// Provider has lookup functions for meta data in a
|
||||
|
@ -49,6 +51,8 @@ var Providers = map[string]Provider{
|
|||
"scaleway": &scaleway.Provider{},
|
||||
"softlayer": &softlayer.Provider{},
|
||||
"triton": &triton.Provider{},
|
||||
"vsphere": &vsphere.Provider{},
|
||||
"packet": &packet.Provider{},
|
||||
}
|
||||
|
||||
// Discover looks up metadata in different cloud environments.
|
||||
|
|
94
vendor/github.com/hashicorp/go-discover/provider/packet/packet_discover.go
generated
vendored
Normal file
94
vendor/github.com/hashicorp/go-discover/provider/packet/packet_discover.go
generated
vendored
Normal file
|
@ -0,0 +1,94 @@
|
|||
package packet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/packethost/packngo"
|
||||
)
|
||||
|
||||
const baseURL = "https://api.packet.net/"
|
||||
|
||||
// Provider struct
|
||||
type Provider struct {
|
||||
userAgent string
|
||||
}
|
||||
|
||||
// SetUserAgent setter
|
||||
func (p *Provider) SetUserAgent(s string) {
|
||||
p.userAgent = s
|
||||
}
|
||||
|
||||
// Help function
|
||||
func (p *Provider) Help() string {
|
||||
return `Packet:
|
||||
provider: "packet"
|
||||
project: UUID of packet project. Required
|
||||
auth_token: Packet authentication token. Required
|
||||
url: Packet REST URL. Optional
|
||||
address_type: "private_v4", "public_v4" or "public_v6". Defaults to "private_v4". Optional
|
||||
|
||||
Variables can also be provided by environmental variables:
|
||||
export PACKET_PROJECT for project
|
||||
export PACKET_URL for url
|
||||
export PACKET_AUTH_TOKEN for auth_token
|
||||
`
|
||||
}
|
||||
|
||||
// Addrs function
|
||||
func (p *Provider) Addrs(args map[string]string, l *log.Logger) ([]string, error) {
|
||||
authToken := argsOrEnv(args, "auth_token", "PACKET_AUTH_TOKEN")
|
||||
projectID := argsOrEnv(args, "project", "PACKET_PROJECT")
|
||||
packetURL := argsOrEnv(args, "url", "PACKET_URL")
|
||||
addressType := args["address_type"]
|
||||
|
||||
if addressType != "private_v4" && addressType != "public_v4" && addressType != "public_v6" {
|
||||
l.Printf("[INFO] discover-packet: Address type %s is not supported. Valid values are {private_v4,public_v4,public_v6}. Falling back to 'private_v4'", addressType)
|
||||
addressType = "private_v4"
|
||||
}
|
||||
|
||||
c, err := client(p.userAgent, packetURL, authToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("discover-packet: Initializing Packet client failed: %s", err)
|
||||
}
|
||||
|
||||
var devices []packngo.Device
|
||||
|
||||
if projectID == "" {
|
||||
return nil, fmt.Errorf("discover-packet: 'project' parameter must be provider")
|
||||
}
|
||||
|
||||
devices, _, err = c.Devices.List(projectID, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("discover-packet: Fetching Packet devices failed: %s", err)
|
||||
}
|
||||
var addrs []string
|
||||
for _, d := range devices {
|
||||
addressFamily := 4
|
||||
if addressType == "public_v6" {
|
||||
addressFamily = 6
|
||||
}
|
||||
for _, n := range d.Network {
|
||||
|
||||
if (n.Public == (addressType == "public_v4" || addressType == "public_v6")) && n.AddressFamily == addressFamily {
|
||||
addrs = append(addrs, n.Address)
|
||||
}
|
||||
}
|
||||
}
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
func client(useragent, url, token string) (*packngo.Client, error) {
|
||||
if url == "" {
|
||||
url = baseURL
|
||||
}
|
||||
|
||||
return packngo.NewClientWithBaseURL(useragent, token, nil, url)
|
||||
}
|
||||
func argsOrEnv(args map[string]string, key, env string) string {
|
||||
if value := args[key]; value != "" {
|
||||
return value
|
||||
}
|
||||
return os.Getenv(env)
|
||||
}
|
417
vendor/github.com/hashicorp/go-discover/provider/vsphere/vsphere_discover.go
generated
vendored
Normal file
417
vendor/github.com/hashicorp/go-discover/provider/vsphere/vsphere_discover.go
generated
vendored
Normal file
|
@ -0,0 +1,417 @@
|
|||
// Package vsphere provides node discovery for VMware vSphere.
|
||||
//
|
||||
// The package performs discovery by searching vCenter for all nodes matching a
|
||||
// certain tag, it then discovers all known IP addresses through VMware tools
|
||||
// that are not loopback or auto-configuration addresses.
|
||||
//
|
||||
// This package requires at least vSphere 6.0 in order to function.
|
||||
package vsphere
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/govmomi"
|
||||
"github.com/vmware/govmomi/find"
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/pkg/vsphere/tags"
|
||||
)
|
||||
|
||||
// providerLog is the local provider logger. This should be initialized from
|
||||
// the provider entry point.
|
||||
var logger *log.Logger
|
||||
|
||||
// setLog sets the logger.
|
||||
func setLog(l *log.Logger) {
|
||||
if l != nil {
|
||||
logger = l
|
||||
} else {
|
||||
logger = log.New(ioutil.Discard, "", 0)
|
||||
}
|
||||
}
|
||||
|
||||
// discoverErr prints out a friendly error heading for the top-level discovery
|
||||
// errors. It should only be used in the Addrs method.
|
||||
func discoverErr(format string, a ...interface{}) error {
|
||||
var s string
|
||||
if len(a) > 1 {
|
||||
s = fmt.Sprintf(format, a)
|
||||
} else {
|
||||
s = format
|
||||
}
|
||||
return fmt.Errorf("discover-vsphere: %s", s)
|
||||
}
|
||||
|
||||
// valueOrEnv provides a way of suppling configuration values through
|
||||
// environment variables. Defined values always take priority.
|
||||
func valueOrEnv(config map[string]string, key, env string) string {
|
||||
if v := config[key]; v != "" {
|
||||
return v
|
||||
}
|
||||
if v := os.Getenv(env); v != "" {
|
||||
logger.Printf("[DEBUG] Using value of %s for configuration of %s", env, key)
|
||||
return v
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// vSphereClient is a client connection manager for the vSphere provider.
|
||||
type vSphereClient struct {
|
||||
// The VIM/govmomi client.
|
||||
VimClient *govmomi.Client
|
||||
|
||||
// The specialized tags client SDK imported from vmware/vic.
|
||||
TagsClient *tags.RestClient
|
||||
}
|
||||
|
||||
// vimURL returns a URL to pass to the VIM SOAP client.
|
||||
func vimURL(server, user, password string) (*url.URL, error) {
|
||||
u, err := url.Parse("https://" + server + "/sdk")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing url: %s", err)
|
||||
}
|
||||
|
||||
u.User = url.UserPassword(user, password)
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// newVSphereClient returns a new vSphereClient after setting up the necessary
|
||||
// connections.
|
||||
func newVSphereClient(ctx context.Context, host, user, password string, insecure bool) (*vSphereClient, error) {
|
||||
logger.Println("[DEBUG] Connecting to vSphere client endpoints")
|
||||
|
||||
client := new(vSphereClient)
|
||||
|
||||
u, err := vimURL(host, user, password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error generating SOAP endpoint url: %s", err)
|
||||
}
|
||||
|
||||
// Set up the VIM/govmomi client connection
|
||||
client.VimClient, err = newVimSession(ctx, u, insecure)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client.TagsClient, err = newRestSession(ctx, u, insecure)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logger.Println("[DEBUG] All vSphere client endpoints connected successfully")
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// newVimSession connects the VIM SOAP API client connection.
|
||||
func newVimSession(ctx context.Context, u *url.URL, insecure bool) (*govmomi.Client, error) {
|
||||
logger.Printf("[DEBUG] Creating new SOAP API session on endpoint %s", u.Host)
|
||||
client, err := govmomi.NewClient(ctx, u, insecure)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error setting up new vSphere SOAP client: %s", err)
|
||||
}
|
||||
|
||||
logger.Println("[DEBUG] SOAP API session creation successful")
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// newRestSession connects to the vSphere REST API endpoint, necessary for
|
||||
// tags.
|
||||
func newRestSession(ctx context.Context, u *url.URL, insecure bool) (*tags.RestClient, error) {
|
||||
logger.Printf("[DEBUG] Creating new CIS REST API session on endpoint %s", u.Host)
|
||||
client := tags.NewClient(u, insecure, "")
|
||||
if err := client.Login(ctx); err != nil {
|
||||
return nil, fmt.Errorf("error connecting to CIS REST endpoint: %s", err)
|
||||
}
|
||||
|
||||
logger.Println("[DEBUG] CIS REST API session creation successful")
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// Provider defines the vSphere discovery provider.
|
||||
type Provider struct{}
|
||||
|
||||
// Help implements the Provider interface for the vsphere package.
|
||||
func (p *Provider) Help() string {
|
||||
return `VMware vSphere:
|
||||
|
||||
provider: "vsphere"
|
||||
tag_name: The name of the tag to look up.
|
||||
category_name: The category of the tag to look up.
|
||||
host: The host of the vSphere server to connect to.
|
||||
user: The username to connect as.
|
||||
password: The password of the user to connect to vSphere as.
|
||||
insecure_ssl: Whether or not to skip SSL certificate validation.
|
||||
timeout: Discovery context timeout (default: 10m)
|
||||
`
|
||||
}
|
||||
|
||||
// Addrs implements the Provider interface for the vsphere package.
|
||||
func (p *Provider) Addrs(args map[string]string, l *log.Logger) ([]string, error) {
|
||||
if args["provider"] != "vsphere" {
|
||||
return nil, discoverErr("invalid provider %s", args["provider"])
|
||||
}
|
||||
|
||||
setLog(l)
|
||||
|
||||
tagName := args["tag_name"]
|
||||
categoryName := args["category_name"]
|
||||
host := valueOrEnv(args, "host", "VSPHERE_SERVER")
|
||||
user := valueOrEnv(args, "user", "VSPHERE_USER")
|
||||
password := valueOrEnv(args, "password", "VSPHERE_PASSWORD")
|
||||
insecure, err := strconv.ParseBool(valueOrEnv(args, "insecure_ssl", "VSPHERE_ALLOW_UNVERIFIED_SSL"))
|
||||
if err != nil {
|
||||
logger.Println("[DEBUG] Non-truthy/falsey value for insecure_ssl, assuming false")
|
||||
}
|
||||
timeout, err := time.ParseDuration(args["timeout"])
|
||||
if err != nil {
|
||||
logger.Println("[DEBUG] Non-time value given for timeout, assuming 10m")
|
||||
timeout = time.Minute * 10
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
client, err := newVSphereClient(ctx, host, user, password, insecure)
|
||||
if err != nil {
|
||||
return nil, discoverErr(err.Error())
|
||||
}
|
||||
|
||||
if tagName == "" || categoryName == "" {
|
||||
return nil, discoverErr("both tag_name and category_name must be specified")
|
||||
}
|
||||
|
||||
logger.Printf("[INFO] Locating all virtual machine IP addresses with tag %q in category %q", tagName, categoryName)
|
||||
|
||||
tagID, err := tagIDFromName(ctx, client.TagsClient, tagName, categoryName)
|
||||
if err != nil {
|
||||
return nil, discoverErr(err.Error())
|
||||
}
|
||||
|
||||
addrs, err := virtualMachineIPsForTag(ctx, client, tagID)
|
||||
if err != nil {
|
||||
return nil, discoverErr(err.Error())
|
||||
}
|
||||
|
||||
logger.Printf("[INFO] Final IP address list: %s", strings.Join(addrs, ","))
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
// tagIDFromName helps convert the tag and category names into the final ID
|
||||
// used for discovery.
|
||||
func tagIDFromName(ctx context.Context, client *tags.RestClient, name, category string) (string, error) {
|
||||
logger.Printf("[DEBUG] Fetching tag ID for tag name %q and category %q", name, category)
|
||||
|
||||
categoryID, err := tagCategoryByName(ctx, client, category)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tagByName(ctx, client, name, categoryID)
|
||||
}
|
||||
|
||||
// tagCategoryByName converts a tag category name into its ID.
|
||||
func tagCategoryByName(ctx context.Context, client *tags.RestClient, name string) (string, error) {
|
||||
cats, err := client.GetCategoriesByName(ctx, name)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not get category for name %q: %s", name, err)
|
||||
}
|
||||
|
||||
if len(cats) < 1 {
|
||||
return "", fmt.Errorf("category name %q not found", name)
|
||||
}
|
||||
if len(cats) > 1 {
|
||||
// Although GetCategoriesByName does not seem to think that tag categories
|
||||
// are unique, empirical observation via the console and API show that they
|
||||
// are. This error case is handled anyway.
|
||||
return "", fmt.Errorf("multiple categories with name %q found", name)
|
||||
}
|
||||
|
||||
return cats[0].ID, nil
|
||||
}
|
||||
|
||||
// tagByName converts a tag name into its ID.
|
||||
func tagByName(ctx context.Context, client *tags.RestClient, name, categoryID string) (string, error) {
|
||||
tids, err := client.GetTagByNameForCategory(ctx, name, categoryID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not get tag for name %q: %s", name, err)
|
||||
}
|
||||
|
||||
if len(tids) < 1 {
|
||||
return "", fmt.Errorf("tag name %q not found in category ID %q", name, categoryID)
|
||||
}
|
||||
if len(tids) > 1 {
|
||||
// This situation is very similar to the one in tagCategoryByName. The API
|
||||
// docs even say that tags need to be unique in categories, yet
|
||||
// GetTagByNameForCategory still returns multiple results.
|
||||
return "", fmt.Errorf("multiple tags with name %q found", name)
|
||||
}
|
||||
|
||||
logger.Printf("[DEBUG] Tag ID is %q", tids[0].ID)
|
||||
return tids[0].ID, nil
|
||||
}
|
||||
|
||||
// virtualMachineIPsForTag is a higher-level wrapper that calls out to
|
||||
// functions to fetch all of the virtual machines matching a certain tag ID,
|
||||
// and then gets all of the IP addresses for those virtual machines.
|
||||
func virtualMachineIPsForTag(ctx context.Context, client *vSphereClient, id string) ([]string, error) {
|
||||
vms, err := virtualMachinesForTag(ctx, client, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ipAddrsForVirtualMachines(ctx, client, vms)
|
||||
}
|
||||
|
||||
// virtualMachinesForTag discovers all of the virtual machines that match a
|
||||
// specific tag ID and returns their higher level helper objects.
|
||||
func virtualMachinesForTag(ctx context.Context, client *vSphereClient, id string) ([]*object.VirtualMachine, error) {
|
||||
logger.Printf("[DEBUG] Locating all virtual machines under tag ID %q", id)
|
||||
|
||||
var vms []*object.VirtualMachine
|
||||
|
||||
objs, err := client.TagsClient.ListAttachedObjects(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i, obj := range objs {
|
||||
switch {
|
||||
case obj.Type == nil || obj.ID == nil:
|
||||
logger.Printf("[WARN] Discovered object at index %d has either no ID or type", i)
|
||||
continue
|
||||
case *obj.Type != "VirtualMachine":
|
||||
logger.Printf("[DEBUG] Discovered object ID %q is not a virutal machine", *obj.ID)
|
||||
continue
|
||||
}
|
||||
vm, err := virtualMachineFromMOID(ctx, client.VimClient, *obj.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error locating virtual machine with ID %q: %s", *obj.ID, err)
|
||||
}
|
||||
vms = append(vms, vm)
|
||||
}
|
||||
|
||||
logger.Printf("[DEBUG] Discovered virtual machines: %s", virtualMachineNames(vms))
|
||||
return vms, nil
|
||||
}
|
||||
|
||||
// ipAddrsForVirtualMachines takes a set of virtual machines and returns a
|
||||
// consolidated list of IP addresses for all of the VMs.
|
||||
func ipAddrsForVirtualMachines(ctx context.Context, client *vSphereClient, vms []*object.VirtualMachine) ([]string, error) {
|
||||
var addrs []string
|
||||
for _, vm := range vms {
|
||||
as, err := buildAndSelectGuestIPs(ctx, vm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addrs = append(addrs, as...)
|
||||
}
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
// virtualMachineFromMOID locates a virtual machine by its managed object
|
||||
// reference ID.
|
||||
func virtualMachineFromMOID(ctx context.Context, client *govmomi.Client, id string) (*object.VirtualMachine, error) {
|
||||
logger.Printf("[DEBUG] Locating VM with managed object ID %q", id)
|
||||
|
||||
finder := find.NewFinder(client.Client, false)
|
||||
|
||||
ref := types.ManagedObjectReference{
|
||||
Type: "VirtualMachine",
|
||||
Value: id,
|
||||
}
|
||||
|
||||
vm, err := finder.ObjectReference(ctx, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Should be safe to return here. If our reference returned here and is not a
|
||||
// VM, then we have bigger problems and to be honest we should be panicking
|
||||
// anyway.
|
||||
return vm.(*object.VirtualMachine), nil
|
||||
}
|
||||
|
||||
// virtualMachineProperties is a convenience method that wraps fetching the
|
||||
// VirtualMachine MO from its higher-level object.
|
||||
//
|
||||
// It takes a list of property keys to fetch. Keeping the property set small
|
||||
// can sometimes result in significant performance improvements.
|
||||
func virtualMachineProperties(ctx context.Context, vm *object.VirtualMachine, keys []string) (*mo.VirtualMachine, error) {
|
||||
logger.Printf("[DEBUG] Fetching properties for VM %q", vm.Name())
|
||||
var props mo.VirtualMachine
|
||||
if err := vm.Properties(ctx, vm.Reference(), keys, &props); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &props, nil
|
||||
}
|
||||
|
||||
// buildAndSelectGuestIPs builds a list of IP addresses known to VMware tools,
|
||||
// skipping local and auto-configuration addresses.
|
||||
//
|
||||
// The builder is non-discriminate and is only deterministic to the order that
|
||||
// it discovers addresses in VMware tools.
|
||||
func buildAndSelectGuestIPs(ctx context.Context, vm *object.VirtualMachine) ([]string, error) {
|
||||
logger.Printf("[DEBUG] Discovering addresses for virtual machine %q", vm.Name())
|
||||
var addrs []string
|
||||
|
||||
props, err := virtualMachineProperties(ctx, vm, []string{"guest.net"})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot fetch properties for VM %q: %s", vm.Name(), err)
|
||||
}
|
||||
|
||||
if props.Guest == nil || props.Guest.Net == nil {
|
||||
logger.Printf("[WARN] No networking stack information available for %q or VMware tools not running", vm.Name())
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Now fetch all IP addresses, checking at the same time to see if the IP
|
||||
// address is eligible to be a primary IP address.
|
||||
for _, n := range props.Guest.Net {
|
||||
if n.IpConfig != nil {
|
||||
for _, addr := range n.IpConfig.IpAddress {
|
||||
if skipIPAddr(net.ParseIP(addr.IpAddress)) {
|
||||
continue
|
||||
}
|
||||
addrs = append(addrs, addr.IpAddress)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.Printf("[INFO] Discovered IP addresses for virtual machine %q: %s", vm.Name(), strings.Join(addrs, ","))
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
// skipIPAddr defines the set of criteria that buildAndSelectGuestIPs uses to
|
||||
// check to see if it needs to skip an IP address.
|
||||
func skipIPAddr(ip net.IP) bool {
|
||||
switch {
|
||||
case ip.IsLinkLocalMulticast():
|
||||
fallthrough
|
||||
case ip.IsLinkLocalUnicast():
|
||||
fallthrough
|
||||
case ip.IsLoopback():
|
||||
fallthrough
|
||||
case ip.IsMulticast():
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// virtualMachineNames is a helper method that returns all the names for a list
|
||||
// of virtual machines, comma separated.
|
||||
func virtualMachineNames(vms []*object.VirtualMachine) string {
|
||||
var s []string
|
||||
for _, vm := range vms {
|
||||
s = append(s, vm.Name())
|
||||
}
|
||||
return strings.Join(s, ",")
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
|
||||
This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
This release contains a bunch of fixes to the package api after some more real
|
||||
world use. There a few breaks in backwards compatibility, but we are tying to
|
||||
minimize them and move towards a 1.0 release.
|
||||
|
||||
### Added
|
||||
- "acceptance" tests which run against production api (will incur charges)
|
||||
- HardwareReservation to Device
|
||||
- RootPassword to Device
|
||||
- Spot market support
|
||||
- Management and Manageable fields to discern between Elastic IPs and device unique IP
|
||||
- Support for Volume attachments to Device and Volume
|
||||
- Support for ProvisionEvents
|
||||
- DoRequest sugar to Client
|
||||
- Add ListProject function to the SSHKeys interface
|
||||
- Operations for switching between Network Modes, aka "L2 support"
|
||||
Support for Organization, Payment Method and Billing address resources
|
||||
|
||||
### Fixed
|
||||
- User.Emails json tag is fixed to match api response
|
||||
- Single error object api response is now handled correctly
|
||||
|
||||
### Changed
|
||||
- IPService was split to DeviceIPService and ProjectIPService
|
||||
- Renamed Device.IPXEScriptUrl -> Device.IPXEScriptURL
|
||||
- Renamed DeviceCreateRequest.HostName -> DeviceCreateRequest.Hostname
|
||||
- Renamed DeviceCreateRequest.IPXEScriptUrl -> DeviceCreateRequest.IPXEScriptURL
|
||||
- Renamed DeviceUpdateRequest.HostName -> DeviceUpdateRequest.Hostname
|
||||
- Renamed DeviceUpdateRequest.IPXEScriptUrl -> DeviceUpdateRequest.IPXEScriptURL
|
||||
- Sync with packet.net api change to /projects/{id}/ips which no longer returns
|
||||
the address in CIDR form
|
||||
- Removed package level exported functions that should have never existed
|
||||
|
||||
## [0.1.0] - 2017-08-17
|
||||
|
||||
Initial release, supports most of the api for interacting with:
|
||||
|
||||
- Plans
|
||||
- Users
|
||||
- Emails
|
||||
- SSH Keys
|
||||
- Devices
|
||||
- Projects
|
||||
- Facilities
|
||||
- Operating Systems
|
||||
- IP Reservations
|
||||
- Volumes
|
|
@ -0,0 +1,56 @@
|
|||
Copyright (c) 2014 The packngo AUTHORS. All rights reserved.
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
======================
|
||||
Portions of the client are based on code at:
|
||||
https://github.com/google/go-github/ and
|
||||
https://github.com/digitalocean/godo
|
||||
|
||||
Copyright (c) 2013 The go-github AUTHORS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
# packngo
|
||||
Packet Go Api Client
|
||||
|
||||
![](https://www.packet.net/media/images/xeiw-packettwitterprofilew.png)
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
`go get github.com/packethost/packngo`
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
To authenticate to the Packet API, you must have your API token exported in env var `PACKET_API_TOKEN`.
|
||||
|
||||
This code snippet initializes Packet API client, and lists your Projects:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/packethost/packngo"
|
||||
)
|
||||
|
||||
func main() {
|
||||
c, err := packngo.NewClient()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ps, _, err := c.Projects.List(nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, p := range ps {
|
||||
log.Println(p.ID, p.Name)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
This lib is used by the official [terraform-provider-packet](https://github.com/terraform-providers/terraform-provider-packet).
|
||||
|
||||
You can also learn a lot from the `*_test.go` sources. Almost all out tests touch the Packet API, so you can see how auth, querying and POSTing works. For example [devices_test.go](devices_test.go).
|
||||
|
||||
|
||||
|
||||
Acceptance Tests
|
||||
----------------
|
||||
|
||||
If you want to run tests against the actual Packet API, you must set envvar `PACKET_TEST_ACTUAL_API` to non-empty string for the `go test`. The device tests wait for the device creation, so it's best to run a few in parallel.
|
||||
|
||||
To run a particular test, you can do
|
||||
|
||||
```
|
||||
$ PACKNGO_TEST_ACTUAL_API=1 go test -v -run=TestAccDeviceBasic
|
||||
```
|
||||
|
||||
If you want to see HTTP requests, set the `PACKNGO_DEBUG` env var to non-empty string, for example:
|
||||
|
||||
```
|
||||
$ PACKNGO_DEBUG=1 PACKNGO_TEST_ACTUAL_API=1 go test -v -run=TestAccVolumeUpdate
|
||||
```
|
||||
|
||||
|
||||
Committing
|
||||
----------
|
||||
|
||||
Before committing, it's a good idea to run `gofmt -w *.go`. ([gofmt](https://golang.org/cmd/gofmt/))
|
|
@ -0,0 +1,7 @@
|
|||
package packngo
|
||||
|
||||
type BillingAddress struct {
|
||||
StreetAddress string `json:"street_address,omitempty"`
|
||||
PostalCode string `json:"postal_code,omitempty"`
|
||||
CountryCode string `json:"country_code_alpha2,omitempty"`
|
||||
}
|
|
@ -0,0 +1,257 @@
|
|||
package packngo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const deviceBasePath = "/devices"
|
||||
|
||||
// DeviceService interface defines available device methods
|
||||
type DeviceService interface {
|
||||
List(ProjectID string, listOpt *ListOptions) ([]Device, *Response, error)
|
||||
Get(string) (*Device, *Response, error)
|
||||
GetExtra(deviceID string, includes, excludes []string) (*Device, *Response, error)
|
||||
Create(*DeviceCreateRequest) (*Device, *Response, error)
|
||||
Update(string, *DeviceUpdateRequest) (*Device, *Response, error)
|
||||
Delete(string) (*Response, error)
|
||||
Reboot(string) (*Response, error)
|
||||
PowerOff(string) (*Response, error)
|
||||
PowerOn(string) (*Response, error)
|
||||
Lock(string) (*Response, error)
|
||||
Unlock(string) (*Response, error)
|
||||
}
|
||||
|
||||
type devicesRoot struct {
|
||||
Devices []Device `json:"devices"`
|
||||
Meta meta `json:"meta"`
|
||||
}
|
||||
|
||||
// Device represents a Packet device
|
||||
type Device struct {
|
||||
ID string `json:"id"`
|
||||
Href string `json:"href,omitempty"`
|
||||
Hostname string `json:"hostname,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Created string `json:"created_at,omitempty"`
|
||||
Updated string `json:"updated_at,omitempty"`
|
||||
Locked bool `json:"locked,omitempty"`
|
||||
BillingCycle string `json:"billing_cycle,omitempty"`
|
||||
Storage map[string]interface{} `json:"storage,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
Network []*IPAddressAssignment `json:"ip_addresses"`
|
||||
Volumes []*Volume `json:"volumes"`
|
||||
OS *OS `json:"operating_system,omitempty"`
|
||||
Plan *Plan `json:"plan,omitempty"`
|
||||
Facility *Facility `json:"facility,omitempty"`
|
||||
Project *Project `json:"project,omitempty"`
|
||||
ProvisionEvents []*ProvisionEvent `json:"provisioning_events,omitempty"`
|
||||
ProvisionPer float32 `json:"provisioning_percentage,omitempty"`
|
||||
UserData string `json:"userdata,omitempty"`
|
||||
RootPassword string `json:"root_password,omitempty"`
|
||||
IPXEScriptURL string `json:"ipxe_script_url,omitempty"`
|
||||
AlwaysPXE bool `json:"always_pxe,omitempty"`
|
||||
HardwareReservation Href `json:"hardware_reservation,omitempty"`
|
||||
SpotInstance bool `json:"spot_instance,omitempty"`
|
||||
SpotPriceMax float64 `json:"spot_price_max,omitempty"`
|
||||
TerminationTime *Timestamp `json:"termination_time,omitempty"`
|
||||
NetworkPorts []Port `json:"network_ports,omitempty"`
|
||||
CustomData map[string]interface{} `json:"customdata,omitempty"`
|
||||
}
|
||||
|
||||
type ProvisionEvent struct {
|
||||
ID string `json:"id"`
|
||||
Body string `json:"body"`
|
||||
CreatedAt *Timestamp `json:"created_at,omitempty"`
|
||||
Href string `json:"href"`
|
||||
Interpolated string `json:"interpolated"`
|
||||
Relationships []Href `json:"relationships"`
|
||||
State string `json:"state"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
func (d Device) String() string {
|
||||
return Stringify(d)
|
||||
}
|
||||
|
||||
// DeviceCreateRequest type used to create a Packet device
|
||||
type DeviceCreateRequest struct {
|
||||
Hostname string `json:"hostname"`
|
||||
Plan string `json:"plan"`
|
||||
Facility string `json:"facility"`
|
||||
OS string `json:"operating_system"`
|
||||
BillingCycle string `json:"billing_cycle"`
|
||||
ProjectID string `json:"project_id"`
|
||||
UserData string `json:"userdata"`
|
||||
Storage string `json:"storage,omitempty"`
|
||||
Tags []string `json:"tags"`
|
||||
IPXEScriptURL string `json:"ipxe_script_url,omitempty"`
|
||||
PublicIPv4SubnetSize int `json:"public_ipv4_subnet_size,omitempty"`
|
||||
AlwaysPXE bool `json:"always_pxe,omitempty"`
|
||||
HardwareReservationID string `json:"hardware_reservation_id,omitempty"`
|
||||
SpotInstance bool `json:"spot_instance,omitempty"`
|
||||
SpotPriceMax float64 `json:"spot_price_max,omitempty,string"`
|
||||
TerminationTime *Timestamp `json:"termination_time,omitempty"`
|
||||
CustomData string `json:"customdata,omitempty"`
|
||||
}
|
||||
|
||||
// DeviceUpdateRequest type used to update a Packet device
|
||||
type DeviceUpdateRequest struct {
|
||||
Hostname *string `json:"hostname,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
UserData *string `json:"userdata,omitempty"`
|
||||
Locked *bool `json:"locked,omitempty"`
|
||||
Tags *[]string `json:"tags,omitempty"`
|
||||
AlwaysPXE *bool `json:"always_pxe,omitempty"`
|
||||
IPXEScriptURL *string `json:"ipxe_script_url,omitempty"`
|
||||
CustomData *string `json:"customdata,omitempty"`
|
||||
}
|
||||
|
||||
func (d DeviceCreateRequest) String() string {
|
||||
return Stringify(d)
|
||||
}
|
||||
|
||||
// DeviceActionRequest type used to execute actions on devices
|
||||
type DeviceActionRequest struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
func (d DeviceActionRequest) String() string {
|
||||
return Stringify(d)
|
||||
}
|
||||
|
||||
// DeviceServiceOp implements DeviceService
|
||||
type DeviceServiceOp struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// List returns devices on a project
|
||||
func (s *DeviceServiceOp) List(projectID string, listOpt *ListOptions) (devices []Device, resp *Response, err error) {
|
||||
params := "include=facility"
|
||||
if listOpt != nil {
|
||||
params = listOpt.createURL()
|
||||
}
|
||||
path := fmt.Sprintf("%s/%s%s?%s", projectBasePath, projectID, deviceBasePath, params)
|
||||
|
||||
for {
|
||||
subset := new(devicesRoot)
|
||||
|
||||
resp, err = s.client.DoRequest("GET", path, nil, subset)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
devices = append(devices, subset.Devices...)
|
||||
|
||||
if subset.Meta.Next != nil && (listOpt == nil || listOpt.Page == 0) {
|
||||
path = subset.Meta.Next.Href
|
||||
if params != "" {
|
||||
path = fmt.Sprintf("%s&%s", path, params)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns a device by id
|
||||
func (s *DeviceServiceOp) Get(deviceID string) (*Device, *Response, error) {
|
||||
return s.GetExtra(deviceID, []string{"facility"}, nil)
|
||||
}
|
||||
|
||||
// GetExtra returns a device by id. Specifying either includes/excludes provides more or less desired
|
||||
// detailed information about resources which would otherwise be represented with an href link
|
||||
func (s *DeviceServiceOp) GetExtra(deviceID string, includes, excludes []string) (*Device, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", deviceBasePath, deviceID)
|
||||
if includes != nil {
|
||||
path += fmt.Sprintf("?include=%s", strings.Join(includes, ","))
|
||||
} else if excludes != nil {
|
||||
path += fmt.Sprintf("?exclude=%s", strings.Join(excludes, ","))
|
||||
}
|
||||
device := new(Device)
|
||||
|
||||
resp, err := s.client.DoRequest("GET", path, nil, device)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return device, resp, err
|
||||
}
|
||||
|
||||
// Create creates a new device
|
||||
func (s *DeviceServiceOp) Create(createRequest *DeviceCreateRequest) (*Device, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s%s", projectBasePath, createRequest.ProjectID, deviceBasePath)
|
||||
device := new(Device)
|
||||
|
||||
resp, err := s.client.DoRequest("POST", path, createRequest, device)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return device, resp, err
|
||||
}
|
||||
|
||||
// Update updates an existing device
|
||||
func (s *DeviceServiceOp) Update(deviceID string, updateRequest *DeviceUpdateRequest) (*Device, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s?include=facility", deviceBasePath, deviceID)
|
||||
device := new(Device)
|
||||
|
||||
resp, err := s.client.DoRequest("PUT", path, updateRequest, device)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return device, resp, err
|
||||
}
|
||||
|
||||
// Delete deletes a device
|
||||
func (s *DeviceServiceOp) Delete(deviceID string) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", deviceBasePath, deviceID)
|
||||
|
||||
return s.client.DoRequest("DELETE", path, nil, nil)
|
||||
}
|
||||
|
||||
// Reboot reboots on a device
|
||||
func (s *DeviceServiceOp) Reboot(deviceID string) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s/actions", deviceBasePath, deviceID)
|
||||
action := &DeviceActionRequest{Type: "reboot"}
|
||||
|
||||
return s.client.DoRequest("POST", path, action, nil)
|
||||
}
|
||||
|
||||
// PowerOff powers on a device
|
||||
func (s *DeviceServiceOp) PowerOff(deviceID string) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s/actions", deviceBasePath, deviceID)
|
||||
action := &DeviceActionRequest{Type: "power_off"}
|
||||
|
||||
return s.client.DoRequest("POST", path, action, nil)
|
||||
}
|
||||
|
||||
// PowerOn powers on a device
|
||||
func (s *DeviceServiceOp) PowerOn(deviceID string) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s/actions", deviceBasePath, deviceID)
|
||||
action := &DeviceActionRequest{Type: "power_on"}
|
||||
|
||||
return s.client.DoRequest("POST", path, action, nil)
|
||||
}
|
||||
|
||||
type lockType struct {
|
||||
Locked bool `json:"locked"`
|
||||
}
|
||||
|
||||
// Lock sets a device to "locked"
|
||||
func (s *DeviceServiceOp) Lock(deviceID string) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", deviceBasePath, deviceID)
|
||||
action := lockType{Locked: true}
|
||||
|
||||
return s.client.DoRequest("PATCH", path, action, nil)
|
||||
}
|
||||
|
||||
// Unlock sets a device to "unlocked"
|
||||
func (s *DeviceServiceOp) Unlock(deviceID string) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", deviceBasePath, deviceID)
|
||||
action := lockType{Locked: false}
|
||||
|
||||
return s.client.DoRequest("PATCH", path, action, nil)
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package packngo
|
||||
|
||||
const emailBasePath = "/emails"
|
||||
|
||||
// EmailService interface defines available email methods
|
||||
type EmailService interface {
|
||||
Get(string) (*Email, *Response, error)
|
||||
}
|
||||
|
||||
// Email represents a user's email address
|
||||
type Email struct {
|
||||
ID string `json:"id"`
|
||||
Address string `json:"address"`
|
||||
Default bool `json:"default,omitempty"`
|
||||
URL string `json:"href,omitempty"`
|
||||
}
|
||||
|
||||
func (e Email) String() string {
|
||||
return Stringify(e)
|
||||
}
|
||||
|
||||
// EmailServiceOp implements EmailService
|
||||
type EmailServiceOp struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Get retrieves an email by id
|
||||
func (s *EmailServiceOp) Get(emailID string) (*Email, *Response, error) {
|
||||
email := new(Email)
|
||||
|
||||
resp, err := s.client.DoRequest("GET", emailBasePath, nil, email)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return email, resp, err
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package packngo
|
||||
|
||||
const facilityBasePath = "/facilities"
|
||||
|
||||
// FacilityService interface defines available facility methods
|
||||
type FacilityService interface {
|
||||
List() ([]Facility, *Response, error)
|
||||
}
|
||||
|
||||
type facilityRoot struct {
|
||||
Facilities []Facility `json:"facilities"`
|
||||
}
|
||||
|
||||
// Facility represents a Packet facility
|
||||
type Facility struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Code string `json:"code,omitempty"`
|
||||
Features []string `json:"features,omitempty"`
|
||||
Address *Address `json:"address,omitempty"`
|
||||
URL string `json:"href,omitempty"`
|
||||
}
|
||||
|
||||
func (f Facility) String() string {
|
||||
return Stringify(f)
|
||||
}
|
||||
|
||||
// Address - the physical address of the facility
|
||||
type Address struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
}
|
||||
|
||||
func (a Address) String() string {
|
||||
return Stringify(a)
|
||||
}
|
||||
|
||||
// FacilityServiceOp implements FacilityService
|
||||
type FacilityServiceOp struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// List returns all available Packet facilities
|
||||
func (s *FacilityServiceOp) List() ([]Facility, *Response, error) {
|
||||
root := new(facilityRoot)
|
||||
|
||||
resp, err := s.client.DoRequest("GET", facilityBasePath, nil, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return root.Facilities, resp, err
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
package packngo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const ipBasePath = "/ips"
|
||||
|
||||
// DeviceIPService handles assignment of addresses from reserved blocks to instances in a project.
|
||||
type DeviceIPService interface {
|
||||
Assign(deviceID string, assignRequest *AddressStruct) (*IPAddressAssignment, *Response, error)
|
||||
Unassign(assignmentID string) (*Response, error)
|
||||
Get(assignmentID string) (*IPAddressAssignment, *Response, error)
|
||||
}
|
||||
|
||||
// ProjectIPService handles reservation of IP address blocks for a project.
|
||||
type ProjectIPService interface {
|
||||
Get(reservationID string) (*IPAddressReservation, *Response, error)
|
||||
List(projectID string) ([]IPAddressReservation, *Response, error)
|
||||
Request(projectID string, ipReservationReq *IPReservationRequest) (*IPAddressReservation, *Response, error)
|
||||
Remove(ipReservationID string) (*Response, error)
|
||||
AvailableAddresses(ipReservationID string, r *AvailableRequest) ([]string, *Response, error)
|
||||
}
|
||||
|
||||
type ipAddressCommon struct {
|
||||
ID string `json:"id"`
|
||||
Address string `json:"address"`
|
||||
Gateway string `json:"gateway"`
|
||||
Network string `json:"network"`
|
||||
AddressFamily int `json:"address_family"`
|
||||
Netmask string `json:"netmask"`
|
||||
Public bool `json:"public"`
|
||||
CIDR int `json:"cidr"`
|
||||
Created string `json:"created_at,omitempty"`
|
||||
Updated string `json:"updated_at,omitempty"`
|
||||
Href string `json:"href"`
|
||||
Management bool `json:"management"`
|
||||
Manageable bool `json:"manageable"`
|
||||
Project Href `json:"project"`
|
||||
}
|
||||
|
||||
// IPAddressReservation is created when user sends IP reservation request for a project (considering it's within quota).
|
||||
type IPAddressReservation struct {
|
||||
ipAddressCommon
|
||||
Assignments []Href `json:"assignments"`
|
||||
Facility Facility `json:"facility,omitempty"`
|
||||
Available string `json:"available"`
|
||||
Addon bool `json:"addon"`
|
||||
Bill bool `json:"bill"`
|
||||
}
|
||||
|
||||
// AvailableResponse is a type for listing of available addresses from a reserved block.
|
||||
type AvailableResponse struct {
|
||||
Available []string `json:"available"`
|
||||
}
|
||||
|
||||
// AvailableRequest is a type for listing available addresses from a reserved block.
|
||||
type AvailableRequest struct {
|
||||
CIDR int `json:"cidr"`
|
||||
}
|
||||
|
||||
// IPAddressAssignment is created when an IP address from reservation block is assigned to a device.
|
||||
type IPAddressAssignment struct {
|
||||
ipAddressCommon
|
||||
AssignedTo Href `json:"assigned_to"`
|
||||
}
|
||||
|
||||
// IPReservationRequest represents the body of a reservation request.
|
||||
type IPReservationRequest struct {
|
||||
Type string `json:"type"`
|
||||
Quantity int `json:"quantity"`
|
||||
Comments string `json:"comments"`
|
||||
Facility string `json:"facility"`
|
||||
}
|
||||
|
||||
// AddressStruct is a helper type for request/response with dict like {"address": ... }
|
||||
type AddressStruct struct {
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
func deleteFromIP(client *Client, resourceID string) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", ipBasePath, resourceID)
|
||||
|
||||
return client.DoRequest("DELETE", path, nil, nil)
|
||||
}
|
||||
|
||||
func (i IPAddressReservation) String() string {
|
||||
return Stringify(i)
|
||||
}
|
||||
|
||||
func (i IPAddressAssignment) String() string {
|
||||
return Stringify(i)
|
||||
}
|
||||
|
||||
// DeviceIPServiceOp is interface for IP-address assignment methods.
|
||||
type DeviceIPServiceOp struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Unassign unassigns an IP address from the device to which it is currently assigned.
|
||||
// This will remove the relationship between an IP and the device and will make the IP
|
||||
// address available to be assigned to another device.
|
||||
func (i *DeviceIPServiceOp) Unassign(assignmentID string) (*Response, error) {
|
||||
return deleteFromIP(i.client, assignmentID)
|
||||
}
|
||||
|
||||
// Assign assigns an IP address to a device.
|
||||
// The IP address must be in one of the IP ranges assigned to the device’s project.
|
||||
func (i *DeviceIPServiceOp) Assign(deviceID string, assignRequest *AddressStruct) (*IPAddressAssignment, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s%s", deviceBasePath, deviceID, ipBasePath)
|
||||
ipa := new(IPAddressAssignment)
|
||||
|
||||
resp, err := i.client.DoRequest("POST", path, assignRequest, ipa)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return ipa, resp, err
|
||||
}
|
||||
|
||||
// Get returns assignment by ID.
|
||||
func (i *DeviceIPServiceOp) Get(assignmentID string) (*IPAddressAssignment, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", ipBasePath, assignmentID)
|
||||
ipa := new(IPAddressAssignment)
|
||||
|
||||
resp, err := i.client.DoRequest("GET", path, nil, ipa)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return ipa, resp, err
|
||||
}
|
||||
|
||||
// ProjectIPServiceOp is interface for IP assignment methods.
|
||||
type ProjectIPServiceOp struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Get returns reservation by ID.
|
||||
func (i *ProjectIPServiceOp) Get(reservationID string) (*IPAddressReservation, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", ipBasePath, reservationID)
|
||||
ipr := new(IPAddressReservation)
|
||||
|
||||
resp, err := i.client.DoRequest("GET", path, nil, ipr)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return ipr, resp, err
|
||||
}
|
||||
|
||||
// List provides a list of IP resevations for a single project.
|
||||
func (i *ProjectIPServiceOp) List(projectID string) ([]IPAddressReservation, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s%s", projectBasePath, projectID, ipBasePath)
|
||||
reservations := new(struct {
|
||||
Reservations []IPAddressReservation `json:"ip_addresses"`
|
||||
})
|
||||
|
||||
resp, err := i.client.DoRequest("GET", path, nil, reservations)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return reservations.Reservations, resp, nil
|
||||
}
|
||||
|
||||
// Request requests more IP space for a project in order to have additional IP addresses to assign to devices.
|
||||
func (i *ProjectIPServiceOp) Request(projectID string, ipReservationReq *IPReservationRequest) (*IPAddressReservation, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s%s", projectBasePath, projectID, ipBasePath)
|
||||
ipr := new(IPAddressReservation)
|
||||
|
||||
resp, err := i.client.DoRequest("POST", path, ipReservationReq, ipr)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return ipr, resp, err
|
||||
}
|
||||
|
||||
// Remove removes an IP reservation from the project.
|
||||
func (i *ProjectIPServiceOp) Remove(ipReservationID string) (*Response, error) {
|
||||
return deleteFromIP(i.client, ipReservationID)
|
||||
}
|
||||
|
||||
// AvailableAddresses lists addresses available from a reserved block
|
||||
func (i *ProjectIPServiceOp) AvailableAddresses(ipReservationID string, r *AvailableRequest) ([]string, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s/available?cidr=%d", ipBasePath, ipReservationID, r.CIDR)
|
||||
ar := new(AvailableResponse)
|
||||
|
||||
resp, err := i.client.DoRequest("GET", path, r, ar)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return ar.Available, resp, nil
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package packngo
|
||||
|
||||
const osBasePath = "/operating-systems"
|
||||
|
||||
// OSService interface defines available operating_systems methods
|
||||
type OSService interface {
|
||||
List() ([]OS, *Response, error)
|
||||
}
|
||||
|
||||
type osRoot struct {
|
||||
OperatingSystems []OS `json:"operating_systems"`
|
||||
}
|
||||
|
||||
// OS represents a Packet operating system
|
||||
type OS struct {
|
||||
Name string `json:"name"`
|
||||
Slug string `json:"slug"`
|
||||
Distro string `json:"distro"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
func (o OS) String() string {
|
||||
return Stringify(o)
|
||||
}
|
||||
|
||||
// OSServiceOp implements OSService
|
||||
type OSServiceOp struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// List returns all available operating systems
|
||||
func (s *OSServiceOp) List() ([]OS, *Response, error) {
|
||||
root := new(osRoot)
|
||||
|
||||
resp, err := s.client.DoRequest("GET", osBasePath, nil, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return root.OperatingSystems, resp, err
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
package packngo
|
||||
|
||||
import "fmt"
|
||||
|
||||
// API documentation https://www.packet.net/developers/api/organizations/
|
||||
const organizationBasePath = "/organizations"
|
||||
|
||||
// OrganizationService interface defines available organization methods
|
||||
type OrganizationService interface {
|
||||
List() ([]Organization, *Response, error)
|
||||
Get(string) (*Organization, *Response, error)
|
||||
Create(*OrganizationCreateRequest) (*Organization, *Response, error)
|
||||
Update(string, *OrganizationUpdateRequest) (*Organization, *Response, error)
|
||||
Delete(string) (*Response, error)
|
||||
ListPaymentMethods(string) ([]PaymentMethod, *Response, error)
|
||||
}
|
||||
|
||||
type organizationsRoot struct {
|
||||
Organizations []Organization `json:"organizations"`
|
||||
}
|
||||
|
||||
// Organization represents a Packet organization
|
||||
type Organization struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Website string `json:"website,omitempty"`
|
||||
Twitter string `json:"twitter,omitempty"`
|
||||
Created string `json:"created_at,omitempty"`
|
||||
Updated string `json:"updated_at,omitempty"`
|
||||
Address Address `json:"address,omitempty"`
|
||||
TaxID string `json:"tax_id,omitempty"`
|
||||
MainPhone string `json:"main_phone,omitempty"`
|
||||
BillingPhone string `json:"billing_phone,omitempty"`
|
||||
CreditAmount float64 `json:"credit_amount,omitempty"`
|
||||
Logo string `json:"logo,omitempty"`
|
||||
LogoThumb string `json:"logo_thumb,omitempty"`
|
||||
Projects []Project `json:"projects,omitempty"`
|
||||
URL string `json:"href,omitempty"`
|
||||
Users []User `json:"members,omitempty"`
|
||||
Owners []User `json:"owners,omitempty"`
|
||||
}
|
||||
|
||||
func (o Organization) String() string {
|
||||
return Stringify(o)
|
||||
}
|
||||
|
||||
// OrganizationCreateRequest type used to create a Packet organization
|
||||
type OrganizationCreateRequest struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Website string `json:"website"`
|
||||
Twitter string `json:"twitter"`
|
||||
Logo string `json:"logo"`
|
||||
}
|
||||
|
||||
func (o OrganizationCreateRequest) String() string {
|
||||
return Stringify(o)
|
||||
}
|
||||
|
||||
// OrganizationUpdateRequest type used to update a Packet organization
|
||||
type OrganizationUpdateRequest struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Website *string `json:"website,omitempty"`
|
||||
Twitter *string `json:"twitter,omitempty"`
|
||||
Logo *string `json:"logo,omitempty"`
|
||||
}
|
||||
|
||||
func (o OrganizationUpdateRequest) String() string {
|
||||
return Stringify(o)
|
||||
}
|
||||
|
||||
// OrganizationServiceOp implements OrganizationService
|
||||
type OrganizationServiceOp struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// List returns the user's organizations
|
||||
func (s *OrganizationServiceOp) List() ([]Organization, *Response, error) {
|
||||
root := new(organizationsRoot)
|
||||
|
||||
resp, err := s.client.DoRequest("GET", organizationBasePath, nil, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return root.Organizations, resp, err
|
||||
}
|
||||
|
||||
// Get returns a organization by id
|
||||
func (s *OrganizationServiceOp) Get(organizationID string) (*Organization, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", organizationBasePath, organizationID)
|
||||
organization := new(Organization)
|
||||
|
||||
resp, err := s.client.DoRequest("GET", path, nil, organization)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return organization, resp, err
|
||||
}
|
||||
|
||||
// Create creates a new organization
|
||||
func (s *OrganizationServiceOp) Create(createRequest *OrganizationCreateRequest) (*Organization, *Response, error) {
|
||||
organization := new(Organization)
|
||||
|
||||
resp, err := s.client.DoRequest("POST", organizationBasePath, createRequest, organization)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return organization, resp, err
|
||||
}
|
||||
|
||||
// Update updates an organization
|
||||
func (s *OrganizationServiceOp) Update(id string, updateRequest *OrganizationUpdateRequest) (*Organization, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", organizationBasePath, id)
|
||||
organization := new(Organization)
|
||||
|
||||
resp, err := s.client.DoRequest("PATCH", path, updateRequest, organization)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return organization, resp, err
|
||||
}
|
||||
|
||||
// Delete deletes an organizationID
|
||||
func (s *OrganizationServiceOp) Delete(organizationID string) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", organizationBasePath, organizationID)
|
||||
|
||||
return s.client.DoRequest("DELETE", path, nil, nil)
|
||||
}
|
||||
|
||||
// ListPaymentMethods returns PaymentMethods for an organization
|
||||
func (s *OrganizationServiceOp) ListPaymentMethods(organizationID string) ([]PaymentMethod, *Response, error) {
|
||||
url := fmt.Sprintf("%s/%s%s", organizationBasePath, organizationID, paymentMethodBasePath)
|
||||
root := new(paymentMethodsRoot)
|
||||
|
||||
resp, err := s.client.DoRequest("GET", url, nil, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return root.PaymentMethods, resp, err
|
||||
}
|
|
@ -0,0 +1,306 @@
|
|||
package packngo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
packetTokenEnvVar = "PACKET_AUTH_TOKEN"
|
||||
libraryVersion = "0.1.0"
|
||||
baseURL = "https://api.packet.net/"
|
||||
userAgent = "packngo/" + libraryVersion
|
||||
mediaType = "application/json"
|
||||
debugEnvVar = "PACKNGO_DEBUG"
|
||||
|
||||
headerRateLimit = "X-RateLimit-Limit"
|
||||
headerRateRemaining = "X-RateLimit-Remaining"
|
||||
headerRateReset = "X-RateLimit-Reset"
|
||||
)
|
||||
|
||||
// ListOptions specifies optional global API parameters
|
||||
type ListOptions struct {
|
||||
// for paginated result sets, page of results to retrieve
|
||||
Page int `url:"page,omitempty"`
|
||||
|
||||
// for paginated result sets, the number of results to return per page
|
||||
PerPage int `url:"per_page,omitempty"`
|
||||
|
||||
// specify which resources you want to return as collections instead of references
|
||||
Includes string
|
||||
}
|
||||
|
||||
func (l *ListOptions) createURL() (url string) {
|
||||
if l.Includes != "" {
|
||||
url += fmt.Sprintf("include=%s", l.Includes)
|
||||
}
|
||||
|
||||
if l.Page != 0 {
|
||||
if url != "" {
|
||||
url += "&"
|
||||
}
|
||||
url += fmt.Sprintf("page=%d", l.Page)
|
||||
}
|
||||
|
||||
if l.PerPage != 0 {
|
||||
if url != "" {
|
||||
url += "&"
|
||||
}
|
||||
url += fmt.Sprintf("per_page=%d", l.PerPage)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// meta contains pagination information
|
||||
type meta struct {
|
||||
Self *Href `json:"self"`
|
||||
First *Href `json:"first"`
|
||||
Last *Href `json:"last"`
|
||||
Previous *Href `json:"previous,omitempty"`
|
||||
Next *Href `json:"next,omitempty"`
|
||||
Total int `json:"total"`
|
||||
CurrentPageNum int `json:"current_page"`
|
||||
LastPageNum int `json:"last_page"`
|
||||
}
|
||||
|
||||
// Response is the http response from api calls
|
||||
type Response struct {
|
||||
*http.Response
|
||||
Rate
|
||||
}
|
||||
|
||||
// Href is an API link
|
||||
type Href struct {
|
||||
Href string `json:"href"`
|
||||
}
|
||||
|
||||
func (r *Response) populateRate() {
|
||||
// parse the rate limit headers and populate Response.Rate
|
||||
if limit := r.Header.Get(headerRateLimit); limit != "" {
|
||||
r.Rate.RequestLimit, _ = strconv.Atoi(limit)
|
||||
}
|
||||
if remaining := r.Header.Get(headerRateRemaining); remaining != "" {
|
||||
r.Rate.RequestsRemaining, _ = strconv.Atoi(remaining)
|
||||
}
|
||||
if reset := r.Header.Get(headerRateReset); reset != "" {
|
||||
if v, _ := strconv.ParseInt(reset, 10, 64); v != 0 {
|
||||
r.Rate.Reset = Timestamp{time.Unix(v, 0)}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorResponse is the http response used on errors
|
||||
type ErrorResponse struct {
|
||||
Response *http.Response
|
||||
Errors []string `json:"errors"`
|
||||
SingleError string `json:"error"`
|
||||
}
|
||||
|
||||
func (r *ErrorResponse) Error() string {
|
||||
return fmt.Sprintf("%v %v: %d %v %v",
|
||||
r.Response.Request.Method, r.Response.Request.URL, r.Response.StatusCode, strings.Join(r.Errors, ", "), r.SingleError)
|
||||
}
|
||||
|
||||
// Client is the base API Client
|
||||
type Client struct {
|
||||
client *http.Client
|
||||
debug bool
|
||||
|
||||
BaseURL *url.URL
|
||||
|
||||
UserAgent string
|
||||
ConsumerToken string
|
||||
APIKey string
|
||||
|
||||
RateLimit Rate
|
||||
|
||||
// Packet Api Objects
|
||||
Plans PlanService
|
||||
Users UserService
|
||||
Emails EmailService
|
||||
SSHKeys SSHKeyService
|
||||
Devices DeviceService
|
||||
Projects ProjectService
|
||||
Facilities FacilityService
|
||||
OperatingSystems OSService
|
||||
DeviceIPs DeviceIPService
|
||||
DevicePorts DevicePortService
|
||||
ProjectIPs ProjectIPService
|
||||
ProjectVirtualNetworks ProjectVirtualNetworkService
|
||||
Volumes VolumeService
|
||||
VolumeAttachments VolumeAttachmentService
|
||||
SpotMarket SpotMarketService
|
||||
Organizations OrganizationService
|
||||
}
|
||||
|
||||
// NewRequest inits a new http request with the proper headers
|
||||
func (c *Client) NewRequest(method, path string, body interface{}) (*http.Request, error) {
|
||||
// relative path to append to the endpoint url, no leading slash please
|
||||
rel, err := url.Parse(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
u := c.BaseURL.ResolveReference(rel)
|
||||
|
||||
// json encode the request body, if any
|
||||
buf := new(bytes.Buffer)
|
||||
if body != nil {
|
||||
err := json.NewEncoder(buf).Encode(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, u.String(), buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Close = true
|
||||
|
||||
req.Header.Add("X-Auth-Token", c.APIKey)
|
||||
req.Header.Add("X-Consumer-Token", c.ConsumerToken)
|
||||
|
||||
req.Header.Add("Content-Type", mediaType)
|
||||
req.Header.Add("Accept", mediaType)
|
||||
req.Header.Add("User-Agent", userAgent)
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// Do executes the http request
|
||||
func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) {
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
response := Response{Response: resp}
|
||||
response.populateRate()
|
||||
if c.debug {
|
||||
o, _ := httputil.DumpResponse(response.Response, true)
|
||||
log.Printf("\n=======[RESPONSE]============\n%s\n\n", string(o))
|
||||
}
|
||||
c.RateLimit = response.Rate
|
||||
|
||||
err = checkResponse(resp)
|
||||
// if the response is an error, return the ErrorResponse
|
||||
if err != nil {
|
||||
return &response, err
|
||||
}
|
||||
|
||||
if v != nil {
|
||||
// if v implements the io.Writer interface, return the raw response
|
||||
if w, ok := v.(io.Writer); ok {
|
||||
io.Copy(w, resp.Body)
|
||||
} else {
|
||||
err = json.NewDecoder(resp.Body).Decode(v)
|
||||
if err != nil {
|
||||
return &response, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &response, err
|
||||
}
|
||||
|
||||
// DoRequest is a convenience method, it calls NewRequest followed by Do
|
||||
// v is the interface to unmarshal the response JSON into
|
||||
func (c *Client) DoRequest(method, path string, body, v interface{}) (*Response, error) {
|
||||
req, err := c.NewRequest(method, path, body)
|
||||
if c.debug {
|
||||
o, _ := httputil.DumpRequestOut(req, true)
|
||||
log.Printf("\n=======[REQUEST]=============\n%s\n", string(o))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Do(req, v)
|
||||
}
|
||||
|
||||
func NewClient() (*Client, error) {
|
||||
apiToken := os.Getenv(packetTokenEnvVar)
|
||||
if apiToken == "" {
|
||||
return nil, fmt.Errorf("you must export %s.", packetTokenEnvVar)
|
||||
}
|
||||
c := NewClientWithAuth("packngo lib", apiToken, nil)
|
||||
return c, nil
|
||||
|
||||
}
|
||||
|
||||
// NewClientWithAuth initializes and returns a Client, use this to get an API Client to operate on
|
||||
// N.B.: Packet's API certificate requires Go 1.5+ to successfully parse. If you are using
|
||||
// an older version of Go, pass in a custom http.Client with a custom TLS configuration
|
||||
// that sets "InsecureSkipVerify" to "true"
|
||||
func NewClientWithAuth(consumerToken string, apiKey string, httpClient *http.Client) *Client {
|
||||
client, _ := NewClientWithBaseURL(consumerToken, apiKey, httpClient, baseURL)
|
||||
return client
|
||||
}
|
||||
|
||||
// NewClientWithBaseURL returns a Client pointing to nonstandard API URL, e.g.
|
||||
// for mocking the remote API
|
||||
func NewClientWithBaseURL(consumerToken string, apiKey string, httpClient *http.Client, apiBaseURL string) (*Client, error) {
|
||||
if httpClient == nil {
|
||||
// Don't fall back on http.DefaultClient as it's not nice to adjust state
|
||||
// implicitly. If the client wants to use http.DefaultClient, they can
|
||||
// pass it in explicitly.
|
||||
httpClient = &http.Client{}
|
||||
}
|
||||
|
||||
u, err := url.Parse(apiBaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := &Client{client: httpClient, BaseURL: u, UserAgent: userAgent, ConsumerToken: consumerToken, APIKey: apiKey}
|
||||
c.debug = os.Getenv(debugEnvVar) != ""
|
||||
c.Plans = &PlanServiceOp{client: c}
|
||||
c.Organizations = &OrganizationServiceOp{client: c}
|
||||
c.Users = &UserServiceOp{client: c}
|
||||
c.Emails = &EmailServiceOp{client: c}
|
||||
c.SSHKeys = &SSHKeyServiceOp{client: c}
|
||||
c.Devices = &DeviceServiceOp{client: c}
|
||||
c.Projects = &ProjectServiceOp{client: c}
|
||||
c.Facilities = &FacilityServiceOp{client: c}
|
||||
c.OperatingSystems = &OSServiceOp{client: c}
|
||||
c.DeviceIPs = &DeviceIPServiceOp{client: c}
|
||||
c.DevicePorts = &DevicePortServiceOp{client: c}
|
||||
c.ProjectVirtualNetworks = &ProjectVirtualNetworkServiceOp{client: c}
|
||||
c.ProjectIPs = &ProjectIPServiceOp{client: c}
|
||||
c.Volumes = &VolumeServiceOp{client: c}
|
||||
c.VolumeAttachments = &VolumeAttachmentServiceOp{client: c}
|
||||
c.SpotMarket = &SpotMarketServiceOp{client: c}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func checkResponse(r *http.Response) error {
|
||||
// return if http status code is within 200 range
|
||||
if c := r.StatusCode; c >= 200 && c <= 299 {
|
||||
// response is good, return
|
||||
return nil
|
||||
}
|
||||
|
||||
errorResponse := &ErrorResponse{Response: r}
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
// if the response has a body, populate the message in errorResponse
|
||||
if err == nil && len(data) > 0 {
|
||||
json.Unmarshal(data, errorResponse)
|
||||
}
|
||||
|
||||
return errorResponse
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package packngo
|
||||
|
||||
// API documentation https://www.packet.net/developers/api/paymentmethods/
|
||||
const paymentMethodBasePath = "/payment-methods"
|
||||
|
||||
// ProjectService interface defines available project methods
|
||||
type PaymentMethodService interface {
|
||||
List() ([]PaymentMethod, *Response, error)
|
||||
Get(string) (*PaymentMethod, *Response, error)
|
||||
Create(*PaymentMethodCreateRequest) (*PaymentMethod, *Response, error)
|
||||
Update(string, *PaymentMethodUpdateRequest) (*PaymentMethod, *Response, error)
|
||||
Delete(string) (*Response, error)
|
||||
}
|
||||
|
||||
type paymentMethodsRoot struct {
|
||||
PaymentMethods []PaymentMethod `json:"payment_methods"`
|
||||
}
|
||||
|
||||
// PaymentMethod represents a Packet payment method of an organization
|
||||
type PaymentMethod struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Created string `json:"created_at,omitempty"`
|
||||
Updated string `json:"updated_at,omitempty"`
|
||||
Nonce string `json:"nonce,omitempty"`
|
||||
Default bool `json:"default,omitempty"`
|
||||
Organization Organization `json:"organization,omitempty"`
|
||||
Projects []Project `json:"projects,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
CardholderName string `json:"cardholder_name,omitempty"`
|
||||
ExpMonth string `json:"expiration_month,omitempty"`
|
||||
ExpYear string `json:"expiration_year,omitempty"`
|
||||
Last4 string `json:"last_4,omitempty"`
|
||||
BillingAddress BillingAddress `json:"billing_address,omitempty"`
|
||||
URL string `json:"href,omitempty"`
|
||||
}
|
||||
|
||||
func (pm PaymentMethod) String() string {
|
||||
return Stringify(pm)
|
||||
}
|
||||
|
||||
// PaymentMethodCreateRequest type used to create a Packet payment method of an organization
|
||||
type PaymentMethodCreateRequest struct {
|
||||
Name string `json:"name"`
|
||||
Nonce string `json:"name"`
|
||||
CardholderName string `json:"cardholder_name,omitempty"`
|
||||
ExpMonth string `json:"expiration_month,omitempty"`
|
||||
ExpYear string `json:"expiration_year,omitempty"`
|
||||
BillingAddress string `json:"billing_address,omitempty"`
|
||||
}
|
||||
|
||||
func (pm PaymentMethodCreateRequest) String() string {
|
||||
return Stringify(pm)
|
||||
}
|
||||
|
||||
// PaymentMethodUpdateRequest type used to update a Packet payment method of an organization
|
||||
type PaymentMethodUpdateRequest struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
CardholderName *string `json:"cardholder_name,omitempty"`
|
||||
ExpMonth *string `json:"expiration_month,omitempty"`
|
||||
ExpYear *string `json:"expiration_year,omitempty"`
|
||||
BillingAddress *string `json:"billing_address,omitempty"`
|
||||
}
|
||||
|
||||
func (pm PaymentMethodUpdateRequest) String() string {
|
||||
return Stringify(pm)
|
||||
}
|
||||
|
||||
// PaymentMethodServiceOp implements PaymentMethodService
|
||||
type PaymentMethodServiceOp struct {
|
||||
client *Client
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
package packngo
|
||||
|
||||
const planBasePath = "/plans"
|
||||
|
||||
// PlanService interface defines available plan methods
|
||||
type PlanService interface {
|
||||
List() ([]Plan, *Response, error)
|
||||
}
|
||||
|
||||
type planRoot struct {
|
||||
Plans []Plan `json:"plans"`
|
||||
}
|
||||
|
||||
// Plan represents a Packet service plan
|
||||
type Plan struct {
|
||||
ID string `json:"id"`
|
||||
Slug string `json:"slug,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Line string `json:"line,omitempty"`
|
||||
Specs *Specs `json:"specs,omitempty"`
|
||||
Pricing *Pricing `json:"pricing,omitempty"`
|
||||
}
|
||||
|
||||
func (p Plan) String() string {
|
||||
return Stringify(p)
|
||||
}
|
||||
|
||||
// Specs - the server specs for a plan
|
||||
type Specs struct {
|
||||
Cpus []*Cpus `json:"cpus,omitempty"`
|
||||
Memory *Memory `json:"memory,omitempty"`
|
||||
Drives []*Drives `json:"drives,omitempty"`
|
||||
Nics []*Nics `json:"nics,omitempty"`
|
||||
Features *Features `json:"features,omitempty"`
|
||||
}
|
||||
|
||||
func (s Specs) String() string {
|
||||
return Stringify(s)
|
||||
}
|
||||
|
||||
// Cpus - the CPU config details for specs on a plan
|
||||
type Cpus struct {
|
||||
Count int `json:"count,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
func (c Cpus) String() string {
|
||||
return Stringify(c)
|
||||
}
|
||||
|
||||
// Memory - the RAM config details for specs on a plan
|
||||
type Memory struct {
|
||||
Total string `json:"total,omitempty"`
|
||||
}
|
||||
|
||||
func (m Memory) String() string {
|
||||
return Stringify(m)
|
||||
}
|
||||
|
||||
// Drives - the storage config details for specs on a plan
|
||||
type Drives struct {
|
||||
Count int `json:"count,omitempty"`
|
||||
Size string `json:"size,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
func (d Drives) String() string {
|
||||
return Stringify(d)
|
||||
}
|
||||
|
||||
// Nics - the network hardware details for specs on a plan
|
||||
type Nics struct {
|
||||
Count int `json:"count,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
func (n Nics) String() string {
|
||||
return Stringify(n)
|
||||
}
|
||||
|
||||
// Features - other features in the specs for a plan
|
||||
type Features struct {
|
||||
Raid bool `json:"raid,omitempty"`
|
||||
Txt bool `json:"txt,omitempty"`
|
||||
}
|
||||
|
||||
func (f Features) String() string {
|
||||
return Stringify(f)
|
||||
}
|
||||
|
||||
// Pricing - the pricing options on a plan
|
||||
type Pricing struct {
|
||||
Hourly float32 `json:"hourly,omitempty"`
|
||||
Monthly float32 `json:"monthly,omitempty"`
|
||||
}
|
||||
|
||||
func (p Pricing) String() string {
|
||||
return Stringify(p)
|
||||
}
|
||||
|
||||
// PlanServiceOp implements PlanService
|
||||
type PlanServiceOp struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// List method returns all available plans
|
||||
func (s *PlanServiceOp) List() ([]Plan, *Response, error) {
|
||||
root := new(planRoot)
|
||||
|
||||
resp, err := s.client.DoRequest("GET", planBasePath, nil, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return root.Plans, resp, err
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
package packngo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const portBasePath = "/ports"
|
||||
|
||||
type NetworkType int
|
||||
|
||||
const (
|
||||
NetworkL3 NetworkType = iota
|
||||
NetworkHybrid
|
||||
NetworkL2Bonded
|
||||
NetworkL2Individual
|
||||
NetworkUnknown
|
||||
)
|
||||
|
||||
// DevicePortService handles operations on a port which belongs to a particular device
|
||||
type DevicePortService interface {
|
||||
Assign(*PortAssignRequest) (*Port, *Response, error)
|
||||
Unassign(*PortAssignRequest) (*Port, *Response, error)
|
||||
Bond(*BondRequest) (*Port, *Response, error)
|
||||
Disbond(*DisbondRequest) (*Port, *Response, error)
|
||||
PortToLayerTwo(string) (*Port, *Response, error)
|
||||
PortToLayerThree(string) (*Port, *Response, error)
|
||||
DeviceToLayerTwo(string) (*Device, error)
|
||||
DeviceToLayerThree(string) (*Device, error)
|
||||
DeviceNetworkType(string) (NetworkType, error)
|
||||
GetBondPort(string) (*Port, error)
|
||||
GetPortByName(string, string) (*Port, error)
|
||||
}
|
||||
|
||||
type PortData struct {
|
||||
MAC string `json:"mac"`
|
||||
Bonded bool `json:"bonded"`
|
||||
}
|
||||
|
||||
type Port struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Data PortData `json:"data"`
|
||||
AttachedVirtualNetworks []VirtualNetwork `json:"virtual_networks"`
|
||||
}
|
||||
|
||||
type AddressRequest struct {
|
||||
AddressFamily int `json:"address_family"`
|
||||
Public bool `json:"public"`
|
||||
}
|
||||
|
||||
type BackToL3Request struct {
|
||||
RequestIPs []AddressRequest `json:"request_ips"`
|
||||
}
|
||||
|
||||
type DevicePortServiceOp struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
type PortAssignRequest struct {
|
||||
PortID string `json:"id"`
|
||||
VirtualNetworkID string `json:"vnid"`
|
||||
}
|
||||
|
||||
type BondRequest struct {
|
||||
PortID string `json:"id"`
|
||||
BulkEnable bool `json:"bulk_enable"`
|
||||
}
|
||||
|
||||
type DisbondRequest struct {
|
||||
PortID string `json:"id"`
|
||||
BulkDisable bool `json:"bulk_disable"`
|
||||
}
|
||||
|
||||
func (i *DevicePortServiceOp) GetBondPort(deviceID string) (*Port, error) {
|
||||
device, _, err := i.client.Devices.Get(deviceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, port := range device.NetworkPorts {
|
||||
if port.Type == "NetworkBondPort" {
|
||||
return &port, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("No bonded port found in device %s", deviceID)
|
||||
}
|
||||
|
||||
func (i *DevicePortServiceOp) GetPortByName(deviceID, name string) (*Port, error) {
|
||||
device, _, err := i.client.Devices.Get(deviceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, port := range device.NetworkPorts {
|
||||
if port.Name == name {
|
||||
return &port, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Port %s not found in device %s", name, deviceID)
|
||||
}
|
||||
|
||||
func (i *DevicePortServiceOp) Assign(par *PortAssignRequest) (*Port, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s/assign", portBasePath, par.PortID)
|
||||
return i.portAction(path, par)
|
||||
}
|
||||
|
||||
func (i *DevicePortServiceOp) Unassign(par *PortAssignRequest) (*Port, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s/unassign", portBasePath, par.PortID)
|
||||
return i.portAction(path, par)
|
||||
}
|
||||
|
||||
func (i *DevicePortServiceOp) Bond(br *BondRequest) (*Port, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s/bond", portBasePath, br.PortID)
|
||||
return i.portAction(path, br)
|
||||
}
|
||||
|
||||
func (i *DevicePortServiceOp) Disbond(dr *DisbondRequest) (*Port, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s/disbond", portBasePath, dr.PortID)
|
||||
return i.portAction(path, dr)
|
||||
}
|
||||
|
||||
func (i *DevicePortServiceOp) portAction(path string, req interface{}) (*Port, *Response, error) {
|
||||
port := new(Port)
|
||||
|
||||
resp, err := i.client.DoRequest("POST", path, req, port)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return port, resp, err
|
||||
}
|
||||
|
||||
func (i *DevicePortServiceOp) PortToLayerTwo(portID string) (*Port, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s/convert/layer-2", portBasePath, portID)
|
||||
port := new(Port)
|
||||
|
||||
resp, err := i.client.DoRequest("POST", path, nil, port)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return port, resp, err
|
||||
}
|
||||
|
||||
func (i *DevicePortServiceOp) PortToLayerThree(portID string) (*Port, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s/convert/layer-3", portBasePath, portID)
|
||||
port := new(Port)
|
||||
|
||||
req := BackToL3Request{
|
||||
RequestIPs: []AddressRequest{
|
||||
AddressRequest{AddressFamily: 4, Public: true},
|
||||
AddressRequest{AddressFamily: 4, Public: false},
|
||||
AddressRequest{AddressFamily: 6, Public: true},
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := i.client.DoRequest("POST", path, &req, port)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return port, resp, err
|
||||
}
|
||||
|
||||
func (i *DevicePortServiceOp) DeviceNetworkType(deviceID string) (NetworkType, error) {
|
||||
d, _, err := i.client.Devices.Get(deviceID)
|
||||
if err != nil {
|
||||
return NetworkUnknown, err
|
||||
}
|
||||
if d.Plan.Slug == "baremetal_0" || d.Plan.Slug == "baremetal_1" {
|
||||
return NetworkL3, nil
|
||||
}
|
||||
if d.Plan.Slug == "baremetal_1e" {
|
||||
return NetworkHybrid, nil
|
||||
}
|
||||
if len(d.NetworkPorts) < 1 {
|
||||
// really?
|
||||
return NetworkL2Individual, nil
|
||||
}
|
||||
if d.NetworkPorts[0].Data.Bonded {
|
||||
if d.NetworkPorts[2].Data.Bonded {
|
||||
for _, ip := range d.Network {
|
||||
if ip.Management {
|
||||
return NetworkL3, nil
|
||||
}
|
||||
}
|
||||
return NetworkL2Bonded, nil
|
||||
} else {
|
||||
return NetworkHybrid, nil
|
||||
}
|
||||
}
|
||||
return NetworkL2Individual, nil
|
||||
}
|
||||
|
||||
func (i *DevicePortServiceOp) DeviceToLayerThree(deviceID string) (*Device, error) {
|
||||
// hopefull all the VLANs are unassigned at this point
|
||||
bond0, err := i.client.DevicePorts.GetBondPort(deviceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bond0, _, err = i.client.DevicePorts.PortToLayerThree(bond0.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d, _, err := i.client.Devices.Get(deviceID)
|
||||
return d, err
|
||||
}
|
||||
|
||||
// DeviceToLayerTwo converts device to L2 networking. Use bond0 to attach VLAN.
|
||||
func (i *DevicePortServiceOp) DeviceToLayerTwo(deviceID string) (*Device, error) {
|
||||
bond0, err := i.client.DevicePorts.GetBondPort(deviceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bond0, _, err = i.client.DevicePorts.PortToLayerTwo(bond0.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d, _, err := i.client.Devices.Get(deviceID)
|
||||
return d, err
|
||||
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
package packngo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const projectBasePath = "/projects"
|
||||
|
||||
// ProjectService interface defines available project methods
|
||||
type ProjectService interface {
|
||||
List(listOpt *ListOptions) ([]Project, *Response, error)
|
||||
Get(string) (*Project, *Response, error)
|
||||
GetExtra(projectID string, includes, excludes []string) (*Project, *Response, error)
|
||||
Create(*ProjectCreateRequest) (*Project, *Response, error)
|
||||
Update(string, *ProjectUpdateRequest) (*Project, *Response, error)
|
||||
Delete(string) (*Response, error)
|
||||
}
|
||||
|
||||
type projectsRoot struct {
|
||||
Projects []Project `json:"projects"`
|
||||
Meta meta `json:"meta"`
|
||||
}
|
||||
|
||||
// Project represents a Packet project
|
||||
type Project struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Organization Organization `json:"organization,omitempty"`
|
||||
Created string `json:"created_at,omitempty"`
|
||||
Updated string `json:"updated_at,omitempty"`
|
||||
Users []User `json:"members,omitempty"`
|
||||
Devices []Device `json:"devices,omitempty"`
|
||||
SSHKeys []SSHKey `json:"ssh_keys,omitempty"`
|
||||
URL string `json:"href,omitempty"`
|
||||
PaymentMethod PaymentMethod `json:"payment_method,omitempty"`
|
||||
}
|
||||
|
||||
func (p Project) String() string {
|
||||
return Stringify(p)
|
||||
}
|
||||
|
||||
// ProjectCreateRequest type used to create a Packet project
|
||||
type ProjectCreateRequest struct {
|
||||
Name string `json:"name"`
|
||||
PaymentMethodID string `json:"payment_method_id,omitempty"`
|
||||
OrganizationID string `json:"organization_id,omitempty"`
|
||||
}
|
||||
|
||||
func (p ProjectCreateRequest) String() string {
|
||||
return Stringify(p)
|
||||
}
|
||||
|
||||
// ProjectUpdateRequest type used to update a Packet project
|
||||
type ProjectUpdateRequest struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
PaymentMethodID *string `json:"payment_method_id,omitempty"`
|
||||
}
|
||||
|
||||
func (p ProjectUpdateRequest) String() string {
|
||||
return Stringify(p)
|
||||
}
|
||||
|
||||
// ProjectServiceOp implements ProjectService
|
||||
type ProjectServiceOp struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// List returns the user's projects
|
||||
func (s *ProjectServiceOp) List(listOpt *ListOptions) (projects []Project, resp *Response, err error) {
|
||||
var params string
|
||||
if listOpt != nil {
|
||||
params = listOpt.createURL()
|
||||
}
|
||||
root := new(projectsRoot)
|
||||
|
||||
path := fmt.Sprintf("%s?%s", projectBasePath, params)
|
||||
|
||||
for {
|
||||
resp, err = s.client.DoRequest("GET", path, nil, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
projects = append(projects, root.Projects...)
|
||||
|
||||
if root.Meta.Next != nil && (listOpt == nil || listOpt.Page == 0) {
|
||||
path = root.Meta.Next.Href
|
||||
if params != "" {
|
||||
path = fmt.Sprintf("%s&%s", path, params)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// GetExtra returns a project by id with extra information
|
||||
func (s *ProjectServiceOp) GetExtra(projectID string, includes, excludes []string) (*Project, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", projectBasePath, projectID)
|
||||
if includes != nil {
|
||||
path += fmt.Sprintf("?include=%s", strings.Join(includes, ","))
|
||||
} else if excludes != nil {
|
||||
path += fmt.Sprintf("?exclude=%s", strings.Join(excludes, ","))
|
||||
}
|
||||
|
||||
project := new(Project)
|
||||
resp, err := s.client.DoRequest("GET", path, nil, project)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return project, resp, err
|
||||
}
|
||||
|
||||
// Get returns a project by id
|
||||
func (s *ProjectServiceOp) Get(projectID string) (*Project, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", projectBasePath, projectID)
|
||||
project := new(Project)
|
||||
|
||||
resp, err := s.client.DoRequest("GET", path, nil, project)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return project, resp, err
|
||||
}
|
||||
|
||||
// Create creates a new project
|
||||
func (s *ProjectServiceOp) Create(createRequest *ProjectCreateRequest) (*Project, *Response, error) {
|
||||
project := new(Project)
|
||||
|
||||
resp, err := s.client.DoRequest("POST", projectBasePath, createRequest, project)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return project, resp, err
|
||||
}
|
||||
|
||||
// Update updates a project
|
||||
func (s *ProjectServiceOp) Update(id string, updateRequest *ProjectUpdateRequest) (*Project, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", projectBasePath, id)
|
||||
project := new(Project)
|
||||
|
||||
resp, err := s.client.DoRequest("PATCH", path, updateRequest, project)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return project, resp, err
|
||||
}
|
||||
|
||||
// Delete deletes a project
|
||||
func (s *ProjectServiceOp) Delete(projectID string) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", projectBasePath, projectID)
|
||||
|
||||
return s.client.DoRequest("DELETE", path, nil, nil)
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package packngo
|
||||
|
||||
// Rate provides the API request rate limit details
|
||||
type Rate struct {
|
||||
RequestLimit int `json:"request_limit"`
|
||||
RequestsRemaining int `json:"requests_remaining"`
|
||||
Reset Timestamp `json:"rate_reset"`
|
||||
}
|
||||
|
||||
func (r Rate) String() string {
|
||||
return Stringify(r)
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package packngo
|
||||
|
||||
const spotMarketBasePath = "/market/spot/prices"
|
||||
|
||||
// SpotMarketService expooses Spot Market methods
|
||||
type SpotMarketService interface {
|
||||
Prices() (PriceMap, *Response, error)
|
||||
}
|
||||
|
||||
// SpotMarketServiceOp implements SpotMarketService
|
||||
type SpotMarketServiceOp struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// PriceMap is a map of [facility][plan]-> float Price
|
||||
type PriceMap map[string]map[string]float64
|
||||
|
||||
// Prices gets current PriceMap from the API
|
||||
func (s *SpotMarketServiceOp) Prices() (PriceMap, *Response, error) {
|
||||
root := new(struct {
|
||||
SMPs map[string]map[string]struct {
|
||||
Price float64 `json:"price"`
|
||||
} `json:"spot_market_prices"`
|
||||
})
|
||||
|
||||
resp, err := s.client.DoRequest("GET", spotMarketBasePath, nil, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
prices := make(PriceMap)
|
||||
for facility, planMap := range root.SMPs {
|
||||
prices[facility] = map[string]float64{}
|
||||
for plan, v := range planMap {
|
||||
prices[facility][plan] = v.Price
|
||||
}
|
||||
}
|
||||
return prices, resp, err
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
package packngo
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
sshKeyBasePath = "/ssh-keys"
|
||||
)
|
||||
|
||||
// SSHKeyService interface defines available device methods
|
||||
type SSHKeyService interface {
|
||||
List() ([]SSHKey, *Response, error)
|
||||
ProjectList(string) ([]SSHKey, *Response, error)
|
||||
Get(string) (*SSHKey, *Response, error)
|
||||
Create(*SSHKeyCreateRequest) (*SSHKey, *Response, error)
|
||||
Update(string, *SSHKeyUpdateRequest) (*SSHKey, *Response, error)
|
||||
Delete(string) (*Response, error)
|
||||
}
|
||||
|
||||
type sshKeyRoot struct {
|
||||
SSHKeys []SSHKey `json:"ssh_keys"`
|
||||
}
|
||||
|
||||
// SSHKey represents a user's ssh key
|
||||
type SSHKey struct {
|
||||
ID string `json:"id"`
|
||||
Label string `json:"label"`
|
||||
Key string `json:"key"`
|
||||
FingerPrint string `json:"fingerprint"`
|
||||
Created string `json:"created_at"`
|
||||
Updated string `json:"updated_at"`
|
||||
User User `json:"user,omitempty"`
|
||||
URL string `json:"href,omitempty"`
|
||||
}
|
||||
|
||||
func (s SSHKey) String() string {
|
||||
return Stringify(s)
|
||||
}
|
||||
|
||||
// SSHKeyCreateRequest type used to create an ssh key
|
||||
type SSHKeyCreateRequest struct {
|
||||
Label string `json:"label"`
|
||||
Key string `json:"key"`
|
||||
ProjectID string `json:"-"`
|
||||
}
|
||||
|
||||
func (s SSHKeyCreateRequest) String() string {
|
||||
return Stringify(s)
|
||||
}
|
||||
|
||||
// SSHKeyUpdateRequest type used to update an ssh key
|
||||
type SSHKeyUpdateRequest struct {
|
||||
Label *string `json:"label,omitempty"`
|
||||
Key *string `json:"key,omitempty"`
|
||||
}
|
||||
|
||||
func (s SSHKeyUpdateRequest) String() string {
|
||||
return Stringify(s)
|
||||
}
|
||||
|
||||
// SSHKeyServiceOp implements SSHKeyService
|
||||
type SSHKeyServiceOp struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
func (s *SSHKeyServiceOp) list(url string) ([]SSHKey, *Response, error) {
|
||||
root := new(sshKeyRoot)
|
||||
|
||||
resp, err := s.client.DoRequest("GET", url, nil, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return root.SSHKeys, resp, err
|
||||
}
|
||||
|
||||
// ProjectList lists ssh keys of a project
|
||||
func (s *SSHKeyServiceOp) ProjectList(projectID string) ([]SSHKey, *Response, error) {
|
||||
return s.list(fmt.Sprintf("%s/%s%s", projectBasePath, projectID, sshKeyBasePath))
|
||||
|
||||
}
|
||||
|
||||
// List returns a user's ssh keys
|
||||
func (s *SSHKeyServiceOp) List() ([]SSHKey, *Response, error) {
|
||||
return s.list(sshKeyBasePath)
|
||||
}
|
||||
|
||||
// Get returns an ssh key by id
|
||||
func (s *SSHKeyServiceOp) Get(sshKeyID string) (*SSHKey, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", sshKeyBasePath, sshKeyID)
|
||||
sshKey := new(SSHKey)
|
||||
|
||||
resp, err := s.client.DoRequest("GET", path, nil, sshKey)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return sshKey, resp, err
|
||||
}
|
||||
|
||||
// Create creates a new ssh key
|
||||
func (s *SSHKeyServiceOp) Create(createRequest *SSHKeyCreateRequest) (*SSHKey, *Response, error) {
|
||||
path := sshKeyBasePath
|
||||
if createRequest.ProjectID != "" {
|
||||
path = fmt.Sprintf("%s/%s%s", projectBasePath, createRequest.ProjectID, sshKeyBasePath)
|
||||
}
|
||||
sshKey := new(SSHKey)
|
||||
|
||||
resp, err := s.client.DoRequest("POST", path, createRequest, sshKey)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return sshKey, resp, err
|
||||
}
|
||||
|
||||
// Update updates an ssh key
|
||||
func (s *SSHKeyServiceOp) Update(id string, updateRequest *SSHKeyUpdateRequest) (*SSHKey, *Response, error) {
|
||||
if updateRequest.Label == nil && updateRequest.Key == nil {
|
||||
return nil, nil, fmt.Errorf("You must set either Label or Key string for SSH Key update")
|
||||
}
|
||||
path := fmt.Sprintf("%s/%s", sshKeyBasePath, id)
|
||||
|
||||
sshKey := new(SSHKey)
|
||||
|
||||
resp, err := s.client.DoRequest("PATCH", path, updateRequest, sshKey)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return sshKey, resp, err
|
||||
}
|
||||
|
||||
// Delete deletes an ssh key
|
||||
func (s *SSHKeyServiceOp) Delete(sshKeyID string) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", sshKeyBasePath, sshKeyID)
|
||||
|
||||
return s.client.DoRequest("DELETE", path, nil, nil)
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package packngo
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Timestamp represents a time that can be unmarshalled from a JSON string
|
||||
// formatted as either an RFC3339 or Unix timestamp. All
|
||||
// exported methods of time.Time can be called on Timestamp.
|
||||
type Timestamp struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
func (t Timestamp) String() string {
|
||||
return t.Time.String()
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
// Time is expected in RFC3339 or Unix format.
|
||||
func (t *Timestamp) UnmarshalJSON(data []byte) (err error) {
|
||||
str := string(data)
|
||||
i, err := strconv.ParseInt(str, 10, 64)
|
||||
if err == nil {
|
||||
t.Time = time.Unix(i, 0)
|
||||
} else {
|
||||
t.Time, err = time.Parse(`"`+time.RFC3339+`"`, str)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Equal reports whether t and u are equal based on time.Equal
|
||||
func (t Timestamp) Equal(u Timestamp) bool {
|
||||
return t.Time.Equal(u.Time)
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package packngo
|
||||
|
||||
const userBasePath = "/users"
|
||||
const userPath = "/user"
|
||||
|
||||
// UserService interface defines available user methods
|
||||
type UserService interface {
|
||||
Get(string) (*User, *Response, error)
|
||||
Current() (*User, *Response, error)
|
||||
}
|
||||
|
||||
// User represents a Packet user
|
||||
type User struct {
|
||||
ID string `json:"id"`
|
||||
FirstName string `json:"first_name,omitempty"`
|
||||
LastName string `json:"last_name,omitempty"`
|
||||
FullName string `json:"full_name,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
TwoFactor string `json:"two_factor_auth,omitempty"`
|
||||
DefaultOrganizationID string `json:"default_organization_id,omitempty"`
|
||||
AvatarURL string `json:"avatar_url,omitempty"`
|
||||
Facebook string `json:"twitter,omitempty"`
|
||||
Twitter string `json:"facebook,omitempty"`
|
||||
LinkedIn string `json:"linkedin,omitempty"`
|
||||
Created string `json:"created_at,omitempty"`
|
||||
Updated string `json:"updated_at,omitempty"`
|
||||
TimeZone string `json:"timezone,omitempty"`
|
||||
Emails []Email `json:"emails,omitempty"`
|
||||
PhoneNumber string `json:"phone_number,omitempty"`
|
||||
URL string `json:"href,omitempty"`
|
||||
}
|
||||
|
||||
func (u User) String() string {
|
||||
return Stringify(u)
|
||||
}
|
||||
|
||||
// UserServiceOp implements UserService
|
||||
type UserServiceOp struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Get method gets a user by userID
|
||||
func (s *UserServiceOp) Get(userID string) (*User, *Response, error) {
|
||||
user := new(User)
|
||||
|
||||
resp, err := s.client.DoRequest("GET", userBasePath, nil, user)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return user, resp, err
|
||||
}
|
||||
|
||||
// Returns the user object for the currently logged-in user.
|
||||
func (s *UserServiceOp) Current() (*User, *Response, error) {
|
||||
user := new(User)
|
||||
|
||||
resp, err := s.client.DoRequest("GET", userPath, nil, user)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return user, resp, err
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
package packngo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var timestampType = reflect.TypeOf(Timestamp{})
|
||||
|
||||
// Stringify creates a string representation of the provided message
|
||||
func Stringify(message interface{}) string {
|
||||
var buf bytes.Buffer
|
||||
v := reflect.ValueOf(message)
|
||||
stringifyValue(&buf, v)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// StreamToString converts a reader to a string
|
||||
func StreamToString(stream io.Reader) string {
|
||||
buf := new(bytes.Buffer)
|
||||
buf.ReadFrom(stream)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// stringifyValue was graciously cargoculted from the goprotubuf library
|
||||
func stringifyValue(w io.Writer, val reflect.Value) {
|
||||
if val.Kind() == reflect.Ptr && val.IsNil() {
|
||||
w.Write([]byte("<nil>"))
|
||||
return
|
||||
}
|
||||
|
||||
v := reflect.Indirect(val)
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.String:
|
||||
fmt.Fprintf(w, `"%s"`, v)
|
||||
case reflect.Slice:
|
||||
w.Write([]byte{'['})
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
if i > 0 {
|
||||
w.Write([]byte{' '})
|
||||
}
|
||||
|
||||
stringifyValue(w, v.Index(i))
|
||||
}
|
||||
|
||||
w.Write([]byte{']'})
|
||||
return
|
||||
case reflect.Struct:
|
||||
if v.Type().Name() != "" {
|
||||
w.Write([]byte(v.Type().String()))
|
||||
}
|
||||
|
||||
// special handling of Timestamp values
|
||||
if v.Type() == timestampType {
|
||||
fmt.Fprintf(w, "{%s}", v.Interface())
|
||||
return
|
||||
}
|
||||
|
||||
w.Write([]byte{'{'})
|
||||
|
||||
var sep bool
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
fv := v.Field(i)
|
||||
if fv.Kind() == reflect.Ptr && fv.IsNil() {
|
||||
continue
|
||||
}
|
||||
if fv.Kind() == reflect.Slice && fv.IsNil() {
|
||||
continue
|
||||
}
|
||||
|
||||
if sep {
|
||||
w.Write([]byte(", "))
|
||||
} else {
|
||||
sep = true
|
||||
}
|
||||
|
||||
w.Write([]byte(v.Type().Field(i).Name))
|
||||
w.Write([]byte{':'})
|
||||
stringifyValue(w, fv)
|
||||
}
|
||||
|
||||
w.Write([]byte{'}'})
|
||||
default:
|
||||
if v.CanInterface() {
|
||||
fmt.Fprint(w, v.Interface())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package packngo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const virtualNetworkBasePath = "/virtual-networks"
|
||||
|
||||
// DevicePortService handles operations on a port which belongs to a particular device
|
||||
type ProjectVirtualNetworkService interface {
|
||||
List(projectID string) (*VirtualNetworkListResponse, *Response, error)
|
||||
Create(*VirtualNetworkCreateRequest) (*VirtualNetwork, *Response, error)
|
||||
Delete(virtualNetworkID string) (*Response, error)
|
||||
}
|
||||
|
||||
type VirtualNetwork struct {
|
||||
ID string `json:"id"`
|
||||
Description string `json:"description,omitempty"`
|
||||
VXLAN int `json:"vxlan,omitempty"`
|
||||
FacilityCode string `json:"facility_code,omitempty"`
|
||||
CreatedAt string `json:"created_at,omitempty"`
|
||||
Href string `json:"href"`
|
||||
}
|
||||
|
||||
type ProjectVirtualNetworkServiceOp struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
type VirtualNetworkListResponse struct {
|
||||
VirtualNetworks []VirtualNetwork `json:"virtual_networks"`
|
||||
}
|
||||
|
||||
func (i *ProjectVirtualNetworkServiceOp) List(projectID string) (*VirtualNetworkListResponse, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s%s", projectBasePath, projectID, virtualNetworkBasePath)
|
||||
output := new(VirtualNetworkListResponse)
|
||||
|
||||
resp, err := i.client.DoRequest("GET", path, nil, output)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return output, resp, nil
|
||||
}
|
||||
|
||||
type VirtualNetworkCreateRequest struct {
|
||||
ProjectID string `json:"project_id"`
|
||||
Description string `json:"description"`
|
||||
Facility string `json:"facility"`
|
||||
VXLAN int `json:"vxlan"`
|
||||
VLAN int `json:"vlan"`
|
||||
}
|
||||
|
||||
type VirtualNetworkCreateResponse struct {
|
||||
VirtualNetwork VirtualNetwork `json:"virtual_networks"`
|
||||
}
|
||||
|
||||
func (i *ProjectVirtualNetworkServiceOp) Create(input *VirtualNetworkCreateRequest) (*VirtualNetwork, *Response, error) {
|
||||
// TODO: May need to add timestamp to output from 'post' request
|
||||
// for the 'created_at' attribute of VirtualNetwork struct since
|
||||
// API response doesn't include it
|
||||
path := fmt.Sprintf("%s/%s%s", projectBasePath, input.ProjectID, virtualNetworkBasePath)
|
||||
output := new(VirtualNetwork)
|
||||
|
||||
resp, err := i.client.DoRequest("POST", path, input, output)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return output, resp, nil
|
||||
}
|
||||
|
||||
func (i *ProjectVirtualNetworkServiceOp) Delete(virtualNetworkID string) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", virtualNetworkBasePath, virtualNetworkID)
|
||||
|
||||
resp, err := i.client.DoRequest("DELETE", path, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
|
@ -0,0 +1,239 @@
|
|||
package packngo
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
volumeBasePath = "/storage"
|
||||
attachmentsBasePath = "/attachments"
|
||||
)
|
||||
|
||||
// VolumeService interface defines available Volume methods
|
||||
type VolumeService interface {
|
||||
List(string, *ListOptions) ([]Volume, *Response, error)
|
||||
Get(string) (*Volume, *Response, error)
|
||||
Update(string, *VolumeUpdateRequest) (*Volume, *Response, error)
|
||||
Delete(string) (*Response, error)
|
||||
Create(*VolumeCreateRequest, string) (*Volume, *Response, error)
|
||||
Lock(string) (*Response, error)
|
||||
Unlock(string) (*Response, error)
|
||||
}
|
||||
|
||||
// VolumeAttachmentService defines attachment methdods
|
||||
type VolumeAttachmentService interface {
|
||||
Get(string) (*VolumeAttachment, *Response, error)
|
||||
Create(string, string) (*VolumeAttachment, *Response, error)
|
||||
Delete(string) (*Response, error)
|
||||
}
|
||||
|
||||
type volumesRoot struct {
|
||||
Volumes []Volume `json:"volumes"`
|
||||
Meta meta `json:"meta"`
|
||||
}
|
||||
|
||||
// Volume represents a volume
|
||||
type Volume struct {
|
||||
Attachments []*VolumeAttachment `json:"attachments,omitempty"`
|
||||
BillingCycle string `json:"billing_cycle,omitempty"`
|
||||
Created string `json:"created_at,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Facility *Facility `json:"facility,omitempty"`
|
||||
Href string `json:"href,omitempty"`
|
||||
ID string `json:"id"`
|
||||
Locked bool `json:"locked,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Plan *Plan `json:"plan,omitempty"`
|
||||
Project *Project `json:"project,omitempty"`
|
||||
Size int `json:"size,omitempty"`
|
||||
SnapshotPolicies []*SnapshotPolicy `json:"snapshot_policies,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Updated string `json:"updated_at,omitempty"`
|
||||
}
|
||||
|
||||
// SnapshotPolicy used to execute actions on volume
|
||||
type SnapshotPolicy struct {
|
||||
ID string `json:"id"`
|
||||
Href string `json:"href"`
|
||||
SnapshotFrequency string `json:"snapshot_frequency,omitempty"`
|
||||
SnapshotCount int `json:"snapshot_count,omitempty"`
|
||||
}
|
||||
|
||||
func (v Volume) String() string {
|
||||
return Stringify(v)
|
||||
}
|
||||
|
||||
// VolumeCreateRequest type used to create a Packet volume
|
||||
type VolumeCreateRequest struct {
|
||||
BillingCycle string `json:"billing_cycle"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Locked bool `json:"locked,omitempty"`
|
||||
Size int `json:"size"`
|
||||
PlanID string `json:"plan_id"`
|
||||
FacilityID string `json:"facility_id"`
|
||||
SnapshotPolicies []*SnapshotPolicy `json:"snapshot_policies,omitempty"`
|
||||
}
|
||||
|
||||
func (v VolumeCreateRequest) String() string {
|
||||
return Stringify(v)
|
||||
}
|
||||
|
||||
// VolumeUpdateRequest type used to update a Packet volume
|
||||
type VolumeUpdateRequest struct {
|
||||
Description *string `json:"description,omitempty"`
|
||||
PlanID *string `json:"plan_id,omitempty"`
|
||||
Size *int `json:"size,omitempty"`
|
||||
BillingCycle *string `json:"billing_cycle,omitempty"`
|
||||
}
|
||||
|
||||
// VolumeAttachment is a type from Packet API
|
||||
type VolumeAttachment struct {
|
||||
Href string `json:"href"`
|
||||
ID string `json:"id"`
|
||||
Volume Volume `json:"volume"`
|
||||
Device Device `json:"device"`
|
||||
}
|
||||
|
||||
func (v VolumeUpdateRequest) String() string {
|
||||
return Stringify(v)
|
||||
}
|
||||
|
||||
// VolumeAttachmentServiceOp implements VolumeService
|
||||
type VolumeAttachmentServiceOp struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// VolumeServiceOp implements VolumeService
|
||||
type VolumeServiceOp struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// List returns the volumes for a project
|
||||
func (v *VolumeServiceOp) List(projectID string, listOpt *ListOptions) (volumes []Volume, resp *Response, err error) {
|
||||
url := fmt.Sprintf("%s/%s%s", projectBasePath, projectID, volumeBasePath)
|
||||
var params string
|
||||
if listOpt != nil {
|
||||
params = listOpt.createURL()
|
||||
if params != "" {
|
||||
url = fmt.Sprintf("%s?%s", url, params)
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
subset := new(volumesRoot)
|
||||
|
||||
resp, err = v.client.DoRequest("GET", url, nil, subset)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
volumes = append(volumes, subset.Volumes...)
|
||||
|
||||
if subset.Meta.Next != nil && (listOpt == nil || listOpt.Page == 0) {
|
||||
url = subset.Meta.Next.Href
|
||||
if params != "" {
|
||||
url = fmt.Sprintf("%s&%s", url, params)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns a volume by id
|
||||
func (v *VolumeServiceOp) Get(volumeID string) (*Volume, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s?include=facility,snapshot_policies,attachments.device", volumeBasePath, volumeID)
|
||||
volume := new(Volume)
|
||||
|
||||
resp, err := v.client.DoRequest("GET", path, nil, volume)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return volume, resp, err
|
||||
}
|
||||
|
||||
// Update updates a volume
|
||||
func (v *VolumeServiceOp) Update(id string, updateRequest *VolumeUpdateRequest) (*Volume, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", volumeBasePath, id)
|
||||
volume := new(Volume)
|
||||
|
||||
resp, err := v.client.DoRequest("PATCH", path, updateRequest, volume)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return volume, resp, err
|
||||
}
|
||||
|
||||
// Delete deletes a volume
|
||||
func (v *VolumeServiceOp) Delete(volumeID string) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", volumeBasePath, volumeID)
|
||||
|
||||
return v.client.DoRequest("DELETE", path, nil, nil)
|
||||
}
|
||||
|
||||
// Create creates a new volume for a project
|
||||
func (v *VolumeServiceOp) Create(createRequest *VolumeCreateRequest, projectID string) (*Volume, *Response, error) {
|
||||
url := fmt.Sprintf("%s/%s%s", projectBasePath, projectID, volumeBasePath)
|
||||
volume := new(Volume)
|
||||
|
||||
resp, err := v.client.DoRequest("POST", url, createRequest, volume)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return volume, resp, err
|
||||
}
|
||||
|
||||
// Attachments
|
||||
|
||||
// Create Attachment, i.e. attach volume to a device
|
||||
func (v *VolumeAttachmentServiceOp) Create(volumeID, deviceID string) (*VolumeAttachment, *Response, error) {
|
||||
url := fmt.Sprintf("%s/%s%s", volumeBasePath, volumeID, attachmentsBasePath)
|
||||
volAttachParam := map[string]string{
|
||||
"device_id": deviceID,
|
||||
}
|
||||
volumeAttachment := new(VolumeAttachment)
|
||||
|
||||
resp, err := v.client.DoRequest("POST", url, volAttachParam, volumeAttachment)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return volumeAttachment, resp, nil
|
||||
}
|
||||
|
||||
// Get gets attachment by id
|
||||
func (v *VolumeAttachmentServiceOp) Get(attachmentID string) (*VolumeAttachment, *Response, error) {
|
||||
path := fmt.Sprintf("%s%s/%s", volumeBasePath, attachmentsBasePath, attachmentID)
|
||||
volumeAttachment := new(VolumeAttachment)
|
||||
|
||||
resp, err := v.client.DoRequest("GET", path, nil, volumeAttachment)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return volumeAttachment, resp, nil
|
||||
}
|
||||
|
||||
// Delete deletes attachment by id
|
||||
func (v *VolumeAttachmentServiceOp) Delete(attachmentID string) (*Response, error) {
|
||||
path := fmt.Sprintf("%s%s/%s", volumeBasePath, attachmentsBasePath, attachmentID)
|
||||
|
||||
return v.client.DoRequest("DELETE", path, nil, nil)
|
||||
}
|
||||
|
||||
// Lock sets a volume to "locked"
|
||||
func (s *VolumeServiceOp) Lock(id string) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", volumeBasePath, id)
|
||||
action := lockType{Locked: true}
|
||||
|
||||
return s.client.DoRequest("PATCH", path, action, nil)
|
||||
}
|
||||
|
||||
// Unlock sets a volume to "unlocked"
|
||||
func (s *VolumeServiceOp) Unlock(id string) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", volumeBasePath, id)
|
||||
action := lockType{Locked: false}
|
||||
|
||||
return s.client.DoRequest("PATCH", path, action, nil)
|
||||
}
|
|
@ -0,0 +1,317 @@
|
|||
# changelog
|
||||
|
||||
### unreleased
|
||||
|
||||
* SetRootCAs on the soap.Client returns an error for invalid certificates
|
||||
|
||||
### 0.18.0 (2018-05-24)
|
||||
|
||||
* Add VirtualDiskManager wrapper to set UUID
|
||||
|
||||
* Add vmxnet2, pcnet32 and sriov to VirtualDeviceList.EthernetCardTypes
|
||||
|
||||
* Add new vSphere 6.7 APIs
|
||||
|
||||
* Decrease LoginExtensionByCertificate tunnel usage
|
||||
|
||||
* SAML token authentication support via SessionManager.LoginByToken
|
||||
|
||||
* New SSO admin client for managing users
|
||||
|
||||
* New STS client for issuing and renewing SAML tokens
|
||||
|
||||
* New Lookup Service client for discovering endpoints such as STS and ssoadmin
|
||||
|
||||
* Switch from gvt to go dep for managing dependencies
|
||||
|
||||
### 0.17.1 (2018-03-19)
|
||||
|
||||
* vcsim: add Destroy method for Folder and Datacenter types
|
||||
|
||||
* In progress.Reader emit final report on EOF.
|
||||
|
||||
* vcsim: add EventManager.QueryEvents
|
||||
|
||||
### 0.17.0 (2018-02-28)
|
||||
|
||||
* Add HostStorageSystem.AttachScsiLun method
|
||||
|
||||
* Avoid possible panic in Datastore.Stat (#969)
|
||||
|
||||
* Destroy event history collectors (#962)
|
||||
|
||||
* Add VirtualDiskManager.CreateChildDisk method
|
||||
|
||||
### 0.16.0 (2017-11-08)
|
||||
|
||||
* Add support for SOAP request operation ID header
|
||||
|
||||
* Moved ovf helpers from govc import.ovf command to ovf and nfc packages
|
||||
|
||||
* Added guest/toolbox (client) package
|
||||
|
||||
* Added toolbox package and toolbox command
|
||||
|
||||
* Added simulator package and vcsim command
|
||||
|
||||
### 0.15.0 (2017-06-19)
|
||||
|
||||
* WaitOptions.MaxWaitSeconds is now optional
|
||||
|
||||
* Support removal of ExtraConfig entries
|
||||
|
||||
* GuestPosixFileAttributes OwnerId and GroupId fields are now pointers,
|
||||
rather than omitempty ints to allow chown with root uid:gid
|
||||
|
||||
* Updated examples/ using view package
|
||||
|
||||
* Add DatastoreFile.TailFunc method
|
||||
|
||||
* Export VirtualMachine.FindSnapshot method
|
||||
|
||||
* Add AuthorizationManager {Enable,Disable}Methods
|
||||
|
||||
* Add PBM client
|
||||
|
||||
### 0.14.0 (2017-04-08)
|
||||
|
||||
* Add view.ContainerView type and methods
|
||||
|
||||
* Add Collector.RetrieveWithFilter method
|
||||
|
||||
* Add property.Filter type
|
||||
|
||||
* Implement EthernetCardBackingInfo for OpaqueNetwork
|
||||
|
||||
* Finder: support changing object root in find mode
|
||||
|
||||
* Add VirtualDiskManager.QueryVirtualDiskInfo
|
||||
|
||||
* Add performance.Manager APIs
|
||||
|
||||
### 0.13.0 (2017-03-02)
|
||||
|
||||
* Add DatastoreFileManager API wrapper
|
||||
|
||||
* Add HostVsanInternalSystem API wrappers
|
||||
|
||||
* Add Container support to view package
|
||||
|
||||
* Finder supports Folder recursion without specifying a path
|
||||
|
||||
* Add VirtualMachine.QueryConfigTarget method
|
||||
|
||||
* Add device option to VirtualMachine.WaitForNetIP
|
||||
|
||||
* Remove _Task suffix from vapp methods
|
||||
|
||||
### 0.12.1 (2016-12-19)
|
||||
|
||||
* Add DiagnosticLog helper
|
||||
|
||||
* Add DatastorePath helper
|
||||
|
||||
### 0.12.0 (2016-12-01)
|
||||
|
||||
* Disable use of service ticket for datastore HTTP access by default
|
||||
|
||||
* Attach context to HTTP requests for cancellations
|
||||
|
||||
* Update to vim25/6.5 API
|
||||
|
||||
### 0.11.4 (2016-11-15)
|
||||
|
||||
* Add object.AuthorizationManager methods: RetrieveRolePermissions, RetrieveAllPermissions, AddRole, RemoveRole, UpdateRole
|
||||
|
||||
### 0.11.3 (2016-11-08)
|
||||
|
||||
* Allow DatastoreFile.Follow reader to drain current body after stopping
|
||||
|
||||
### 0.11.2 (2016-11-01)
|
||||
|
||||
* Avoid possible NPE in VirtualMachine.Device method
|
||||
|
||||
* Add support for OpaqueNetwork type to Finder
|
||||
|
||||
* Add HostConfigManager.AccountManager support for ESX 5.5
|
||||
|
||||
### 0.11.1 (2016-10-27)
|
||||
|
||||
* Add Finder.ResourcePoolListAll method
|
||||
|
||||
### 0.11.0 (2016-10-25)
|
||||
|
||||
* Add object.DistributedVirtualPortgroup.Reconfigure method
|
||||
|
||||
### 0.10.0 (2016-10-20)
|
||||
|
||||
* Add option to set soap.Client.UserAgent
|
||||
|
||||
* Add service ticket thumbprint validation
|
||||
|
||||
* Update use of http.DefaultTransport fields to 1.7
|
||||
|
||||
* Set default locale to en_US (override with GOVMOMI_LOCALE env var)
|
||||
|
||||
* Add object.HostCertificateInfo (types.HostCertificateManagerCertificateInfo helpers)
|
||||
|
||||
* Add object.HostCertificateManager type and HostConfigManager.CertificateManager method
|
||||
|
||||
* Add soap.Client SetRootCAs and SetDialTLS methods
|
||||
|
||||
### 0.9.0 (2016-09-09)
|
||||
|
||||
* Add object.DatastoreFile helpers for streaming and tailing datastore files
|
||||
|
||||
* Add object VirtualMachine.Unregister method
|
||||
|
||||
* Add object.ListView methods: Add, Remove, Reset
|
||||
|
||||
* Update to Go 1.7 - using stdlib's context package
|
||||
|
||||
### 0.8.0 (2016-06-30)
|
||||
|
||||
* Add session.Manager.AcquireLocalTicket
|
||||
|
||||
* Include StoragePod in Finder.FolderList
|
||||
|
||||
* Add Finder methods for finding by ManagedObjectReference: Element, ObjectReference
|
||||
|
||||
* Add mo.ManagedObjectReference methods: Reference, String, FromString
|
||||
|
||||
* Add support using SessionManagerGenericServiceTicket.HostName for Datastore HTTP access
|
||||
|
||||
### 0.7.1 (2016-06-03)
|
||||
|
||||
* Fix object.ObjectName method
|
||||
|
||||
### 0.7.0 (2016-06-02)
|
||||
|
||||
* Move InventoryPath field to object.Common
|
||||
|
||||
* Add HostDatastoreSystem.CreateLocalDatastore method
|
||||
|
||||
* Add DatastoreNamespaceManager methods: CreateDirectory, DeleteDirectory
|
||||
|
||||
* Add HostServiceSystem
|
||||
|
||||
* Add HostStorageSystem methods: MarkAsSdd, MarkAsNonSdd, MarkAsLocal, MarkAsNonLocal
|
||||
|
||||
* Add HostStorageSystem.RescanAllHba method
|
||||
|
||||
### 0.6.2 (2016-05-11)
|
||||
|
||||
* Get complete file details in Datastore.Stat
|
||||
|
||||
* SOAP decoding fixes
|
||||
|
||||
* Add VirtualMachine.RemoveAllSnapshot
|
||||
|
||||
### 0.6.1 (2016-04-30)
|
||||
|
||||
* Fix mo.Entity interface
|
||||
|
||||
### 0.6.0 (2016-04-29)
|
||||
|
||||
* Add Common.Rename method
|
||||
|
||||
* Add mo.Entity interface
|
||||
|
||||
* Add OptionManager
|
||||
|
||||
* Add Finder.FolderList method
|
||||
|
||||
* Add VirtualMachine.WaitForNetIP method
|
||||
|
||||
* Add VirtualMachine.RevertToSnapshot method
|
||||
|
||||
* Add Datastore.Download method
|
||||
|
||||
### 0.5.0 (2016-03-30)
|
||||
|
||||
Generated fields using xsd type 'int' change to Go type 'int32'
|
||||
|
||||
VirtualDevice.UnitNumber field changed to pointer type
|
||||
|
||||
### 0.4.0 (2016-02-26)
|
||||
|
||||
* Add method to convert virtual device list to array with virtual device
|
||||
changes that can be used in the VirtualMachineConfigSpec.
|
||||
|
||||
* Make datastore cluster traversable in lister
|
||||
|
||||
* Add finder.DatastoreCluster methods (also known as storage pods)
|
||||
|
||||
* Add Drone CI check
|
||||
|
||||
* Add object.Datastore Type and AttachedClusterHosts methods
|
||||
|
||||
* Add finder.*OrDefault methods
|
||||
|
||||
### 0.3.0 (2016-01-16)
|
||||
|
||||
* Add object.VirtualNicManager wrapper
|
||||
|
||||
* Add object.HostVsanSystem wrapper
|
||||
|
||||
* Add object.HostSystem methods: EnterMaintenanceMode, ExitMaintenanceMode, Disconnect, Reconnect
|
||||
|
||||
* Add finder.Folder method
|
||||
|
||||
* Add object.Common.Destroy method
|
||||
|
||||
* Add object.ComputeResource.Reconfigure method
|
||||
|
||||
* Add license.AssignmentManager wrapper
|
||||
|
||||
* Add object.HostFirewallSystem wrapper
|
||||
|
||||
* Add object.DiagnosticManager wrapper
|
||||
|
||||
* Add LoginExtensionByCertificate support
|
||||
|
||||
* Add object.ExtensionManager
|
||||
|
||||
...
|
||||
|
||||
### 0.2.0 (2015-09-15)
|
||||
|
||||
* Update to vim25/6.0 API
|
||||
|
||||
* Stop returning children from `ManagedObjectList`
|
||||
|
||||
Change the `ManagedObjectList` function in the `find` package to only
|
||||
return the managed objects specified by the path argument and not their
|
||||
children. The original behavior was used by govc's `ls` command and is
|
||||
now available in the newly added function `ManagedObjectListChildren`.
|
||||
|
||||
* Add retry functionality to vim25 package
|
||||
|
||||
* Change finder functions to no longer take varargs
|
||||
|
||||
The `find` package had functions to return a list of objects, given a
|
||||
variable number of patterns. This makes it impossible to distinguish which
|
||||
patterns produced results and which ones didn't.
|
||||
|
||||
In particular for govc, where multiple arguments can be passed from the
|
||||
command line, it is useful to let the user know which ones produce results
|
||||
and which ones don't.
|
||||
|
||||
To evaluate multiple patterns, the user should call the find functions
|
||||
multiple times (either serially or in parallel).
|
||||
|
||||
* Make optional boolean fields pointers (`vim25/types`).
|
||||
|
||||
False is the zero value of a boolean field, which means they are not serialized
|
||||
if the field is marked "omitempty". If the field is a pointer instead, the zero
|
||||
value will be the nil pointer, and both true and false values are serialized.
|
||||
|
||||
### 0.1.0 (2015-03-17)
|
||||
|
||||
Prior to this version the API of this library was in flux.
|
||||
|
||||
Notable changes w.r.t. the state of this library before March 2015 are:
|
||||
|
||||
* All functions that may execute a request take a `context.Context` parameter.
|
||||
* The `vim25` package contains a minimal client implementation.
|
||||
* The property collector and its convenience functions live in the `property` package.
|
|
@ -0,0 +1,101 @@
|
|||
# Contributing to govmomi
|
||||
|
||||
## Getting started
|
||||
|
||||
First, fork the repository on GitHub to your personal account.
|
||||
|
||||
Note that _GOPATH_ can be any directory, the example below uses _$HOME/govmomi_.
|
||||
Change _$USER_ below to your github username if they are not the same.
|
||||
|
||||
``` shell
|
||||
export GOPATH=$HOME/govmomi
|
||||
go get github.com/vmware/govmomi
|
||||
cd $GOPATH/src/github.com/vmware/govmomi
|
||||
git config push.default nothing # anything to avoid pushing to vmware/govmomi by default
|
||||
git remote rename origin vmware
|
||||
git remote add $USER git@github.com:$USER/govmomi.git
|
||||
git fetch $USER
|
||||
```
|
||||
|
||||
## Installing from source
|
||||
|
||||
Compile the govmomi libraries and install govc using:
|
||||
|
||||
``` shell
|
||||
go install -v github.com/vmware/govmomi/govc
|
||||
```
|
||||
|
||||
Note that **govc/build.sh** is only used for building release binaries.
|
||||
|
||||
## Contribution flow
|
||||
|
||||
This is a rough outline of what a contributor's workflow looks like:
|
||||
|
||||
- Create a topic branch from where you want to base your work.
|
||||
- Make commits of logical units.
|
||||
- Make sure your commit messages are in the proper format (see below).
|
||||
- Update CHANGELOG.md and/or govc/CHANGELOG.md when appropriate.
|
||||
- Push your changes to a topic branch in your fork of the repository.
|
||||
- Submit a pull request to vmware/govmomi.
|
||||
|
||||
Example:
|
||||
|
||||
``` shell
|
||||
git checkout -b my-new-feature vmware/master
|
||||
git commit -a
|
||||
git push $USER my-new-feature
|
||||
```
|
||||
|
||||
### Stay in sync with upstream
|
||||
|
||||
When your branch gets out of sync with the vmware/master branch, use the following to update:
|
||||
|
||||
``` shell
|
||||
git checkout my-new-feature
|
||||
git fetch -a
|
||||
git rebase vmware/master
|
||||
git push --force-with-lease $USER my-new-feature
|
||||
```
|
||||
|
||||
### Updating pull requests
|
||||
|
||||
If your PR fails to pass CI or needs changes based on code review, you'll most likely want to squash these changes into
|
||||
existing commits.
|
||||
|
||||
If your pull request contains a single commit or your changes are related to the most recent commit, you can simply
|
||||
amend the commit.
|
||||
|
||||
``` shell
|
||||
git add .
|
||||
git commit --amend
|
||||
git push --force-with-lease $USER my-new-feature
|
||||
```
|
||||
|
||||
If you need to squash changes into an earlier commit, you can use:
|
||||
|
||||
``` shell
|
||||
git add .
|
||||
git commit --fixup <commit>
|
||||
git rebase -i --autosquash vmware/master
|
||||
git push --force-with-lease $USER my-new-feature
|
||||
```
|
||||
|
||||
Be sure to add a comment to the PR indicating your new changes are ready to review, as github does not generate a
|
||||
notification when you git push.
|
||||
|
||||
### Code style
|
||||
|
||||
The coding style suggested by the Golang community is used in govmomi. See the
|
||||
[style doc](https://github.com/golang/go/wiki/CodeReviewComments) for details.
|
||||
|
||||
Try to limit column width to 120 characters for both code and markdown documents such as this one.
|
||||
|
||||
### Format of the Commit Message
|
||||
|
||||
We follow the conventions on [How to Write a Git Commit Message](http://chris.beams.io/posts/git-commit/).
|
||||
|
||||
Be sure to include any related GitHub issue references in the commit message.
|
||||
|
||||
## Reporting Bugs and Creating Issues
|
||||
|
||||
When opening a new issue, try to roughly follow the commit message format conventions above.
|
|
@ -0,0 +1,83 @@
|
|||
# People who can (and typically have) contributed to this repository.
|
||||
#
|
||||
# This script is generated by contributors.sh
|
||||
#
|
||||
|
||||
Abhijeet Kasurde <akasurde@redhat.com>
|
||||
abrarshivani <abrarshivani@users.noreply.github.com>
|
||||
Adam Shannon <adamkshannon@gmail.com>
|
||||
akutz <sakutz@gmail.com>
|
||||
Alessandro Cortiana <alessandro.cortiana@gmail.com>
|
||||
Alex Bozhenko <alexbozhenko@fb.com>
|
||||
Alvaro Miranda <kikitux@gmail.com>
|
||||
amandahla <amanda.andrade@serpro.gov.br>
|
||||
Amanda H. L. de Andrade <amanda.andrade@serpro.gov.br>
|
||||
Amit Bathla <abathla@.vmware.com>
|
||||
amit bezalel <amit.bezalel@hpe.com>
|
||||
Andrew Chin <andrew@andrewtchin.com>
|
||||
Anfernee Yongkun Gui <agui@vmware.com>
|
||||
aniketGslab <aniket.shinde@gslab.com>
|
||||
Arran Walker <arran.walker@zopa.com>
|
||||
Aryeh Weinreb <aryehweinreb@gmail.com>
|
||||
Austin Parker <aparker@apprenda.com>
|
||||
Balu Dontu <bdontu@vmware.com>
|
||||
bastienbc <bastien.barbe.creuly@gmail.com>
|
||||
Bob Killen <killen.bob@gmail.com>
|
||||
Brad Fitzpatrick <bradfitz@golang.org>
|
||||
Bruce Downs <bruceadowns@gmail.com>
|
||||
Cédric Blomart <cblomart@gmail.com>
|
||||
Chris Marchesi <chrism@vancluevertech.com>
|
||||
Christian Höltje <docwhat@gerf.org>
|
||||
Clint Greenwood <cgreenwood@vmware.com>
|
||||
Danny Lockard <danny.lockard@banno.com>
|
||||
Dave Tucker <dave@dtucker.co.uk>
|
||||
Davide Agnello <dagnello@hp.com>
|
||||
David Stark <dave@davidstark.name>
|
||||
Deric Crago <deric.crago@gmail.com>
|
||||
Doug MacEachern <dougm@vmware.com>
|
||||
Eloy Coto <eloy.coto@gmail.com>
|
||||
Eric Gray <egray@vmware.com>
|
||||
Eric Yutao <eric.yutao@gmail.com>
|
||||
Erik Hollensbe <github@hollensbe.org>
|
||||
Fabio Rapposelli <fabio@vmware.com>
|
||||
Faiyaz Ahmed <ahmedf@vmware.com>
|
||||
forkbomber <forkbomber@users.noreply.github.com>
|
||||
Gavin Gray <gavin@infinio.com>
|
||||
Gavrie Philipson <gavrie.philipson@elastifile.com>
|
||||
George Hicken <ghicken@vmware.com>
|
||||
Gerrit Renker <Gerrit.Renker@ctl.io>
|
||||
gthombare <gthombare@vmware.com>
|
||||
Hasan Mahmood <mahmoodh@vmware.com>
|
||||
Henrik Hodne <henrik@travis-ci.com>
|
||||
Isaac Rodman <isaac@eyz.us>
|
||||
Ivan Porto Carrero <icarrero@vmware.com>
|
||||
Jason Kincl <jkincl@gmail.com>
|
||||
Jeremy Canady <jcanady@jackhenry.com>
|
||||
jeremy-clerc <jeremy@clerc.io>
|
||||
João Pereira <joaodrp@gmail.com>
|
||||
Jorge Sevilla <jorge.sevilla@rstor.io>
|
||||
leslie-qiwa <leslie.qiwa@gmail.com>
|
||||
Louie Jiang <jiangl@vmware.com>
|
||||
Marc Carmier <mcarmier@gmail.com>
|
||||
Matthew Cosgrove <matthew.cosgrove@dell.com>
|
||||
Mevan Samaratunga <mevansam@gmail.com>
|
||||
Nicolas Lamirault <nicolas.lamirault@gmail.com>
|
||||
Omar Kohl <omarkohl@gmail.com>
|
||||
Parham Alvani <parham.alvani@gmail.com>
|
||||
Pieter Noordhuis <pnoordhuis@vmware.com>
|
||||
runner.mei <runner.mei@gmail.com>
|
||||
S.Çağlar Onur <conur@vmware.com>
|
||||
Sergey Ignatov <sergey.ignatov@jetbrains.com>
|
||||
Steve Purcell <steve@sanityinc.com>
|
||||
Takaaki Furukawa <takaaki.frkw@gmail.com>
|
||||
tanishi <tanishi503@gmail.com>
|
||||
Ted Zlatanov <tzz@lifelogs.com>
|
||||
Thibaut Ackermann <thibaut.ackermann@alcatel-lucent.com>
|
||||
Trevor Dawe <trevor.dawe@gmail.com>
|
||||
Vadim Egorov <vegorov@vmware.com>
|
||||
Volodymyr Bobyr <pupsua@gmail.com>
|
||||
Witold Krecicki <wpk@culm.net>
|
||||
Yang Yang <yangy@vmware.com>
|
||||
Yuya Kusakabe <yuya.kusakabe@gmail.com>
|
||||
Zach Tucker <ztucker@vmware.com>
|
||||
Zee Yang <zeey@vmware.com>
|
|
@ -0,0 +1,4 @@
|
|||
FROM scratch
|
||||
LABEL maintainer="fabio@vmware.com"
|
||||
COPY govc /
|
||||
ENTRYPOINT [ "/govc" ]
|
|
@ -0,0 +1,44 @@
|
|||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
branch = "improvements"
|
||||
name = "github.com/davecgh/go-xdr"
|
||||
packages = ["xdr2"]
|
||||
revision = "4930550ba2e22f87187498acfd78348b15f4e7a8"
|
||||
source = "https://github.com/rasky/go-xdr"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/google/uuid"
|
||||
packages = ["."]
|
||||
revision = "6a5e28554805e78ea6141142aba763936c4761c0"
|
||||
|
||||
[[projects]]
|
||||
branch = "govmomi"
|
||||
name = "github.com/kr/pretty"
|
||||
packages = ["."]
|
||||
revision = "2ee9d7453c02ef7fa518a83ae23644eb8872186a"
|
||||
source = "https://github.com/dougm/pretty"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/kr/text"
|
||||
packages = ["."]
|
||||
revision = "7cafcd837844e784b526369c9bce262804aebc60"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/vmware/vmw-guestinfo"
|
||||
packages = [
|
||||
"bdoor",
|
||||
"message",
|
||||
"vmcheck"
|
||||
]
|
||||
revision = "25eff159a728be87e103a0b8045e08273f4dbec4"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "376638fa6c0621cbd980caf8fc53494d880886f100663da8de47ecb6e596e439"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
|
@ -0,0 +1,19 @@
|
|||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# Refer to https://github.com/toml-lang/toml for detailed TOML docs.
|
||||
|
||||
[prune]
|
||||
non-go = true
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
||||
[[constraint]]
|
||||
branch = "improvements"
|
||||
name = "github.com/davecgh/go-xdr"
|
||||
source = "https://github.com/rasky/go-xdr"
|
||||
|
||||
[[constraint]]
|
||||
branch = "govmomi"
|
||||
name = "github.com/kr/pretty"
|
||||
source = "https://github.com/dougm/pretty"
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,29 @@
|
|||
.PHONY: test
|
||||
|
||||
all: check test
|
||||
|
||||
check: goimports govet
|
||||
|
||||
goimports:
|
||||
@echo checking go imports...
|
||||
@go get golang.org/x/tools/cmd/goimports
|
||||
@! goimports -d . 2>&1 | egrep -v '^$$'
|
||||
|
||||
govet:
|
||||
@echo checking go vet...
|
||||
@go tool vet -structtags=false -methods=false $$(find . -mindepth 1 -maxdepth 1 -type d -not -name vendor)
|
||||
|
||||
install:
|
||||
go install -v github.com/vmware/govmomi/govc
|
||||
go install -v github.com/vmware/govmomi/vcsim
|
||||
|
||||
go-test:
|
||||
go test -race -v $(TEST_OPTS) ./...
|
||||
|
||||
govc-test: install
|
||||
(cd govc/test && ./vendor/github.com/sstephenson/bats/libexec/bats -t .)
|
||||
|
||||
test: go-test govc-test
|
||||
|
||||
doc: install
|
||||
./govc/usage.sh > ./govc/USAGE.md
|
|
@ -0,0 +1,86 @@
|
|||
[![Build Status](https://travis-ci.org/vmware/govmomi.png?branch=master)](https://travis-ci.org/vmware/govmomi)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/vmware/govmomi)](https://goreportcard.com/report/github.com/vmware/govmomi)
|
||||
|
||||
# govmomi
|
||||
|
||||
A Go library for interacting with VMware vSphere APIs (ESXi and/or vCenter).
|
||||
|
||||
In addition to the vSphere API client, this repository includes:
|
||||
|
||||
* [govc](./govc) - vSphere CLI
|
||||
|
||||
* [vcsim](./vcsim) - vSphere API mock framework
|
||||
|
||||
* [toolbox](./toolbox) - VM guest tools framework
|
||||
|
||||
## Compatibility
|
||||
|
||||
This library is built for and tested against ESXi and vCenter 6.0, 6.5 and 6.7.
|
||||
|
||||
It may work with versions 5.5 and 5.1, but neither are officially supported.
|
||||
|
||||
## Documentation
|
||||
|
||||
The APIs exposed by this library very closely follow the API described in the [VMware vSphere API Reference Documentation][apiref].
|
||||
Refer to this document to become familiar with the upstream API.
|
||||
|
||||
The code in the `govmomi` package is a wrapper for the code that is generated from the vSphere API description.
|
||||
It primarily provides convenience functions for working with the vSphere API.
|
||||
See [godoc.org][godoc] for documentation.
|
||||
|
||||
[apiref]:http://pubs.vmware.com/vsphere-6-5/index.jsp#com.vmware.wssdk.apiref.doc/right-pane.html
|
||||
[godoc]:http://godoc.org/github.com/vmware/govmomi
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
go get -u github.com/vmware/govmomi
|
||||
```
|
||||
|
||||
## Discussion
|
||||
|
||||
Contributors and users are encouraged to collaborate using GitHub issues and/or
|
||||
[Slack](https://vmwarecode.slack.com/messages/govmomi).
|
||||
Access to Slack requires a [VMware {code} membership](https://code.vmware.com/join/).
|
||||
|
||||
## Status
|
||||
|
||||
Changes to the API are subject to [semantic versioning](http://semver.org).
|
||||
|
||||
Refer to the [CHANGELOG](CHANGELOG.md) for version to version changes.
|
||||
|
||||
## Projects using govmomi
|
||||
|
||||
* [Docker Machine](https://github.com/docker/machine/tree/master/drivers/vmwarevsphere)
|
||||
|
||||
* [Docker InfraKit](https://github.com/docker/infrakit/tree/master/pkg/provider/vsphere)
|
||||
|
||||
* [Docker LinuxKit](https://github.com/linuxkit/linuxkit/tree/master/src/cmd/linuxkit)
|
||||
|
||||
* [Kubernetes](https://github.com/kubernetes/kubernetes/tree/master/pkg/cloudprovider/providers/vsphere)
|
||||
|
||||
* [Kubernetes kops](https://github.com/kubernetes/kops/tree/master/upup/pkg/fi/cloudup/vsphere)
|
||||
|
||||
* [Terraform](https://github.com/terraform-providers/terraform-provider-vsphere)
|
||||
|
||||
* [Packer](https://github.com/jetbrains-infra/packer-builder-vsphere)
|
||||
|
||||
* [VMware VIC Engine](https://github.com/vmware/vic)
|
||||
|
||||
* [Travis CI](https://github.com/travis-ci/jupiter-brain)
|
||||
|
||||
* [collectd-vsphere](https://github.com/travis-ci/collectd-vsphere)
|
||||
|
||||
* [Gru](https://github.com/dnaeon/gru)
|
||||
|
||||
* [Libretto](https://github.com/apcera/libretto/tree/master/virtualmachine/vsphere)
|
||||
|
||||
## Related projects
|
||||
|
||||
* [rbvmomi](https://github.com/vmware/rbvmomi)
|
||||
|
||||
* [pyvmomi](https://github.com/vmware/pyvmomi)
|
||||
|
||||
## License
|
||||
|
||||
govmomi is available under the [Apache 2 license](LICENSE).
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
Copyright (c) 2014-2016 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
/*
|
||||
This package is the root package of the govmomi library.
|
||||
|
||||
The library is structured as follows:
|
||||
|
||||
Package vim25
|
||||
|
||||
The minimal usable functionality is available through the vim25 package.
|
||||
It contains subpackages that contain generated types, managed objects, and all
|
||||
available methods. The vim25 package is entirely independent of the other
|
||||
packages in the govmomi tree -- it has no dependencies on its peers.
|
||||
|
||||
The vim25 package itself contains a client structure that is
|
||||
passed around throughout the entire library. It abstracts a session and its
|
||||
immutable state. See the vim25 package for more information.
|
||||
|
||||
Package session
|
||||
|
||||
The session package contains an abstraction for the session manager that allows
|
||||
a user to login and logout. It also provides access to the current session
|
||||
(i.e. to determine if the user is in fact logged in)
|
||||
|
||||
Package object
|
||||
|
||||
The object package contains wrappers for a selection of managed objects. The
|
||||
constructors of these objects all take a *vim25.Client, which they pass along
|
||||
to derived objects, if applicable.
|
||||
|
||||
Package govc
|
||||
|
||||
The govc package contains the govc CLI. The code in this tree is not intended
|
||||
to be used as a library. Any functionality that govc contains that _could_ be
|
||||
used as a library function but isn't, _should_ live in a root level package.
|
||||
|
||||
Other packages
|
||||
|
||||
Other packages, such as "event", "guest", or "license", provide wrappers for
|
||||
the respective subsystems. They are typically not needed in normal workflows so
|
||||
are kept outside the object package.
|
||||
*/
|
||||
package govmomi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
|
||||
"github.com/vmware/govmomi/property"
|
||||
"github.com/vmware/govmomi/session"
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
*vim25.Client
|
||||
|
||||
SessionManager *session.Manager
|
||||
}
|
||||
|
||||
// NewClient creates a new client from a URL. The client authenticates with the
|
||||
// server with username/password before returning if the URL contains user information.
|
||||
func NewClient(ctx context.Context, u *url.URL, insecure bool) (*Client, error) {
|
||||
soapClient := soap.NewClient(u, insecure)
|
||||
vimClient, err := vim25.NewClient(ctx, soapClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := &Client{
|
||||
Client: vimClient,
|
||||
SessionManager: session.NewManager(vimClient),
|
||||
}
|
||||
|
||||
// Only login if the URL contains user information.
|
||||
if u.User != nil {
|
||||
err = c.Login(ctx, u.User)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Login dispatches to the SessionManager.
|
||||
func (c *Client) Login(ctx context.Context, u *url.Userinfo) error {
|
||||
return c.SessionManager.Login(ctx, u)
|
||||
}
|
||||
|
||||
// Logout dispatches to the SessionManager.
|
||||
func (c *Client) Logout(ctx context.Context) error {
|
||||
// Close any idle connections after logging out.
|
||||
defer c.Client.CloseIdleConnections()
|
||||
return c.SessionManager.Logout(ctx)
|
||||
}
|
||||
|
||||
// PropertyCollector returns the session's default property collector.
|
||||
func (c *Client) PropertyCollector() *property.Collector {
|
||||
return property.DefaultCollector(c.Client)
|
||||
}
|
||||
|
||||
// RetrieveOne dispatches to the Retrieve function on the default property collector.
|
||||
func (c *Client) RetrieveOne(ctx context.Context, obj types.ManagedObjectReference, p []string, dst interface{}) error {
|
||||
return c.PropertyCollector().RetrieveOne(ctx, obj, p, dst)
|
||||
}
|
||||
|
||||
// Retrieve dispatches to the Retrieve function on the default property collector.
|
||||
func (c *Client) Retrieve(ctx context.Context, objs []types.ManagedObjectReference, p []string, dst interface{}) error {
|
||||
return c.PropertyCollector().Retrieve(ctx, objs, p, dst)
|
||||
}
|
||||
|
||||
// Wait dispatches to property.Wait.
|
||||
func (c *Client) Wait(ctx context.Context, obj types.ManagedObjectReference, ps []string, f func([]types.PropertyChange) bool) error {
|
||||
return property.Wait(ctx, c.PropertyCollector(), obj, ps, f)
|
||||
}
|
||||
|
||||
// IsVC returns true if we are connected to a vCenter
|
||||
func (c *Client) IsVC() bool {
|
||||
return c.Client.IsVC()
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
Copyright (c) 2014-2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 find implements inventory listing and searching.
|
||||
|
||||
The Finder is an alternative to the object.SearchIndex FindByInventoryPath() and FindChild() methods.
|
||||
SearchIndex.FindByInventoryPath requires an absolute path, whereas the Finder also supports relative paths
|
||||
and patterns via path.Match.
|
||||
SearchIndex.FindChild requires a parent to find the child, whereas the Finder also supports an ancestor via
|
||||
recursive object traversal.
|
||||
|
||||
The various Finder methods accept a "path" argument, which can absolute or relative to the Folder for the object type.
|
||||
The Finder supports two modes, "list" and "find". The "list" mode behaves like the "ls" command, only searching within
|
||||
the immediate path. The "find" mode behaves like the "find" command, with the search starting at the immediate path but
|
||||
also recursing into sub Folders relative to the Datacenter. The default mode is "list" if the given path contains a "/",
|
||||
otherwise "find" mode is used.
|
||||
|
||||
The exception is to use a "..." wildcard with a path to find all objects recursively underneath any root object.
|
||||
For example: VirtualMachineList("/DC1/...")
|
||||
|
||||
See also: https://github.com/vmware/govmomi/blob/master/govc/README.md#usage
|
||||
*/
|
||||
package find
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 find
|
||||
|
||||
import "fmt"
|
||||
|
||||
type NotFoundError struct {
|
||||
kind string
|
||||
path string
|
||||
}
|
||||
|
||||
func (e *NotFoundError) Error() string {
|
||||
return fmt.Sprintf("%s '%s' not found", e.kind, e.path)
|
||||
}
|
||||
|
||||
type MultipleFoundError struct {
|
||||
kind string
|
||||
path string
|
||||
}
|
||||
|
||||
func (e *MultipleFoundError) Error() string {
|
||||
return fmt.Sprintf("path '%s' resolves to multiple %ss", e.path, e.kind)
|
||||
}
|
||||
|
||||
type DefaultNotFoundError struct {
|
||||
kind string
|
||||
}
|
||||
|
||||
func (e *DefaultNotFoundError) Error() string {
|
||||
return fmt.Sprintf("no default %s found", e.kind)
|
||||
}
|
||||
|
||||
type DefaultMultipleFoundError struct {
|
||||
kind string
|
||||
}
|
||||
|
||||
func (e DefaultMultipleFoundError) Error() string {
|
||||
return fmt.Sprintf("default %s resolves to multiple instances, please specify", e.kind)
|
||||
}
|
||||
|
||||
func toDefaultError(err error) error {
|
||||
switch e := err.(type) {
|
||||
case *NotFoundError:
|
||||
return &DefaultNotFoundError{e.kind}
|
||||
case *MultipleFoundError:
|
||||
return &DefaultMultipleFoundError{e.kind}
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,253 @@
|
|||
/*
|
||||
Copyright (c) 2014-2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 find
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/govmomi/list"
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/property"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
)
|
||||
|
||||
// spec is used to specify per-search configuration, independent of the Finder instance.
|
||||
type spec struct {
|
||||
// Relative returns the root object to resolve Relative paths (starts with ".")
|
||||
Relative func(ctx context.Context) (object.Reference, error)
|
||||
|
||||
// ListMode can be used to optionally force "ls" behavior, rather than "find" behavior
|
||||
ListMode *bool
|
||||
|
||||
// Contents configures the Recurser to list the Contents of traversable leaf nodes.
|
||||
// This is typically set to true when used from the ls command, where listing
|
||||
// a folder means listing its Contents. This is typically set to false for
|
||||
// commands that take managed entities that are not folders as input.
|
||||
Contents bool
|
||||
|
||||
// Parents specifies the types which can contain the child types being searched for.
|
||||
// for example, when searching for a HostSystem, parent types can be
|
||||
// "ComputeResource" or "ClusterComputeResource".
|
||||
Parents []string
|
||||
|
||||
// Include specifies which types to be included in the results, used only in "find" mode.
|
||||
Include []string
|
||||
|
||||
// Nested should be set to types that can be Nested, used only in "find" mode.
|
||||
Nested []string
|
||||
|
||||
// ChildType avoids traversing into folders that can't contain the Include types, used only in "find" mode.
|
||||
ChildType []string
|
||||
}
|
||||
|
||||
func (s *spec) traversable(o mo.Reference) bool {
|
||||
ref := o.Reference()
|
||||
|
||||
switch ref.Type {
|
||||
case "Datacenter":
|
||||
if len(s.Include) == 1 && s.Include[0] == "Datacenter" {
|
||||
// No point in traversing deeper as Datacenters cannot be nested
|
||||
return false
|
||||
}
|
||||
return true
|
||||
case "Folder":
|
||||
if f, ok := o.(mo.Folder); ok {
|
||||
// TODO: Not making use of this yet, but here we can optimize when searching the entire
|
||||
// inventory across Datacenters for specific types, for example: 'govc ls -t VirtualMachine /**'
|
||||
// should not traverse into a Datacenter's host, network or datatore folders.
|
||||
if !s.traversableChildType(f.ChildType) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
for _, kind := range s.Parents {
|
||||
if kind == ref.Type {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *spec) traversableChildType(ctypes []string) bool {
|
||||
if len(s.ChildType) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, t := range ctypes {
|
||||
for _, c := range s.ChildType {
|
||||
if t == c {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *spec) wanted(e list.Element) bool {
|
||||
if len(s.Include) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
w := e.Object.Reference().Type
|
||||
|
||||
for _, kind := range s.Include {
|
||||
if w == kind {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// listMode is a global option to revert to the original Finder behavior,
|
||||
// disabling the newer "find" mode.
|
||||
var listMode = os.Getenv("GOVMOMI_FINDER_LIST_MODE") == "true"
|
||||
|
||||
func (s *spec) listMode(isPath bool) bool {
|
||||
if listMode {
|
||||
return true
|
||||
}
|
||||
|
||||
if s.ListMode != nil {
|
||||
return *s.ListMode
|
||||
}
|
||||
|
||||
return isPath
|
||||
}
|
||||
|
||||
type recurser struct {
|
||||
Collector *property.Collector
|
||||
|
||||
// All configures the recurses to fetch complete objects for leaf nodes.
|
||||
All bool
|
||||
}
|
||||
|
||||
func (r recurser) List(ctx context.Context, s *spec, root list.Element, parts []string) ([]list.Element, error) {
|
||||
if len(parts) == 0 {
|
||||
// Include non-traversable leaf elements in result. For example, consider
|
||||
// the pattern "./vm/my-vm-*", where the pattern should match the VMs and
|
||||
// not try to traverse them.
|
||||
//
|
||||
// Include traversable leaf elements in result, if the contents
|
||||
// field is set to false.
|
||||
//
|
||||
if !s.Contents || !s.traversable(root.Object.Reference()) {
|
||||
return []list.Element{root}, nil
|
||||
}
|
||||
}
|
||||
|
||||
k := list.Lister{
|
||||
Collector: r.Collector,
|
||||
Reference: root.Object.Reference(),
|
||||
Prefix: root.Path,
|
||||
}
|
||||
|
||||
if r.All && len(parts) < 2 {
|
||||
k.All = true
|
||||
}
|
||||
|
||||
in, err := k.List(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// This folder is a leaf as far as the glob goes.
|
||||
if len(parts) == 0 {
|
||||
return in, nil
|
||||
}
|
||||
|
||||
all := parts
|
||||
pattern := parts[0]
|
||||
parts = parts[1:]
|
||||
|
||||
var out []list.Element
|
||||
for _, e := range in {
|
||||
matched, err := path.Match(pattern, path.Base(e.Path))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !matched {
|
||||
matched = strings.HasSuffix(e.Path, "/"+path.Join(all...))
|
||||
if matched {
|
||||
// name contains a '/'
|
||||
out = append(out, e)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
nres, err := r.List(ctx, s, e, parts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out = append(out, nres...)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (r recurser) Find(ctx context.Context, s *spec, root list.Element, parts []string) ([]list.Element, error) {
|
||||
var out []list.Element
|
||||
|
||||
if len(parts) > 0 {
|
||||
pattern := parts[0]
|
||||
matched, err := path.Match(pattern, path.Base(root.Path))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if matched && s.wanted(root) {
|
||||
out = append(out, root)
|
||||
}
|
||||
}
|
||||
|
||||
if !s.traversable(root.Object) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
k := list.Lister{
|
||||
Collector: r.Collector,
|
||||
Reference: root.Object.Reference(),
|
||||
Prefix: root.Path,
|
||||
}
|
||||
|
||||
in, err := k.List(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, e := range in {
|
||||
nres, err := r.Find(ctx, s, e, parts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out = append(out, nres...)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
|
@ -0,0 +1,563 @@
|
|||
/*
|
||||
Copyright (c) 2014-2016 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 list
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
"reflect"
|
||||
|
||||
"github.com/vmware/govmomi/property"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type Element struct {
|
||||
Path string
|
||||
Object mo.Reference
|
||||
}
|
||||
|
||||
func (e Element) String() string {
|
||||
return fmt.Sprintf("%s @ %s", e.Object.Reference(), e.Path)
|
||||
}
|
||||
|
||||
func ToElement(r mo.Reference, prefix string) Element {
|
||||
var name string
|
||||
|
||||
// Comments about types to be expected in folders copied from the
|
||||
// documentation of the Folder managed object:
|
||||
// http://pubs.vmware.com/vsphere-55/topic/com.vmware.wssdk.apiref.doc/vim.Folder.html
|
||||
switch m := r.(type) {
|
||||
case mo.Folder:
|
||||
name = m.Name
|
||||
case mo.StoragePod:
|
||||
name = m.Name
|
||||
|
||||
// { "vim.Datacenter" } - Identifies the root folder and its descendant
|
||||
// folders. Data center folders can contain child data center folders and
|
||||
// Datacenter managed objects. Datacenter objects contain virtual machine,
|
||||
// compute resource, network entity, and datastore folders.
|
||||
case mo.Datacenter:
|
||||
name = m.Name
|
||||
|
||||
// { "vim.Virtualmachine", "vim.VirtualApp" } - Identifies a virtual machine
|
||||
// folder. A virtual machine folder may contain child virtual machine
|
||||
// folders. It also can contain VirtualMachine managed objects, templates,
|
||||
// and VirtualApp managed objects.
|
||||
case mo.VirtualMachine:
|
||||
name = m.Name
|
||||
case mo.VirtualApp:
|
||||
name = m.Name
|
||||
|
||||
// { "vim.ComputeResource" } - Identifies a compute resource
|
||||
// folder, which contains child compute resource folders and ComputeResource
|
||||
// hierarchies.
|
||||
case mo.ComputeResource:
|
||||
name = m.Name
|
||||
case mo.ClusterComputeResource:
|
||||
name = m.Name
|
||||
case mo.HostSystem:
|
||||
name = m.Name
|
||||
case mo.ResourcePool:
|
||||
name = m.Name
|
||||
|
||||
// { "vim.Network" } - Identifies a network entity folder.
|
||||
// Network entity folders on a vCenter Server can contain Network,
|
||||
// DistributedVirtualSwitch, and DistributedVirtualPortgroup managed objects.
|
||||
// Network entity folders on an ESXi host can contain only Network objects.
|
||||
case mo.Network:
|
||||
name = m.Name
|
||||
case mo.OpaqueNetwork:
|
||||
name = m.Name
|
||||
case mo.DistributedVirtualSwitch:
|
||||
name = m.Name
|
||||
case mo.DistributedVirtualPortgroup:
|
||||
name = m.Name
|
||||
case mo.VmwareDistributedVirtualSwitch:
|
||||
name = m.Name
|
||||
|
||||
// { "vim.Datastore" } - Identifies a datastore folder. Datastore folders can
|
||||
// contain child datastore folders and Datastore managed objects.
|
||||
case mo.Datastore:
|
||||
name = m.Name
|
||||
|
||||
default:
|
||||
panic("not implemented for type " + reflect.TypeOf(r).String())
|
||||
}
|
||||
|
||||
e := Element{
|
||||
Path: path.Join(prefix, name),
|
||||
Object: r,
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
type Lister struct {
|
||||
Collector *property.Collector
|
||||
Reference types.ManagedObjectReference
|
||||
Prefix string
|
||||
All bool
|
||||
}
|
||||
|
||||
func (l Lister) retrieveProperties(ctx context.Context, req types.RetrieveProperties, dst *[]interface{}) error {
|
||||
res, err := l.Collector.RetrieveProperties(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Instead of using mo.LoadRetrievePropertiesResponse, use a custom loop to
|
||||
// iterate over the results and ignore entries that have properties that
|
||||
// could not be retrieved (a non-empty `missingSet` property). Since the
|
||||
// returned objects are enumerated by vSphere in the first place, any object
|
||||
// that has a non-empty `missingSet` property is indicative of a race
|
||||
// condition in vSphere where the object was enumerated initially, but was
|
||||
// removed before its properties could be collected.
|
||||
for _, p := range res.Returnval {
|
||||
v, err := mo.ObjectContentToType(p)
|
||||
if err != nil {
|
||||
// Ignore fault if it is ManagedObjectNotFound
|
||||
if soap.IsVimFault(err) {
|
||||
switch soap.ToVimFault(err).(type) {
|
||||
case *types.ManagedObjectNotFound:
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
*dst = append(*dst, v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l Lister) List(ctx context.Context) ([]Element, error) {
|
||||
switch l.Reference.Type {
|
||||
case "Folder", "StoragePod":
|
||||
return l.ListFolder(ctx)
|
||||
case "Datacenter":
|
||||
return l.ListDatacenter(ctx)
|
||||
case "ComputeResource", "ClusterComputeResource":
|
||||
// Treat ComputeResource and ClusterComputeResource as one and the same.
|
||||
// It doesn't matter from the perspective of the lister.
|
||||
return l.ListComputeResource(ctx)
|
||||
case "ResourcePool":
|
||||
return l.ListResourcePool(ctx)
|
||||
case "HostSystem":
|
||||
return l.ListHostSystem(ctx)
|
||||
case "VirtualApp":
|
||||
return l.ListVirtualApp(ctx)
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot traverse type " + l.Reference.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func (l Lister) ListFolder(ctx context.Context) ([]Element, error) {
|
||||
spec := types.PropertyFilterSpec{
|
||||
ObjectSet: []types.ObjectSpec{
|
||||
{
|
||||
Obj: l.Reference,
|
||||
SelectSet: []types.BaseSelectionSpec{
|
||||
&types.TraversalSpec{
|
||||
Path: "childEntity",
|
||||
Skip: types.NewBool(false),
|
||||
Type: "Folder",
|
||||
},
|
||||
},
|
||||
Skip: types.NewBool(true),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Retrieve all objects that we can deal with
|
||||
childTypes := []string{
|
||||
"Folder",
|
||||
"Datacenter",
|
||||
"VirtualApp",
|
||||
"VirtualMachine",
|
||||
"Network",
|
||||
"ComputeResource",
|
||||
"ClusterComputeResource",
|
||||
"Datastore",
|
||||
"DistributedVirtualSwitch",
|
||||
}
|
||||
|
||||
for _, t := range childTypes {
|
||||
pspec := types.PropertySpec{
|
||||
Type: t,
|
||||
}
|
||||
|
||||
if l.All {
|
||||
pspec.All = types.NewBool(true)
|
||||
} else {
|
||||
pspec.PathSet = []string{"name"}
|
||||
|
||||
// Additional basic properties.
|
||||
switch t {
|
||||
case "Folder":
|
||||
pspec.PathSet = append(pspec.PathSet, "childType")
|
||||
case "ComputeResource", "ClusterComputeResource":
|
||||
// The ComputeResource and ClusterComputeResource are dereferenced in
|
||||
// the ResourcePoolFlag. Make sure they always have their resourcePool
|
||||
// field populated.
|
||||
pspec.PathSet = append(pspec.PathSet, "resourcePool")
|
||||
}
|
||||
}
|
||||
|
||||
spec.PropSet = append(spec.PropSet, pspec)
|
||||
}
|
||||
|
||||
req := types.RetrieveProperties{
|
||||
SpecSet: []types.PropertyFilterSpec{spec},
|
||||
}
|
||||
|
||||
var dst []interface{}
|
||||
|
||||
err := l.retrieveProperties(ctx, req, &dst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
es := []Element{}
|
||||
for _, v := range dst {
|
||||
es = append(es, ToElement(v.(mo.Reference), l.Prefix))
|
||||
}
|
||||
|
||||
return es, nil
|
||||
}
|
||||
|
||||
func (l Lister) ListDatacenter(ctx context.Context) ([]Element, error) {
|
||||
ospec := types.ObjectSpec{
|
||||
Obj: l.Reference,
|
||||
Skip: types.NewBool(true),
|
||||
}
|
||||
|
||||
// Include every datastore folder in the select set
|
||||
fields := []string{
|
||||
"vmFolder",
|
||||
"hostFolder",
|
||||
"datastoreFolder",
|
||||
"networkFolder",
|
||||
}
|
||||
|
||||
for _, f := range fields {
|
||||
tspec := types.TraversalSpec{
|
||||
Path: f,
|
||||
Skip: types.NewBool(false),
|
||||
Type: "Datacenter",
|
||||
}
|
||||
|
||||
ospec.SelectSet = append(ospec.SelectSet, &tspec)
|
||||
}
|
||||
|
||||
pspec := types.PropertySpec{
|
||||
Type: "Folder",
|
||||
}
|
||||
|
||||
if l.All {
|
||||
pspec.All = types.NewBool(true)
|
||||
} else {
|
||||
pspec.PathSet = []string{"name", "childType"}
|
||||
}
|
||||
|
||||
req := types.RetrieveProperties{
|
||||
SpecSet: []types.PropertyFilterSpec{
|
||||
{
|
||||
ObjectSet: []types.ObjectSpec{ospec},
|
||||
PropSet: []types.PropertySpec{pspec},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var dst []interface{}
|
||||
|
||||
err := l.retrieveProperties(ctx, req, &dst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
es := []Element{}
|
||||
for _, v := range dst {
|
||||
es = append(es, ToElement(v.(mo.Reference), l.Prefix))
|
||||
}
|
||||
|
||||
return es, nil
|
||||
}
|
||||
|
||||
func (l Lister) ListComputeResource(ctx context.Context) ([]Element, error) {
|
||||
ospec := types.ObjectSpec{
|
||||
Obj: l.Reference,
|
||||
Skip: types.NewBool(true),
|
||||
}
|
||||
|
||||
fields := []string{
|
||||
"host",
|
||||
"resourcePool",
|
||||
}
|
||||
|
||||
for _, f := range fields {
|
||||
tspec := types.TraversalSpec{
|
||||
Path: f,
|
||||
Skip: types.NewBool(false),
|
||||
Type: "ComputeResource",
|
||||
}
|
||||
|
||||
ospec.SelectSet = append(ospec.SelectSet, &tspec)
|
||||
}
|
||||
|
||||
childTypes := []string{
|
||||
"HostSystem",
|
||||
"ResourcePool",
|
||||
}
|
||||
|
||||
var pspecs []types.PropertySpec
|
||||
for _, t := range childTypes {
|
||||
pspec := types.PropertySpec{
|
||||
Type: t,
|
||||
}
|
||||
|
||||
if l.All {
|
||||
pspec.All = types.NewBool(true)
|
||||
} else {
|
||||
pspec.PathSet = []string{"name"}
|
||||
}
|
||||
|
||||
pspecs = append(pspecs, pspec)
|
||||
}
|
||||
|
||||
req := types.RetrieveProperties{
|
||||
SpecSet: []types.PropertyFilterSpec{
|
||||
{
|
||||
ObjectSet: []types.ObjectSpec{ospec},
|
||||
PropSet: pspecs,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var dst []interface{}
|
||||
|
||||
err := l.retrieveProperties(ctx, req, &dst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
es := []Element{}
|
||||
for _, v := range dst {
|
||||
es = append(es, ToElement(v.(mo.Reference), l.Prefix))
|
||||
}
|
||||
|
||||
return es, nil
|
||||
}
|
||||
|
||||
func (l Lister) ListResourcePool(ctx context.Context) ([]Element, error) {
|
||||
ospec := types.ObjectSpec{
|
||||
Obj: l.Reference,
|
||||
Skip: types.NewBool(true),
|
||||
}
|
||||
|
||||
fields := []string{
|
||||
"resourcePool",
|
||||
}
|
||||
|
||||
for _, f := range fields {
|
||||
tspec := types.TraversalSpec{
|
||||
Path: f,
|
||||
Skip: types.NewBool(false),
|
||||
Type: "ResourcePool",
|
||||
}
|
||||
|
||||
ospec.SelectSet = append(ospec.SelectSet, &tspec)
|
||||
}
|
||||
|
||||
childTypes := []string{
|
||||
"ResourcePool",
|
||||
}
|
||||
|
||||
var pspecs []types.PropertySpec
|
||||
for _, t := range childTypes {
|
||||
pspec := types.PropertySpec{
|
||||
Type: t,
|
||||
}
|
||||
|
||||
if l.All {
|
||||
pspec.All = types.NewBool(true)
|
||||
} else {
|
||||
pspec.PathSet = []string{"name"}
|
||||
}
|
||||
|
||||
pspecs = append(pspecs, pspec)
|
||||
}
|
||||
|
||||
req := types.RetrieveProperties{
|
||||
SpecSet: []types.PropertyFilterSpec{
|
||||
{
|
||||
ObjectSet: []types.ObjectSpec{ospec},
|
||||
PropSet: pspecs,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var dst []interface{}
|
||||
|
||||
err := l.retrieveProperties(ctx, req, &dst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
es := []Element{}
|
||||
for _, v := range dst {
|
||||
es = append(es, ToElement(v.(mo.Reference), l.Prefix))
|
||||
}
|
||||
|
||||
return es, nil
|
||||
}
|
||||
|
||||
func (l Lister) ListHostSystem(ctx context.Context) ([]Element, error) {
|
||||
ospec := types.ObjectSpec{
|
||||
Obj: l.Reference,
|
||||
Skip: types.NewBool(true),
|
||||
}
|
||||
|
||||
fields := []string{
|
||||
"datastore",
|
||||
"network",
|
||||
"vm",
|
||||
}
|
||||
|
||||
for _, f := range fields {
|
||||
tspec := types.TraversalSpec{
|
||||
Path: f,
|
||||
Skip: types.NewBool(false),
|
||||
Type: "HostSystem",
|
||||
}
|
||||
|
||||
ospec.SelectSet = append(ospec.SelectSet, &tspec)
|
||||
}
|
||||
|
||||
childTypes := []string{
|
||||
"Datastore",
|
||||
"Network",
|
||||
"VirtualMachine",
|
||||
}
|
||||
|
||||
var pspecs []types.PropertySpec
|
||||
for _, t := range childTypes {
|
||||
pspec := types.PropertySpec{
|
||||
Type: t,
|
||||
}
|
||||
|
||||
if l.All {
|
||||
pspec.All = types.NewBool(true)
|
||||
} else {
|
||||
pspec.PathSet = []string{"name"}
|
||||
}
|
||||
|
||||
pspecs = append(pspecs, pspec)
|
||||
}
|
||||
|
||||
req := types.RetrieveProperties{
|
||||
SpecSet: []types.PropertyFilterSpec{
|
||||
{
|
||||
ObjectSet: []types.ObjectSpec{ospec},
|
||||
PropSet: pspecs,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var dst []interface{}
|
||||
|
||||
err := l.retrieveProperties(ctx, req, &dst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
es := []Element{}
|
||||
for _, v := range dst {
|
||||
es = append(es, ToElement(v.(mo.Reference), l.Prefix))
|
||||
}
|
||||
|
||||
return es, nil
|
||||
}
|
||||
|
||||
func (l Lister) ListVirtualApp(ctx context.Context) ([]Element, error) {
|
||||
ospec := types.ObjectSpec{
|
||||
Obj: l.Reference,
|
||||
Skip: types.NewBool(true),
|
||||
}
|
||||
|
||||
fields := []string{
|
||||
"resourcePool",
|
||||
"vm",
|
||||
}
|
||||
|
||||
for _, f := range fields {
|
||||
tspec := types.TraversalSpec{
|
||||
Path: f,
|
||||
Skip: types.NewBool(false),
|
||||
Type: "VirtualApp",
|
||||
}
|
||||
|
||||
ospec.SelectSet = append(ospec.SelectSet, &tspec)
|
||||
}
|
||||
|
||||
childTypes := []string{
|
||||
"ResourcePool",
|
||||
"VirtualMachine",
|
||||
}
|
||||
|
||||
var pspecs []types.PropertySpec
|
||||
for _, t := range childTypes {
|
||||
pspec := types.PropertySpec{
|
||||
Type: t,
|
||||
}
|
||||
|
||||
if l.All {
|
||||
pspec.All = types.NewBool(true)
|
||||
} else {
|
||||
pspec.PathSet = []string{"name"}
|
||||
}
|
||||
|
||||
pspecs = append(pspecs, pspec)
|
||||
}
|
||||
|
||||
req := types.RetrieveProperties{
|
||||
SpecSet: []types.PropertyFilterSpec{
|
||||
{
|
||||
ObjectSet: []types.ObjectSpec{ospec},
|
||||
PropSet: pspecs,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var dst []interface{}
|
||||
|
||||
err := l.retrieveProperties(ctx, req, &dst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
es := []Element{}
|
||||
for _, v := range dst {
|
||||
es = append(es, ToElement(v.(mo.Reference), l.Prefix))
|
||||
}
|
||||
|
||||
return es, nil
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
Copyright (c) 2014 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 list
|
||||
|
||||
import (
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ToParts(p string) []string {
|
||||
p = path.Clean(p)
|
||||
if p == "/" {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
if len(p) > 0 {
|
||||
// Prefix ./ if relative
|
||||
if p[0] != '/' && p[0] != '.' {
|
||||
p = "./" + p
|
||||
}
|
||||
}
|
||||
|
||||
ps := strings.Split(p, "/")
|
||||
if ps[0] == "" {
|
||||
// Start at root
|
||||
ps = ps[1:]
|
||||
}
|
||||
|
||||
return ps
|
||||
}
|
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
Copyright (c) 2015-2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 nfc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
|
||||
"github.com/vmware/govmomi/property"
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/progress"
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type Lease struct {
|
||||
types.ManagedObjectReference
|
||||
|
||||
c *vim25.Client
|
||||
}
|
||||
|
||||
func NewLease(c *vim25.Client, ref types.ManagedObjectReference) *Lease {
|
||||
return &Lease{ref, c}
|
||||
}
|
||||
|
||||
// Abort wraps methods.Abort
|
||||
func (l *Lease) Abort(ctx context.Context, fault *types.LocalizedMethodFault) error {
|
||||
req := types.HttpNfcLeaseAbort{
|
||||
This: l.Reference(),
|
||||
Fault: fault,
|
||||
}
|
||||
|
||||
_, err := methods.HttpNfcLeaseAbort(ctx, l.c, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Complete wraps methods.Complete
|
||||
func (l *Lease) Complete(ctx context.Context) error {
|
||||
req := types.HttpNfcLeaseComplete{
|
||||
This: l.Reference(),
|
||||
}
|
||||
|
||||
_, err := methods.HttpNfcLeaseComplete(ctx, l.c, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetManifest wraps methods.GetManifest
|
||||
func (l *Lease) GetManifest(ctx context.Context) error {
|
||||
req := types.HttpNfcLeaseGetManifest{
|
||||
This: l.Reference(),
|
||||
}
|
||||
|
||||
_, err := methods.HttpNfcLeaseGetManifest(ctx, l.c, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Progress wraps methods.Progress
|
||||
func (l *Lease) Progress(ctx context.Context, percent int32) error {
|
||||
req := types.HttpNfcLeaseProgress{
|
||||
This: l.Reference(),
|
||||
Percent: percent,
|
||||
}
|
||||
|
||||
_, err := methods.HttpNfcLeaseProgress(ctx, l.c, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type LeaseInfo struct {
|
||||
types.HttpNfcLeaseInfo
|
||||
|
||||
Items []FileItem
|
||||
}
|
||||
|
||||
func (l *Lease) newLeaseInfo(li *types.HttpNfcLeaseInfo, items []types.OvfFileItem) (*LeaseInfo, error) {
|
||||
info := &LeaseInfo{
|
||||
HttpNfcLeaseInfo: *li,
|
||||
}
|
||||
|
||||
for _, device := range li.DeviceUrl {
|
||||
u, err := l.c.ParseURL(device.Url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if device.SslThumbprint != "" {
|
||||
// TODO: prefer host management IP
|
||||
l.c.SetThumbprint(u.Host, device.SslThumbprint)
|
||||
}
|
||||
|
||||
if len(items) == 0 {
|
||||
// this is an export
|
||||
item := types.OvfFileItem{
|
||||
DeviceId: device.Key,
|
||||
Path: device.TargetId,
|
||||
Size: device.FileSize,
|
||||
}
|
||||
|
||||
if item.Size == 0 {
|
||||
item.Size = li.TotalDiskCapacityInKB * 1024
|
||||
}
|
||||
|
||||
if item.Path == "" {
|
||||
item.Path = path.Base(device.Url)
|
||||
}
|
||||
|
||||
info.Items = append(info.Items, NewFileItem(u, item))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// this is an import
|
||||
for _, item := range items {
|
||||
if device.ImportKey == item.DeviceId {
|
||||
info.Items = append(info.Items, NewFileItem(u, item))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (l *Lease) Wait(ctx context.Context, items []types.OvfFileItem) (*LeaseInfo, error) {
|
||||
var lease mo.HttpNfcLease
|
||||
|
||||
pc := property.DefaultCollector(l.c)
|
||||
err := property.Wait(ctx, pc, l.Reference(), []string{"state", "info", "error"}, func(pc []types.PropertyChange) bool {
|
||||
done := false
|
||||
|
||||
for _, c := range pc {
|
||||
if c.Val == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
switch c.Name {
|
||||
case "error":
|
||||
val := c.Val.(types.LocalizedMethodFault)
|
||||
lease.Error = &val
|
||||
done = true
|
||||
case "info":
|
||||
val := c.Val.(types.HttpNfcLeaseInfo)
|
||||
lease.Info = &val
|
||||
case "state":
|
||||
lease.State = c.Val.(types.HttpNfcLeaseState)
|
||||
if lease.State != types.HttpNfcLeaseStateInitializing {
|
||||
done = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return done
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if lease.State == types.HttpNfcLeaseStateReady {
|
||||
return l.newLeaseInfo(lease.Info, items)
|
||||
}
|
||||
|
||||
if lease.Error != nil {
|
||||
return nil, errors.New(lease.Error.LocalizedMessage)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unexpected nfc lease state: %s", lease.State)
|
||||
}
|
||||
|
||||
func (l *Lease) StartUpdater(ctx context.Context, info *LeaseInfo) *LeaseUpdater {
|
||||
return newLeaseUpdater(ctx, l, info)
|
||||
}
|
||||
|
||||
func (l *Lease) Upload(ctx context.Context, item FileItem, f io.Reader, opts soap.Upload) error {
|
||||
if opts.Progress == nil {
|
||||
opts.Progress = item
|
||||
} else {
|
||||
opts.Progress = progress.Tee(item, opts.Progress)
|
||||
}
|
||||
|
||||
// Non-disk files (such as .iso) use the PUT method.
|
||||
// Overwrite: t header is also required in this case (ovftool does the same)
|
||||
if item.Create {
|
||||
opts.Method = "PUT"
|
||||
opts.Headers = map[string]string{
|
||||
"Overwrite": "t",
|
||||
}
|
||||
} else {
|
||||
opts.Method = "POST"
|
||||
opts.Type = "application/x-vnd.vmware-streamVmdk"
|
||||
}
|
||||
|
||||
return l.c.Upload(ctx, f, item.URL, &opts)
|
||||
}
|
||||
|
||||
func (l *Lease) DownloadFile(ctx context.Context, file string, item FileItem, opts soap.Download) error {
|
||||
if opts.Progress == nil {
|
||||
opts.Progress = item
|
||||
} else {
|
||||
opts.Progress = progress.Tee(item, opts.Progress)
|
||||
}
|
||||
|
||||
return l.c.DownloadFile(ctx, file, item.URL, &opts)
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
Copyright (c) 2014-2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 nfc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/url"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/progress"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type FileItem struct {
|
||||
types.OvfFileItem
|
||||
URL *url.URL
|
||||
|
||||
ch chan progress.Report
|
||||
}
|
||||
|
||||
func NewFileItem(u *url.URL, item types.OvfFileItem) FileItem {
|
||||
return FileItem{
|
||||
OvfFileItem: item,
|
||||
URL: u,
|
||||
ch: make(chan progress.Report),
|
||||
}
|
||||
}
|
||||
|
||||
func (o FileItem) Sink() chan<- progress.Report {
|
||||
return o.ch
|
||||
}
|
||||
|
||||
// File converts the FileItem.OvfFileItem to an OvfFile
|
||||
func (o FileItem) File() types.OvfFile {
|
||||
return types.OvfFile{
|
||||
DeviceId: o.DeviceId,
|
||||
Path: o.Path,
|
||||
Size: o.Size,
|
||||
}
|
||||
}
|
||||
|
||||
type LeaseUpdater struct {
|
||||
lease *Lease
|
||||
|
||||
pos int64 // Number of bytes
|
||||
total int64 // Total number of bytes
|
||||
|
||||
done chan struct{} // When lease updater should stop
|
||||
|
||||
wg sync.WaitGroup // Track when update loop is done
|
||||
}
|
||||
|
||||
func newLeaseUpdater(ctx context.Context, lease *Lease, info *LeaseInfo) *LeaseUpdater {
|
||||
l := LeaseUpdater{
|
||||
lease: lease,
|
||||
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
for _, item := range info.Items {
|
||||
l.total += item.Size
|
||||
go l.waitForProgress(item)
|
||||
}
|
||||
|
||||
// Kickstart update loop
|
||||
l.wg.Add(1)
|
||||
go l.run()
|
||||
|
||||
return &l
|
||||
}
|
||||
|
||||
func (l *LeaseUpdater) waitForProgress(item FileItem) {
|
||||
var pos, total int64
|
||||
|
||||
total = item.Size
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-l.done:
|
||||
return
|
||||
case p, ok := <-item.ch:
|
||||
// Return in case of error
|
||||
if ok && p.Error() != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !ok {
|
||||
// Last element on the channel, add to total
|
||||
atomic.AddInt64(&l.pos, total-pos)
|
||||
return
|
||||
}
|
||||
|
||||
// Approximate progress in number of bytes
|
||||
x := int64(float32(total) * (p.Percentage() / 100.0))
|
||||
atomic.AddInt64(&l.pos, x-pos)
|
||||
pos = x
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LeaseUpdater) run() {
|
||||
defer l.wg.Done()
|
||||
|
||||
tick := time.NewTicker(2 * time.Second)
|
||||
defer tick.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-l.done:
|
||||
return
|
||||
case <-tick.C:
|
||||
// From the vim api HttpNfcLeaseProgress(percent) doc, percent ==
|
||||
// "Completion status represented as an integer in the 0-100 range."
|
||||
// Always report the current value of percent, as it will renew the
|
||||
// lease even if the value hasn't changed or is 0.
|
||||
percent := int32(float32(100*atomic.LoadInt64(&l.pos)) / float32(l.total))
|
||||
err := l.lease.Progress(context.TODO(), percent)
|
||||
if err != nil {
|
||||
log.Printf("NFC lease progress: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LeaseUpdater) Done() {
|
||||
close(l.done)
|
||||
l.wg.Wait()
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type AuthorizationManager struct {
|
||||
Common
|
||||
}
|
||||
|
||||
func NewAuthorizationManager(c *vim25.Client) *AuthorizationManager {
|
||||
m := AuthorizationManager{
|
||||
Common: NewCommon(c, *c.ServiceContent.AuthorizationManager),
|
||||
}
|
||||
|
||||
return &m
|
||||
}
|
||||
|
||||
type AuthorizationRoleList []types.AuthorizationRole
|
||||
|
||||
func (l AuthorizationRoleList) ById(id int32) *types.AuthorizationRole {
|
||||
for _, role := range l {
|
||||
if role.RoleId == id {
|
||||
return &role
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l AuthorizationRoleList) ByName(name string) *types.AuthorizationRole {
|
||||
for _, role := range l {
|
||||
if role.Name == name {
|
||||
return &role
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m AuthorizationManager) RoleList(ctx context.Context) (AuthorizationRoleList, error) {
|
||||
var am mo.AuthorizationManager
|
||||
|
||||
err := m.Properties(ctx, m.Reference(), []string{"roleList"}, &am)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return AuthorizationRoleList(am.RoleList), nil
|
||||
}
|
||||
|
||||
func (m AuthorizationManager) RetrieveEntityPermissions(ctx context.Context, entity types.ManagedObjectReference, inherited bool) ([]types.Permission, error) {
|
||||
req := types.RetrieveEntityPermissions{
|
||||
This: m.Reference(),
|
||||
Entity: entity,
|
||||
Inherited: inherited,
|
||||
}
|
||||
|
||||
res, err := methods.RetrieveEntityPermissions(ctx, m.Client(), &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res.Returnval, nil
|
||||
}
|
||||
|
||||
func (m AuthorizationManager) RemoveEntityPermission(ctx context.Context, entity types.ManagedObjectReference, user string, isGroup bool) error {
|
||||
req := types.RemoveEntityPermission{
|
||||
This: m.Reference(),
|
||||
Entity: entity,
|
||||
User: user,
|
||||
IsGroup: isGroup,
|
||||
}
|
||||
|
||||
_, err := methods.RemoveEntityPermission(ctx, m.Client(), &req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m AuthorizationManager) SetEntityPermissions(ctx context.Context, entity types.ManagedObjectReference, permission []types.Permission) error {
|
||||
req := types.SetEntityPermissions{
|
||||
This: m.Reference(),
|
||||
Entity: entity,
|
||||
Permission: permission,
|
||||
}
|
||||
|
||||
_, err := methods.SetEntityPermissions(ctx, m.Client(), &req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m AuthorizationManager) RetrieveRolePermissions(ctx context.Context, id int32) ([]types.Permission, error) {
|
||||
req := types.RetrieveRolePermissions{
|
||||
This: m.Reference(),
|
||||
RoleId: id,
|
||||
}
|
||||
|
||||
res, err := methods.RetrieveRolePermissions(ctx, m.Client(), &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res.Returnval, nil
|
||||
}
|
||||
|
||||
func (m AuthorizationManager) RetrieveAllPermissions(ctx context.Context) ([]types.Permission, error) {
|
||||
req := types.RetrieveAllPermissions{
|
||||
This: m.Reference(),
|
||||
}
|
||||
|
||||
res, err := methods.RetrieveAllPermissions(ctx, m.Client(), &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res.Returnval, nil
|
||||
}
|
||||
|
||||
func (m AuthorizationManager) AddRole(ctx context.Context, name string, ids []string) (int32, error) {
|
||||
req := types.AddAuthorizationRole{
|
||||
This: m.Reference(),
|
||||
Name: name,
|
||||
PrivIds: ids,
|
||||
}
|
||||
|
||||
res, err := methods.AddAuthorizationRole(ctx, m.Client(), &req)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return res.Returnval, nil
|
||||
}
|
||||
|
||||
func (m AuthorizationManager) RemoveRole(ctx context.Context, id int32, failIfUsed bool) error {
|
||||
req := types.RemoveAuthorizationRole{
|
||||
This: m.Reference(),
|
||||
RoleId: id,
|
||||
FailIfUsed: failIfUsed,
|
||||
}
|
||||
|
||||
_, err := methods.RemoveAuthorizationRole(ctx, m.Client(), &req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m AuthorizationManager) UpdateRole(ctx context.Context, id int32, name string, ids []string) error {
|
||||
req := types.UpdateAuthorizationRole{
|
||||
This: m.Reference(),
|
||||
RoleId: id,
|
||||
NewName: name,
|
||||
PrivIds: ids,
|
||||
}
|
||||
|
||||
_, err := methods.UpdateAuthorizationRole(ctx, m.Client(), &req)
|
||||
return err
|
||||
}
|
86
vendor/github.com/vmware/govmomi/object/authorization_manager_internal.go
generated
vendored
Normal file
86
vendor/github.com/vmware/govmomi/object/authorization_manager_internal.go
generated
vendored
Normal file
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type DisabledMethodRequest struct {
|
||||
Method string `xml:"method"`
|
||||
Reason string `xml:"reasonId"`
|
||||
}
|
||||
|
||||
type disableMethodsRequest struct {
|
||||
This types.ManagedObjectReference `xml:"_this"`
|
||||
Entity []types.ManagedObjectReference `xml:"entity"`
|
||||
Method []DisabledMethodRequest `xml:"method"`
|
||||
Source string `xml:"sourceId"`
|
||||
Scope bool `xml:"sessionScope,omitempty"`
|
||||
}
|
||||
|
||||
type disableMethodsBody struct {
|
||||
Req *disableMethodsRequest `xml:"urn:internalvim25 DisableMethods,omitempty"`
|
||||
Res interface{} `xml:"urn:vim25 DisableMethodsResponse,omitempty"`
|
||||
Err *soap.Fault `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault,omitempty"`
|
||||
}
|
||||
|
||||
func (b *disableMethodsBody) Fault() *soap.Fault { return b.Err }
|
||||
|
||||
func (m AuthorizationManager) DisableMethods(ctx context.Context, entity []types.ManagedObjectReference, method []DisabledMethodRequest, source string) error {
|
||||
var reqBody, resBody disableMethodsBody
|
||||
|
||||
reqBody.Req = &disableMethodsRequest{
|
||||
This: m.Reference(),
|
||||
Entity: entity,
|
||||
Method: method,
|
||||
Source: source,
|
||||
}
|
||||
|
||||
return m.Client().RoundTrip(ctx, &reqBody, &resBody)
|
||||
}
|
||||
|
||||
type enableMethodsRequest struct {
|
||||
This types.ManagedObjectReference `xml:"_this"`
|
||||
Entity []types.ManagedObjectReference `xml:"entity"`
|
||||
Method []string `xml:"method"`
|
||||
Source string `xml:"sourceId"`
|
||||
}
|
||||
|
||||
type enableMethodsBody struct {
|
||||
Req *enableMethodsRequest `xml:"urn:internalvim25 EnableMethods,omitempty"`
|
||||
Res interface{} `xml:"urn:vim25 EnableMethodsResponse,omitempty"`
|
||||
Err *soap.Fault `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault,omitempty"`
|
||||
}
|
||||
|
||||
func (b *enableMethodsBody) Fault() *soap.Fault { return b.Err }
|
||||
|
||||
func (m AuthorizationManager) EnableMethods(ctx context.Context, entity []types.ManagedObjectReference, method []string, source string) error {
|
||||
var reqBody, resBody enableMethodsBody
|
||||
|
||||
reqBody.Req = &enableMethodsRequest{
|
||||
This: m.Reference(),
|
||||
Entity: entity,
|
||||
Method: method,
|
||||
Source: source,
|
||||
}
|
||||
|
||||
return m.Client().RoundTrip(ctx, &reqBody, &resBody)
|
||||
}
|
70
vendor/github.com/vmware/govmomi/object/cluster_compute_resource.go
generated
vendored
Normal file
70
vendor/github.com/vmware/govmomi/object/cluster_compute_resource.go
generated
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type ClusterComputeResource struct {
|
||||
ComputeResource
|
||||
}
|
||||
|
||||
func NewClusterComputeResource(c *vim25.Client, ref types.ManagedObjectReference) *ClusterComputeResource {
|
||||
return &ClusterComputeResource{
|
||||
ComputeResource: *NewComputeResource(c, ref),
|
||||
}
|
||||
}
|
||||
|
||||
func (c ClusterComputeResource) Configuration(ctx context.Context) (*types.ClusterConfigInfoEx, error) {
|
||||
var obj mo.ClusterComputeResource
|
||||
|
||||
err := c.Properties(ctx, c.Reference(), []string{"configurationEx"}, &obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return obj.ConfigurationEx.(*types.ClusterConfigInfoEx), nil
|
||||
}
|
||||
|
||||
func (c ClusterComputeResource) AddHost(ctx context.Context, spec types.HostConnectSpec, asConnected bool, license *string, resourcePool *types.ManagedObjectReference) (*Task, error) {
|
||||
req := types.AddHost_Task{
|
||||
This: c.Reference(),
|
||||
Spec: spec,
|
||||
AsConnected: asConnected,
|
||||
}
|
||||
|
||||
if license != nil {
|
||||
req.License = *license
|
||||
}
|
||||
|
||||
if resourcePool != nil {
|
||||
req.ResourcePool = resourcePool
|
||||
}
|
||||
|
||||
res, err := methods.AddHost_Task(ctx, c.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(c.c, res.Returnval), nil
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
"github.com/vmware/govmomi/property"
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotSupported = errors.New("product/version specific feature not supported by target")
|
||||
)
|
||||
|
||||
// Common contains the fields and functions common to all objects.
|
||||
type Common struct {
|
||||
InventoryPath string
|
||||
|
||||
c *vim25.Client
|
||||
r types.ManagedObjectReference
|
||||
}
|
||||
|
||||
func (c Common) String() string {
|
||||
ref := fmt.Sprintf("%v", c.Reference())
|
||||
|
||||
if c.InventoryPath == "" {
|
||||
return ref
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s @ %s", ref, c.InventoryPath)
|
||||
}
|
||||
|
||||
func NewCommon(c *vim25.Client, r types.ManagedObjectReference) Common {
|
||||
return Common{c: c, r: r}
|
||||
}
|
||||
|
||||
func (c Common) Reference() types.ManagedObjectReference {
|
||||
return c.r
|
||||
}
|
||||
|
||||
func (c Common) Client() *vim25.Client {
|
||||
return c.c
|
||||
}
|
||||
|
||||
// Name returns the base name of the InventoryPath field
|
||||
func (c Common) Name() string {
|
||||
if c.InventoryPath == "" {
|
||||
return ""
|
||||
}
|
||||
return path.Base(c.InventoryPath)
|
||||
}
|
||||
|
||||
func (c *Common) SetInventoryPath(p string) {
|
||||
c.InventoryPath = p
|
||||
}
|
||||
|
||||
// ObjectName returns the base name of the InventoryPath field if set,
|
||||
// otherwise fetches the mo.ManagedEntity.Name field via the property collector.
|
||||
func (c Common) ObjectName(ctx context.Context) (string, error) {
|
||||
var o mo.ManagedEntity
|
||||
|
||||
err := c.Properties(ctx, c.Reference(), []string{"name"}, &o)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if o.Name != "" {
|
||||
return o.Name, nil
|
||||
}
|
||||
|
||||
// Network has its own "name" field...
|
||||
var n mo.Network
|
||||
|
||||
err = c.Properties(ctx, c.Reference(), []string{"name"}, &n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return n.Name, nil
|
||||
}
|
||||
|
||||
func (c Common) Properties(ctx context.Context, r types.ManagedObjectReference, ps []string, dst interface{}) error {
|
||||
return property.DefaultCollector(c.c).RetrieveOne(ctx, r, ps, dst)
|
||||
}
|
||||
|
||||
func (c Common) Destroy(ctx context.Context) (*Task, error) {
|
||||
req := types.Destroy_Task{
|
||||
This: c.Reference(),
|
||||
}
|
||||
|
||||
res, err := methods.Destroy_Task(ctx, c.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(c.c, res.Returnval), nil
|
||||
}
|
||||
|
||||
func (c Common) Rename(ctx context.Context, name string) (*Task, error) {
|
||||
req := types.Rename_Task{
|
||||
This: c.Reference(),
|
||||
NewName: name,
|
||||
}
|
||||
|
||||
res, err := methods.Rename_Task(ctx, c.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(c.c, res.Returnval), nil
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path"
|
||||
|
||||
"github.com/vmware/govmomi/property"
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type ComputeResource struct {
|
||||
Common
|
||||
}
|
||||
|
||||
func NewComputeResource(c *vim25.Client, ref types.ManagedObjectReference) *ComputeResource {
|
||||
return &ComputeResource{
|
||||
Common: NewCommon(c, ref),
|
||||
}
|
||||
}
|
||||
|
||||
func (c ComputeResource) Hosts(ctx context.Context) ([]*HostSystem, error) {
|
||||
var cr mo.ComputeResource
|
||||
|
||||
err := c.Properties(ctx, c.Reference(), []string{"host"}, &cr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(cr.Host) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var hs []mo.HostSystem
|
||||
pc := property.DefaultCollector(c.Client())
|
||||
err = pc.Retrieve(ctx, cr.Host, []string{"name"}, &hs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var hosts []*HostSystem
|
||||
|
||||
for _, h := range hs {
|
||||
host := NewHostSystem(c.Client(), h.Reference())
|
||||
host.InventoryPath = path.Join(c.InventoryPath, h.Name)
|
||||
hosts = append(hosts, host)
|
||||
}
|
||||
|
||||
return hosts, nil
|
||||
}
|
||||
|
||||
func (c ComputeResource) Datastores(ctx context.Context) ([]*Datastore, error) {
|
||||
var cr mo.ComputeResource
|
||||
|
||||
err := c.Properties(ctx, c.Reference(), []string{"datastore"}, &cr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var dss []*Datastore
|
||||
for _, ref := range cr.Datastore {
|
||||
ds := NewDatastore(c.c, ref)
|
||||
dss = append(dss, ds)
|
||||
}
|
||||
|
||||
return dss, nil
|
||||
}
|
||||
|
||||
func (c ComputeResource) ResourcePool(ctx context.Context) (*ResourcePool, error) {
|
||||
var cr mo.ComputeResource
|
||||
|
||||
err := c.Properties(ctx, c.Reference(), []string{"resourcePool"}, &cr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewResourcePool(c.c, *cr.ResourcePool), nil
|
||||
}
|
||||
|
||||
func (c ComputeResource) Reconfigure(ctx context.Context, spec types.BaseComputeResourceConfigSpec, modify bool) (*Task, error) {
|
||||
req := types.ReconfigureComputeResource_Task{
|
||||
This: c.Reference(),
|
||||
Spec: spec,
|
||||
Modify: modify,
|
||||
}
|
||||
|
||||
res, err := methods.ReconfigureComputeResource_Task(ctx, c.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(c.c, res.Returnval), nil
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrKeyNameNotFound = errors.New("key name not found")
|
||||
)
|
||||
|
||||
type CustomFieldsManager struct {
|
||||
Common
|
||||
}
|
||||
|
||||
// GetCustomFieldsManager wraps NewCustomFieldsManager, returning ErrNotSupported
|
||||
// when the client is not connected to a vCenter instance.
|
||||
func GetCustomFieldsManager(c *vim25.Client) (*CustomFieldsManager, error) {
|
||||
if c.ServiceContent.CustomFieldsManager == nil {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
return NewCustomFieldsManager(c), nil
|
||||
}
|
||||
|
||||
func NewCustomFieldsManager(c *vim25.Client) *CustomFieldsManager {
|
||||
m := CustomFieldsManager{
|
||||
Common: NewCommon(c, *c.ServiceContent.CustomFieldsManager),
|
||||
}
|
||||
|
||||
return &m
|
||||
}
|
||||
|
||||
func (m CustomFieldsManager) Add(ctx context.Context, name string, moType string, fieldDefPolicy *types.PrivilegePolicyDef, fieldPolicy *types.PrivilegePolicyDef) (*types.CustomFieldDef, error) {
|
||||
req := types.AddCustomFieldDef{
|
||||
This: m.Reference(),
|
||||
Name: name,
|
||||
MoType: moType,
|
||||
FieldDefPolicy: fieldDefPolicy,
|
||||
FieldPolicy: fieldPolicy,
|
||||
}
|
||||
|
||||
res, err := methods.AddCustomFieldDef(ctx, m.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &res.Returnval, nil
|
||||
}
|
||||
|
||||
func (m CustomFieldsManager) Remove(ctx context.Context, key int32) error {
|
||||
req := types.RemoveCustomFieldDef{
|
||||
This: m.Reference(),
|
||||
Key: key,
|
||||
}
|
||||
|
||||
_, err := methods.RemoveCustomFieldDef(ctx, m.c, &req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m CustomFieldsManager) Rename(ctx context.Context, key int32, name string) error {
|
||||
req := types.RenameCustomFieldDef{
|
||||
This: m.Reference(),
|
||||
Key: key,
|
||||
Name: name,
|
||||
}
|
||||
|
||||
_, err := methods.RenameCustomFieldDef(ctx, m.c, &req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m CustomFieldsManager) Set(ctx context.Context, entity types.ManagedObjectReference, key int32, value string) error {
|
||||
req := types.SetField{
|
||||
This: m.Reference(),
|
||||
Entity: entity,
|
||||
Key: key,
|
||||
Value: value,
|
||||
}
|
||||
|
||||
_, err := methods.SetField(ctx, m.c, &req)
|
||||
return err
|
||||
}
|
||||
|
||||
type CustomFieldDefList []types.CustomFieldDef
|
||||
|
||||
func (m CustomFieldsManager) Field(ctx context.Context) (CustomFieldDefList, error) {
|
||||
var fm mo.CustomFieldsManager
|
||||
|
||||
err := m.Properties(ctx, m.Reference(), []string{"field"}, &fm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fm.Field, nil
|
||||
}
|
||||
|
||||
func (m CustomFieldsManager) FindKey(ctx context.Context, name string) (int32, error) {
|
||||
field, err := m.Field(ctx)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
for _, def := range field {
|
||||
if def.Name == name {
|
||||
return def.Key, nil
|
||||
}
|
||||
}
|
||||
|
||||
k, err := strconv.Atoi(name)
|
||||
if err == nil {
|
||||
// assume literal int key
|
||||
return int32(k), nil
|
||||
}
|
||||
|
||||
return -1, ErrKeyNameNotFound
|
||||
}
|
||||
|
||||
func (l CustomFieldDefList) ByKey(key int32) *types.CustomFieldDef {
|
||||
for _, def := range l {
|
||||
if def.Key == key {
|
||||
return &def
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
166
vendor/github.com/vmware/govmomi/object/customization_spec_manager.go
generated
vendored
Normal file
166
vendor/github.com/vmware/govmomi/object/customization_spec_manager.go
generated
vendored
Normal file
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type CustomizationSpecManager struct {
|
||||
Common
|
||||
}
|
||||
|
||||
func NewCustomizationSpecManager(c *vim25.Client) *CustomizationSpecManager {
|
||||
cs := CustomizationSpecManager{
|
||||
Common: NewCommon(c, *c.ServiceContent.CustomizationSpecManager),
|
||||
}
|
||||
|
||||
return &cs
|
||||
}
|
||||
|
||||
func (cs CustomizationSpecManager) DoesCustomizationSpecExist(ctx context.Context, name string) (bool, error) {
|
||||
req := types.DoesCustomizationSpecExist{
|
||||
This: cs.Reference(),
|
||||
Name: name,
|
||||
}
|
||||
|
||||
res, err := methods.DoesCustomizationSpecExist(ctx, cs.c, &req)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return res.Returnval, nil
|
||||
}
|
||||
|
||||
func (cs CustomizationSpecManager) GetCustomizationSpec(ctx context.Context, name string) (*types.CustomizationSpecItem, error) {
|
||||
req := types.GetCustomizationSpec{
|
||||
This: cs.Reference(),
|
||||
Name: name,
|
||||
}
|
||||
|
||||
res, err := methods.GetCustomizationSpec(ctx, cs.c, &req)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &res.Returnval, nil
|
||||
}
|
||||
|
||||
func (cs CustomizationSpecManager) CreateCustomizationSpec(ctx context.Context, item types.CustomizationSpecItem) error {
|
||||
req := types.CreateCustomizationSpec{
|
||||
This: cs.Reference(),
|
||||
Item: item,
|
||||
}
|
||||
|
||||
_, err := methods.CreateCustomizationSpec(ctx, cs.c, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs CustomizationSpecManager) OverwriteCustomizationSpec(ctx context.Context, item types.CustomizationSpecItem) error {
|
||||
req := types.OverwriteCustomizationSpec{
|
||||
This: cs.Reference(),
|
||||
Item: item,
|
||||
}
|
||||
|
||||
_, err := methods.OverwriteCustomizationSpec(ctx, cs.c, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs CustomizationSpecManager) DeleteCustomizationSpec(ctx context.Context, name string) error {
|
||||
req := types.DeleteCustomizationSpec{
|
||||
This: cs.Reference(),
|
||||
Name: name,
|
||||
}
|
||||
|
||||
_, err := methods.DeleteCustomizationSpec(ctx, cs.c, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs CustomizationSpecManager) DuplicateCustomizationSpec(ctx context.Context, name string, newName string) error {
|
||||
req := types.DuplicateCustomizationSpec{
|
||||
This: cs.Reference(),
|
||||
Name: name,
|
||||
NewName: newName,
|
||||
}
|
||||
|
||||
_, err := methods.DuplicateCustomizationSpec(ctx, cs.c, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs CustomizationSpecManager) RenameCustomizationSpec(ctx context.Context, name string, newName string) error {
|
||||
req := types.RenameCustomizationSpec{
|
||||
This: cs.Reference(),
|
||||
Name: name,
|
||||
NewName: newName,
|
||||
}
|
||||
|
||||
_, err := methods.RenameCustomizationSpec(ctx, cs.c, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs CustomizationSpecManager) CustomizationSpecItemToXml(ctx context.Context, item types.CustomizationSpecItem) (string, error) {
|
||||
req := types.CustomizationSpecItemToXml{
|
||||
This: cs.Reference(),
|
||||
Item: item,
|
||||
}
|
||||
|
||||
res, err := methods.CustomizationSpecItemToXml(ctx, cs.c, &req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return res.Returnval, nil
|
||||
}
|
||||
|
||||
func (cs CustomizationSpecManager) XmlToCustomizationSpecItem(ctx context.Context, xml string) (*types.CustomizationSpecItem, error) {
|
||||
req := types.XmlToCustomizationSpecItem{
|
||||
This: cs.Reference(),
|
||||
SpecItemXml: xml,
|
||||
}
|
||||
|
||||
res, err := methods.XmlToCustomizationSpecItem(ctx, cs.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &res.Returnval, nil
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type DatacenterFolders struct {
|
||||
VmFolder *Folder
|
||||
HostFolder *Folder
|
||||
DatastoreFolder *Folder
|
||||
NetworkFolder *Folder
|
||||
}
|
||||
|
||||
type Datacenter struct {
|
||||
Common
|
||||
}
|
||||
|
||||
func NewDatacenter(c *vim25.Client, ref types.ManagedObjectReference) *Datacenter {
|
||||
return &Datacenter{
|
||||
Common: NewCommon(c, ref),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Datacenter) Folders(ctx context.Context) (*DatacenterFolders, error) {
|
||||
var md mo.Datacenter
|
||||
|
||||
ps := []string{"name", "vmFolder", "hostFolder", "datastoreFolder", "networkFolder"}
|
||||
err := d.Properties(ctx, d.Reference(), ps, &md)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
df := &DatacenterFolders{
|
||||
VmFolder: NewFolder(d.c, md.VmFolder),
|
||||
HostFolder: NewFolder(d.c, md.HostFolder),
|
||||
DatastoreFolder: NewFolder(d.c, md.DatastoreFolder),
|
||||
NetworkFolder: NewFolder(d.c, md.NetworkFolder),
|
||||
}
|
||||
|
||||
paths := []struct {
|
||||
name string
|
||||
path *string
|
||||
}{
|
||||
{"vm", &df.VmFolder.InventoryPath},
|
||||
{"host", &df.HostFolder.InventoryPath},
|
||||
{"datastore", &df.DatastoreFolder.InventoryPath},
|
||||
{"network", &df.NetworkFolder.InventoryPath},
|
||||
}
|
||||
|
||||
for _, p := range paths {
|
||||
*p.path = fmt.Sprintf("/%s/%s", md.Name, p.name)
|
||||
}
|
||||
|
||||
return df, nil
|
||||
}
|
||||
|
||||
func (d Datacenter) Destroy(ctx context.Context) (*Task, error) {
|
||||
req := types.Destroy_Task{
|
||||
This: d.Reference(),
|
||||
}
|
||||
|
||||
res, err := methods.Destroy_Task(ctx, d.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(d.c, res.Returnval), nil
|
||||
}
|
||||
|
||||
// PowerOnVM powers on multiple virtual machines with a single vCenter call.
|
||||
// If called against ESX, serially powers on the list of VMs and the returned *Task will always be nil.
|
||||
func (d Datacenter) PowerOnVM(ctx context.Context, vm []types.ManagedObjectReference, option ...types.BaseOptionValue) (*Task, error) {
|
||||
if d.Client().IsVC() {
|
||||
req := types.PowerOnMultiVM_Task{
|
||||
This: d.Reference(),
|
||||
Vm: vm,
|
||||
Option: option,
|
||||
}
|
||||
|
||||
res, err := methods.PowerOnMultiVM_Task(ctx, d.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(d.c, res.Returnval), nil
|
||||
}
|
||||
|
||||
for _, ref := range vm {
|
||||
obj := NewVirtualMachine(d.Client(), ref)
|
||||
task, err := obj.PowerOn(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = task.Wait(ctx)
|
||||
if err != nil {
|
||||
// Ignore any InvalidPowerState fault, as it indicates the VM is already powered on
|
||||
if f, ok := err.(types.HasFault); ok {
|
||||
if _, ok = f.Fault().(*types.InvalidPowerState); !ok {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
|
@ -0,0 +1,435 @@
|
|||
/*
|
||||
Copyright (c) 2015-2016 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/vmware/govmomi/property"
|
||||
"github.com/vmware/govmomi/session"
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
// DatastoreNoSuchDirectoryError is returned when a directory could not be found.
|
||||
type DatastoreNoSuchDirectoryError struct {
|
||||
verb string
|
||||
subject string
|
||||
}
|
||||
|
||||
func (e DatastoreNoSuchDirectoryError) Error() string {
|
||||
return fmt.Sprintf("cannot %s '%s': No such directory", e.verb, e.subject)
|
||||
}
|
||||
|
||||
// DatastoreNoSuchFileError is returned when a file could not be found.
|
||||
type DatastoreNoSuchFileError struct {
|
||||
verb string
|
||||
subject string
|
||||
}
|
||||
|
||||
func (e DatastoreNoSuchFileError) Error() string {
|
||||
return fmt.Sprintf("cannot %s '%s': No such file", e.verb, e.subject)
|
||||
}
|
||||
|
||||
type Datastore struct {
|
||||
Common
|
||||
|
||||
DatacenterPath string
|
||||
}
|
||||
|
||||
func NewDatastore(c *vim25.Client, ref types.ManagedObjectReference) *Datastore {
|
||||
return &Datastore{
|
||||
Common: NewCommon(c, ref),
|
||||
}
|
||||
}
|
||||
|
||||
func (d Datastore) Path(path string) string {
|
||||
return (&DatastorePath{
|
||||
Datastore: d.Name(),
|
||||
Path: path,
|
||||
}).String()
|
||||
}
|
||||
|
||||
// NewURL constructs a url.URL with the given file path for datastore access over HTTP.
|
||||
func (d Datastore) NewURL(path string) *url.URL {
|
||||
u := d.c.URL()
|
||||
|
||||
return &url.URL{
|
||||
Scheme: u.Scheme,
|
||||
Host: u.Host,
|
||||
Path: fmt.Sprintf("/folder/%s", path),
|
||||
RawQuery: url.Values{
|
||||
"dcPath": []string{d.DatacenterPath},
|
||||
"dsName": []string{d.Name()},
|
||||
}.Encode(),
|
||||
}
|
||||
}
|
||||
|
||||
// URL is deprecated, use NewURL instead.
|
||||
func (d Datastore) URL(ctx context.Context, dc *Datacenter, path string) (*url.URL, error) {
|
||||
return d.NewURL(path), nil
|
||||
}
|
||||
|
||||
func (d Datastore) Browser(ctx context.Context) (*HostDatastoreBrowser, error) {
|
||||
var do mo.Datastore
|
||||
|
||||
err := d.Properties(ctx, d.Reference(), []string{"browser"}, &do)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewHostDatastoreBrowser(d.c, do.Browser), nil
|
||||
}
|
||||
|
||||
func (d Datastore) useServiceTicket() bool {
|
||||
// If connected to workstation, service ticketing not supported
|
||||
// If connected to ESX, service ticketing not needed
|
||||
if !d.c.IsVC() {
|
||||
return false
|
||||
}
|
||||
|
||||
key := "GOVMOMI_USE_SERVICE_TICKET"
|
||||
|
||||
val := d.c.URL().Query().Get(key)
|
||||
if val == "" {
|
||||
val = os.Getenv(key)
|
||||
}
|
||||
|
||||
if val == "1" || val == "true" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (d Datastore) useServiceTicketHostName(name string) bool {
|
||||
// No need if talking directly to ESX.
|
||||
if !d.c.IsVC() {
|
||||
return false
|
||||
}
|
||||
|
||||
// If version happens to be < 5.1
|
||||
if name == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// If the HostSystem is using DHCP on a network without dynamic DNS,
|
||||
// HostSystem.Config.Network.DnsConfig.HostName is set to "localhost" by default.
|
||||
// This resolves to "localhost.localdomain" by default via /etc/hosts on ESX.
|
||||
// In that case, we will stick with the HostSystem.Name which is the IP address that
|
||||
// was used to connect the host to VC.
|
||||
if name == "localhost.localdomain" {
|
||||
return false
|
||||
}
|
||||
|
||||
// Still possible to have HostName that don't resolve via DNS,
|
||||
// so we default to false.
|
||||
key := "GOVMOMI_USE_SERVICE_TICKET_HOSTNAME"
|
||||
|
||||
val := d.c.URL().Query().Get(key)
|
||||
if val == "" {
|
||||
val = os.Getenv(key)
|
||||
}
|
||||
|
||||
if val == "1" || val == "true" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type datastoreServiceTicketHostKey struct{}
|
||||
|
||||
// HostContext returns a Context where the given host will be used for datastore HTTP access
|
||||
// via the ServiceTicket method.
|
||||
func (d Datastore) HostContext(ctx context.Context, host *HostSystem) context.Context {
|
||||
return context.WithValue(ctx, datastoreServiceTicketHostKey{}, host)
|
||||
}
|
||||
|
||||
// ServiceTicket obtains a ticket via AcquireGenericServiceTicket and returns it an http.Cookie with the url.URL
|
||||
// that can be used along with the ticket cookie to access the given path. An host is chosen at random unless the
|
||||
// the given Context was created with a specific host via the HostContext method.
|
||||
func (d Datastore) ServiceTicket(ctx context.Context, path string, method string) (*url.URL, *http.Cookie, error) {
|
||||
u := d.NewURL(path)
|
||||
|
||||
host, ok := ctx.Value(datastoreServiceTicketHostKey{}).(*HostSystem)
|
||||
|
||||
if !ok {
|
||||
if !d.useServiceTicket() {
|
||||
return u, nil, nil
|
||||
}
|
||||
|
||||
hosts, err := d.AttachedHosts(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if len(hosts) == 0 {
|
||||
// Fallback to letting vCenter choose a host
|
||||
return u, nil, nil
|
||||
}
|
||||
|
||||
// Pick a random attached host
|
||||
host = hosts[rand.Intn(len(hosts))]
|
||||
}
|
||||
|
||||
ips, err := host.ManagementIPs(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if len(ips) > 0 {
|
||||
// prefer a ManagementIP
|
||||
u.Host = ips[0].String()
|
||||
} else {
|
||||
// fallback to inventory name
|
||||
u.Host, err = host.ObjectName(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// VC datacenter path will not be valid against ESX
|
||||
q := u.Query()
|
||||
delete(q, "dcPath")
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
spec := types.SessionManagerHttpServiceRequestSpec{
|
||||
Url: u.String(),
|
||||
// See SessionManagerHttpServiceRequestSpecMethod enum
|
||||
Method: fmt.Sprintf("http%s%s", method[0:1], strings.ToLower(method[1:])),
|
||||
}
|
||||
|
||||
sm := session.NewManager(d.Client())
|
||||
|
||||
ticket, err := sm.AcquireGenericServiceTicket(ctx, &spec)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
cookie := &http.Cookie{
|
||||
Name: "vmware_cgi_ticket",
|
||||
Value: ticket.Id,
|
||||
}
|
||||
|
||||
if d.useServiceTicketHostName(ticket.HostName) {
|
||||
u.Host = ticket.HostName
|
||||
}
|
||||
|
||||
d.Client().SetThumbprint(u.Host, ticket.SslThumbprint)
|
||||
|
||||
return u, cookie, nil
|
||||
}
|
||||
|
||||
func (d Datastore) uploadTicket(ctx context.Context, path string, param *soap.Upload) (*url.URL, *soap.Upload, error) {
|
||||
p := soap.DefaultUpload
|
||||
if param != nil {
|
||||
p = *param // copy
|
||||
}
|
||||
|
||||
u, ticket, err := d.ServiceTicket(ctx, path, p.Method)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
p.Ticket = ticket
|
||||
|
||||
return u, &p, nil
|
||||
}
|
||||
|
||||
func (d Datastore) downloadTicket(ctx context.Context, path string, param *soap.Download) (*url.URL, *soap.Download, error) {
|
||||
p := soap.DefaultDownload
|
||||
if param != nil {
|
||||
p = *param // copy
|
||||
}
|
||||
|
||||
u, ticket, err := d.ServiceTicket(ctx, path, p.Method)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
p.Ticket = ticket
|
||||
|
||||
return u, &p, nil
|
||||
}
|
||||
|
||||
// Upload via soap.Upload with an http service ticket
|
||||
func (d Datastore) Upload(ctx context.Context, f io.Reader, path string, param *soap.Upload) error {
|
||||
u, p, err := d.uploadTicket(ctx, path, param)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.Client().Upload(ctx, f, u, p)
|
||||
}
|
||||
|
||||
// UploadFile via soap.Upload with an http service ticket
|
||||
func (d Datastore) UploadFile(ctx context.Context, file string, path string, param *soap.Upload) error {
|
||||
u, p, err := d.uploadTicket(ctx, path, param)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.Client().UploadFile(ctx, file, u, p)
|
||||
}
|
||||
|
||||
// Download via soap.Download with an http service ticket
|
||||
func (d Datastore) Download(ctx context.Context, path string, param *soap.Download) (io.ReadCloser, int64, error) {
|
||||
u, p, err := d.downloadTicket(ctx, path, param)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return d.Client().Download(ctx, u, p)
|
||||
}
|
||||
|
||||
// DownloadFile via soap.Download with an http service ticket
|
||||
func (d Datastore) DownloadFile(ctx context.Context, path string, file string, param *soap.Download) error {
|
||||
u, p, err := d.downloadTicket(ctx, path, param)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.Client().DownloadFile(ctx, file, u, p)
|
||||
}
|
||||
|
||||
// AttachedHosts returns hosts that have this Datastore attached, accessible and writable.
|
||||
func (d Datastore) AttachedHosts(ctx context.Context) ([]*HostSystem, error) {
|
||||
var ds mo.Datastore
|
||||
var hosts []*HostSystem
|
||||
|
||||
pc := property.DefaultCollector(d.Client())
|
||||
err := pc.RetrieveOne(ctx, d.Reference(), []string{"host"}, &ds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mounts := make(map[types.ManagedObjectReference]types.DatastoreHostMount)
|
||||
var refs []types.ManagedObjectReference
|
||||
for _, host := range ds.Host {
|
||||
refs = append(refs, host.Key)
|
||||
mounts[host.Key] = host
|
||||
}
|
||||
|
||||
var hs []mo.HostSystem
|
||||
err = pc.Retrieve(ctx, refs, []string{"runtime.connectionState", "runtime.powerState"}, &hs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, host := range hs {
|
||||
if host.Runtime.ConnectionState == types.HostSystemConnectionStateConnected &&
|
||||
host.Runtime.PowerState == types.HostSystemPowerStatePoweredOn {
|
||||
|
||||
mount := mounts[host.Reference()]
|
||||
info := mount.MountInfo
|
||||
|
||||
if *info.Mounted && *info.Accessible && info.AccessMode == string(types.HostMountModeReadWrite) {
|
||||
hosts = append(hosts, NewHostSystem(d.Client(), mount.Key))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hosts, nil
|
||||
}
|
||||
|
||||
// AttachedClusterHosts returns hosts that have this Datastore attached, accessible and writable and are members of the given cluster.
|
||||
func (d Datastore) AttachedClusterHosts(ctx context.Context, cluster *ComputeResource) ([]*HostSystem, error) {
|
||||
var hosts []*HostSystem
|
||||
|
||||
clusterHosts, err := cluster.Hosts(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
attachedHosts, err := d.AttachedHosts(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
refs := make(map[types.ManagedObjectReference]bool)
|
||||
for _, host := range attachedHosts {
|
||||
refs[host.Reference()] = true
|
||||
}
|
||||
|
||||
for _, host := range clusterHosts {
|
||||
if refs[host.Reference()] {
|
||||
hosts = append(hosts, host)
|
||||
}
|
||||
}
|
||||
|
||||
return hosts, nil
|
||||
}
|
||||
|
||||
func (d Datastore) Stat(ctx context.Context, file string) (types.BaseFileInfo, error) {
|
||||
b, err := d.Browser(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spec := types.HostDatastoreBrowserSearchSpec{
|
||||
Details: &types.FileQueryFlags{
|
||||
FileType: true,
|
||||
FileSize: true,
|
||||
Modification: true,
|
||||
FileOwner: types.NewBool(true),
|
||||
},
|
||||
MatchPattern: []string{path.Base(file)},
|
||||
}
|
||||
|
||||
dsPath := d.Path(path.Dir(file))
|
||||
task, err := b.SearchDatastore(ctx, dsPath, &spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info, err := task.WaitForResult(ctx, nil)
|
||||
if err != nil {
|
||||
if types.IsFileNotFound(err) {
|
||||
// FileNotFound means the base path doesn't exist.
|
||||
return nil, DatastoreNoSuchDirectoryError{"stat", dsPath}
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := info.Result.(types.HostDatastoreBrowserSearchResults)
|
||||
if len(res.File) == 0 {
|
||||
// File doesn't exist
|
||||
return nil, DatastoreNoSuchFileError{"stat", d.Path(file)}
|
||||
}
|
||||
|
||||
return res.File[0], nil
|
||||
|
||||
}
|
||||
|
||||
// Type returns the type of file system volume.
|
||||
func (d Datastore) Type(ctx context.Context) (types.HostFileSystemVolumeFileSystemType, error) {
|
||||
var mds mo.Datastore
|
||||
|
||||
if err := d.Properties(ctx, d.Reference(), []string{"summary.type"}, &mds); err != nil {
|
||||
return types.HostFileSystemVolumeFileSystemType(""), err
|
||||
}
|
||||
return types.HostFileSystemVolumeFileSystemType(mds.Summary.Type), nil
|
||||
}
|
|
@ -0,0 +1,414 @@
|
|||
/*
|
||||
Copyright (c) 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
)
|
||||
|
||||
// DatastoreFile implements io.Reader, io.Seeker and io.Closer interfaces for datastore file access.
|
||||
type DatastoreFile struct {
|
||||
d Datastore
|
||||
ctx context.Context
|
||||
name string
|
||||
|
||||
buf io.Reader
|
||||
body io.ReadCloser
|
||||
length int64
|
||||
offset struct {
|
||||
read, seek int64
|
||||
}
|
||||
}
|
||||
|
||||
// Open opens the named file relative to the Datastore.
|
||||
func (d Datastore) Open(ctx context.Context, name string) (*DatastoreFile, error) {
|
||||
return &DatastoreFile{
|
||||
d: d,
|
||||
name: name,
|
||||
length: -1,
|
||||
ctx: ctx,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Read reads up to len(b) bytes from the DatastoreFile.
|
||||
func (f *DatastoreFile) Read(b []byte) (int, error) {
|
||||
if f.offset.read != f.offset.seek {
|
||||
// A Seek() call changed the offset, we need to issue a new GET
|
||||
_ = f.Close()
|
||||
|
||||
f.offset.read = f.offset.seek
|
||||
} else if f.buf != nil {
|
||||
// f.buf + f behaves like an io.MultiReader
|
||||
n, err := f.buf.Read(b)
|
||||
if err == io.EOF {
|
||||
f.buf = nil // buffer has been drained
|
||||
}
|
||||
if n > 0 {
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
|
||||
body, err := f.get()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
n, err := body.Read(b)
|
||||
|
||||
f.offset.read += int64(n)
|
||||
f.offset.seek += int64(n)
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Close closes the DatastoreFile.
|
||||
func (f *DatastoreFile) Close() error {
|
||||
var err error
|
||||
|
||||
if f.body != nil {
|
||||
err = f.body.Close()
|
||||
f.body = nil
|
||||
}
|
||||
|
||||
f.buf = nil
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Seek sets the offset for the next Read on the DatastoreFile.
|
||||
func (f *DatastoreFile) Seek(offset int64, whence int) (int64, error) {
|
||||
switch whence {
|
||||
case io.SeekStart:
|
||||
case io.SeekCurrent:
|
||||
offset += f.offset.seek
|
||||
case io.SeekEnd:
|
||||
if f.length < 0 {
|
||||
_, err := f.Stat()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
offset += f.length
|
||||
default:
|
||||
return 0, errors.New("Seek: invalid whence")
|
||||
}
|
||||
|
||||
// allow negative SeekStart for initial Range request
|
||||
if offset < 0 {
|
||||
return 0, errors.New("Seek: invalid offset")
|
||||
}
|
||||
|
||||
f.offset.seek = offset
|
||||
|
||||
return offset, nil
|
||||
}
|
||||
|
||||
type fileStat struct {
|
||||
file *DatastoreFile
|
||||
header http.Header
|
||||
}
|
||||
|
||||
func (s *fileStat) Name() string {
|
||||
return path.Base(s.file.name)
|
||||
}
|
||||
|
||||
func (s *fileStat) Size() int64 {
|
||||
return s.file.length
|
||||
}
|
||||
|
||||
func (s *fileStat) Mode() os.FileMode {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s *fileStat) ModTime() time.Time {
|
||||
return time.Now() // no Last-Modified
|
||||
}
|
||||
|
||||
func (s *fileStat) IsDir() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *fileStat) Sys() interface{} {
|
||||
return s.header
|
||||
}
|
||||
|
||||
func statusError(res *http.Response) error {
|
||||
if res.StatusCode == http.StatusNotFound {
|
||||
return os.ErrNotExist
|
||||
}
|
||||
return errors.New(res.Status)
|
||||
}
|
||||
|
||||
// Stat returns the os.FileInfo interface describing file.
|
||||
func (f *DatastoreFile) Stat() (os.FileInfo, error) {
|
||||
// TODO: consider using Datastore.Stat() instead
|
||||
u, p, err := f.d.downloadTicket(f.ctx, f.name, &soap.Download{Method: "HEAD"})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := f.d.Client().DownloadRequest(f.ctx, u, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, statusError(res)
|
||||
}
|
||||
|
||||
f.length = res.ContentLength
|
||||
|
||||
return &fileStat{f, res.Header}, nil
|
||||
}
|
||||
|
||||
func (f *DatastoreFile) get() (io.Reader, error) {
|
||||
if f.body != nil {
|
||||
return f.body, nil
|
||||
}
|
||||
|
||||
u, p, err := f.d.downloadTicket(f.ctx, f.name, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if f.offset.read != 0 {
|
||||
p.Headers = map[string]string{
|
||||
"Range": fmt.Sprintf("bytes=%d-", f.offset.read),
|
||||
}
|
||||
}
|
||||
|
||||
res, err := f.d.Client().DownloadRequest(f.ctx, u, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch res.StatusCode {
|
||||
case http.StatusOK:
|
||||
f.length = res.ContentLength
|
||||
case http.StatusPartialContent:
|
||||
var start, end int
|
||||
cr := res.Header.Get("Content-Range")
|
||||
_, err = fmt.Sscanf(cr, "bytes %d-%d/%d", &start, &end, &f.length)
|
||||
if err != nil {
|
||||
f.length = -1
|
||||
}
|
||||
case http.StatusRequestedRangeNotSatisfiable:
|
||||
// ok: Read() will return io.EOF
|
||||
default:
|
||||
return nil, statusError(res)
|
||||
}
|
||||
|
||||
if f.length < 0 {
|
||||
_ = res.Body.Close()
|
||||
return nil, errors.New("unable to determine file size")
|
||||
}
|
||||
|
||||
f.body = res.Body
|
||||
|
||||
return f.body, nil
|
||||
}
|
||||
|
||||
func lastIndexLines(s []byte, line *int, include func(l int, m string) bool) (int64, bool) {
|
||||
i := len(s) - 1
|
||||
done := false
|
||||
|
||||
for i > 0 {
|
||||
o := bytes.LastIndexByte(s[:i], '\n')
|
||||
if o < 0 {
|
||||
break
|
||||
}
|
||||
|
||||
msg := string(s[o+1 : i+1])
|
||||
if !include(*line, msg) {
|
||||
done = true
|
||||
break
|
||||
} else {
|
||||
i = o
|
||||
*line++
|
||||
}
|
||||
}
|
||||
|
||||
return int64(i), done
|
||||
}
|
||||
|
||||
// Tail seeks to the position of the last N lines of the file.
|
||||
func (f *DatastoreFile) Tail(n int) error {
|
||||
return f.TailFunc(n, func(line int, _ string) bool { return n > line })
|
||||
}
|
||||
|
||||
// TailFunc will seek backwards in the datastore file until it hits a line that does
|
||||
// not satisfy the supplied `include` function.
|
||||
func (f *DatastoreFile) TailFunc(lines int, include func(line int, message string) bool) error {
|
||||
// Read the file in reverse using bsize chunks
|
||||
const bsize = int64(1024 * 16)
|
||||
|
||||
fsize, err := f.Seek(0, io.SeekEnd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if lines == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
chunk := int64(-1)
|
||||
|
||||
buf := bytes.NewBuffer(make([]byte, 0, bsize))
|
||||
line := 0
|
||||
|
||||
for {
|
||||
var eof bool
|
||||
var pos int64
|
||||
|
||||
nread := bsize
|
||||
|
||||
offset := chunk * bsize
|
||||
remain := fsize + offset
|
||||
|
||||
if remain < 0 {
|
||||
if pos, err = f.Seek(0, io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nread = bsize + remain
|
||||
eof = true
|
||||
} else {
|
||||
if pos, err = f.Seek(offset, io.SeekEnd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = io.CopyN(buf, f, nread); err != nil {
|
||||
if err != io.EOF {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
b := buf.Bytes()
|
||||
idx, done := lastIndexLines(b, &line, include)
|
||||
|
||||
if done {
|
||||
if chunk == -1 {
|
||||
// We found all N lines in the last chunk of the file.
|
||||
// The seek offset is also now at the current end of file.
|
||||
// Save this buffer to avoid another GET request when Read() is called.
|
||||
buf.Next(int(idx + 1))
|
||||
f.buf = buf
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err = f.Seek(pos+idx+1, io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if eof {
|
||||
if remain < 0 {
|
||||
// We found < N lines in the entire file, so seek to the start.
|
||||
_, _ = f.Seek(0, io.SeekStart)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
chunk--
|
||||
buf.Reset()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type followDatastoreFile struct {
|
||||
r *DatastoreFile
|
||||
c chan struct{}
|
||||
i time.Duration
|
||||
o sync.Once
|
||||
}
|
||||
|
||||
// Read reads up to len(b) bytes from the DatastoreFile being followed.
|
||||
// This method will block until data is read, an error other than io.EOF is returned or Close() is called.
|
||||
func (f *followDatastoreFile) Read(p []byte) (int, error) {
|
||||
offset := f.r.offset.seek
|
||||
stop := false
|
||||
|
||||
for {
|
||||
n, err := f.r.Read(p)
|
||||
if err != nil && err == io.EOF {
|
||||
_ = f.r.Close() // GET request body has been drained.
|
||||
if stop {
|
||||
return n, err
|
||||
}
|
||||
err = nil
|
||||
}
|
||||
|
||||
if n > 0 {
|
||||
return n, err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-f.c:
|
||||
// Wake up and stop polling once the body has been drained
|
||||
stop = true
|
||||
case <-time.After(f.i):
|
||||
}
|
||||
|
||||
info, serr := f.r.Stat()
|
||||
if serr != nil {
|
||||
// Return EOF rather than 404 if the file goes away
|
||||
if serr == os.ErrNotExist {
|
||||
_ = f.r.Close()
|
||||
return 0, io.EOF
|
||||
}
|
||||
return 0, serr
|
||||
}
|
||||
|
||||
if info.Size() < offset {
|
||||
// assume file has be truncated
|
||||
offset, err = f.r.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close will stop Follow polling and close the underlying DatastoreFile.
|
||||
func (f *followDatastoreFile) Close() error {
|
||||
f.o.Do(func() { close(f.c) })
|
||||
return nil
|
||||
}
|
||||
|
||||
// Follow returns an io.ReadCloser to stream the file contents as data is appended.
|
||||
func (f *DatastoreFile) Follow(interval time.Duration) io.ReadCloser {
|
||||
return &followDatastoreFile{
|
||||
r: f,
|
||||
c: make(chan struct{}),
|
||||
i: interval,
|
||||
}
|
||||
}
|
228
vendor/github.com/vmware/govmomi/object/datastore_file_manager.go
generated
vendored
Normal file
228
vendor/github.com/vmware/govmomi/object/datastore_file_manager.go
generated
vendored
Normal file
|
@ -0,0 +1,228 @@
|
|||
/*
|
||||
Copyright (c) 2017-2018 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/progress"
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
)
|
||||
|
||||
// DatastoreFileManager combines FileManager and VirtualDiskManager to manage files on a Datastore
|
||||
type DatastoreFileManager struct {
|
||||
Datacenter *Datacenter
|
||||
Datastore *Datastore
|
||||
FileManager *FileManager
|
||||
VirtualDiskManager *VirtualDiskManager
|
||||
|
||||
Force bool
|
||||
DatacenterTarget *Datacenter
|
||||
}
|
||||
|
||||
// NewFileManager creates a new instance of DatastoreFileManager
|
||||
func (d Datastore) NewFileManager(dc *Datacenter, force bool) *DatastoreFileManager {
|
||||
c := d.Client()
|
||||
|
||||
m := &DatastoreFileManager{
|
||||
Datacenter: dc,
|
||||
Datastore: &d,
|
||||
FileManager: NewFileManager(c),
|
||||
VirtualDiskManager: NewVirtualDiskManager(c),
|
||||
Force: force,
|
||||
DatacenterTarget: dc,
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *DatastoreFileManager) WithProgress(ctx context.Context, s progress.Sinker) context.Context {
|
||||
return context.WithValue(ctx, m, s)
|
||||
}
|
||||
|
||||
func (m *DatastoreFileManager) wait(ctx context.Context, task *Task) error {
|
||||
var logger progress.Sinker
|
||||
if s, ok := ctx.Value(m).(progress.Sinker); ok {
|
||||
logger = s
|
||||
}
|
||||
_, err := task.WaitForResult(ctx, logger)
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete dispatches to the appropriate Delete method based on file name extension
|
||||
func (m *DatastoreFileManager) Delete(ctx context.Context, name string) error {
|
||||
switch path.Ext(name) {
|
||||
case ".vmdk":
|
||||
return m.DeleteVirtualDisk(ctx, name)
|
||||
default:
|
||||
return m.DeleteFile(ctx, name)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteFile calls FileManager.DeleteDatastoreFile
|
||||
func (m *DatastoreFileManager) DeleteFile(ctx context.Context, name string) error {
|
||||
p := m.Path(name)
|
||||
|
||||
task, err := m.FileManager.DeleteDatastoreFile(ctx, p.String(), m.Datacenter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return m.wait(ctx, task)
|
||||
}
|
||||
|
||||
// DeleteVirtualDisk calls VirtualDiskManager.DeleteVirtualDisk
|
||||
// Regardless of the Datastore type, DeleteVirtualDisk will fail if 'ddb.deletable=false',
|
||||
// so if Force=true this method attempts to set 'ddb.deletable=true' before starting the delete task.
|
||||
func (m *DatastoreFileManager) DeleteVirtualDisk(ctx context.Context, name string) error {
|
||||
p := m.Path(name)
|
||||
|
||||
var merr error
|
||||
|
||||
if m.Force {
|
||||
merr = m.markDiskAsDeletable(ctx, p)
|
||||
}
|
||||
|
||||
task, err := m.VirtualDiskManager.DeleteVirtualDisk(ctx, p.String(), m.Datacenter)
|
||||
if err != nil {
|
||||
log.Printf("markDiskAsDeletable(%s): %s", p, merr)
|
||||
return err
|
||||
}
|
||||
|
||||
return m.wait(ctx, task)
|
||||
}
|
||||
|
||||
// CopyFile calls FileManager.CopyDatastoreFile
|
||||
func (m *DatastoreFileManager) CopyFile(ctx context.Context, src string, dst string) error {
|
||||
srcp := m.Path(src)
|
||||
dstp := m.Path(dst)
|
||||
|
||||
task, err := m.FileManager.CopyDatastoreFile(ctx, srcp.String(), m.Datacenter, dstp.String(), m.DatacenterTarget, m.Force)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return m.wait(ctx, task)
|
||||
}
|
||||
|
||||
// Copy dispatches to the appropriate FileManager or VirtualDiskManager Copy method based on file name extension
|
||||
func (m *DatastoreFileManager) Copy(ctx context.Context, src string, dst string) error {
|
||||
srcp := m.Path(src)
|
||||
dstp := m.Path(dst)
|
||||
|
||||
f := m.FileManager.CopyDatastoreFile
|
||||
|
||||
if srcp.IsVMDK() {
|
||||
// types.VirtualDiskSpec=nil as it is not implemented by vCenter
|
||||
f = func(ctx context.Context, src string, srcDC *Datacenter, dst string, dstDC *Datacenter, force bool) (*Task, error) {
|
||||
return m.VirtualDiskManager.CopyVirtualDisk(ctx, src, srcDC, dst, dstDC, nil, force)
|
||||
}
|
||||
}
|
||||
|
||||
task, err := f(ctx, srcp.String(), m.Datacenter, dstp.String(), m.DatacenterTarget, m.Force)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return m.wait(ctx, task)
|
||||
}
|
||||
|
||||
// MoveFile calls FileManager.MoveDatastoreFile
|
||||
func (m *DatastoreFileManager) MoveFile(ctx context.Context, src string, dst string) error {
|
||||
srcp := m.Path(src)
|
||||
dstp := m.Path(dst)
|
||||
|
||||
task, err := m.FileManager.MoveDatastoreFile(ctx, srcp.String(), m.Datacenter, dstp.String(), m.DatacenterTarget, m.Force)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return m.wait(ctx, task)
|
||||
}
|
||||
|
||||
// Move dispatches to the appropriate FileManager or VirtualDiskManager Move method based on file name extension
|
||||
func (m *DatastoreFileManager) Move(ctx context.Context, src string, dst string) error {
|
||||
srcp := m.Path(src)
|
||||
dstp := m.Path(dst)
|
||||
|
||||
f := m.FileManager.MoveDatastoreFile
|
||||
|
||||
if srcp.IsVMDK() {
|
||||
f = m.VirtualDiskManager.MoveVirtualDisk
|
||||
}
|
||||
|
||||
task, err := f(ctx, srcp.String(), m.Datacenter, dstp.String(), m.DatacenterTarget, m.Force)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return m.wait(ctx, task)
|
||||
}
|
||||
|
||||
// Path converts path name to a DatastorePath
|
||||
func (m *DatastoreFileManager) Path(name string) *DatastorePath {
|
||||
var p DatastorePath
|
||||
|
||||
if !p.FromString(name) {
|
||||
p.Path = name
|
||||
p.Datastore = m.Datastore.Name()
|
||||
}
|
||||
|
||||
return &p
|
||||
}
|
||||
|
||||
func (m *DatastoreFileManager) markDiskAsDeletable(ctx context.Context, path *DatastorePath) error {
|
||||
r, _, err := m.Datastore.Download(ctx, path.Path, &soap.DefaultDownload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer r.Close()
|
||||
|
||||
hasFlag := false
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
s := bufio.NewScanner(&io.LimitedReader{R: r, N: 2048}) // should be only a few hundred bytes, limit to be sure
|
||||
|
||||
for s.Scan() {
|
||||
line := s.Text()
|
||||
if strings.HasPrefix(line, "ddb.deletable") {
|
||||
hasFlag = true
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Fprintln(buf, line)
|
||||
}
|
||||
|
||||
if err := s.Err(); err != nil {
|
||||
return err // any error other than EOF
|
||||
}
|
||||
|
||||
if !hasFlag {
|
||||
return nil // already deletable, so leave as-is
|
||||
}
|
||||
|
||||
// rewrite the .vmdk with ddb.deletable flag removed (the default is true)
|
||||
return m.Datastore.Upload(ctx, buf, path.Path, &soap.DefaultUpload)
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DatastorePath contains the components of a datastore path.
|
||||
type DatastorePath struct {
|
||||
Datastore string
|
||||
Path string
|
||||
}
|
||||
|
||||
// FromString parses a datastore path.
|
||||
// Returns true if the path could be parsed, false otherwise.
|
||||
func (p *DatastorePath) FromString(s string) bool {
|
||||
if len(s) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
s = strings.TrimSpace(s)
|
||||
|
||||
if !strings.HasPrefix(s, "[") {
|
||||
return false
|
||||
}
|
||||
|
||||
s = s[1:]
|
||||
|
||||
ix := strings.Index(s, "]")
|
||||
if ix < 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
p.Datastore = s[:ix]
|
||||
p.Path = strings.TrimSpace(s[ix+1:])
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// String formats a datastore path.
|
||||
func (p *DatastorePath) String() string {
|
||||
s := fmt.Sprintf("[%s]", p.Datastore)
|
||||
|
||||
if p.Path == "" {
|
||||
return s
|
||||
}
|
||||
|
||||
return strings.Join([]string{s, p.Path}, " ")
|
||||
}
|
||||
|
||||
// IsVMDK returns true if Path has a ".vmdk" extension
|
||||
func (p *DatastorePath) IsVMDK() bool {
|
||||
return path.Ext(p.Path) == ".vmdk"
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
)
|
||||
|
||||
// DiagnosticLog wraps DiagnosticManager.BrowseLog
|
||||
type DiagnosticLog struct {
|
||||
m DiagnosticManager
|
||||
|
||||
Key string
|
||||
Host *HostSystem
|
||||
|
||||
Start int32
|
||||
}
|
||||
|
||||
// Seek to log position starting at the last nlines of the log
|
||||
func (l *DiagnosticLog) Seek(ctx context.Context, nlines int32) error {
|
||||
h, err := l.m.BrowseLog(ctx, l.Host, l.Key, math.MaxInt32, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.Start = h.LineEnd - nlines
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Copy log starting from l.Start to the given io.Writer
|
||||
// Returns on error or when end of log is reached.
|
||||
func (l *DiagnosticLog) Copy(ctx context.Context, w io.Writer) (int, error) {
|
||||
const max = 500 // VC max == 500, ESX max == 1000
|
||||
written := 0
|
||||
|
||||
for {
|
||||
h, err := l.m.BrowseLog(ctx, l.Host, l.Key, l.Start, max)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
for _, line := range h.LineText {
|
||||
n, err := fmt.Fprintln(w, line)
|
||||
written += n
|
||||
if err != nil {
|
||||
return written, err
|
||||
}
|
||||
}
|
||||
|
||||
l.Start += int32(len(h.LineText))
|
||||
|
||||
if l.Start >= h.LineEnd {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return written, nil
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type DiagnosticManager struct {
|
||||
Common
|
||||
}
|
||||
|
||||
func NewDiagnosticManager(c *vim25.Client) *DiagnosticManager {
|
||||
m := DiagnosticManager{
|
||||
Common: NewCommon(c, *c.ServiceContent.DiagnosticManager),
|
||||
}
|
||||
|
||||
return &m
|
||||
}
|
||||
|
||||
func (m DiagnosticManager) Log(ctx context.Context, host *HostSystem, key string) *DiagnosticLog {
|
||||
return &DiagnosticLog{
|
||||
m: m,
|
||||
Key: key,
|
||||
Host: host,
|
||||
}
|
||||
}
|
||||
|
||||
func (m DiagnosticManager) BrowseLog(ctx context.Context, host *HostSystem, key string, start, lines int32) (*types.DiagnosticManagerLogHeader, error) {
|
||||
req := types.BrowseDiagnosticLog{
|
||||
This: m.Reference(),
|
||||
Key: key,
|
||||
Start: start,
|
||||
Lines: lines,
|
||||
}
|
||||
|
||||
if host != nil {
|
||||
ref := host.Reference()
|
||||
req.Host = &ref
|
||||
}
|
||||
|
||||
res, err := methods.BrowseDiagnosticLog(ctx, m.Client(), &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &res.Returnval, nil
|
||||
}
|
||||
|
||||
func (m DiagnosticManager) GenerateLogBundles(ctx context.Context, includeDefault bool, host []*HostSystem) (*Task, error) {
|
||||
req := types.GenerateLogBundles_Task{
|
||||
This: m.Reference(),
|
||||
IncludeDefault: includeDefault,
|
||||
}
|
||||
|
||||
if host != nil {
|
||||
for _, h := range host {
|
||||
req.Host = append(req.Host, h.Reference())
|
||||
}
|
||||
}
|
||||
|
||||
res, err := methods.GenerateLogBundles_Task(ctx, m.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(m.c, res.Returnval), nil
|
||||
}
|
||||
|
||||
func (m DiagnosticManager) QueryDescriptions(ctx context.Context, host *HostSystem) ([]types.DiagnosticManagerLogDescriptor, error) {
|
||||
req := types.QueryDescriptions{
|
||||
This: m.Reference(),
|
||||
}
|
||||
|
||||
if host != nil {
|
||||
ref := host.Reference()
|
||||
req.Host = &ref
|
||||
}
|
||||
|
||||
res, err := methods.QueryDescriptions(ctx, m.Client(), &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res.Returnval, nil
|
||||
}
|
80
vendor/github.com/vmware/govmomi/object/distributed_virtual_portgroup.go
generated
vendored
Normal file
80
vendor/github.com/vmware/govmomi/object/distributed_virtual_portgroup.go
generated
vendored
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type DistributedVirtualPortgroup struct {
|
||||
Common
|
||||
}
|
||||
|
||||
func NewDistributedVirtualPortgroup(c *vim25.Client, ref types.ManagedObjectReference) *DistributedVirtualPortgroup {
|
||||
return &DistributedVirtualPortgroup{
|
||||
Common: NewCommon(c, ref),
|
||||
}
|
||||
}
|
||||
|
||||
// EthernetCardBackingInfo returns the VirtualDeviceBackingInfo for this DistributedVirtualPortgroup
|
||||
func (p DistributedVirtualPortgroup) EthernetCardBackingInfo(ctx context.Context) (types.BaseVirtualDeviceBackingInfo, error) {
|
||||
var dvp mo.DistributedVirtualPortgroup
|
||||
var dvs mo.DistributedVirtualSwitch
|
||||
prop := "config.distributedVirtualSwitch"
|
||||
|
||||
if err := p.Properties(ctx, p.Reference(), []string{"key", prop}, &dvp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// "This property should always be set unless the user's setting does not have System.Read privilege on the object referred to by this property."
|
||||
if dvp.Config.DistributedVirtualSwitch == nil {
|
||||
return nil, fmt.Errorf("no System.Read privilege on: %s.%s", p.Reference(), prop)
|
||||
}
|
||||
|
||||
if err := p.Properties(ctx, *dvp.Config.DistributedVirtualSwitch, []string{"uuid"}, &dvs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
backing := &types.VirtualEthernetCardDistributedVirtualPortBackingInfo{
|
||||
Port: types.DistributedVirtualSwitchPortConnection{
|
||||
PortgroupKey: dvp.Key,
|
||||
SwitchUuid: dvs.Uuid,
|
||||
},
|
||||
}
|
||||
|
||||
return backing, nil
|
||||
}
|
||||
|
||||
func (p DistributedVirtualPortgroup) Reconfigure(ctx context.Context, spec types.DVPortgroupConfigSpec) (*Task, error) {
|
||||
req := types.ReconfigureDVPortgroup_Task{
|
||||
This: p.Reference(),
|
||||
Spec: spec,
|
||||
}
|
||||
|
||||
res, err := methods.ReconfigureDVPortgroup_Task(ctx, p.Client(), &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(p.Client(), res.Returnval), nil
|
||||
}
|
80
vendor/github.com/vmware/govmomi/object/distributed_virtual_switch.go
generated
vendored
Normal file
80
vendor/github.com/vmware/govmomi/object/distributed_virtual_switch.go
generated
vendored
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type DistributedVirtualSwitch struct {
|
||||
Common
|
||||
}
|
||||
|
||||
func NewDistributedVirtualSwitch(c *vim25.Client, ref types.ManagedObjectReference) *DistributedVirtualSwitch {
|
||||
return &DistributedVirtualSwitch{
|
||||
Common: NewCommon(c, ref),
|
||||
}
|
||||
}
|
||||
|
||||
func (s DistributedVirtualSwitch) EthernetCardBackingInfo(ctx context.Context) (types.BaseVirtualDeviceBackingInfo, error) {
|
||||
return nil, ErrNotSupported // TODO: just to satisfy NetworkReference interface for the finder
|
||||
}
|
||||
|
||||
func (s DistributedVirtualSwitch) Reconfigure(ctx context.Context, spec types.BaseDVSConfigSpec) (*Task, error) {
|
||||
req := types.ReconfigureDvs_Task{
|
||||
This: s.Reference(),
|
||||
Spec: spec,
|
||||
}
|
||||
|
||||
res, err := methods.ReconfigureDvs_Task(ctx, s.Client(), &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(s.Client(), res.Returnval), nil
|
||||
}
|
||||
|
||||
func (s DistributedVirtualSwitch) AddPortgroup(ctx context.Context, spec []types.DVPortgroupConfigSpec) (*Task, error) {
|
||||
req := types.AddDVPortgroup_Task{
|
||||
This: s.Reference(),
|
||||
Spec: spec,
|
||||
}
|
||||
|
||||
res, err := methods.AddDVPortgroup_Task(ctx, s.Client(), &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(s.Client(), res.Returnval), nil
|
||||
}
|
||||
|
||||
func (s DistributedVirtualSwitch) FetchDVPorts(ctx context.Context, criteria *types.DistributedVirtualSwitchPortCriteria) ([]types.DistributedVirtualPort, error) {
|
||||
req := &types.FetchDVPorts{
|
||||
This: s.Reference(),
|
||||
Criteria: criteria,
|
||||
}
|
||||
|
||||
res, err := methods.FetchDVPorts(ctx, s.Client(), req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res.Returnval, nil
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type ExtensionManager struct {
|
||||
Common
|
||||
}
|
||||
|
||||
// GetExtensionManager wraps NewExtensionManager, returning ErrNotSupported
|
||||
// when the client is not connected to a vCenter instance.
|
||||
func GetExtensionManager(c *vim25.Client) (*ExtensionManager, error) {
|
||||
if c.ServiceContent.ExtensionManager == nil {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
return NewExtensionManager(c), nil
|
||||
}
|
||||
|
||||
func NewExtensionManager(c *vim25.Client) *ExtensionManager {
|
||||
o := ExtensionManager{
|
||||
Common: NewCommon(c, *c.ServiceContent.ExtensionManager),
|
||||
}
|
||||
|
||||
return &o
|
||||
}
|
||||
|
||||
func (m ExtensionManager) List(ctx context.Context) ([]types.Extension, error) {
|
||||
var em mo.ExtensionManager
|
||||
|
||||
err := m.Properties(ctx, m.Reference(), []string{"extensionList"}, &em)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return em.ExtensionList, nil
|
||||
}
|
||||
|
||||
func (m ExtensionManager) Find(ctx context.Context, key string) (*types.Extension, error) {
|
||||
req := types.FindExtension{
|
||||
This: m.Reference(),
|
||||
ExtensionKey: key,
|
||||
}
|
||||
|
||||
res, err := methods.FindExtension(ctx, m.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res.Returnval, nil
|
||||
}
|
||||
|
||||
func (m ExtensionManager) Register(ctx context.Context, extension types.Extension) error {
|
||||
req := types.RegisterExtension{
|
||||
This: m.Reference(),
|
||||
Extension: extension,
|
||||
}
|
||||
|
||||
_, err := methods.RegisterExtension(ctx, m.c, &req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m ExtensionManager) SetCertificate(ctx context.Context, key string, certificatePem string) error {
|
||||
req := types.SetExtensionCertificate{
|
||||
This: m.Reference(),
|
||||
ExtensionKey: key,
|
||||
CertificatePem: certificatePem,
|
||||
}
|
||||
|
||||
_, err := methods.SetExtensionCertificate(ctx, m.c, &req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m ExtensionManager) Unregister(ctx context.Context, key string) error {
|
||||
req := types.UnregisterExtension{
|
||||
This: m.Reference(),
|
||||
ExtensionKey: key,
|
||||
}
|
||||
|
||||
_, err := methods.UnregisterExtension(ctx, m.c, &req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m ExtensionManager) Update(ctx context.Context, extension types.Extension) error {
|
||||
req := types.UpdateExtension{
|
||||
This: m.Reference(),
|
||||
Extension: extension,
|
||||
}
|
||||
|
||||
_, err := methods.UpdateExtension(ctx, m.c, &req)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type FileManager struct {
|
||||
Common
|
||||
}
|
||||
|
||||
func NewFileManager(c *vim25.Client) *FileManager {
|
||||
f := FileManager{
|
||||
Common: NewCommon(c, *c.ServiceContent.FileManager),
|
||||
}
|
||||
|
||||
return &f
|
||||
}
|
||||
|
||||
func (f FileManager) CopyDatastoreFile(ctx context.Context, sourceName string, sourceDatacenter *Datacenter, destinationName string, destinationDatacenter *Datacenter, force bool) (*Task, error) {
|
||||
req := types.CopyDatastoreFile_Task{
|
||||
This: f.Reference(),
|
||||
SourceName: sourceName,
|
||||
DestinationName: destinationName,
|
||||
Force: types.NewBool(force),
|
||||
}
|
||||
|
||||
if sourceDatacenter != nil {
|
||||
ref := sourceDatacenter.Reference()
|
||||
req.SourceDatacenter = &ref
|
||||
}
|
||||
|
||||
if destinationDatacenter != nil {
|
||||
ref := destinationDatacenter.Reference()
|
||||
req.DestinationDatacenter = &ref
|
||||
}
|
||||
|
||||
res, err := methods.CopyDatastoreFile_Task(ctx, f.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(f.c, res.Returnval), nil
|
||||
}
|
||||
|
||||
// DeleteDatastoreFile deletes the specified file or folder from the datastore.
|
||||
func (f FileManager) DeleteDatastoreFile(ctx context.Context, name string, dc *Datacenter) (*Task, error) {
|
||||
req := types.DeleteDatastoreFile_Task{
|
||||
This: f.Reference(),
|
||||
Name: name,
|
||||
}
|
||||
|
||||
if dc != nil {
|
||||
ref := dc.Reference()
|
||||
req.Datacenter = &ref
|
||||
}
|
||||
|
||||
res, err := methods.DeleteDatastoreFile_Task(ctx, f.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(f.c, res.Returnval), nil
|
||||
}
|
||||
|
||||
// MakeDirectory creates a folder using the specified name.
|
||||
func (f FileManager) MakeDirectory(ctx context.Context, name string, dc *Datacenter, createParentDirectories bool) error {
|
||||
req := types.MakeDirectory{
|
||||
This: f.Reference(),
|
||||
Name: name,
|
||||
CreateParentDirectories: types.NewBool(createParentDirectories),
|
||||
}
|
||||
|
||||
if dc != nil {
|
||||
ref := dc.Reference()
|
||||
req.Datacenter = &ref
|
||||
}
|
||||
|
||||
_, err := methods.MakeDirectory(ctx, f.c, &req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (f FileManager) MoveDatastoreFile(ctx context.Context, sourceName string, sourceDatacenter *Datacenter, destinationName string, destinationDatacenter *Datacenter, force bool) (*Task, error) {
|
||||
req := types.MoveDatastoreFile_Task{
|
||||
This: f.Reference(),
|
||||
SourceName: sourceName,
|
||||
DestinationName: destinationName,
|
||||
Force: types.NewBool(force),
|
||||
}
|
||||
|
||||
if sourceDatacenter != nil {
|
||||
ref := sourceDatacenter.Reference()
|
||||
req.SourceDatacenter = &ref
|
||||
}
|
||||
|
||||
if destinationDatacenter != nil {
|
||||
ref := destinationDatacenter.Reference()
|
||||
req.DestinationDatacenter = &ref
|
||||
}
|
||||
|
||||
res, err := methods.MoveDatastoreFile_Task(ctx, f.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(f.c, res.Returnval), nil
|
||||
}
|
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type Folder struct {
|
||||
Common
|
||||
}
|
||||
|
||||
func NewFolder(c *vim25.Client, ref types.ManagedObjectReference) *Folder {
|
||||
return &Folder{
|
||||
Common: NewCommon(c, ref),
|
||||
}
|
||||
}
|
||||
|
||||
func NewRootFolder(c *vim25.Client) *Folder {
|
||||
f := NewFolder(c, c.ServiceContent.RootFolder)
|
||||
f.InventoryPath = "/"
|
||||
return f
|
||||
}
|
||||
|
||||
func (f Folder) Children(ctx context.Context) ([]Reference, error) {
|
||||
var mf mo.Folder
|
||||
|
||||
err := f.Properties(ctx, f.Reference(), []string{"childEntity"}, &mf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var rs []Reference
|
||||
for _, e := range mf.ChildEntity {
|
||||
if r := NewReference(f.c, e); r != nil {
|
||||
rs = append(rs, r)
|
||||
}
|
||||
}
|
||||
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
func (f Folder) CreateDatacenter(ctx context.Context, datacenter string) (*Datacenter, error) {
|
||||
req := types.CreateDatacenter{
|
||||
This: f.Reference(),
|
||||
Name: datacenter,
|
||||
}
|
||||
|
||||
res, err := methods.CreateDatacenter(ctx, f.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Response will be nil if this is an ESX host that does not belong to a vCenter
|
||||
if res == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return NewDatacenter(f.c, res.Returnval), nil
|
||||
}
|
||||
|
||||
func (f Folder) CreateCluster(ctx context.Context, cluster string, spec types.ClusterConfigSpecEx) (*ClusterComputeResource, error) {
|
||||
req := types.CreateClusterEx{
|
||||
This: f.Reference(),
|
||||
Name: cluster,
|
||||
Spec: spec,
|
||||
}
|
||||
|
||||
res, err := methods.CreateClusterEx(ctx, f.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Response will be nil if this is an ESX host that does not belong to a vCenter
|
||||
if res == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return NewClusterComputeResource(f.c, res.Returnval), nil
|
||||
}
|
||||
|
||||
func (f Folder) CreateFolder(ctx context.Context, name string) (*Folder, error) {
|
||||
req := types.CreateFolder{
|
||||
This: f.Reference(),
|
||||
Name: name,
|
||||
}
|
||||
|
||||
res, err := methods.CreateFolder(ctx, f.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewFolder(f.c, res.Returnval), err
|
||||
}
|
||||
|
||||
func (f Folder) CreateStoragePod(ctx context.Context, name string) (*StoragePod, error) {
|
||||
req := types.CreateStoragePod{
|
||||
This: f.Reference(),
|
||||
Name: name,
|
||||
}
|
||||
|
||||
res, err := methods.CreateStoragePod(ctx, f.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewStoragePod(f.c, res.Returnval), err
|
||||
}
|
||||
|
||||
func (f Folder) AddStandaloneHost(ctx context.Context, spec types.HostConnectSpec, addConnected bool, license *string, compResSpec *types.BaseComputeResourceConfigSpec) (*Task, error) {
|
||||
req := types.AddStandaloneHost_Task{
|
||||
This: f.Reference(),
|
||||
Spec: spec,
|
||||
AddConnected: addConnected,
|
||||
}
|
||||
|
||||
if license != nil {
|
||||
req.License = *license
|
||||
}
|
||||
|
||||
if compResSpec != nil {
|
||||
req.CompResSpec = *compResSpec
|
||||
}
|
||||
|
||||
res, err := methods.AddStandaloneHost_Task(ctx, f.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(f.c, res.Returnval), nil
|
||||
}
|
||||
|
||||
func (f Folder) CreateVM(ctx context.Context, config types.VirtualMachineConfigSpec, pool *ResourcePool, host *HostSystem) (*Task, error) {
|
||||
req := types.CreateVM_Task{
|
||||
This: f.Reference(),
|
||||
Config: config,
|
||||
Pool: pool.Reference(),
|
||||
}
|
||||
|
||||
if host != nil {
|
||||
ref := host.Reference()
|
||||
req.Host = &ref
|
||||
}
|
||||
|
||||
res, err := methods.CreateVM_Task(ctx, f.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(f.c, res.Returnval), nil
|
||||
}
|
||||
|
||||
func (f Folder) RegisterVM(ctx context.Context, path string, name string, asTemplate bool, pool *ResourcePool, host *HostSystem) (*Task, error) {
|
||||
req := types.RegisterVM_Task{
|
||||
This: f.Reference(),
|
||||
Path: path,
|
||||
AsTemplate: asTemplate,
|
||||
}
|
||||
|
||||
if name != "" {
|
||||
req.Name = name
|
||||
}
|
||||
|
||||
if host != nil {
|
||||
ref := host.Reference()
|
||||
req.Host = &ref
|
||||
}
|
||||
|
||||
if pool != nil {
|
||||
ref := pool.Reference()
|
||||
req.Pool = &ref
|
||||
}
|
||||
|
||||
res, err := methods.RegisterVM_Task(ctx, f.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(f.c, res.Returnval), nil
|
||||
}
|
||||
|
||||
func (f Folder) CreateDVS(ctx context.Context, spec types.DVSCreateSpec) (*Task, error) {
|
||||
req := types.CreateDVS_Task{
|
||||
This: f.Reference(),
|
||||
Spec: spec,
|
||||
}
|
||||
|
||||
res, err := methods.CreateDVS_Task(ctx, f.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(f.c, res.Returnval), nil
|
||||
}
|
||||
|
||||
func (f Folder) MoveInto(ctx context.Context, list []types.ManagedObjectReference) (*Task, error) {
|
||||
req := types.MoveIntoFolder_Task{
|
||||
This: f.Reference(),
|
||||
List: list,
|
||||
}
|
||||
|
||||
res, err := methods.MoveIntoFolder_Task(ctx, f.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(f.c, res.Returnval), nil
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type HistoryCollector struct {
|
||||
Common
|
||||
}
|
||||
|
||||
func NewHistoryCollector(c *vim25.Client, ref types.ManagedObjectReference) *HistoryCollector {
|
||||
return &HistoryCollector{
|
||||
Common: NewCommon(c, ref),
|
||||
}
|
||||
}
|
||||
|
||||
func (h HistoryCollector) Destroy(ctx context.Context) error {
|
||||
req := types.DestroyCollector{
|
||||
This: h.Reference(),
|
||||
}
|
||||
|
||||
_, err := methods.DestroyCollector(ctx, h.c, &req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (h HistoryCollector) Reset(ctx context.Context) error {
|
||||
req := types.ResetCollector{
|
||||
This: h.Reference(),
|
||||
}
|
||||
|
||||
_, err := methods.ResetCollector(ctx, h.c, &req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (h HistoryCollector) Rewind(ctx context.Context) error {
|
||||
req := types.RewindCollector{
|
||||
This: h.Reference(),
|
||||
}
|
||||
|
||||
_, err := methods.RewindCollector(ctx, h.c, &req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (h HistoryCollector) SetPageSize(ctx context.Context, maxCount int32) error {
|
||||
req := types.SetCollectorPageSize{
|
||||
This: h.Reference(),
|
||||
MaxCount: maxCount,
|
||||
}
|
||||
|
||||
_, err := methods.SetCollectorPageSize(ctx, h.c, &req)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type HostAccountManager struct {
|
||||
Common
|
||||
}
|
||||
|
||||
func NewHostAccountManager(c *vim25.Client, ref types.ManagedObjectReference) *HostAccountManager {
|
||||
return &HostAccountManager{
|
||||
Common: NewCommon(c, ref),
|
||||
}
|
||||
}
|
||||
|
||||
func (m HostAccountManager) Create(ctx context.Context, user *types.HostAccountSpec) error {
|
||||
req := types.CreateUser{
|
||||
This: m.Reference(),
|
||||
User: user,
|
||||
}
|
||||
|
||||
_, err := methods.CreateUser(ctx, m.Client(), &req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m HostAccountManager) Update(ctx context.Context, user *types.HostAccountSpec) error {
|
||||
req := types.UpdateUser{
|
||||
This: m.Reference(),
|
||||
User: user,
|
||||
}
|
||||
|
||||
_, err := methods.UpdateUser(ctx, m.Client(), &req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m HostAccountManager) Remove(ctx context.Context, userName string) error {
|
||||
req := types.RemoveUser{
|
||||
This: m.Reference(),
|
||||
UserName: userName,
|
||||
}
|
||||
|
||||
_, err := methods.RemoveUser(ctx, m.Client(), &req)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,250 @@
|
|||
/*
|
||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
// HostCertificateInfo provides helpers for types.HostCertificateManagerCertificateInfo
|
||||
type HostCertificateInfo struct {
|
||||
types.HostCertificateManagerCertificateInfo
|
||||
|
||||
ThumbprintSHA1 string
|
||||
ThumbprintSHA256 string
|
||||
|
||||
Err error
|
||||
Certificate *x509.Certificate `json:"-"`
|
||||
|
||||
subjectName *pkix.Name
|
||||
issuerName *pkix.Name
|
||||
}
|
||||
|
||||
// FromCertificate converts x509.Certificate to HostCertificateInfo
|
||||
func (info *HostCertificateInfo) FromCertificate(cert *x509.Certificate) *HostCertificateInfo {
|
||||
info.Certificate = cert
|
||||
info.subjectName = &cert.Subject
|
||||
info.issuerName = &cert.Issuer
|
||||
|
||||
info.Issuer = info.fromName(info.issuerName)
|
||||
info.NotBefore = &cert.NotBefore
|
||||
info.NotAfter = &cert.NotAfter
|
||||
info.Subject = info.fromName(info.subjectName)
|
||||
|
||||
info.ThumbprintSHA1 = soap.ThumbprintSHA1(cert)
|
||||
|
||||
// SHA-256 for info purposes only, API fields all use SHA-1
|
||||
sum := sha256.Sum256(cert.Raw)
|
||||
hex := make([]string, len(sum))
|
||||
for i, b := range sum {
|
||||
hex[i] = fmt.Sprintf("%02X", b)
|
||||
}
|
||||
info.ThumbprintSHA256 = strings.Join(hex, ":")
|
||||
|
||||
if info.Status == "" {
|
||||
info.Status = string(types.HostCertificateManagerCertificateInfoCertificateStatusUnknown)
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
// FromURL connects to the given URL.Host via tls.Dial with the given tls.Config and populates the HostCertificateInfo
|
||||
// via tls.ConnectionState. If the certificate was verified with the given tls.Config, the Err field will be nil.
|
||||
// Otherwise, Err will be set to the x509.UnknownAuthorityError or x509.HostnameError.
|
||||
// If tls.Dial returns an error of any other type, that error is returned.
|
||||
func (info *HostCertificateInfo) FromURL(u *url.URL, config *tls.Config) error {
|
||||
addr := u.Host
|
||||
if !(strings.LastIndex(addr, ":") > strings.LastIndex(addr, "]")) {
|
||||
addr += ":443"
|
||||
}
|
||||
|
||||
conn, err := tls.Dial("tcp", addr, config)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case x509.UnknownAuthorityError:
|
||||
case x509.HostnameError:
|
||||
default:
|
||||
return err
|
||||
}
|
||||
|
||||
info.Err = err
|
||||
|
||||
conn, err = tls.Dial("tcp", addr, &tls.Config{InsecureSkipVerify: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
info.Status = string(types.HostCertificateManagerCertificateInfoCertificateStatusGood)
|
||||
}
|
||||
|
||||
state := conn.ConnectionState()
|
||||
_ = conn.Close()
|
||||
info.FromCertificate(state.PeerCertificates[0])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var emailAddressOID = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}
|
||||
|
||||
func (info *HostCertificateInfo) fromName(name *pkix.Name) string {
|
||||
var attrs []string
|
||||
|
||||
oids := map[string]string{
|
||||
emailAddressOID.String(): "emailAddress",
|
||||
}
|
||||
|
||||
for _, attr := range name.Names {
|
||||
if key, ok := oids[attr.Type.String()]; ok {
|
||||
attrs = append(attrs, fmt.Sprintf("%s=%s", key, attr.Value))
|
||||
}
|
||||
}
|
||||
|
||||
attrs = append(attrs, fmt.Sprintf("CN=%s", name.CommonName))
|
||||
|
||||
add := func(key string, vals []string) {
|
||||
for _, val := range vals {
|
||||
attrs = append(attrs, fmt.Sprintf("%s=%s", key, val))
|
||||
}
|
||||
}
|
||||
|
||||
elts := []struct {
|
||||
key string
|
||||
val []string
|
||||
}{
|
||||
{"OU", name.OrganizationalUnit},
|
||||
{"O", name.Organization},
|
||||
{"L", name.Locality},
|
||||
{"ST", name.Province},
|
||||
{"C", name.Country},
|
||||
}
|
||||
|
||||
for _, elt := range elts {
|
||||
add(elt.key, elt.val)
|
||||
}
|
||||
|
||||
return strings.Join(attrs, ",")
|
||||
}
|
||||
|
||||
func (info *HostCertificateInfo) toName(s string) *pkix.Name {
|
||||
var name pkix.Name
|
||||
|
||||
for _, pair := range strings.Split(s, ",") {
|
||||
attr := strings.SplitN(pair, "=", 2)
|
||||
if len(attr) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
v := attr[1]
|
||||
|
||||
switch strings.ToLower(attr[0]) {
|
||||
case "cn":
|
||||
name.CommonName = v
|
||||
case "ou":
|
||||
name.OrganizationalUnit = append(name.OrganizationalUnit, v)
|
||||
case "o":
|
||||
name.Organization = append(name.Organization, v)
|
||||
case "l":
|
||||
name.Locality = append(name.Locality, v)
|
||||
case "st":
|
||||
name.Province = append(name.Province, v)
|
||||
case "c":
|
||||
name.Country = append(name.Country, v)
|
||||
case "emailaddress":
|
||||
name.Names = append(name.Names, pkix.AttributeTypeAndValue{Type: emailAddressOID, Value: v})
|
||||
}
|
||||
}
|
||||
|
||||
return &name
|
||||
}
|
||||
|
||||
// SubjectName parses Subject into a pkix.Name
|
||||
func (info *HostCertificateInfo) SubjectName() *pkix.Name {
|
||||
if info.subjectName != nil {
|
||||
return info.subjectName
|
||||
}
|
||||
|
||||
return info.toName(info.Subject)
|
||||
}
|
||||
|
||||
// IssuerName parses Issuer into a pkix.Name
|
||||
func (info *HostCertificateInfo) IssuerName() *pkix.Name {
|
||||
if info.issuerName != nil {
|
||||
return info.issuerName
|
||||
}
|
||||
|
||||
return info.toName(info.Issuer)
|
||||
}
|
||||
|
||||
// Write outputs info similar to the Chrome Certificate Viewer.
|
||||
func (info *HostCertificateInfo) Write(w io.Writer) error {
|
||||
tw := tabwriter.NewWriter(w, 2, 0, 2, ' ', 0)
|
||||
|
||||
s := func(val string) string {
|
||||
if val != "" {
|
||||
return val
|
||||
}
|
||||
return "<Not Part Of Certificate>"
|
||||
}
|
||||
|
||||
ss := func(val []string) string {
|
||||
return s(strings.Join(val, ","))
|
||||
}
|
||||
|
||||
name := func(n *pkix.Name) {
|
||||
fmt.Fprintf(tw, " Common Name (CN):\t%s\n", s(n.CommonName))
|
||||
fmt.Fprintf(tw, " Organization (O):\t%s\n", ss(n.Organization))
|
||||
fmt.Fprintf(tw, " Organizational Unit (OU):\t%s\n", ss(n.OrganizationalUnit))
|
||||
}
|
||||
|
||||
status := info.Status
|
||||
if info.Err != nil {
|
||||
status = fmt.Sprintf("ERROR %s", info.Err)
|
||||
}
|
||||
fmt.Fprintf(tw, "Certificate Status:\t%s\n", status)
|
||||
|
||||
fmt.Fprintln(tw, "Issued To:\t")
|
||||
name(info.SubjectName())
|
||||
|
||||
fmt.Fprintln(tw, "Issued By:\t")
|
||||
name(info.IssuerName())
|
||||
|
||||
fmt.Fprintln(tw, "Validity Period:\t")
|
||||
fmt.Fprintf(tw, " Issued On:\t%s\n", info.NotBefore)
|
||||
fmt.Fprintf(tw, " Expires On:\t%s\n", info.NotAfter)
|
||||
|
||||
if info.ThumbprintSHA1 != "" {
|
||||
fmt.Fprintln(tw, "Thumbprints:\t")
|
||||
if info.ThumbprintSHA256 != "" {
|
||||
fmt.Fprintf(tw, " SHA-256 Thumbprint:\t%s\n", info.ThumbprintSHA256)
|
||||
}
|
||||
fmt.Fprintf(tw, " SHA-1 Thumbprint:\t%s\n", info.ThumbprintSHA1)
|
||||
}
|
||||
|
||||
return tw.Flush()
|
||||
}
|
162
vendor/github.com/vmware/govmomi/object/host_certificate_manager.go
generated
vendored
Normal file
162
vendor/github.com/vmware/govmomi/object/host_certificate_manager.go
generated
vendored
Normal file
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/vmware/govmomi/property"
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
// HostCertificateManager provides helper methods around the HostSystem.ConfigManager.CertificateManager
|
||||
type HostCertificateManager struct {
|
||||
Common
|
||||
Host *HostSystem
|
||||
}
|
||||
|
||||
// NewHostCertificateManager creates a new HostCertificateManager helper
|
||||
func NewHostCertificateManager(c *vim25.Client, ref types.ManagedObjectReference, host types.ManagedObjectReference) *HostCertificateManager {
|
||||
return &HostCertificateManager{
|
||||
Common: NewCommon(c, ref),
|
||||
Host: NewHostSystem(c, host),
|
||||
}
|
||||
}
|
||||
|
||||
// CertificateInfo wraps the host CertificateManager certificateInfo property with the HostCertificateInfo helper.
|
||||
// The ThumbprintSHA1 field is set to HostSystem.Summary.Config.SslThumbprint if the host system is managed by a vCenter.
|
||||
func (m HostCertificateManager) CertificateInfo(ctx context.Context) (*HostCertificateInfo, error) {
|
||||
var hs mo.HostSystem
|
||||
var cm mo.HostCertificateManager
|
||||
|
||||
pc := property.DefaultCollector(m.Client())
|
||||
|
||||
err := pc.RetrieveOne(ctx, m.Reference(), []string{"certificateInfo"}, &cm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_ = pc.RetrieveOne(ctx, m.Host.Reference(), []string{"summary.config.sslThumbprint"}, &hs)
|
||||
|
||||
return &HostCertificateInfo{
|
||||
HostCertificateManagerCertificateInfo: cm.CertificateInfo,
|
||||
ThumbprintSHA1: hs.Summary.Config.SslThumbprint,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GenerateCertificateSigningRequest requests the host system to generate a certificate-signing request (CSR) for itself.
|
||||
// The CSR is then typically provided to a Certificate Authority to sign and issue the SSL certificate for the host system.
|
||||
// Use InstallServerCertificate to import this certificate.
|
||||
func (m HostCertificateManager) GenerateCertificateSigningRequest(ctx context.Context, useIPAddressAsCommonName bool) (string, error) {
|
||||
req := types.GenerateCertificateSigningRequest{
|
||||
This: m.Reference(),
|
||||
UseIpAddressAsCommonName: useIPAddressAsCommonName,
|
||||
}
|
||||
|
||||
res, err := methods.GenerateCertificateSigningRequest(ctx, m.Client(), &req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return res.Returnval, nil
|
||||
}
|
||||
|
||||
// GenerateCertificateSigningRequestByDn requests the host system to generate a certificate-signing request (CSR) for itself.
|
||||
// Alternative version similar to GenerateCertificateSigningRequest but takes a Distinguished Name (DN) as a parameter.
|
||||
func (m HostCertificateManager) GenerateCertificateSigningRequestByDn(ctx context.Context, distinguishedName string) (string, error) {
|
||||
req := types.GenerateCertificateSigningRequestByDn{
|
||||
This: m.Reference(),
|
||||
DistinguishedName: distinguishedName,
|
||||
}
|
||||
|
||||
res, err := methods.GenerateCertificateSigningRequestByDn(ctx, m.Client(), &req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return res.Returnval, nil
|
||||
}
|
||||
|
||||
// InstallServerCertificate imports the given SSL certificate to the host system.
|
||||
func (m HostCertificateManager) InstallServerCertificate(ctx context.Context, cert string) error {
|
||||
req := types.InstallServerCertificate{
|
||||
This: m.Reference(),
|
||||
Cert: cert,
|
||||
}
|
||||
|
||||
_, err := methods.InstallServerCertificate(ctx, m.Client(), &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// NotifyAffectedService is internal, not exposing as we don't have a use case other than with InstallServerCertificate
|
||||
// Without this call, hostd needs to be restarted to use the updated certificate
|
||||
// Note: using Refresh as it has the same struct/signature, we just need to use different xml name tags
|
||||
body := struct {
|
||||
Req *types.Refresh `xml:"urn:vim25 NotifyAffectedServices,omitempty"`
|
||||
Res *types.RefreshResponse `xml:"urn:vim25 NotifyAffectedServicesResponse,omitempty"`
|
||||
methods.RefreshBody
|
||||
}{
|
||||
Req: &types.Refresh{This: m.Reference()},
|
||||
}
|
||||
|
||||
return m.Client().RoundTrip(ctx, &body, &body)
|
||||
}
|
||||
|
||||
// ListCACertificateRevocationLists returns the SSL CRLs of Certificate Authorities that are trusted by the host system.
|
||||
func (m HostCertificateManager) ListCACertificateRevocationLists(ctx context.Context) ([]string, error) {
|
||||
req := types.ListCACertificateRevocationLists{
|
||||
This: m.Reference(),
|
||||
}
|
||||
|
||||
res, err := methods.ListCACertificateRevocationLists(ctx, m.Client(), &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res.Returnval, nil
|
||||
}
|
||||
|
||||
// ListCACertificates returns the SSL certificates of Certificate Authorities that are trusted by the host system.
|
||||
func (m HostCertificateManager) ListCACertificates(ctx context.Context) ([]string, error) {
|
||||
req := types.ListCACertificates{
|
||||
This: m.Reference(),
|
||||
}
|
||||
|
||||
res, err := methods.ListCACertificates(ctx, m.Client(), &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res.Returnval, nil
|
||||
}
|
||||
|
||||
// ReplaceCACertificatesAndCRLs replaces the trusted CA certificates and CRL used by the host system.
|
||||
// These determine whether the server can verify the identity of an external entity.
|
||||
func (m HostCertificateManager) ReplaceCACertificatesAndCRLs(ctx context.Context, caCert []string, caCrl []string) error {
|
||||
req := types.ReplaceCACertificatesAndCRLs{
|
||||
This: m.Reference(),
|
||||
CaCert: caCert,
|
||||
CaCrl: caCrl,
|
||||
}
|
||||
|
||||
_, err := methods.ReplaceCACertificatesAndCRLs(ctx, m.Client(), &req)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,196 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type HostConfigManager struct {
|
||||
Common
|
||||
}
|
||||
|
||||
func NewHostConfigManager(c *vim25.Client, ref types.ManagedObjectReference) *HostConfigManager {
|
||||
return &HostConfigManager{
|
||||
Common: NewCommon(c, ref),
|
||||
}
|
||||
}
|
||||
|
||||
func (m HostConfigManager) DatastoreSystem(ctx context.Context) (*HostDatastoreSystem, error) {
|
||||
var h mo.HostSystem
|
||||
|
||||
err := m.Properties(ctx, m.Reference(), []string{"configManager.datastoreSystem"}, &h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewHostDatastoreSystem(m.c, *h.ConfigManager.DatastoreSystem), nil
|
||||
}
|
||||
|
||||
func (m HostConfigManager) NetworkSystem(ctx context.Context) (*HostNetworkSystem, error) {
|
||||
var h mo.HostSystem
|
||||
|
||||
err := m.Properties(ctx, m.Reference(), []string{"configManager.networkSystem"}, &h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewHostNetworkSystem(m.c, *h.ConfigManager.NetworkSystem), nil
|
||||
}
|
||||
|
||||
func (m HostConfigManager) FirewallSystem(ctx context.Context) (*HostFirewallSystem, error) {
|
||||
var h mo.HostSystem
|
||||
|
||||
err := m.Properties(ctx, m.Reference(), []string{"configManager.firewallSystem"}, &h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewHostFirewallSystem(m.c, *h.ConfigManager.FirewallSystem), nil
|
||||
}
|
||||
|
||||
func (m HostConfigManager) StorageSystem(ctx context.Context) (*HostStorageSystem, error) {
|
||||
var h mo.HostSystem
|
||||
|
||||
err := m.Properties(ctx, m.Reference(), []string{"configManager.storageSystem"}, &h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewHostStorageSystem(m.c, *h.ConfigManager.StorageSystem), nil
|
||||
}
|
||||
|
||||
func (m HostConfigManager) VirtualNicManager(ctx context.Context) (*HostVirtualNicManager, error) {
|
||||
var h mo.HostSystem
|
||||
|
||||
err := m.Properties(ctx, m.Reference(), []string{"configManager.virtualNicManager"}, &h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewHostVirtualNicManager(m.c, *h.ConfigManager.VirtualNicManager, m.Reference()), nil
|
||||
}
|
||||
|
||||
func (m HostConfigManager) VsanSystem(ctx context.Context) (*HostVsanSystem, error) {
|
||||
var h mo.HostSystem
|
||||
|
||||
err := m.Properties(ctx, m.Reference(), []string{"configManager.vsanSystem"}, &h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Added in 5.5
|
||||
if h.ConfigManager.VsanSystem == nil {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
return NewHostVsanSystem(m.c, *h.ConfigManager.VsanSystem), nil
|
||||
}
|
||||
|
||||
func (m HostConfigManager) VsanInternalSystem(ctx context.Context) (*HostVsanInternalSystem, error) {
|
||||
var h mo.HostSystem
|
||||
|
||||
err := m.Properties(ctx, m.Reference(), []string{"configManager.vsanInternalSystem"}, &h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Added in 5.5
|
||||
if h.ConfigManager.VsanInternalSystem == nil {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
return NewHostVsanInternalSystem(m.c, *h.ConfigManager.VsanInternalSystem), nil
|
||||
}
|
||||
|
||||
func (m HostConfigManager) AccountManager(ctx context.Context) (*HostAccountManager, error) {
|
||||
var h mo.HostSystem
|
||||
|
||||
err := m.Properties(ctx, m.Reference(), []string{"configManager.accountManager"}, &h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ref := h.ConfigManager.AccountManager // Added in 6.0
|
||||
if ref == nil {
|
||||
// Versions < 5.5 can use the ServiceContent ref,
|
||||
// but we can only use it when connected directly to ESX.
|
||||
c := m.Client()
|
||||
if !c.IsVC() {
|
||||
ref = c.ServiceContent.AccountManager
|
||||
}
|
||||
|
||||
if ref == nil {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
}
|
||||
|
||||
return NewHostAccountManager(m.c, *ref), nil
|
||||
}
|
||||
|
||||
func (m HostConfigManager) OptionManager(ctx context.Context) (*OptionManager, error) {
|
||||
var h mo.HostSystem
|
||||
|
||||
err := m.Properties(ctx, m.Reference(), []string{"configManager.advancedOption"}, &h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewOptionManager(m.c, *h.ConfigManager.AdvancedOption), nil
|
||||
}
|
||||
|
||||
func (m HostConfigManager) ServiceSystem(ctx context.Context) (*HostServiceSystem, error) {
|
||||
var h mo.HostSystem
|
||||
|
||||
err := m.Properties(ctx, m.Reference(), []string{"configManager.serviceSystem"}, &h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewHostServiceSystem(m.c, *h.ConfigManager.ServiceSystem), nil
|
||||
}
|
||||
|
||||
func (m HostConfigManager) CertificateManager(ctx context.Context) (*HostCertificateManager, error) {
|
||||
var h mo.HostSystem
|
||||
|
||||
err := m.Properties(ctx, m.Reference(), []string{"configManager.certificateManager"}, &h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Added in 6.0
|
||||
if h.ConfigManager.CertificateManager == nil {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
return NewHostCertificateManager(m.c, *h.ConfigManager.CertificateManager, m.Reference()), nil
|
||||
}
|
||||
|
||||
func (m HostConfigManager) DateTimeSystem(ctx context.Context) (*HostDateTimeSystem, error) {
|
||||
var h mo.HostSystem
|
||||
|
||||
err := m.Properties(ctx, m.Reference(), []string{"configManager.dateTimeSystem"}, &h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewHostDateTimeSystem(m.c, *h.ConfigManager.DateTimeSystem), nil
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type HostDatastoreBrowser struct {
|
||||
Common
|
||||
}
|
||||
|
||||
func NewHostDatastoreBrowser(c *vim25.Client, ref types.ManagedObjectReference) *HostDatastoreBrowser {
|
||||
return &HostDatastoreBrowser{
|
||||
Common: NewCommon(c, ref),
|
||||
}
|
||||
}
|
||||
|
||||
func (b HostDatastoreBrowser) SearchDatastore(ctx context.Context, datastorePath string, searchSpec *types.HostDatastoreBrowserSearchSpec) (*Task, error) {
|
||||
req := types.SearchDatastore_Task{
|
||||
This: b.Reference(),
|
||||
DatastorePath: datastorePath,
|
||||
SearchSpec: searchSpec,
|
||||
}
|
||||
|
||||
res, err := methods.SearchDatastore_Task(ctx, b.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(b.c, res.Returnval), nil
|
||||
}
|
||||
|
||||
func (b HostDatastoreBrowser) SearchDatastoreSubFolders(ctx context.Context, datastorePath string, searchSpec *types.HostDatastoreBrowserSearchSpec) (*Task, error) {
|
||||
req := types.SearchDatastoreSubFolders_Task{
|
||||
This: b.Reference(),
|
||||
DatastorePath: datastorePath,
|
||||
SearchSpec: searchSpec,
|
||||
}
|
||||
|
||||
res, err := methods.SearchDatastoreSubFolders_Task(ctx, b.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(b.c, res.Returnval), nil
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type HostDatastoreSystem struct {
|
||||
Common
|
||||
}
|
||||
|
||||
func NewHostDatastoreSystem(c *vim25.Client, ref types.ManagedObjectReference) *HostDatastoreSystem {
|
||||
return &HostDatastoreSystem{
|
||||
Common: NewCommon(c, ref),
|
||||
}
|
||||
}
|
||||
|
||||
func (s HostDatastoreSystem) CreateLocalDatastore(ctx context.Context, name string, path string) (*Datastore, error) {
|
||||
req := types.CreateLocalDatastore{
|
||||
This: s.Reference(),
|
||||
Name: name,
|
||||
Path: path,
|
||||
}
|
||||
|
||||
res, err := methods.CreateLocalDatastore(ctx, s.Client(), &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewDatastore(s.Client(), res.Returnval), nil
|
||||
}
|
||||
|
||||
func (s HostDatastoreSystem) CreateNasDatastore(ctx context.Context, spec types.HostNasVolumeSpec) (*Datastore, error) {
|
||||
req := types.CreateNasDatastore{
|
||||
This: s.Reference(),
|
||||
Spec: spec,
|
||||
}
|
||||
|
||||
res, err := methods.CreateNasDatastore(ctx, s.Client(), &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewDatastore(s.Client(), res.Returnval), nil
|
||||
}
|
||||
|
||||
func (s HostDatastoreSystem) CreateVmfsDatastore(ctx context.Context, spec types.VmfsDatastoreCreateSpec) (*Datastore, error) {
|
||||
req := types.CreateVmfsDatastore{
|
||||
This: s.Reference(),
|
||||
Spec: spec,
|
||||
}
|
||||
|
||||
res, err := methods.CreateVmfsDatastore(ctx, s.Client(), &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewDatastore(s.Client(), res.Returnval), nil
|
||||
}
|
||||
|
||||
func (s HostDatastoreSystem) Remove(ctx context.Context, ds *Datastore) error {
|
||||
req := types.RemoveDatastore{
|
||||
This: s.Reference(),
|
||||
Datastore: ds.Reference(),
|
||||
}
|
||||
|
||||
_, err := methods.RemoveDatastore(ctx, s.Client(), &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s HostDatastoreSystem) QueryAvailableDisksForVmfs(ctx context.Context) ([]types.HostScsiDisk, error) {
|
||||
req := types.QueryAvailableDisksForVmfs{
|
||||
This: s.Reference(),
|
||||
}
|
||||
|
||||
res, err := methods.QueryAvailableDisksForVmfs(ctx, s.Client(), &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res.Returnval, nil
|
||||
}
|
||||
|
||||
func (s HostDatastoreSystem) QueryVmfsDatastoreCreateOptions(ctx context.Context, devicePath string) ([]types.VmfsDatastoreOption, error) {
|
||||
req := types.QueryVmfsDatastoreCreateOptions{
|
||||
This: s.Reference(),
|
||||
DevicePath: devicePath,
|
||||
}
|
||||
|
||||
res, err := methods.QueryVmfsDatastoreCreateOptions(ctx, s.Client(), &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res.Returnval, nil
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type HostDateTimeSystem struct {
|
||||
Common
|
||||
}
|
||||
|
||||
func NewHostDateTimeSystem(c *vim25.Client, ref types.ManagedObjectReference) *HostDateTimeSystem {
|
||||
return &HostDateTimeSystem{
|
||||
Common: NewCommon(c, ref),
|
||||
}
|
||||
}
|
||||
|
||||
func (s HostDateTimeSystem) UpdateConfig(ctx context.Context, config types.HostDateTimeConfig) error {
|
||||
req := types.UpdateDateTimeConfig{
|
||||
This: s.Reference(),
|
||||
Config: config,
|
||||
}
|
||||
|
||||
_, err := methods.UpdateDateTimeConfig(ctx, s.c, &req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s HostDateTimeSystem) Update(ctx context.Context, date time.Time) error {
|
||||
req := types.UpdateDateTime{
|
||||
This: s.Reference(),
|
||||
DateTime: date,
|
||||
}
|
||||
|
||||
_, err := methods.UpdateDateTime(ctx, s.c, &req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s HostDateTimeSystem) Query(ctx context.Context) (*time.Time, error) {
|
||||
req := types.QueryDateTime{
|
||||
This: s.Reference(),
|
||||
}
|
||||
|
||||
res, err := methods.QueryDateTime(ctx, s.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &res.Returnval, nil
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type HostFirewallSystem struct {
|
||||
Common
|
||||
}
|
||||
|
||||
func NewHostFirewallSystem(c *vim25.Client, ref types.ManagedObjectReference) *HostFirewallSystem {
|
||||
return &HostFirewallSystem{
|
||||
Common: NewCommon(c, ref),
|
||||
}
|
||||
}
|
||||
|
||||
func (s HostFirewallSystem) DisableRuleset(ctx context.Context, id string) error {
|
||||
req := types.DisableRuleset{
|
||||
This: s.Reference(),
|
||||
Id: id,
|
||||
}
|
||||
|
||||
_, err := methods.DisableRuleset(ctx, s.c, &req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s HostFirewallSystem) EnableRuleset(ctx context.Context, id string) error {
|
||||
req := types.EnableRuleset{
|
||||
This: s.Reference(),
|
||||
Id: id,
|
||||
}
|
||||
|
||||
_, err := methods.EnableRuleset(ctx, s.c, &req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s HostFirewallSystem) Refresh(ctx context.Context) error {
|
||||
req := types.RefreshFirewall{
|
||||
This: s.Reference(),
|
||||
}
|
||||
|
||||
_, err := methods.RefreshFirewall(ctx, s.c, &req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s HostFirewallSystem) Info(ctx context.Context) (*types.HostFirewallInfo, error) {
|
||||
var fs mo.HostFirewallSystem
|
||||
|
||||
err := s.Properties(ctx, s.Reference(), []string{"firewallInfo"}, &fs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fs.FirewallInfo, nil
|
||||
}
|
||||
|
||||
// HostFirewallRulesetList provides helpers for a slice of types.HostFirewallRuleset
|
||||
type HostFirewallRulesetList []types.HostFirewallRuleset
|
||||
|
||||
// ByRule returns a HostFirewallRulesetList where Direction, PortType and Protocol are equal and Port is within range
|
||||
func (l HostFirewallRulesetList) ByRule(rule types.HostFirewallRule) HostFirewallRulesetList {
|
||||
var matches HostFirewallRulesetList
|
||||
|
||||
for _, rs := range l {
|
||||
for _, r := range rs.Rule {
|
||||
if r.PortType != rule.PortType ||
|
||||
r.Protocol != rule.Protocol ||
|
||||
r.Direction != rule.Direction {
|
||||
continue
|
||||
}
|
||||
|
||||
if r.EndPort == 0 && rule.Port == r.Port ||
|
||||
rule.Port >= r.Port && rule.Port <= r.EndPort {
|
||||
matches = append(matches, rs)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matches
|
||||
}
|
||||
|
||||
// EnabledByRule returns a HostFirewallRulesetList with Match(rule) applied and filtered via Enabled()
|
||||
// if enabled param is true, otherwise filtered via Disabled().
|
||||
// An error is returned if the resulting list is empty.
|
||||
func (l HostFirewallRulesetList) EnabledByRule(rule types.HostFirewallRule, enabled bool) (HostFirewallRulesetList, error) {
|
||||
var matched, skipped HostFirewallRulesetList
|
||||
var matchedKind, skippedKind string
|
||||
|
||||
l = l.ByRule(rule)
|
||||
|
||||
if enabled {
|
||||
matched = l.Enabled()
|
||||
matchedKind = "enabled"
|
||||
|
||||
skipped = l.Disabled()
|
||||
skippedKind = "disabled"
|
||||
} else {
|
||||
matched = l.Disabled()
|
||||
matchedKind = "disabled"
|
||||
|
||||
skipped = l.Enabled()
|
||||
skippedKind = "enabled"
|
||||
}
|
||||
|
||||
if len(matched) == 0 {
|
||||
msg := fmt.Sprintf("%d %s firewall rulesets match %s %s %s %d, %d %s rulesets match",
|
||||
len(matched), matchedKind,
|
||||
rule.Direction, rule.Protocol, rule.PortType, rule.Port,
|
||||
len(skipped), skippedKind)
|
||||
|
||||
if len(skipped) != 0 {
|
||||
msg += fmt.Sprintf(": %s", strings.Join(skipped.Keys(), ", "))
|
||||
}
|
||||
|
||||
return nil, errors.New(msg)
|
||||
}
|
||||
|
||||
return matched, nil
|
||||
}
|
||||
|
||||
// Enabled returns a HostFirewallRulesetList with enabled rules
|
||||
func (l HostFirewallRulesetList) Enabled() HostFirewallRulesetList {
|
||||
var matches HostFirewallRulesetList
|
||||
|
||||
for _, rs := range l {
|
||||
if rs.Enabled {
|
||||
matches = append(matches, rs)
|
||||
}
|
||||
}
|
||||
|
||||
return matches
|
||||
}
|
||||
|
||||
// Disabled returns a HostFirewallRulesetList with disabled rules
|
||||
func (l HostFirewallRulesetList) Disabled() HostFirewallRulesetList {
|
||||
var matches HostFirewallRulesetList
|
||||
|
||||
for _, rs := range l {
|
||||
if !rs.Enabled {
|
||||
matches = append(matches, rs)
|
||||
}
|
||||
}
|
||||
|
||||
return matches
|
||||
}
|
||||
|
||||
// Keys returns the HostFirewallRuleset.Key for each ruleset in the list
|
||||
func (l HostFirewallRulesetList) Keys() []string {
|
||||
var keys []string
|
||||
|
||||
for _, rs := range l {
|
||||
keys = append(keys, rs.Key)
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
|
@ -0,0 +1,358 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type HostNetworkSystem struct {
|
||||
Common
|
||||
}
|
||||
|
||||
func NewHostNetworkSystem(c *vim25.Client, ref types.ManagedObjectReference) *HostNetworkSystem {
|
||||
return &HostNetworkSystem{
|
||||
Common: NewCommon(c, ref),
|
||||
}
|
||||
}
|
||||
|
||||
// AddPortGroup wraps methods.AddPortGroup
|
||||
func (o HostNetworkSystem) AddPortGroup(ctx context.Context, portgrp types.HostPortGroupSpec) error {
|
||||
req := types.AddPortGroup{
|
||||
This: o.Reference(),
|
||||
Portgrp: portgrp,
|
||||
}
|
||||
|
||||
_, err := methods.AddPortGroup(ctx, o.c, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddServiceConsoleVirtualNic wraps methods.AddServiceConsoleVirtualNic
|
||||
func (o HostNetworkSystem) AddServiceConsoleVirtualNic(ctx context.Context, portgroup string, nic types.HostVirtualNicSpec) (string, error) {
|
||||
req := types.AddServiceConsoleVirtualNic{
|
||||
This: o.Reference(),
|
||||
Portgroup: portgroup,
|
||||
Nic: nic,
|
||||
}
|
||||
|
||||
res, err := methods.AddServiceConsoleVirtualNic(ctx, o.c, &req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return res.Returnval, nil
|
||||
}
|
||||
|
||||
// AddVirtualNic wraps methods.AddVirtualNic
|
||||
func (o HostNetworkSystem) AddVirtualNic(ctx context.Context, portgroup string, nic types.HostVirtualNicSpec) (string, error) {
|
||||
req := types.AddVirtualNic{
|
||||
This: o.Reference(),
|
||||
Portgroup: portgroup,
|
||||
Nic: nic,
|
||||
}
|
||||
|
||||
res, err := methods.AddVirtualNic(ctx, o.c, &req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return res.Returnval, nil
|
||||
}
|
||||
|
||||
// AddVirtualSwitch wraps methods.AddVirtualSwitch
|
||||
func (o HostNetworkSystem) AddVirtualSwitch(ctx context.Context, vswitchName string, spec *types.HostVirtualSwitchSpec) error {
|
||||
req := types.AddVirtualSwitch{
|
||||
This: o.Reference(),
|
||||
VswitchName: vswitchName,
|
||||
Spec: spec,
|
||||
}
|
||||
|
||||
_, err := methods.AddVirtualSwitch(ctx, o.c, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// QueryNetworkHint wraps methods.QueryNetworkHint
|
||||
func (o HostNetworkSystem) QueryNetworkHint(ctx context.Context, device []string) error {
|
||||
req := types.QueryNetworkHint{
|
||||
This: o.Reference(),
|
||||
Device: device,
|
||||
}
|
||||
|
||||
_, err := methods.QueryNetworkHint(ctx, o.c, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RefreshNetworkSystem wraps methods.RefreshNetworkSystem
|
||||
func (o HostNetworkSystem) RefreshNetworkSystem(ctx context.Context) error {
|
||||
req := types.RefreshNetworkSystem{
|
||||
This: o.Reference(),
|
||||
}
|
||||
|
||||
_, err := methods.RefreshNetworkSystem(ctx, o.c, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemovePortGroup wraps methods.RemovePortGroup
|
||||
func (o HostNetworkSystem) RemovePortGroup(ctx context.Context, pgName string) error {
|
||||
req := types.RemovePortGroup{
|
||||
This: o.Reference(),
|
||||
PgName: pgName,
|
||||
}
|
||||
|
||||
_, err := methods.RemovePortGroup(ctx, o.c, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveServiceConsoleVirtualNic wraps methods.RemoveServiceConsoleVirtualNic
|
||||
func (o HostNetworkSystem) RemoveServiceConsoleVirtualNic(ctx context.Context, device string) error {
|
||||
req := types.RemoveServiceConsoleVirtualNic{
|
||||
This: o.Reference(),
|
||||
Device: device,
|
||||
}
|
||||
|
||||
_, err := methods.RemoveServiceConsoleVirtualNic(ctx, o.c, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveVirtualNic wraps methods.RemoveVirtualNic
|
||||
func (o HostNetworkSystem) RemoveVirtualNic(ctx context.Context, device string) error {
|
||||
req := types.RemoveVirtualNic{
|
||||
This: o.Reference(),
|
||||
Device: device,
|
||||
}
|
||||
|
||||
_, err := methods.RemoveVirtualNic(ctx, o.c, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveVirtualSwitch wraps methods.RemoveVirtualSwitch
|
||||
func (o HostNetworkSystem) RemoveVirtualSwitch(ctx context.Context, vswitchName string) error {
|
||||
req := types.RemoveVirtualSwitch{
|
||||
This: o.Reference(),
|
||||
VswitchName: vswitchName,
|
||||
}
|
||||
|
||||
_, err := methods.RemoveVirtualSwitch(ctx, o.c, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestartServiceConsoleVirtualNic wraps methods.RestartServiceConsoleVirtualNic
|
||||
func (o HostNetworkSystem) RestartServiceConsoleVirtualNic(ctx context.Context, device string) error {
|
||||
req := types.RestartServiceConsoleVirtualNic{
|
||||
This: o.Reference(),
|
||||
Device: device,
|
||||
}
|
||||
|
||||
_, err := methods.RestartServiceConsoleVirtualNic(ctx, o.c, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateConsoleIpRouteConfig wraps methods.UpdateConsoleIpRouteConfig
|
||||
func (o HostNetworkSystem) UpdateConsoleIpRouteConfig(ctx context.Context, config types.BaseHostIpRouteConfig) error {
|
||||
req := types.UpdateConsoleIpRouteConfig{
|
||||
This: o.Reference(),
|
||||
Config: config,
|
||||
}
|
||||
|
||||
_, err := methods.UpdateConsoleIpRouteConfig(ctx, o.c, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateDnsConfig wraps methods.UpdateDnsConfig
|
||||
func (o HostNetworkSystem) UpdateDnsConfig(ctx context.Context, config types.BaseHostDnsConfig) error {
|
||||
req := types.UpdateDnsConfig{
|
||||
This: o.Reference(),
|
||||
Config: config,
|
||||
}
|
||||
|
||||
_, err := methods.UpdateDnsConfig(ctx, o.c, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateIpRouteConfig wraps methods.UpdateIpRouteConfig
|
||||
func (o HostNetworkSystem) UpdateIpRouteConfig(ctx context.Context, config types.BaseHostIpRouteConfig) error {
|
||||
req := types.UpdateIpRouteConfig{
|
||||
This: o.Reference(),
|
||||
Config: config,
|
||||
}
|
||||
|
||||
_, err := methods.UpdateIpRouteConfig(ctx, o.c, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateIpRouteTableConfig wraps methods.UpdateIpRouteTableConfig
|
||||
func (o HostNetworkSystem) UpdateIpRouteTableConfig(ctx context.Context, config types.HostIpRouteTableConfig) error {
|
||||
req := types.UpdateIpRouteTableConfig{
|
||||
This: o.Reference(),
|
||||
Config: config,
|
||||
}
|
||||
|
||||
_, err := methods.UpdateIpRouteTableConfig(ctx, o.c, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateNetworkConfig wraps methods.UpdateNetworkConfig
|
||||
func (o HostNetworkSystem) UpdateNetworkConfig(ctx context.Context, config types.HostNetworkConfig, changeMode string) (*types.HostNetworkConfigResult, error) {
|
||||
req := types.UpdateNetworkConfig{
|
||||
This: o.Reference(),
|
||||
Config: config,
|
||||
ChangeMode: changeMode,
|
||||
}
|
||||
|
||||
res, err := methods.UpdateNetworkConfig(ctx, o.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &res.Returnval, nil
|
||||
}
|
||||
|
||||
// UpdatePhysicalNicLinkSpeed wraps methods.UpdatePhysicalNicLinkSpeed
|
||||
func (o HostNetworkSystem) UpdatePhysicalNicLinkSpeed(ctx context.Context, device string, linkSpeed *types.PhysicalNicLinkInfo) error {
|
||||
req := types.UpdatePhysicalNicLinkSpeed{
|
||||
This: o.Reference(),
|
||||
Device: device,
|
||||
LinkSpeed: linkSpeed,
|
||||
}
|
||||
|
||||
_, err := methods.UpdatePhysicalNicLinkSpeed(ctx, o.c, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdatePortGroup wraps methods.UpdatePortGroup
|
||||
func (o HostNetworkSystem) UpdatePortGroup(ctx context.Context, pgName string, portgrp types.HostPortGroupSpec) error {
|
||||
req := types.UpdatePortGroup{
|
||||
This: o.Reference(),
|
||||
PgName: pgName,
|
||||
Portgrp: portgrp,
|
||||
}
|
||||
|
||||
_, err := methods.UpdatePortGroup(ctx, o.c, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateServiceConsoleVirtualNic wraps methods.UpdateServiceConsoleVirtualNic
|
||||
func (o HostNetworkSystem) UpdateServiceConsoleVirtualNic(ctx context.Context, device string, nic types.HostVirtualNicSpec) error {
|
||||
req := types.UpdateServiceConsoleVirtualNic{
|
||||
This: o.Reference(),
|
||||
Device: device,
|
||||
Nic: nic,
|
||||
}
|
||||
|
||||
_, err := methods.UpdateServiceConsoleVirtualNic(ctx, o.c, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateVirtualNic wraps methods.UpdateVirtualNic
|
||||
func (o HostNetworkSystem) UpdateVirtualNic(ctx context.Context, device string, nic types.HostVirtualNicSpec) error {
|
||||
req := types.UpdateVirtualNic{
|
||||
This: o.Reference(),
|
||||
Device: device,
|
||||
Nic: nic,
|
||||
}
|
||||
|
||||
_, err := methods.UpdateVirtualNic(ctx, o.c, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateVirtualSwitch wraps methods.UpdateVirtualSwitch
|
||||
func (o HostNetworkSystem) UpdateVirtualSwitch(ctx context.Context, vswitchName string, spec types.HostVirtualSwitchSpec) error {
|
||||
req := types.UpdateVirtualSwitch{
|
||||
This: o.Reference(),
|
||||
VswitchName: vswitchName,
|
||||
Spec: spec,
|
||||
}
|
||||
|
||||
_, err := methods.UpdateVirtualSwitch(ctx, o.c, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type HostServiceSystem struct {
|
||||
Common
|
||||
}
|
||||
|
||||
func NewHostServiceSystem(c *vim25.Client, ref types.ManagedObjectReference) *HostServiceSystem {
|
||||
return &HostServiceSystem{
|
||||
Common: NewCommon(c, ref),
|
||||
}
|
||||
}
|
||||
|
||||
func (s HostServiceSystem) Service(ctx context.Context) ([]types.HostService, error) {
|
||||
var ss mo.HostServiceSystem
|
||||
|
||||
err := s.Properties(ctx, s.Reference(), []string{"serviceInfo.service"}, &ss)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ss.ServiceInfo.Service, nil
|
||||
}
|
||||
|
||||
func (s HostServiceSystem) Start(ctx context.Context, id string) error {
|
||||
req := types.StartService{
|
||||
This: s.Reference(),
|
||||
Id: id,
|
||||
}
|
||||
|
||||
_, err := methods.StartService(ctx, s.Client(), &req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s HostServiceSystem) Stop(ctx context.Context, id string) error {
|
||||
req := types.StopService{
|
||||
This: s.Reference(),
|
||||
Id: id,
|
||||
}
|
||||
|
||||
_, err := methods.StopService(ctx, s.Client(), &req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s HostServiceSystem) Restart(ctx context.Context, id string) error {
|
||||
req := types.RestartService{
|
||||
This: s.Reference(),
|
||||
Id: id,
|
||||
}
|
||||
|
||||
_, err := methods.RestartService(ctx, s.Client(), &req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s HostServiceSystem) UpdatePolicy(ctx context.Context, id string, policy string) error {
|
||||
req := types.UpdateServicePolicy{
|
||||
This: s.Reference(),
|
||||
Id: id,
|
||||
Policy: policy,
|
||||
}
|
||||
|
||||
_, err := methods.UpdateServicePolicy(ctx, s.Client(), &req)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type HostStorageSystem struct {
|
||||
Common
|
||||
}
|
||||
|
||||
func NewHostStorageSystem(c *vim25.Client, ref types.ManagedObjectReference) *HostStorageSystem {
|
||||
return &HostStorageSystem{
|
||||
Common: NewCommon(c, ref),
|
||||
}
|
||||
}
|
||||
|
||||
func (s HostStorageSystem) RetrieveDiskPartitionInfo(ctx context.Context, devicePath string) (*types.HostDiskPartitionInfo, error) {
|
||||
req := types.RetrieveDiskPartitionInfo{
|
||||
This: s.Reference(),
|
||||
DevicePath: []string{devicePath},
|
||||
}
|
||||
|
||||
res, err := methods.RetrieveDiskPartitionInfo(ctx, s.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if res.Returnval == nil || len(res.Returnval) == 0 {
|
||||
return nil, errors.New("no partition info")
|
||||
}
|
||||
|
||||
return &res.Returnval[0], nil
|
||||
}
|
||||
|
||||
func (s HostStorageSystem) ComputeDiskPartitionInfo(ctx context.Context, devicePath string, layout types.HostDiskPartitionLayout) (*types.HostDiskPartitionInfo, error) {
|
||||
req := types.ComputeDiskPartitionInfo{
|
||||
This: s.Reference(),
|
||||
DevicePath: devicePath,
|
||||
Layout: layout,
|
||||
}
|
||||
|
||||
res, err := methods.ComputeDiskPartitionInfo(ctx, s.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &res.Returnval, nil
|
||||
}
|
||||
|
||||
func (s HostStorageSystem) UpdateDiskPartitionInfo(ctx context.Context, devicePath string, spec types.HostDiskPartitionSpec) error {
|
||||
req := types.UpdateDiskPartitions{
|
||||
This: s.Reference(),
|
||||
DevicePath: devicePath,
|
||||
Spec: spec,
|
||||
}
|
||||
|
||||
_, err := methods.UpdateDiskPartitions(ctx, s.c, &req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s HostStorageSystem) RescanAllHba(ctx context.Context) error {
|
||||
req := types.RescanAllHba{
|
||||
This: s.Reference(),
|
||||
}
|
||||
|
||||
_, err := methods.RescanAllHba(ctx, s.c, &req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s HostStorageSystem) Refresh(ctx context.Context) error {
|
||||
req := types.RefreshStorageSystem{
|
||||
This: s.Reference(),
|
||||
}
|
||||
|
||||
_, err := methods.RefreshStorageSystem(ctx, s.c, &req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s HostStorageSystem) RescanVmfs(ctx context.Context) error {
|
||||
req := types.RescanVmfs{
|
||||
This: s.Reference(),
|
||||
}
|
||||
|
||||
_, err := methods.RescanVmfs(ctx, s.c, &req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s HostStorageSystem) MarkAsSsd(ctx context.Context, uuid string) (*Task, error) {
|
||||
req := types.MarkAsSsd_Task{
|
||||
This: s.Reference(),
|
||||
ScsiDiskUuid: uuid,
|
||||
}
|
||||
|
||||
res, err := methods.MarkAsSsd_Task(ctx, s.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(s.c, res.Returnval), nil
|
||||
}
|
||||
|
||||
func (s HostStorageSystem) MarkAsNonSsd(ctx context.Context, uuid string) (*Task, error) {
|
||||
req := types.MarkAsNonSsd_Task{
|
||||
This: s.Reference(),
|
||||
ScsiDiskUuid: uuid,
|
||||
}
|
||||
|
||||
res, err := methods.MarkAsNonSsd_Task(ctx, s.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(s.c, res.Returnval), nil
|
||||
}
|
||||
|
||||
func (s HostStorageSystem) MarkAsLocal(ctx context.Context, uuid string) (*Task, error) {
|
||||
req := types.MarkAsLocal_Task{
|
||||
This: s.Reference(),
|
||||
ScsiDiskUuid: uuid,
|
||||
}
|
||||
|
||||
res, err := methods.MarkAsLocal_Task(ctx, s.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(s.c, res.Returnval), nil
|
||||
}
|
||||
|
||||
func (s HostStorageSystem) MarkAsNonLocal(ctx context.Context, uuid string) (*Task, error) {
|
||||
req := types.MarkAsNonLocal_Task{
|
||||
This: s.Reference(),
|
||||
ScsiDiskUuid: uuid,
|
||||
}
|
||||
|
||||
res, err := methods.MarkAsNonLocal_Task(ctx, s.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(s.c, res.Returnval), nil
|
||||
}
|
||||
|
||||
func (s HostStorageSystem) AttachScsiLun(ctx context.Context, uuid string) error {
|
||||
req := types.AttachScsiLun{
|
||||
This: s.Reference(),
|
||||
LunUuid: uuid,
|
||||
}
|
||||
|
||||
_, err := methods.AttachScsiLun(ctx, s.c, &req)
|
||||
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type HostSystem struct {
|
||||
Common
|
||||
}
|
||||
|
||||
func NewHostSystem(c *vim25.Client, ref types.ManagedObjectReference) *HostSystem {
|
||||
return &HostSystem{
|
||||
Common: NewCommon(c, ref),
|
||||
}
|
||||
}
|
||||
|
||||
func (h HostSystem) ConfigManager() *HostConfigManager {
|
||||
return NewHostConfigManager(h.c, h.Reference())
|
||||
}
|
||||
|
||||
func (h HostSystem) ResourcePool(ctx context.Context) (*ResourcePool, error) {
|
||||
var mh mo.HostSystem
|
||||
|
||||
err := h.Properties(ctx, h.Reference(), []string{"parent"}, &mh)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var mcr *mo.ComputeResource
|
||||
var parent interface{}
|
||||
|
||||
switch mh.Parent.Type {
|
||||
case "ComputeResource":
|
||||
mcr = new(mo.ComputeResource)
|
||||
parent = mcr
|
||||
case "ClusterComputeResource":
|
||||
mcc := new(mo.ClusterComputeResource)
|
||||
mcr = &mcc.ComputeResource
|
||||
parent = mcc
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown host parent type: %s", mh.Parent.Type)
|
||||
}
|
||||
|
||||
err = h.Properties(ctx, *mh.Parent, []string{"resourcePool"}, parent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pool := NewResourcePool(h.c, *mcr.ResourcePool)
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
func (h HostSystem) ManagementIPs(ctx context.Context) ([]net.IP, error) {
|
||||
var mh mo.HostSystem
|
||||
|
||||
err := h.Properties(ctx, h.Reference(), []string{"config.virtualNicManagerInfo.netConfig"}, &mh)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ips []net.IP
|
||||
for _, nc := range mh.Config.VirtualNicManagerInfo.NetConfig {
|
||||
if nc.NicType == "management" && len(nc.CandidateVnic) > 0 {
|
||||
ip := net.ParseIP(nc.CandidateVnic[0].Spec.Ip.IpAddress)
|
||||
if ip != nil {
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ips, nil
|
||||
}
|
||||
|
||||
func (h HostSystem) Disconnect(ctx context.Context) (*Task, error) {
|
||||
req := types.DisconnectHost_Task{
|
||||
This: h.Reference(),
|
||||
}
|
||||
|
||||
res, err := methods.DisconnectHost_Task(ctx, h.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(h.c, res.Returnval), nil
|
||||
}
|
||||
|
||||
func (h HostSystem) Reconnect(ctx context.Context, cnxSpec *types.HostConnectSpec, reconnectSpec *types.HostSystemReconnectSpec) (*Task, error) {
|
||||
req := types.ReconnectHost_Task{
|
||||
This: h.Reference(),
|
||||
CnxSpec: cnxSpec,
|
||||
ReconnectSpec: reconnectSpec,
|
||||
}
|
||||
|
||||
res, err := methods.ReconnectHost_Task(ctx, h.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(h.c, res.Returnval), nil
|
||||
}
|
||||
|
||||
func (h HostSystem) EnterMaintenanceMode(ctx context.Context, timeout int32, evacuate bool, spec *types.HostMaintenanceSpec) (*Task, error) {
|
||||
req := types.EnterMaintenanceMode_Task{
|
||||
This: h.Reference(),
|
||||
Timeout: timeout,
|
||||
EvacuatePoweredOffVms: types.NewBool(evacuate),
|
||||
MaintenanceSpec: spec,
|
||||
}
|
||||
|
||||
res, err := methods.EnterMaintenanceMode_Task(ctx, h.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(h.c, res.Returnval), nil
|
||||
}
|
||||
|
||||
func (h HostSystem) ExitMaintenanceMode(ctx context.Context, timeout int32) (*Task, error) {
|
||||
req := types.ExitMaintenanceMode_Task{
|
||||
This: h.Reference(),
|
||||
Timeout: timeout,
|
||||
}
|
||||
|
||||
res, err := methods.ExitMaintenanceMode_Task(ctx, h.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(h.c, res.Returnval), nil
|
||||
}
|
93
vendor/github.com/vmware/govmomi/object/host_virtual_nic_manager.go
generated
vendored
Normal file
93
vendor/github.com/vmware/govmomi/object/host_virtual_nic_manager.go
generated
vendored
Normal file
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type HostVirtualNicManager struct {
|
||||
Common
|
||||
Host *HostSystem
|
||||
}
|
||||
|
||||
func NewHostVirtualNicManager(c *vim25.Client, ref types.ManagedObjectReference, host types.ManagedObjectReference) *HostVirtualNicManager {
|
||||
return &HostVirtualNicManager{
|
||||
Common: NewCommon(c, ref),
|
||||
Host: NewHostSystem(c, host),
|
||||
}
|
||||
}
|
||||
|
||||
func (m HostVirtualNicManager) Info(ctx context.Context) (*types.HostVirtualNicManagerInfo, error) {
|
||||
var vnm mo.HostVirtualNicManager
|
||||
|
||||
err := m.Properties(ctx, m.Reference(), []string{"info"}, &vnm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &vnm.Info, nil
|
||||
}
|
||||
|
||||
func (m HostVirtualNicManager) DeselectVnic(ctx context.Context, nicType string, device string) error {
|
||||
if nicType == string(types.HostVirtualNicManagerNicTypeVsan) {
|
||||
// Avoid fault.NotSupported:
|
||||
// "Error deselecting device '$device': VSAN interfaces must be deselected using vim.host.VsanSystem"
|
||||
s, err := m.Host.ConfigManager().VsanSystem(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.updateVnic(ctx, device, false)
|
||||
}
|
||||
|
||||
req := types.DeselectVnicForNicType{
|
||||
This: m.Reference(),
|
||||
NicType: nicType,
|
||||
Device: device,
|
||||
}
|
||||
|
||||
_, err := methods.DeselectVnicForNicType(ctx, m.Client(), &req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m HostVirtualNicManager) SelectVnic(ctx context.Context, nicType string, device string) error {
|
||||
if nicType == string(types.HostVirtualNicManagerNicTypeVsan) {
|
||||
// Avoid fault.NotSupported:
|
||||
// "Error selecting device '$device': VSAN interfaces must be selected using vim.host.VsanSystem"
|
||||
s, err := m.Host.ConfigManager().VsanSystem(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.updateVnic(ctx, device, true)
|
||||
}
|
||||
|
||||
req := types.SelectVnicForNicType{
|
||||
This: m.Reference(),
|
||||
NicType: nicType,
|
||||
Device: device,
|
||||
}
|
||||
|
||||
_, err := methods.SelectVnicForNicType(ctx, m.Client(), &req)
|
||||
return err
|
||||
}
|
117
vendor/github.com/vmware/govmomi/object/host_vsan_internal_system.go
generated
vendored
Normal file
117
vendor/github.com/vmware/govmomi/object/host_vsan_internal_system.go
generated
vendored
Normal file
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type HostVsanInternalSystem struct {
|
||||
Common
|
||||
}
|
||||
|
||||
func NewHostVsanInternalSystem(c *vim25.Client, ref types.ManagedObjectReference) *HostVsanInternalSystem {
|
||||
m := HostVsanInternalSystem{
|
||||
Common: NewCommon(c, ref),
|
||||
}
|
||||
|
||||
return &m
|
||||
}
|
||||
|
||||
// QueryVsanObjectUuidsByFilter returns vSAN DOM object uuids by filter.
|
||||
func (m HostVsanInternalSystem) QueryVsanObjectUuidsByFilter(ctx context.Context, uuids []string, limit int32, version int32) ([]string, error) {
|
||||
req := types.QueryVsanObjectUuidsByFilter{
|
||||
This: m.Reference(),
|
||||
Uuids: uuids,
|
||||
Limit: &limit,
|
||||
Version: version,
|
||||
}
|
||||
|
||||
res, err := methods.QueryVsanObjectUuidsByFilter(ctx, m.Client(), &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res.Returnval, nil
|
||||
}
|
||||
|
||||
type VsanObjExtAttrs struct {
|
||||
Type string `json:"Object type"`
|
||||
Class string `json:"Object class"`
|
||||
Size string `json:"Object size"`
|
||||
Path string `json:"Object path"`
|
||||
Name string `json:"User friendly name"`
|
||||
}
|
||||
|
||||
func (a *VsanObjExtAttrs) DatastorePath(dir string) string {
|
||||
l := len(dir)
|
||||
path := a.Path
|
||||
|
||||
if len(path) >= l {
|
||||
path = a.Path[l:]
|
||||
}
|
||||
|
||||
if path != "" {
|
||||
return path
|
||||
}
|
||||
|
||||
return a.Name // vmnamespace
|
||||
}
|
||||
|
||||
// GetVsanObjExtAttrs is internal and intended for troubleshooting/debugging situations in the field.
|
||||
// WARNING: This API can be slow because we do IOs (reads) to all the objects.
|
||||
func (m HostVsanInternalSystem) GetVsanObjExtAttrs(ctx context.Context, uuids []string) (map[string]VsanObjExtAttrs, error) {
|
||||
req := types.GetVsanObjExtAttrs{
|
||||
This: m.Reference(),
|
||||
Uuids: uuids,
|
||||
}
|
||||
|
||||
res, err := methods.GetVsanObjExtAttrs(ctx, m.Client(), &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var attrs map[string]VsanObjExtAttrs
|
||||
|
||||
err = json.Unmarshal([]byte(res.Returnval), &attrs)
|
||||
|
||||
return attrs, err
|
||||
}
|
||||
|
||||
// DeleteVsanObjects is internal and intended for troubleshooting/debugging only.
|
||||
// WARNING: This API can be slow because we do IOs to all the objects.
|
||||
// DOM won't allow access to objects which have lost quorum. Such objects can be deleted with the optional "force" flag.
|
||||
// These objects may however re-appear with quorum if the absent components come back (network partition gets resolved, etc.)
|
||||
func (m HostVsanInternalSystem) DeleteVsanObjects(ctx context.Context, uuids []string, force *bool) ([]types.HostVsanInternalSystemDeleteVsanObjectsResult, error) {
|
||||
req := types.DeleteVsanObjects{
|
||||
This: m.Reference(),
|
||||
Uuids: uuids,
|
||||
Force: force,
|
||||
}
|
||||
|
||||
res, err := methods.DeleteVsanObjects(ctx, m.Client(), &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res.Returnval, nil
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type HostVsanSystem struct {
|
||||
Common
|
||||
}
|
||||
|
||||
func NewHostVsanSystem(c *vim25.Client, ref types.ManagedObjectReference) *HostVsanSystem {
|
||||
return &HostVsanSystem{
|
||||
Common: NewCommon(c, ref),
|
||||
}
|
||||
}
|
||||
|
||||
func (s HostVsanSystem) Update(ctx context.Context, config types.VsanHostConfigInfo) (*Task, error) {
|
||||
req := types.UpdateVsan_Task{
|
||||
This: s.Reference(),
|
||||
Config: config,
|
||||
}
|
||||
|
||||
res, err := methods.UpdateVsan_Task(ctx, s.Client(), &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewTask(s.Client(), res.Returnval), nil
|
||||
}
|
||||
|
||||
// updateVnic in support of the HostVirtualNicManager.{SelectVnic,DeselectVnic} methods
|
||||
func (s HostVsanSystem) updateVnic(ctx context.Context, device string, enable bool) error {
|
||||
var vsan mo.HostVsanSystem
|
||||
|
||||
err := s.Properties(ctx, s.Reference(), []string{"config.networkInfo.port"}, &vsan)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info := vsan.Config
|
||||
|
||||
var port []types.VsanHostConfigInfoNetworkInfoPortConfig
|
||||
|
||||
for _, p := range info.NetworkInfo.Port {
|
||||
if p.Device == device {
|
||||
continue
|
||||
}
|
||||
|
||||
port = append(port, p)
|
||||
}
|
||||
|
||||
if enable {
|
||||
port = append(port, types.VsanHostConfigInfoNetworkInfoPortConfig{
|
||||
Device: device,
|
||||
})
|
||||
}
|
||||
|
||||
info.NetworkInfo.Port = port
|
||||
|
||||
task, err := s.Update(ctx, info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = task.WaitForResult(ctx, nil)
|
||||
return err
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue