diff --git a/.gitignore b/.gitignore index fcf26d07..63c18adb 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ world/players world/whitelist.sqlite world/world/stats/Pixowl.json goproxy/goproxy +dockercraft diff --git a/Dockerfile b/Dockerfile index 109e4268..e3d3cd2a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,20 +1,24 @@ -FROM golang:1.5.1 +FROM golang:1.6 + +ENV DOCKER_VERSION 1.11.1 # Copy latest docker client(s) -COPY ./docker/docker-1.9.1 /bin/docker-1.9.1 -RUN chmod +x /bin/docker-* +RUN curl -sSL -o docker.tgz https://get.docker.com/builds/Linux/x86_64/docker-${DOCKER_VERSION}.tgz &&\ + tar -xvf docker.tgz --strip-components=1 -C /bin && rm docker.tgz &&\ + chmod +x /bin/docker-* &&\ + ln -s /bin/docker /bin/docker-${DOCKER_VERSION} # Copy Go code and install applications -COPY ./go /go -RUN cd /go/src/goproxy; go install -RUN cd /go/src/gosetup; go install +WORKDIR /go/src/github.com/docker/dockercraft +COPY . . +RUN go install # Download Cuberite server (Minecraft C++ server) # and load up a special empty world for Dockercraft WORKDIR /srv RUN sh -c "$(wget -qO - https://raw.githubusercontent.com/cuberite/cuberite/master/easyinstall.sh)" && mv Server cuberite_server +RUN ln -s /srv/cuberite_server/Cuberite /usr/bin/cuberite COPY ./world world COPY ./docs/img/logo64x64.png logo.png -COPY ./start.sh start.sh -CMD ["/bin/bash","/srv/start.sh"] +ENTRYPOINT ["/go/bin/dockercraft", "-daemon"] diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..de52a10f --- /dev/null +++ b/Makefile @@ -0,0 +1,47 @@ +.PHONY: all test test-local install-deps lint fmt vet build serve + +REPO_NAME = dockercraft +REPO_OWNER = docker +PKG_NAME = github.com/${REPO_OWNER}/${REPO_NAME} +IMAGE = golang:1.6 +IMAGE_NAME = dockercraft-dev +CONTAINER_NAME = dockercraft-dev-container + +all: test build + +test-local: install-deps fmt lint vet + @echo "+ $@" + @go test -v . +test: + @docker run -v ${shell pwd}:/go/src/${PKG_NAME} -w /go/src/${PKG_NAME} ${IMAGE} make test-local + +install-deps: + @echo "+ $@" + @go get -u github.com/golang/lint/golint + @apt-get -qq update && apt-get -qq -y install lua5.1 + +lint: + @echo "+ $@" + @test -z "$$(golint ./... | grep -v vendor/ | tee /dev/stderr)" + +fmt: + @echo "+ $@" + @test -z "$$(gofmt -s -l . | grep -v vendor/ | tee /dev/stderr)" + @luac -p ./world/Plugins/Docker/*.lua + +vet: + @echo "+ $@" + @go vet . + +build: + @echo "+ $@" + @docker build -t ${IMAGE_NAME} . + +serve: + @docker run -it --rm \ + --name ${CONTAINER_NAME} \ + -p 8080:8080 \ + -p 25566:25565 \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v /Users/$$USER/.docker/machine:/root/.docker/machine \ + ${IMAGE_NAME} -debug diff --git a/README.md b/README.md index 20f673b8..c9b57085 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,14 @@ A simple Minecraft Docker client, to visualize and manage Docker containers. gaetan/dockercraft ``` - Mounting `/var/run/docker.sock` inside the container is necessary to send requests to the Docker remote API. + You must mount one or both of the following: + + - `/var/run/docker.sock` for a local docker instance + - `~/.docker/machine/machines` for your docker machines! + + Please do not mount BOTH where `/var/run/docker.sock` is located on a machine managed by docker machine. + Bad things may happen! In this case there will be a "local" and "" world both subscribed to + events from the same daemon. The default port for a Minecraft server is *25565*, if you prefer a different one: `-p :25565` @@ -62,11 +69,25 @@ A simple Minecraft Docker client, to visualize and manage Docker containers. ![Dockercraft](../master/docs/img/landscape.png?raw=true) +## (Experimental) Docker Machine support + +If you mount the `~/.docker/machine/machines` directory, Dockercraft will attempt to manage your machines for you. This has only been tested with Docker Machine version 0.7.0. + +When you log in, you will be placed in the default world. The criteria for selecting this are as follows: + +- `local` world if you've also mounted `/var/run/docker.sock` +- `default` world if you have a machine called `default` +- the first world discovered by docker-machine in alphabetical order + +To view all available worlds, use the `/worlds` command from within your minecraft client + +To portal to another world, `/portal `. +For example, to portal to the default world I would `/portal default` + ## Upcoming features This is just the beginning for Dockercraft! We should be able to support a lot more Docker features like: -- List [Docker Machines](https://docs.docker.com/machine/) and use portals to see what's inside - Support more Docker commands - Display [logs](https://docs.docker.com/v1.8/reference/commandline/logs/) (for each container, pushing a simple button) - Represent links diff --git a/circle.yml b/circle.yml new file mode 100644 index 00000000..d7d8e41b --- /dev/null +++ b/circle.yml @@ -0,0 +1,12 @@ +--- +machine: + services: + - docker + +dependencies: + override: + - echo "Nothing to see here.." + +test: + override: + - make test diff --git a/docker-compose.yml b/docker-compose.yml index c7ac8fef..5a61908c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,8 @@ dockercraft: build: . volumes: - "/var/run/docker.sock:/var/run/docker.sock" + - "/Users/dave/.docker/machine:/root/.docker/machine" ports: - "25565:25565" - tty: true \ No newline at end of file + tty: true + stdin_open: true diff --git a/docker/docker-1.9.1 b/docker/docker-1.9.1 deleted file mode 100644 index 8090020c..00000000 Binary files a/docker/docker-1.9.1 and /dev/null differ diff --git a/go/src/github.com/Sirupsen/logrus/.gitignore b/go/src/github.com/Sirupsen/logrus/.gitignore deleted file mode 100644 index 66be63a0..00000000 --- a/go/src/github.com/Sirupsen/logrus/.gitignore +++ /dev/null @@ -1 +0,0 @@ -logrus diff --git a/go/src/github.com/Sirupsen/logrus/.travis.yml b/go/src/github.com/Sirupsen/logrus/.travis.yml deleted file mode 100644 index ec641142..00000000 --- a/go/src/github.com/Sirupsen/logrus/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: go -go: - - 1.3 - - 1.4 - - tip -install: - - go get -t ./... diff --git a/go/src/github.com/Sirupsen/logrus/CHANGELOG.md b/go/src/github.com/Sirupsen/logrus/CHANGELOG.md deleted file mode 100644 index 53616dbc..00000000 --- a/go/src/github.com/Sirupsen/logrus/CHANGELOG.md +++ /dev/null @@ -1,53 +0,0 @@ -# 0.9.0 (Unreleased) - -* logrus/text_formatter: don't emit empty msg -* logrus/hooks/airbrake: move out of main repository -* logrus/hooks/sentry: move out of main repository - -# 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) diff --git a/go/src/github.com/Sirupsen/logrus/README.md b/go/src/github.com/Sirupsen/logrus/README.md deleted file mode 100644 index 3e526c1b..00000000 --- a/go/src/github.com/Sirupsen/logrus/README.md +++ /dev/null @@ -1,356 +0,0 @@ -# Logrus :walrus: [![Build Status](https://travis-ci.org/Sirupsen/logrus.svg?branch=master)](https://travis-ci.org/Sirupsen/logrus) [![godoc reference](https://godoc.org/github.com/Sirupsen/logrus?status.png)][godoc] - -Logrus is a structured logger for Go (golang), completely API compatible with -the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not -yet stable (pre 1.0). Logrus itself is completely stable and has been used in -many large deployments. The core API is unlikely to change much but please -version control your Logrus to make sure you aren't fetching latest `master` on -every build.** - -Nicely color-coded in development (when a TTY is attached, otherwise just -plain text): - -![Colored](http://i.imgur.com/PY7qMwd.png) - -With `log.Formatter = new(logrus.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.Formatter = new(&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 -``` - -#### 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 stderr instead of stdout, could also be a file. - log.SetOutput(os.Stderr) - - // 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 ( - "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.Stderr - - log.WithFields(logrus.Fields{ - "animal": "walrus", - "size": 10, - }).Info("A group of walrus emerges from the ocean") -} -``` - -#### Fields - -Logrus encourages careful, structured logging though 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. - -#### 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 "aibrake" - 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). - -| Hook | Description | -| ----- | ----------- | -| [Airbrake](https://github.com/gemnasium/logrus-airbrake-hook) | Send errors to the Airbrake API V3. Uses the official [`gobrake`](https://github.com/airbrake/gobrake) behind the scenes. | -| [Airbrake "legacy"](https://github.com/gemnasium/logrus-airbrake-legacy-hook) | Send errors to an exception tracking service compatible with the Airbrake API V2. Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. | -| [Papertrail](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go) | Send errors to the Papertrail hosted logging service via UDP. | -| [Syslog](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. | -| [BugSnag](https://github.com/Sirupsen/logrus/blob/master/hooks/bugsnag/bugsnag.go) | Send errors to the Bugsnag exception tracking service. | -| [Sentry](https://github.com/evalphobia/logrus_sentry) | Send errors to the Sentry error logging and aggregation service. | -| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. | -| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) | -| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. | -| [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` | -| [Graylog](https://github.com/gemnasium/logrus-graylog-hook) | Hook for logging to [Graylog](http://graylog2.org/) | -| [Raygun](https://github.com/squirkle/logrus-raygun-hook) | Hook for logging to [Raygun.io](http://raygun.io/) | -| [LFShook](https://github.com/rifflock/lfshook) | Hook for logging to the local filesystem | -| [Honeybadger](https://github.com/agonzalezro/logrus_honeybadger) | Hook for sending exceptions to Honeybadger | -| [Mail](https://github.com/zbindenren/logrus_mail) | Hook for sending exceptions via mail | -| [Rollrus](https://github.com/heroku/rollrus) | Hook for sending errors to rollbar | -| [Fluentd](https://github.com/evalphobia/logrus_fluent) | Hook for logging to fluentd | -| [Mongodb](https://github.com/weekface/mgorus) | Hook for logging to mongodb | - -#### 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` -* `logrus.JSONFormatter`. Logs fields as JSON. -* `logrus/formatters/logstash.LogstashFormatter`. Logs fields as [Logstash](http://logstash.net) Events. - - ```go - logrus.SetFormatter(&logstash.LogstashFormatter{Type: “application_name"}) - ``` - -Third party logging formatters: - -* [`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`. - -#### 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. - - -[godoc]: https://godoc.org/github.com/Sirupsen/logrus diff --git a/go/src/github.com/Sirupsen/logrus/entry_test.go b/go/src/github.com/Sirupsen/logrus/entry_test.go deleted file mode 100644 index 99c3b41d..00000000 --- a/go/src/github.com/Sirupsen/logrus/entry_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package logrus - -import ( - "bytes" - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestEntryWithError(t *testing.T) { - - assert := assert.New(t) - - defer func() { - ErrorKey = "error" - }() - - err := fmt.Errorf("kaboom at layer %d", 4711) - - assert.Equal(err, WithError(err).Data["error"]) - - logger := New() - logger.Out = &bytes.Buffer{} - entry := NewEntry(logger) - - assert.Equal(err, entry.WithError(err).Data["error"]) - - ErrorKey = "err" - - assert.Equal(err, entry.WithError(err).Data["err"]) - -} - -func TestEntryPanicln(t *testing.T) { - errBoom := fmt.Errorf("boom time") - - defer func() { - p := recover() - assert.NotNil(t, p) - - switch pVal := p.(type) { - case *Entry: - assert.Equal(t, "kaboom", pVal.Message) - assert.Equal(t, errBoom, pVal.Data["err"]) - default: - t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal) - } - }() - - logger := New() - logger.Out = &bytes.Buffer{} - entry := NewEntry(logger) - entry.WithField("err", errBoom).Panicln("kaboom") -} - -func TestEntryPanicf(t *testing.T) { - errBoom := fmt.Errorf("boom again") - - defer func() { - p := recover() - assert.NotNil(t, p) - - switch pVal := p.(type) { - case *Entry: - assert.Equal(t, "kaboom true", pVal.Message) - assert.Equal(t, errBoom, pVal.Data["err"]) - default: - t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal) - } - }() - - logger := New() - logger.Out = &bytes.Buffer{} - entry := NewEntry(logger) - entry.WithField("err", errBoom).Panicf("kaboom %v", true) -} diff --git a/go/src/github.com/Sirupsen/logrus/formatter_bench_test.go b/go/src/github.com/Sirupsen/logrus/formatter_bench_test.go deleted file mode 100644 index c6d290c7..00000000 --- a/go/src/github.com/Sirupsen/logrus/formatter_bench_test.go +++ /dev/null @@ -1,98 +0,0 @@ -package logrus - -import ( - "fmt" - "testing" - "time" -) - -// smallFields is a small size data set for benchmarking -var smallFields = Fields{ - "foo": "bar", - "baz": "qux", - "one": "two", - "three": "four", -} - -// largeFields is a large size data set for benchmarking -var largeFields = Fields{ - "foo": "bar", - "baz": "qux", - "one": "two", - "three": "four", - "five": "six", - "seven": "eight", - "nine": "ten", - "eleven": "twelve", - "thirteen": "fourteen", - "fifteen": "sixteen", - "seventeen": "eighteen", - "nineteen": "twenty", - "a": "b", - "c": "d", - "e": "f", - "g": "h", - "i": "j", - "k": "l", - "m": "n", - "o": "p", - "q": "r", - "s": "t", - "u": "v", - "w": "x", - "y": "z", - "this": "will", - "make": "thirty", - "entries": "yeah", -} - -var errorFields = Fields{ - "foo": fmt.Errorf("bar"), - "baz": fmt.Errorf("qux"), -} - -func BenchmarkErrorTextFormatter(b *testing.B) { - doBenchmark(b, &TextFormatter{DisableColors: true}, errorFields) -} - -func BenchmarkSmallTextFormatter(b *testing.B) { - doBenchmark(b, &TextFormatter{DisableColors: true}, smallFields) -} - -func BenchmarkLargeTextFormatter(b *testing.B) { - doBenchmark(b, &TextFormatter{DisableColors: true}, largeFields) -} - -func BenchmarkSmallColoredTextFormatter(b *testing.B) { - doBenchmark(b, &TextFormatter{ForceColors: true}, smallFields) -} - -func BenchmarkLargeColoredTextFormatter(b *testing.B) { - doBenchmark(b, &TextFormatter{ForceColors: true}, largeFields) -} - -func BenchmarkSmallJSONFormatter(b *testing.B) { - doBenchmark(b, &JSONFormatter{}, smallFields) -} - -func BenchmarkLargeJSONFormatter(b *testing.B) { - doBenchmark(b, &JSONFormatter{}, largeFields) -} - -func doBenchmark(b *testing.B, formatter Formatter, fields Fields) { - entry := &Entry{ - Time: time.Time{}, - Level: InfoLevel, - Message: "message", - Data: fields, - } - var d []byte - var err error - for i := 0; i < b.N; i++ { - d, err = formatter.Format(entry) - if err != nil { - b.Fatal(err) - } - b.SetBytes(int64(len(d))) - } -} diff --git a/go/src/github.com/Sirupsen/logrus/formatters/logstash/logstash_test.go b/go/src/github.com/Sirupsen/logrus/formatters/logstash/logstash_test.go deleted file mode 100644 index d8814a0e..00000000 --- a/go/src/github.com/Sirupsen/logrus/formatters/logstash/logstash_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package logstash - -import ( - "bytes" - "encoding/json" - "github.com/Sirupsen/logrus" - "github.com/stretchr/testify/assert" - "testing" -) - -func TestLogstashFormatter(t *testing.T) { - assert := assert.New(t) - - lf := LogstashFormatter{Type: "abc"} - - fields := logrus.Fields{ - "message": "def", - "level": "ijk", - "type": "lmn", - "one": 1, - "pi": 3.14, - "bool": true, - } - - entry := logrus.WithFields(fields) - entry.Message = "msg" - entry.Level = logrus.InfoLevel - - b, _ := lf.Format(entry) - - var data map[string]interface{} - dec := json.NewDecoder(bytes.NewReader(b)) - dec.UseNumber() - dec.Decode(&data) - - // base fields - assert.Equal(json.Number("1"), data["@version"]) - assert.NotEmpty(data["@timestamp"]) - assert.Equal("abc", data["type"]) - assert.Equal("msg", data["message"]) - assert.Equal("info", data["level"]) - - // substituted fields - assert.Equal("def", data["fields.message"]) - assert.Equal("ijk", data["fields.level"]) - assert.Equal("lmn", data["fields.type"]) - - // formats - assert.Equal(json.Number("1"), data["one"]) - assert.Equal(json.Number("3.14"), data["pi"]) - assert.Equal(true, data["bool"]) -} diff --git a/go/src/github.com/Sirupsen/logrus/hook_test.go b/go/src/github.com/Sirupsen/logrus/hook_test.go deleted file mode 100644 index 13f34cb6..00000000 --- a/go/src/github.com/Sirupsen/logrus/hook_test.go +++ /dev/null @@ -1,122 +0,0 @@ -package logrus - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -type TestHook struct { - Fired bool -} - -func (hook *TestHook) Fire(entry *Entry) error { - hook.Fired = true - return nil -} - -func (hook *TestHook) Levels() []Level { - return []Level{ - DebugLevel, - InfoLevel, - WarnLevel, - ErrorLevel, - FatalLevel, - PanicLevel, - } -} - -func TestHookFires(t *testing.T) { - hook := new(TestHook) - - LogAndAssertJSON(t, func(log *Logger) { - log.Hooks.Add(hook) - assert.Equal(t, hook.Fired, false) - - log.Print("test") - }, func(fields Fields) { - assert.Equal(t, hook.Fired, true) - }) -} - -type ModifyHook struct { -} - -func (hook *ModifyHook) Fire(entry *Entry) error { - entry.Data["wow"] = "whale" - return nil -} - -func (hook *ModifyHook) Levels() []Level { - return []Level{ - DebugLevel, - InfoLevel, - WarnLevel, - ErrorLevel, - FatalLevel, - PanicLevel, - } -} - -func TestHookCanModifyEntry(t *testing.T) { - hook := new(ModifyHook) - - LogAndAssertJSON(t, func(log *Logger) { - log.Hooks.Add(hook) - log.WithField("wow", "elephant").Print("test") - }, func(fields Fields) { - assert.Equal(t, fields["wow"], "whale") - }) -} - -func TestCanFireMultipleHooks(t *testing.T) { - hook1 := new(ModifyHook) - hook2 := new(TestHook) - - LogAndAssertJSON(t, func(log *Logger) { - log.Hooks.Add(hook1) - log.Hooks.Add(hook2) - - log.WithField("wow", "elephant").Print("test") - }, func(fields Fields) { - assert.Equal(t, fields["wow"], "whale") - assert.Equal(t, hook2.Fired, true) - }) -} - -type ErrorHook struct { - Fired bool -} - -func (hook *ErrorHook) Fire(entry *Entry) error { - hook.Fired = true - return nil -} - -func (hook *ErrorHook) Levels() []Level { - return []Level{ - ErrorLevel, - } -} - -func TestErrorHookShouldntFireOnInfo(t *testing.T) { - hook := new(ErrorHook) - - LogAndAssertJSON(t, func(log *Logger) { - log.Hooks.Add(hook) - log.Info("test") - }, func(fields Fields) { - assert.Equal(t, hook.Fired, false) - }) -} - -func TestErrorHookShouldFireOnError(t *testing.T) { - hook := new(ErrorHook) - - LogAndAssertJSON(t, func(log *Logger) { - log.Hooks.Add(hook) - log.Error("test") - }, func(fields Fields) { - assert.Equal(t, hook.Fired, true) - }) -} diff --git a/go/src/github.com/Sirupsen/logrus/hooks/bugsnag/bugsnag.go b/go/src/github.com/Sirupsen/logrus/hooks/bugsnag/bugsnag.go deleted file mode 100644 index d20a0f54..00000000 --- a/go/src/github.com/Sirupsen/logrus/hooks/bugsnag/bugsnag.go +++ /dev/null @@ -1,68 +0,0 @@ -package logrus_bugsnag - -import ( - "errors" - - "github.com/Sirupsen/logrus" - "github.com/bugsnag/bugsnag-go" -) - -type bugsnagHook struct{} - -// ErrBugsnagUnconfigured is returned if NewBugsnagHook is called before -// bugsnag.Configure. Bugsnag must be configured before the hook. -var ErrBugsnagUnconfigured = errors.New("bugsnag must be configured before installing this logrus hook") - -// ErrBugsnagSendFailed indicates that the hook failed to submit an error to -// bugsnag. The error was successfully generated, but `bugsnag.Notify()` -// failed. -type ErrBugsnagSendFailed struct { - err error -} - -func (e ErrBugsnagSendFailed) Error() string { - return "failed to send error to Bugsnag: " + e.err.Error() -} - -// NewBugsnagHook initializes a logrus hook which sends exceptions to an -// exception-tracking service compatible with the Bugsnag API. Before using -// this hook, you must call bugsnag.Configure(). The returned object should be -// registered with a log via `AddHook()` -// -// Entries that trigger an Error, Fatal or Panic should now include an "error" -// field to send to Bugsnag. -func NewBugsnagHook() (*bugsnagHook, error) { - if bugsnag.Config.APIKey == "" { - return nil, ErrBugsnagUnconfigured - } - return &bugsnagHook{}, nil -} - -// Fire forwards an error to Bugsnag. Given a logrus.Entry, it extracts the -// "error" field (or the Message if the error isn't present) and sends it off. -func (hook *bugsnagHook) Fire(entry *logrus.Entry) error { - var notifyErr error - err, ok := entry.Data["error"].(error) - if ok { - notifyErr = err - } else { - notifyErr = errors.New(entry.Message) - } - - bugsnagErr := bugsnag.Notify(notifyErr) - if bugsnagErr != nil { - return ErrBugsnagSendFailed{bugsnagErr} - } - - return nil -} - -// Levels enumerates the log levels on which the error should be forwarded to -// bugsnag: everything at or above the "Error" level. -func (hook *bugsnagHook) Levels() []logrus.Level { - return []logrus.Level{ - logrus.ErrorLevel, - logrus.FatalLevel, - logrus.PanicLevel, - } -} diff --git a/go/src/github.com/Sirupsen/logrus/hooks/bugsnag/bugsnag_test.go b/go/src/github.com/Sirupsen/logrus/hooks/bugsnag/bugsnag_test.go deleted file mode 100644 index e9ea298d..00000000 --- a/go/src/github.com/Sirupsen/logrus/hooks/bugsnag/bugsnag_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package logrus_bugsnag - -import ( - "encoding/json" - "errors" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/Sirupsen/logrus" - "github.com/bugsnag/bugsnag-go" -) - -type notice struct { - Events []struct { - Exceptions []struct { - Message string `json:"message"` - } `json:"exceptions"` - } `json:"events"` -} - -func TestNoticeReceived(t *testing.T) { - msg := make(chan string, 1) - expectedMsg := "foo" - - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var notice notice - data, _ := ioutil.ReadAll(r.Body) - if err := json.Unmarshal(data, ¬ice); err != nil { - t.Error(err) - } - _ = r.Body.Close() - - msg <- notice.Events[0].Exceptions[0].Message - })) - defer ts.Close() - - hook := &bugsnagHook{} - - bugsnag.Configure(bugsnag.Configuration{ - Endpoint: ts.URL, - ReleaseStage: "production", - APIKey: "12345678901234567890123456789012", - Synchronous: true, - }) - - log := logrus.New() - log.Hooks.Add(hook) - - log.WithFields(logrus.Fields{ - "error": errors.New(expectedMsg), - }).Error("Bugsnag will not see this string") - - select { - case received := <-msg: - if received != expectedMsg { - t.Errorf("Unexpected message received: %s", received) - } - case <-time.After(time.Second): - t.Error("Timed out; no notice received by Bugsnag API") - } -} diff --git a/go/src/github.com/Sirupsen/logrus/hooks/papertrail/README.md b/go/src/github.com/Sirupsen/logrus/hooks/papertrail/README.md deleted file mode 100644 index ae61e922..00000000 --- a/go/src/github.com/Sirupsen/logrus/hooks/papertrail/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# Papertrail Hook for Logrus :walrus: - -[Papertrail](https://papertrailapp.com) provides hosted log management. Once stored in Papertrail, you can [group](http://help.papertrailapp.com/kb/how-it-works/groups/) your logs on various dimensions, [search](http://help.papertrailapp.com/kb/how-it-works/search-syntax) them, and trigger [alerts](http://help.papertrailapp.com/kb/how-it-works/alerts). - -In most deployments, you'll want to send logs to Papertrail via their [remote_syslog](http://help.papertrailapp.com/kb/configuration/configuring-centralized-logging-from-text-log-files-in-unix/) daemon, which requires no application-specific configuration. This hook is intended for relatively low-volume logging, likely in managed cloud hosting deployments where installing `remote_syslog` is not possible. - -## Usage - -You can find your Papertrail UDP port on your [Papertrail account page](https://papertrailapp.com/account/destinations). Substitute it below for `YOUR_PAPERTRAIL_UDP_PORT`. - -For `YOUR_APP_NAME`, substitute a short string that will readily identify your application or service in the logs. - -```go -import ( - "log/syslog" - "github.com/Sirupsen/logrus" - "github.com/Sirupsen/logrus/hooks/papertrail" -) - -func main() { - log := logrus.New() - hook, err := logrus_papertrail.NewPapertrailHook("logs.papertrailapp.com", YOUR_PAPERTRAIL_UDP_PORT, YOUR_APP_NAME) - - if err == nil { - log.Hooks.Add(hook) - } -} -``` diff --git a/go/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail.go b/go/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail.go deleted file mode 100644 index c0f10c1b..00000000 --- a/go/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail.go +++ /dev/null @@ -1,55 +0,0 @@ -package logrus_papertrail - -import ( - "fmt" - "net" - "os" - "time" - - "github.com/Sirupsen/logrus" -) - -const ( - format = "Jan 2 15:04:05" -) - -// PapertrailHook to send logs to a logging service compatible with the Papertrail API. -type PapertrailHook struct { - Host string - Port int - AppName string - UDPConn net.Conn -} - -// NewPapertrailHook creates a hook to be added to an instance of logger. -func NewPapertrailHook(host string, port int, appName string) (*PapertrailHook, error) { - conn, err := net.Dial("udp", fmt.Sprintf("%s:%d", host, port)) - return &PapertrailHook{host, port, appName, conn}, err -} - -// Fire is called when a log event is fired. -func (hook *PapertrailHook) Fire(entry *logrus.Entry) error { - date := time.Now().Format(format) - msg, _ := entry.String() - payload := fmt.Sprintf("<22> %s %s: %s", date, hook.AppName, msg) - - bytesWritten, err := hook.UDPConn.Write([]byte(payload)) - if err != nil { - fmt.Fprintf(os.Stderr, "Unable to send log line to Papertrail via UDP. Wrote %d bytes before error: %v", bytesWritten, err) - return err - } - - return nil -} - -// Levels returns the available logging levels. -func (hook *PapertrailHook) Levels() []logrus.Level { - return []logrus.Level{ - logrus.PanicLevel, - logrus.FatalLevel, - logrus.ErrorLevel, - logrus.WarnLevel, - logrus.InfoLevel, - logrus.DebugLevel, - } -} diff --git a/go/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail_test.go b/go/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail_test.go deleted file mode 100644 index 96318d00..00000000 --- a/go/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package logrus_papertrail - -import ( - "fmt" - "testing" - - "github.com/Sirupsen/logrus" - "github.com/stvp/go-udp-testing" -) - -func TestWritingToUDP(t *testing.T) { - port := 16661 - udp.SetAddr(fmt.Sprintf(":%d", port)) - - hook, err := NewPapertrailHook("localhost", port, "test") - if err != nil { - t.Errorf("Unable to connect to local UDP server.") - } - - log := logrus.New() - log.Hooks.Add(hook) - - udp.ShouldReceive(t, "foo", func() { - log.Info("foo") - }) -} diff --git a/go/src/github.com/Sirupsen/logrus/hooks/syslog/README.md b/go/src/github.com/Sirupsen/logrus/hooks/syslog/README.md deleted file mode 100644 index 066704b3..00000000 --- a/go/src/github.com/Sirupsen/logrus/hooks/syslog/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# Syslog Hooks for Logrus :walrus: - -## Usage - -```go -import ( - "log/syslog" - "github.com/Sirupsen/logrus" - logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog" -) - -func main() { - log := logrus.New() - hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") - - if err == nil { - log.Hooks.Add(hook) - } -} -``` - -If you want to connect to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). Just assign empty string to the first two parameters of `NewSyslogHook`. It should look like the following. - -```go -import ( - "log/syslog" - "github.com/Sirupsen/logrus" - logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog" -) - -func main() { - log := logrus.New() - hook, err := logrus_syslog.NewSyslogHook("", "", syslog.LOG_INFO, "") - - if err == nil { - log.Hooks.Add(hook) - } -} -``` \ No newline at end of file diff --git a/go/src/github.com/Sirupsen/logrus/hooks/syslog/syslog_test.go b/go/src/github.com/Sirupsen/logrus/hooks/syslog/syslog_test.go deleted file mode 100644 index 42762dc1..00000000 --- a/go/src/github.com/Sirupsen/logrus/hooks/syslog/syslog_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package logrus_syslog - -import ( - "github.com/Sirupsen/logrus" - "log/syslog" - "testing" -) - -func TestLocalhostAddAndPrint(t *testing.T) { - log := logrus.New() - hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") - - if err != nil { - t.Errorf("Unable to connect to local syslog.") - } - - log.Hooks.Add(hook) - - for _, level := range hook.Levels() { - if len(log.Hooks[level]) != 1 { - t.Errorf("SyslogHook was not added. The length of log.Hooks[%v]: %v", level, len(log.Hooks[level])) - } - } - - log.Info("Congratulations!") -} diff --git a/go/src/github.com/Sirupsen/logrus/json_formatter_test.go b/go/src/github.com/Sirupsen/logrus/json_formatter_test.go deleted file mode 100644 index 1d708732..00000000 --- a/go/src/github.com/Sirupsen/logrus/json_formatter_test.go +++ /dev/null @@ -1,120 +0,0 @@ -package logrus - -import ( - "encoding/json" - "errors" - - "testing" -) - -func TestErrorNotLost(t *testing.T) { - formatter := &JSONFormatter{} - - b, err := formatter.Format(WithField("error", errors.New("wild walrus"))) - if err != nil { - t.Fatal("Unable to format entry: ", err) - } - - entry := make(map[string]interface{}) - err = json.Unmarshal(b, &entry) - if err != nil { - t.Fatal("Unable to unmarshal formatted entry: ", err) - } - - if entry["error"] != "wild walrus" { - t.Fatal("Error field not set") - } -} - -func TestErrorNotLostOnFieldNotNamedError(t *testing.T) { - formatter := &JSONFormatter{} - - b, err := formatter.Format(WithField("omg", errors.New("wild walrus"))) - if err != nil { - t.Fatal("Unable to format entry: ", err) - } - - entry := make(map[string]interface{}) - err = json.Unmarshal(b, &entry) - if err != nil { - t.Fatal("Unable to unmarshal formatted entry: ", err) - } - - if entry["omg"] != "wild walrus" { - t.Fatal("Error field not set") - } -} - -func TestFieldClashWithTime(t *testing.T) { - formatter := &JSONFormatter{} - - b, err := formatter.Format(WithField("time", "right now!")) - if err != nil { - t.Fatal("Unable to format entry: ", err) - } - - entry := make(map[string]interface{}) - err = json.Unmarshal(b, &entry) - if err != nil { - t.Fatal("Unable to unmarshal formatted entry: ", err) - } - - if entry["fields.time"] != "right now!" { - t.Fatal("fields.time not set to original time field") - } - - if entry["time"] != "0001-01-01T00:00:00Z" { - t.Fatal("time field not set to current time, was: ", entry["time"]) - } -} - -func TestFieldClashWithMsg(t *testing.T) { - formatter := &JSONFormatter{} - - b, err := formatter.Format(WithField("msg", "something")) - if err != nil { - t.Fatal("Unable to format entry: ", err) - } - - entry := make(map[string]interface{}) - err = json.Unmarshal(b, &entry) - if err != nil { - t.Fatal("Unable to unmarshal formatted entry: ", err) - } - - if entry["fields.msg"] != "something" { - t.Fatal("fields.msg not set to original msg field") - } -} - -func TestFieldClashWithLevel(t *testing.T) { - formatter := &JSONFormatter{} - - b, err := formatter.Format(WithField("level", "something")) - if err != nil { - t.Fatal("Unable to format entry: ", err) - } - - entry := make(map[string]interface{}) - err = json.Unmarshal(b, &entry) - if err != nil { - t.Fatal("Unable to unmarshal formatted entry: ", err) - } - - if entry["fields.level"] != "something" { - t.Fatal("fields.level not set to original level field") - } -} - -func TestJSONEntryEndsWithNewline(t *testing.T) { - formatter := &JSONFormatter{} - - b, err := formatter.Format(WithField("level", "something")) - if err != nil { - t.Fatal("Unable to format entry: ", err) - } - - if b[len(b)-1] != '\n' { - t.Fatal("Expected JSON log entry to end with a newline") - } -} diff --git a/go/src/github.com/Sirupsen/logrus/logrus_test.go b/go/src/github.com/Sirupsen/logrus/logrus_test.go deleted file mode 100644 index efaacea2..00000000 --- a/go/src/github.com/Sirupsen/logrus/logrus_test.go +++ /dev/null @@ -1,301 +0,0 @@ -package logrus - -import ( - "bytes" - "encoding/json" - "strconv" - "strings" - "sync" - "testing" - - "github.com/stretchr/testify/assert" -) - -func LogAndAssertJSON(t *testing.T, log func(*Logger), assertions func(fields Fields)) { - var buffer bytes.Buffer - var fields Fields - - logger := New() - logger.Out = &buffer - logger.Formatter = new(JSONFormatter) - - log(logger) - - err := json.Unmarshal(buffer.Bytes(), &fields) - assert.Nil(t, err) - - assertions(fields) -} - -func LogAndAssertText(t *testing.T, log func(*Logger), assertions func(fields map[string]string)) { - var buffer bytes.Buffer - - logger := New() - logger.Out = &buffer - logger.Formatter = &TextFormatter{ - DisableColors: true, - } - - log(logger) - - fields := make(map[string]string) - for _, kv := range strings.Split(buffer.String(), " ") { - if !strings.Contains(kv, "=") { - continue - } - kvArr := strings.Split(kv, "=") - key := strings.TrimSpace(kvArr[0]) - val := kvArr[1] - if kvArr[1][0] == '"' { - var err error - val, err = strconv.Unquote(val) - assert.NoError(t, err) - } - fields[key] = val - } - assertions(fields) -} - -func TestPrint(t *testing.T) { - LogAndAssertJSON(t, func(log *Logger) { - log.Print("test") - }, func(fields Fields) { - assert.Equal(t, fields["msg"], "test") - assert.Equal(t, fields["level"], "info") - }) -} - -func TestInfo(t *testing.T) { - LogAndAssertJSON(t, func(log *Logger) { - log.Info("test") - }, func(fields Fields) { - assert.Equal(t, fields["msg"], "test") - assert.Equal(t, fields["level"], "info") - }) -} - -func TestWarn(t *testing.T) { - LogAndAssertJSON(t, func(log *Logger) { - log.Warn("test") - }, func(fields Fields) { - assert.Equal(t, fields["msg"], "test") - assert.Equal(t, fields["level"], "warning") - }) -} - -func TestInfolnShouldAddSpacesBetweenStrings(t *testing.T) { - LogAndAssertJSON(t, func(log *Logger) { - log.Infoln("test", "test") - }, func(fields Fields) { - assert.Equal(t, fields["msg"], "test test") - }) -} - -func TestInfolnShouldAddSpacesBetweenStringAndNonstring(t *testing.T) { - LogAndAssertJSON(t, func(log *Logger) { - log.Infoln("test", 10) - }, func(fields Fields) { - assert.Equal(t, fields["msg"], "test 10") - }) -} - -func TestInfolnShouldAddSpacesBetweenTwoNonStrings(t *testing.T) { - LogAndAssertJSON(t, func(log *Logger) { - log.Infoln(10, 10) - }, func(fields Fields) { - assert.Equal(t, fields["msg"], "10 10") - }) -} - -func TestInfoShouldAddSpacesBetweenTwoNonStrings(t *testing.T) { - LogAndAssertJSON(t, func(log *Logger) { - log.Infoln(10, 10) - }, func(fields Fields) { - assert.Equal(t, fields["msg"], "10 10") - }) -} - -func TestInfoShouldNotAddSpacesBetweenStringAndNonstring(t *testing.T) { - LogAndAssertJSON(t, func(log *Logger) { - log.Info("test", 10) - }, func(fields Fields) { - assert.Equal(t, fields["msg"], "test10") - }) -} - -func TestInfoShouldNotAddSpacesBetweenStrings(t *testing.T) { - LogAndAssertJSON(t, func(log *Logger) { - log.Info("test", "test") - }, func(fields Fields) { - assert.Equal(t, fields["msg"], "testtest") - }) -} - -func TestWithFieldsShouldAllowAssignments(t *testing.T) { - var buffer bytes.Buffer - var fields Fields - - logger := New() - logger.Out = &buffer - logger.Formatter = new(JSONFormatter) - - localLog := logger.WithFields(Fields{ - "key1": "value1", - }) - - localLog.WithField("key2", "value2").Info("test") - err := json.Unmarshal(buffer.Bytes(), &fields) - assert.Nil(t, err) - - assert.Equal(t, "value2", fields["key2"]) - assert.Equal(t, "value1", fields["key1"]) - - buffer = bytes.Buffer{} - fields = Fields{} - localLog.Info("test") - err = json.Unmarshal(buffer.Bytes(), &fields) - assert.Nil(t, err) - - _, ok := fields["key2"] - assert.Equal(t, false, ok) - assert.Equal(t, "value1", fields["key1"]) -} - -func TestUserSuppliedFieldDoesNotOverwriteDefaults(t *testing.T) { - LogAndAssertJSON(t, func(log *Logger) { - log.WithField("msg", "hello").Info("test") - }, func(fields Fields) { - assert.Equal(t, fields["msg"], "test") - }) -} - -func TestUserSuppliedMsgFieldHasPrefix(t *testing.T) { - LogAndAssertJSON(t, func(log *Logger) { - log.WithField("msg", "hello").Info("test") - }, func(fields Fields) { - assert.Equal(t, fields["msg"], "test") - assert.Equal(t, fields["fields.msg"], "hello") - }) -} - -func TestUserSuppliedTimeFieldHasPrefix(t *testing.T) { - LogAndAssertJSON(t, func(log *Logger) { - log.WithField("time", "hello").Info("test") - }, func(fields Fields) { - assert.Equal(t, fields["fields.time"], "hello") - }) -} - -func TestUserSuppliedLevelFieldHasPrefix(t *testing.T) { - LogAndAssertJSON(t, func(log *Logger) { - log.WithField("level", 1).Info("test") - }, func(fields Fields) { - assert.Equal(t, fields["level"], "info") - assert.Equal(t, fields["fields.level"], 1.0) // JSON has floats only - }) -} - -func TestDefaultFieldsAreNotPrefixed(t *testing.T) { - LogAndAssertText(t, func(log *Logger) { - ll := log.WithField("herp", "derp") - ll.Info("hello") - ll.Info("bye") - }, func(fields map[string]string) { - for _, fieldName := range []string{"fields.level", "fields.time", "fields.msg"} { - if _, ok := fields[fieldName]; ok { - t.Fatalf("should not have prefixed %q: %v", fieldName, fields) - } - } - }) -} - -func TestDoubleLoggingDoesntPrefixPreviousFields(t *testing.T) { - - var buffer bytes.Buffer - var fields Fields - - logger := New() - logger.Out = &buffer - logger.Formatter = new(JSONFormatter) - - llog := logger.WithField("context", "eating raw fish") - - llog.Info("looks delicious") - - err := json.Unmarshal(buffer.Bytes(), &fields) - assert.NoError(t, err, "should have decoded first message") - assert.Equal(t, len(fields), 4, "should only have msg/time/level/context fields") - assert.Equal(t, fields["msg"], "looks delicious") - assert.Equal(t, fields["context"], "eating raw fish") - - buffer.Reset() - - llog.Warn("omg it is!") - - err = json.Unmarshal(buffer.Bytes(), &fields) - assert.NoError(t, err, "should have decoded second message") - assert.Equal(t, len(fields), 4, "should only have msg/time/level/context fields") - assert.Equal(t, fields["msg"], "omg it is!") - assert.Equal(t, fields["context"], "eating raw fish") - assert.Nil(t, fields["fields.msg"], "should not have prefixed previous `msg` entry") - -} - -func TestConvertLevelToString(t *testing.T) { - assert.Equal(t, "debug", DebugLevel.String()) - assert.Equal(t, "info", InfoLevel.String()) - assert.Equal(t, "warning", WarnLevel.String()) - assert.Equal(t, "error", ErrorLevel.String()) - assert.Equal(t, "fatal", FatalLevel.String()) - assert.Equal(t, "panic", PanicLevel.String()) -} - -func TestParseLevel(t *testing.T) { - l, err := ParseLevel("panic") - assert.Nil(t, err) - assert.Equal(t, PanicLevel, l) - - l, err = ParseLevel("fatal") - assert.Nil(t, err) - assert.Equal(t, FatalLevel, l) - - l, err = ParseLevel("error") - assert.Nil(t, err) - assert.Equal(t, ErrorLevel, l) - - l, err = ParseLevel("warn") - assert.Nil(t, err) - assert.Equal(t, WarnLevel, l) - - l, err = ParseLevel("warning") - assert.Nil(t, err) - assert.Equal(t, WarnLevel, l) - - l, err = ParseLevel("info") - assert.Nil(t, err) - assert.Equal(t, InfoLevel, l) - - l, err = ParseLevel("debug") - assert.Nil(t, err) - assert.Equal(t, DebugLevel, l) - - l, err = ParseLevel("invalid") - assert.Equal(t, "not a valid logrus Level: \"invalid\"", err.Error()) -} - -func TestGetSetLevelRace(t *testing.T) { - wg := sync.WaitGroup{} - for i := 0; i < 100; i++ { - wg.Add(1) - go func(i int) { - defer wg.Done() - if i%2 == 0 { - SetLevel(InfoLevel) - } else { - GetLevel() - } - }(i) - - } - wg.Wait() -} diff --git a/go/src/github.com/Sirupsen/logrus/text_formatter_test.go b/go/src/github.com/Sirupsen/logrus/text_formatter_test.go deleted file mode 100644 index e25a44f6..00000000 --- a/go/src/github.com/Sirupsen/logrus/text_formatter_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package logrus - -import ( - "bytes" - "errors" - "testing" - "time" -) - -func TestQuoting(t *testing.T) { - tf := &TextFormatter{DisableColors: true} - - checkQuoting := func(q bool, value interface{}) { - b, _ := tf.Format(WithField("test", value)) - idx := bytes.Index(b, ([]byte)("test=")) - cont := bytes.Contains(b[idx+5:], []byte{'"'}) - if cont != q { - if q { - t.Errorf("quoting expected for: %#v", value) - } else { - t.Errorf("quoting not expected for: %#v", value) - } - } - } - - checkQuoting(false, "abcd") - checkQuoting(false, "v1.0") - checkQuoting(false, "1234567890") - checkQuoting(true, "/foobar") - checkQuoting(true, "x y") - checkQuoting(true, "x,y") - checkQuoting(false, errors.New("invalid")) - checkQuoting(true, errors.New("invalid argument")) -} - -func TestTimestampFormat(t *testing.T) { - checkTimeStr := func(format string) { - customFormatter := &TextFormatter{DisableColors: true, TimestampFormat: format} - customStr, _ := customFormatter.Format(WithField("test", "test")) - timeStart := bytes.Index(customStr, ([]byte)("time=")) - timeEnd := bytes.Index(customStr, ([]byte)("level=")) - timeStr := customStr[timeStart+5 : timeEnd-1] - if timeStr[0] == '"' && timeStr[len(timeStr)-1] == '"' { - timeStr = timeStr[1 : len(timeStr)-1] - } - if format == "" { - format = time.RFC3339 - } - _, e := time.Parse(format, (string)(timeStr)) - if e != nil { - t.Errorf("time string \"%s\" did not match provided time format \"%s\": %s", timeStr, format, e) - } - } - - checkTimeStr("2006-01-02T15:04:05.000000000Z07:00") - checkTimeStr("Mon Jan _2 15:04:05 2006") - checkTimeStr("") -} - -// TODO add tests for sorting etc., this requires a parser for the text -// formatter output. diff --git a/go/src/github.com/Sirupsen/logrus/writer.go b/go/src/github.com/Sirupsen/logrus/writer.go deleted file mode 100644 index 1e30b1c7..00000000 --- a/go/src/github.com/Sirupsen/logrus/writer.go +++ /dev/null @@ -1,31 +0,0 @@ -package logrus - -import ( - "bufio" - "io" - "runtime" -) - -func (logger *Logger) Writer() *io.PipeWriter { - reader, writer := io.Pipe() - - go logger.writerScanner(reader) - runtime.SetFinalizer(writer, writerFinalizer) - - return writer -} - -func (logger *Logger) writerScanner(reader *io.PipeReader) { - scanner := bufio.NewScanner(reader) - for scanner.Scan() { - logger.Print(scanner.Text()) - } - if err := scanner.Err(); err != nil { - logger.Errorf("Error while reading from Writer: %s", err) - } - reader.Close() -} - -func writerFinalizer(writer *io.PipeWriter) { - writer.Close() -} diff --git a/go/src/github.com/docker/docker/pkg/units/duration_test.go b/go/src/github.com/docker/docker/pkg/units/duration_test.go deleted file mode 100755 index fcfb6b7b..00000000 --- a/go/src/github.com/docker/docker/pkg/units/duration_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package units - -import ( - "testing" - "time" -) - -func TestHumanDuration(t *testing.T) { - // Useful duration abstractions - day := 24 * time.Hour - week := 7 * day - month := 30 * day - year := 365 * day - - assertEquals(t, "Less than a second", HumanDuration(450*time.Millisecond)) - assertEquals(t, "47 seconds", HumanDuration(47*time.Second)) - assertEquals(t, "About a minute", HumanDuration(1*time.Minute)) - assertEquals(t, "3 minutes", HumanDuration(3*time.Minute)) - assertEquals(t, "35 minutes", HumanDuration(35*time.Minute)) - assertEquals(t, "35 minutes", HumanDuration(35*time.Minute+40*time.Second)) - assertEquals(t, "About an hour", HumanDuration(1*time.Hour)) - assertEquals(t, "About an hour", HumanDuration(1*time.Hour+45*time.Minute)) - assertEquals(t, "3 hours", HumanDuration(3*time.Hour)) - assertEquals(t, "3 hours", HumanDuration(3*time.Hour+59*time.Minute)) - assertEquals(t, "4 hours", HumanDuration(3*time.Hour+60*time.Minute)) - assertEquals(t, "24 hours", HumanDuration(24*time.Hour)) - assertEquals(t, "36 hours", HumanDuration(1*day+12*time.Hour)) - assertEquals(t, "2 days", HumanDuration(2*day)) - assertEquals(t, "7 days", HumanDuration(7*day)) - assertEquals(t, "13 days", HumanDuration(13*day+5*time.Hour)) - assertEquals(t, "2 weeks", HumanDuration(2*week)) - assertEquals(t, "2 weeks", HumanDuration(2*week+4*day)) - assertEquals(t, "3 weeks", HumanDuration(3*week)) - assertEquals(t, "4 weeks", HumanDuration(4*week)) - assertEquals(t, "4 weeks", HumanDuration(4*week+3*day)) - assertEquals(t, "4 weeks", HumanDuration(1*month)) - assertEquals(t, "6 weeks", HumanDuration(1*month+2*week)) - assertEquals(t, "8 weeks", HumanDuration(2*month)) - assertEquals(t, "3 months", HumanDuration(3*month+1*week)) - assertEquals(t, "5 months", HumanDuration(5*month+2*week)) - assertEquals(t, "13 months", HumanDuration(13*month)) - assertEquals(t, "23 months", HumanDuration(23*month)) - assertEquals(t, "24 months", HumanDuration(24*month)) - assertEquals(t, "2 years", HumanDuration(24*month+2*week)) - assertEquals(t, "3 years", HumanDuration(3*year+2*month)) -} diff --git a/go/src/github.com/docker/docker/pkg/units/size_test.go b/go/src/github.com/docker/docker/pkg/units/size_test.go deleted file mode 100755 index 67c3b81e..00000000 --- a/go/src/github.com/docker/docker/pkg/units/size_test.go +++ /dev/null @@ -1,108 +0,0 @@ -package units - -import ( - "reflect" - "runtime" - "strings" - "testing" -) - -func TestBytesSize(t *testing.T) { - assertEquals(t, "1 KiB", BytesSize(1024)) - assertEquals(t, "1 MiB", BytesSize(1024*1024)) - assertEquals(t, "1 MiB", BytesSize(1048576)) - assertEquals(t, "2 MiB", BytesSize(2*MiB)) - assertEquals(t, "3.42 GiB", BytesSize(3.42*GiB)) - assertEquals(t, "5.372 TiB", BytesSize(5.372*TiB)) - assertEquals(t, "2.22 PiB", BytesSize(2.22*PiB)) -} - -func TestHumanSize(t *testing.T) { - assertEquals(t, "1 kB", HumanSize(1000)) - assertEquals(t, "1.024 kB", HumanSize(1024)) - assertEquals(t, "1 MB", HumanSize(1000000)) - assertEquals(t, "1.049 MB", HumanSize(1048576)) - assertEquals(t, "2 MB", HumanSize(2*MB)) - assertEquals(t, "3.42 GB", HumanSize(float64(3.42*GB))) - assertEquals(t, "5.372 TB", HumanSize(float64(5.372*TB))) - assertEquals(t, "2.22 PB", HumanSize(float64(2.22*PB))) -} - -func TestFromHumanSize(t *testing.T) { - assertSuccessEquals(t, 32, FromHumanSize, "32") - assertSuccessEquals(t, 32, FromHumanSize, "32b") - assertSuccessEquals(t, 32, FromHumanSize, "32B") - assertSuccessEquals(t, 32*KB, FromHumanSize, "32k") - assertSuccessEquals(t, 32*KB, FromHumanSize, "32K") - assertSuccessEquals(t, 32*KB, FromHumanSize, "32kb") - assertSuccessEquals(t, 32*KB, FromHumanSize, "32Kb") - assertSuccessEquals(t, 32*MB, FromHumanSize, "32Mb") - assertSuccessEquals(t, 32*GB, FromHumanSize, "32Gb") - assertSuccessEquals(t, 32*TB, FromHumanSize, "32Tb") - assertSuccessEquals(t, 32*PB, FromHumanSize, "32Pb") - - assertError(t, FromHumanSize, "") - assertError(t, FromHumanSize, "hello") - assertError(t, FromHumanSize, "-32") - assertError(t, FromHumanSize, "32.3") - assertError(t, FromHumanSize, " 32 ") - assertError(t, FromHumanSize, "32.3Kb") - assertError(t, FromHumanSize, "32 mb") - assertError(t, FromHumanSize, "32m b") - assertError(t, FromHumanSize, "32bm") -} - -func TestRAMInBytes(t *testing.T) { - assertSuccessEquals(t, 32, RAMInBytes, "32") - assertSuccessEquals(t, 32, RAMInBytes, "32b") - assertSuccessEquals(t, 32, RAMInBytes, "32B") - assertSuccessEquals(t, 32*KiB, RAMInBytes, "32k") - assertSuccessEquals(t, 32*KiB, RAMInBytes, "32K") - assertSuccessEquals(t, 32*KiB, RAMInBytes, "32kb") - assertSuccessEquals(t, 32*KiB, RAMInBytes, "32Kb") - assertSuccessEquals(t, 32*MiB, RAMInBytes, "32Mb") - assertSuccessEquals(t, 32*GiB, RAMInBytes, "32Gb") - assertSuccessEquals(t, 32*TiB, RAMInBytes, "32Tb") - assertSuccessEquals(t, 32*PiB, RAMInBytes, "32Pb") - assertSuccessEquals(t, 32*PiB, RAMInBytes, "32PB") - assertSuccessEquals(t, 32*PiB, RAMInBytes, "32P") - - assertError(t, RAMInBytes, "") - assertError(t, RAMInBytes, "hello") - assertError(t, RAMInBytes, "-32") - assertError(t, RAMInBytes, "32.3") - assertError(t, RAMInBytes, " 32 ") - assertError(t, RAMInBytes, "32.3Kb") - assertError(t, RAMInBytes, "32 mb") - assertError(t, RAMInBytes, "32m b") - assertError(t, RAMInBytes, "32bm") -} - -func assertEquals(t *testing.T, expected, actual interface{}) { - if expected != actual { - t.Errorf("Expected '%v' but got '%v'", expected, actual) - } -} - -// func that maps to the parse function signatures as testing abstraction -type parseFn func(string) (int64, error) - -// Define 'String()' for pretty-print -func (fn parseFn) String() string { - fnName := runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name() - return fnName[strings.LastIndex(fnName, ".")+1:] -} - -func assertSuccessEquals(t *testing.T, expected int64, fn parseFn, arg string) { - res, err := fn(arg) - if err != nil || res != expected { - t.Errorf("%s(\"%s\") -> expected '%d' but got '%d' with error '%v'", fn, arg, expected, res, err) - } -} - -func assertError(t *testing.T, fn parseFn, arg string) { - res, err := fn(arg) - if err == nil && res != -1 { - t.Errorf("%s(\"%s\") -> expected error but got '%d'", fn, arg, res) - } -} diff --git a/go/src/github.com/samalba/dockerclient/.gitignore b/go/src/github.com/samalba/dockerclient/.gitignore deleted file mode 100755 index 00268614..00000000 --- a/go/src/github.com/samalba/dockerclient/.gitignore +++ /dev/null @@ -1,22 +0,0 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so - -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - -*.exe diff --git a/go/src/github.com/samalba/dockerclient/README.md b/go/src/github.com/samalba/dockerclient/README.md deleted file mode 100755 index 5a5027b8..00000000 --- a/go/src/github.com/samalba/dockerclient/README.md +++ /dev/null @@ -1,98 +0,0 @@ -Docker client library in Go -=========================== -[![GoDoc](http://godoc.org/github.com/samalba/dockerclient?status.png)](http://godoc.org/github.com/samalba/dockerclient) - -Well maintained docker client library. - -# How to use it? - -Here is an example showing how to use it: - -```go -package main - -import ( - "github.com/samalba/dockerclient" - "log" - "time" - "os" -) - -// Callback used to listen to Docker's events -func eventCallback(event *dockerclient.Event, ec chan error, args ...interface{}) { - log.Printf("Received event: %#v\n", *event) -} - -func main() { - // Init the client - docker, _ := dockerclient.NewDockerClient("unix:///var/run/docker.sock", nil) - - // Get only running containers - containers, err := docker.ListContainers(false, false, "") - if err != nil { - log.Fatal(err) - } - for _, c := range containers { - log.Println(c.Id, c.Names) - } - - // Inspect the first container returned - if len(containers) > 0 { - id := containers[0].Id - info, _ := docker.InspectContainer(id) - log.Println(info) - } - - // Build a docker image - // some.tar contains the build context (Dockerfile any any files it needs to add/copy) - dockerBuildContext, err := os.Open("some.tar") - defer dockerBuildContext.Close() - buildImageConfig := &dockerclient.BuildImage{ - Context: dockerBuildContext, - RepoName: "your_image_name", - SuppressOutput: false, - } - reader, err := docker.BuildImage(buildImageConfig) - if err != nil { - log.Fatal(err) - } - - // Create a container - containerConfig := &dockerclient.ContainerConfig{ - Image: "ubuntu:14.04", - Cmd: []string{"bash"}, - AttachStdin: true, - Tty: true} - containerId, err := docker.CreateContainer(containerConfig, "foobar") - if err != nil { - log.Fatal(err) - } - - // Start the container - hostConfig := &dockerclient.HostConfig{} - err = docker.StartContainer(containerId, hostConfig) - if err != nil { - log.Fatal(err) - } - - // Stop the container (with 5 seconds timeout) - docker.StopContainer(containerId, 5) - - // Listen to events - docker.StartMonitorEvents(eventCallback, nil) - - // Hold the execution to look at the events coming - time.Sleep(3600 * time.Second) -} -``` - -# Maintainers - -List of people you can ping for feedback on Pull Requests or any questions. - -- [Sam Alba](https://github.com/samalba) -- [Michael Crosby](https://github.com/crosbymichael) -- [Andrea Luzzardi](https://github.com/aluzzardi) -- [Victor Vieux](https://github.com/vieux) -- [Evan Hazlett](https://github.com/ehazlett) -- [Donald Huang](https://github.com/donhcd) diff --git a/go/src/github.com/samalba/dockerclient/auth_test.go b/go/src/github.com/samalba/dockerclient/auth_test.go deleted file mode 100755 index 99801b22..00000000 --- a/go/src/github.com/samalba/dockerclient/auth_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package dockerclient - -import ( - "testing" -) - -func TestAuthEncode(t *testing.T) { - a := AuthConfig{Username: "foo", Password: "password", Email: "bar@baz.com"} - expected := "eyJ1c2VybmFtZSI6ImZvbyIsInBhc3N3b3JkIjoicGFzc3dvcmQiLCJlbWFpbCI6ImJhckBiYXouY29tIn0K" - got, _ := a.encode() - - if expected != got { - t.Errorf("testAuthEncode failed. Expected [%s] got [%s]", expected, got) - } -} diff --git a/go/src/github.com/samalba/dockerclient/dockerclient_test.go b/go/src/github.com/samalba/dockerclient/dockerclient_test.go deleted file mode 100755 index afc1b071..00000000 --- a/go/src/github.com/samalba/dockerclient/dockerclient_test.go +++ /dev/null @@ -1,241 +0,0 @@ -package dockerclient - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "reflect" - "strings" - "testing" - "time" - - "github.com/docker/docker/pkg/stdcopy" -) - -func assertEqual(t *testing.T, a interface{}, b interface{}, message string) { - if a == b { - return - } - if len(message) == 0 { - message = fmt.Sprintf("%v != %v", a, b) - } - t.Fatal(message) -} - -func testDockerClient(t *testing.T) *DockerClient { - client, err := NewDockerClient(testHTTPServer.URL, nil) - if err != nil { - t.Fatal("Cannot init the docker client") - } - return client -} - -func TestInfo(t *testing.T) { - client := testDockerClient(t) - info, err := client.Info() - if err != nil { - t.Fatal("Cannot get server info") - } - assertEqual(t, info.Images, int64(1), "") - assertEqual(t, info.Containers, int64(2), "") -} - -func TestKillContainer(t *testing.T) { - client := testDockerClient(t) - if err := client.KillContainer("23132acf2ac", "5"); err != nil { - t.Fatal("cannot kill container: %s", err) - } -} - -func TestWait(t *testing.T) { - client := testDockerClient(t) - - // This provokes an error on the server. - select { - case wr := <-client.Wait("1234"): - assertEqual(t, wr.ExitCode, int(-1), "") - case <-time.After(2 * time.Second): - t.Fatal("Timed out!") - } - - // Valid case. - select { - case wr := <-client.Wait("valid-id"): - assertEqual(t, wr.ExitCode, int(0), "") - case <-time.After(2 * time.Second): - t.Fatal("Timed out!") - } -} - -func TestPullImage(t *testing.T) { - client := testDockerClient(t) - err := client.PullImage("busybox", nil) - if err != nil { - t.Fatal("unable to pull busybox") - } - - err = client.PullImage("haproxy", nil) - if err != nil { - t.Fatal("unable to pull haproxy") - } - - err = client.PullImage("wrongimg", nil) - if err == nil { - t.Fatal("should return error when it fails to pull wrongimg") - } -} - -func TestListContainers(t *testing.T) { - client := testDockerClient(t) - containers, err := client.ListContainers(true, false, "") - if err != nil { - t.Fatal("cannot get containers: %s", err) - } - assertEqual(t, len(containers), 1, "") - cnt := containers[0] - assertEqual(t, cnt.SizeRw, int64(0), "") -} - -func TestContainerChanges(t *testing.T) { - client := testDockerClient(t) - changes, err := client.ContainerChanges("foobar") - if err != nil { - t.Fatal("cannot get container changes: %s", err) - } - assertEqual(t, len(changes), 3, "unexpected number of changes") - c := changes[0] - assertEqual(t, c.Path, "/dev", "unexpected") - assertEqual(t, c.Kind, 0, "unexpected") -} - -func TestListContainersWithSize(t *testing.T) { - client := testDockerClient(t) - containers, err := client.ListContainers(true, true, "") - if err != nil { - t.Fatal("cannot get containers: %s", err) - } - assertEqual(t, len(containers), 1, "") - cnt := containers[0] - assertEqual(t, cnt.SizeRw, int64(123), "") -} - -func TestListContainersWithFilters(t *testing.T) { - client := testDockerClient(t) - containers, err := client.ListContainers(true, true, "{'id':['332375cfbc23edb921a21026314c3497674ba8bdcb2c85e0e65ebf2017f688ce']}") - if err != nil { - t.Fatal("cannot get containers: %s", err) - } - assertEqual(t, len(containers), 1, "") - - containers, err = client.ListContainers(true, true, "{'id':['332375cfbc23edb921a21026314c3497674ba8bdcb2c85e0e65ebf2017f688cf']}") - if err != nil { - t.Fatal("cannot get containers: %s", err) - } - assertEqual(t, len(containers), 0, "") -} - -func TestContainerLogs(t *testing.T) { - client := testDockerClient(t) - containerId := "foobar" - logOptions := &LogOptions{ - Follow: true, - Stdout: true, - Stderr: true, - Timestamps: true, - Tail: 10, - } - logsReader, err := client.ContainerLogs(containerId, logOptions) - if err != nil { - t.Fatal("cannot read logs from server") - } - - stdoutBuffer := new(bytes.Buffer) - stderrBuffer := new(bytes.Buffer) - if _, err = stdcopy.StdCopy(stdoutBuffer, stderrBuffer, logsReader); err != nil { - t.Fatal("cannot read logs from logs reader") - } - stdoutLogs := strings.TrimSpace(stdoutBuffer.String()) - stderrLogs := strings.TrimSpace(stderrBuffer.String()) - stdoutLogLines := strings.Split(stdoutLogs, "\n") - stderrLogLines := strings.Split(stderrLogs, "\n") - if len(stdoutLogLines) != 5 { - t.Fatalf("wrong number of stdout logs: len=%d", len(stdoutLogLines)) - } - if len(stderrLogLines) != 5 { - t.Fatalf("wrong number of stderr logs: len=%d", len(stdoutLogLines)) - } - for i, line := range stdoutLogLines { - expectedSuffix := fmt.Sprintf("Z line %d", 41+2*i) - if !strings.HasSuffix(line, expectedSuffix) { - t.Fatalf("expected stdout log line \"%s\" to end with \"%s\"", line, expectedSuffix) - } - } - for i, line := range stderrLogLines { - expectedSuffix := fmt.Sprintf("Z line %d", 40+2*i) - if !strings.HasSuffix(line, expectedSuffix) { - t.Fatalf("expected stderr log line \"%s\" to end with \"%s\"", line, expectedSuffix) - } - } -} - -func TestMonitorEvents(t *testing.T) { - client := testDockerClient(t) - decoder := json.NewDecoder(bytes.NewBufferString(eventsResp)) - var expectedEvents []Event - for { - var event Event - if err := decoder.Decode(&event); err != nil { - if err == io.EOF { - break - } else { - t.Fatalf("cannot parse expected resp: %s", err.Error()) - } - } else { - expectedEvents = append(expectedEvents, event) - } - } - - // test passing stop chan - stopChan := make(chan struct{}) - eventInfoChan, err := client.MonitorEvents(nil, stopChan) - if err != nil { - t.Fatalf("cannot get events from server: %s", err.Error()) - } - - eventInfo := <-eventInfoChan - if eventInfo.Error != nil || eventInfo.Event != expectedEvents[0] { - t.Fatalf("got:\n%#v\nexpected:\n%#v", eventInfo, expectedEvents[0]) - } - close(stopChan) - for i := 0; i < 3; i++ { - _, ok := <-eventInfoChan - if i == 2 && ok { - t.Fatalf("read more than 2 events successfully after closing stopChan") - } - } - - // test when you don't pass stop chan - eventInfoChan, err = client.MonitorEvents(nil, nil) - if err != nil { - t.Fatalf("cannot get events from server: %s", err.Error()) - } - - for i, expectedEvent := range expectedEvents { - t.Logf("on iter %d\n", i) - eventInfo := <-eventInfoChan - if eventInfo.Error != nil || eventInfo.Event != expectedEvent { - t.Fatalf("index %d, got:\n%#v\nexpected:\n%#v", i, eventInfo, expectedEvent) - } - t.Logf("done with iter %d\n", i) - } -} - -func TestDockerClientInterface(t *testing.T) { - iface := reflect.TypeOf((*Client)(nil)).Elem() - test := testDockerClient(t) - - if !reflect.TypeOf(test).Implements(iface) { - t.Fatalf("DockerClient does not implement the Client interface") - } -} diff --git a/go/src/github.com/samalba/dockerclient/engine_mock_test.go b/go/src/github.com/samalba/dockerclient/engine_mock_test.go deleted file mode 100755 index 7d3a6d93..00000000 --- a/go/src/github.com/samalba/dockerclient/engine_mock_test.go +++ /dev/null @@ -1,245 +0,0 @@ -package dockerclient - -import ( - "encoding/json" - "fmt" - "io" - "log" - "net/http" - "net/http/httptest" - "strconv" - "time" - - "github.com/docker/docker/pkg/ioutils" - "github.com/docker/docker/pkg/jsonlog" - "github.com/docker/docker/pkg/stdcopy" - "github.com/docker/docker/pkg/timeutils" - "github.com/gorilla/mux" -) - -var ( - testHTTPServer *httptest.Server -) - -func init() { - r := mux.NewRouter() - baseURL := "/" + APIVersion - r.HandleFunc(baseURL+"/info", handlerGetInfo).Methods("GET") - r.HandleFunc(baseURL+"/containers/json", handlerGetContainers).Methods("GET") - r.HandleFunc(baseURL+"/containers/{id}/logs", handleContainerLogs).Methods("GET") - r.HandleFunc(baseURL+"/containers/{id}/changes", handleContainerChanges).Methods("GET") - r.HandleFunc(baseURL+"/containers/{id}/kill", handleContainerKill).Methods("POST") - r.HandleFunc(baseURL+"/containers/{id}/wait", handleWait).Methods("POST") - r.HandleFunc(baseURL+"/images/create", handleImagePull).Methods("POST") - r.HandleFunc(baseURL+"/events", handleEvents).Methods("GET") - testHTTPServer = httptest.NewServer(handlerAccessLog(r)) -} - -func handlerAccessLog(handler http.Handler) http.Handler { - logHandler := func(w http.ResponseWriter, r *http.Request) { - log.Printf("%s \"%s %s\"", r.RemoteAddr, r.Method, r.URL) - handler.ServeHTTP(w, r) - } - return http.HandlerFunc(logHandler) -} - -func handleContainerKill(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "{%q:%q", "Id", "421373210afd132") -} - -func handleWait(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - if vars["id"] == "valid-id" { - fmt.Fprintf(w, `{"StatusCode":0}`) - } else { - http.Error(w, "failed", 500) - } -} - -func handleImagePull(w http.ResponseWriter, r *http.Request) { - imageName := r.URL.Query()["fromImage"][0] - responses := []map[string]interface{}{{ - "status": fmt.Sprintf("Pulling repository mydockerregistry/%s", imageName), - }} - switch imageName { - case "busybox": - responses = append(responses, map[string]interface{}{ - "status": "Status: Image is up to date for mydockerregistry/busybox", - }) - case "haproxy": - fmt.Fprintf(w, haproxyPullOutput) - return - default: - errorMsg := fmt.Sprintf("Error: image %s not found", imageName) - responses = append(responses, map[string]interface{}{ - "errorDetail": map[string]interface{}{ - "message": errorMsg, - }, - "error": errorMsg, - }) - } - for _, response := range responses { - json.NewEncoder(w).Encode(response) - } -} - -func handleContainerLogs(w http.ResponseWriter, r *http.Request) { - var outStream, errStream io.Writer - outStream = ioutils.NewWriteFlusher(w) - - // not sure how to test follow - if err := r.ParseForm(); err != nil { - http.Error(w, err.Error(), 500) - } - stdout, stderr := getBoolValue(r.Form.Get("stdout")), getBoolValue(r.Form.Get("stderr")) - if stderr { - errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) - } - if stdout { - outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) - } - var i int - if tail, err := strconv.Atoi(r.Form.Get("tail")); err == nil && tail > 0 { - i = 50 - tail - if i < 0 { - i = 0 - } - } - for ; i < 50; i++ { - line := fmt.Sprintf("line %d", i) - if getBoolValue(r.Form.Get("timestamps")) { - l := &jsonlog.JSONLog{Log: line, Created: time.Now().UTC()} - line = fmt.Sprintf("%s %s", l.Created.Format(timeutils.RFC3339NanoFixed), line) - } - if i%2 == 0 && stderr { - fmt.Fprintln(errStream, line) - } else if i%2 == 1 && stdout { - fmt.Fprintln(outStream, line) - } - } -} - -func handleContainerChanges(w http.ResponseWriter, r *http.Request) { - writeHeaders(w, 200, "changes") - body := `[ - { - "Path": "/dev", - "Kind": 0 - }, - { - "Path": "/dev/kmsg", - "Kind": 1 - }, - { - "Path": "/test", - "Kind": 1 - } - ]` - w.Write([]byte(body)) -} - -func getBoolValue(boolString string) bool { - switch boolString { - case "1": - return true - case "True": - return true - case "true": - return true - default: - return false - } -} - -func writeHeaders(w http.ResponseWriter, code int, jobName string) { - h := w.Header() - h.Add("Content-Type", "application/json") - if jobName != "" { - h.Add("Job-Name", jobName) - } - w.WriteHeader(code) -} - -func handlerGetInfo(w http.ResponseWriter, r *http.Request) { - writeHeaders(w, 200, "info") - body := `{ - "Containers": 2, - "Debug": 1, - "Driver": "aufs", - "DriverStatus": [["Root Dir", "/mnt/sda1/var/lib/docker/aufs"], - ["Dirs", "0"]], - "ExecutionDriver": "native-0.2", - "IPv4Forwarding": 1, - "Images": 1, - "IndexServerAddress": "https://index.docker.io/v1/", - "InitPath": "/usr/local/bin/docker", - "InitSha1": "", - "KernelVersion": "3.16.4-tinycore64", - "MemoryLimit": 1, - "NEventsListener": 0, - "NFd": 10, - "NGoroutines": 11, - "OperatingSystem": "Boot2Docker 1.3.1 (TCL 5.4); master : a083df4 - Thu Jan 01 00:00:00 UTC 1970", - "SwapLimit": 1}` - w.Write([]byte(body)) -} - -func handlerGetContainers(w http.ResponseWriter, r *http.Request) { - writeHeaders(w, 200, "containers") - body := `[ - { - "Status": "Up 39 seconds", - "Ports": [ - { - "Type": "tcp", - "PublicPort": 49163, - "PrivatePort": 8080, - "IP": "0.0.0.0" - } - ], - "Names": [ - "/trusting_heisenberg" - ], - "Image": "foo:latest", - "Id": "332375cfbc23edb921a21026314c3497674ba8bdcb2c85e0e65ebf2017f688ce", - "Created": 1415720105, - "Command": "/bin/go-run" - } - ]` - if v, ok := r.URL.Query()["size"]; ok { - if v[0] == "1" { - body = `[ - { - "Status": "Up 39 seconds", - "Ports": [ - { - "Type": "tcp", - "PublicPort": 49163, - "PrivatePort": 8080, - "IP": "0.0.0.0" - } - ], - "Names": [ - "/trusting_heisenberg" - ], - "Image": "foo:latest", - "Id": "332375cfbc23edb921a21026314c3497674ba8bdcb2c85e0e65ebf2017f688ce", - "Created": 1415720105, - "SizeRootFs": 12345, - "SizeRW": 123, - "Command": "/bin/go-run" - } - ]` - } - } - if v, ok := r.URL.Query()["filters"]; ok { - if v[0] != "{'id':['332375cfbc23edb921a21026314c3497674ba8bdcb2c85e0e65ebf2017f688ce']}" { - body = "[]" - } - } - w.Write([]byte(body)) -} - -func handleEvents(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(eventsResp)) -} diff --git a/go/src/github.com/samalba/dockerclient/mockclient/mock_test.go b/go/src/github.com/samalba/dockerclient/mockclient/mock_test.go deleted file mode 100755 index 8d91bcf6..00000000 --- a/go/src/github.com/samalba/dockerclient/mockclient/mock_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package mockclient - -import ( - "reflect" - "testing" - - "github.com/samalba/dockerclient" -) - -func TestMock(t *testing.T) { - mock := NewMockClient() - mock.On("Version").Return(&dockerclient.Version{Version: "foo"}, nil).Once() - - v, err := mock.Version() - if err != nil { - t.Fatal(err) - } - if v.Version != "foo" { - t.Fatal(v) - } - - mock.Mock.AssertExpectations(t) -} - -func TestMockInterface(t *testing.T) { - iface := reflect.TypeOf((*dockerclient.Client)(nil)).Elem() - mock := NewMockClient() - - if !reflect.TypeOf(mock).Implements(iface) { - t.Fatalf("Mock does not implement the Client interface") - } -} diff --git a/go/src/gosetup/main.go b/go/src/gosetup/main.go deleted file mode 100644 index 61a672b9..00000000 --- a/go/src/gosetup/main.go +++ /dev/null @@ -1,67 +0,0 @@ -package main - -import ( - "os" - "path" - "net/http" - "io" - - "github.com/Sirupsen/logrus" - "github.com/samalba/dockerclient" -) - -// instance of DockerClient allowing for making calls to the docker daemon -// remote API -var dockerClient *dockerclient.DockerClient - - -func main() { - - // init docker client object - var err error - dockerClient, err = dockerclient.NewDockerClient("unix:///var/run/docker.sock", nil) - if err != nil { - logrus.Fatal(err.Error()) - } - - // get the version of the docker daemon so we can be sure the corresponding - // docker client is installed and install it if necessary - version, err := dockerClient.Version() - if err != nil { - logrus.Fatal(err.Error()) - } - dockerDaemonVersion := version.Version - - // name of docker binary that is needed - dockerBinaryName := "docker-" + dockerDaemonVersion - logrus.Println("looking for docker binary named:", dockerBinaryName) - - filename := path.Join("/bin", dockerBinaryName) - - if _, err := os.Stat(filename); os.IsNotExist(err) { - - logrus.Println("docker binary (version " + dockerDaemonVersion + ") not found.") - logrus.Println("downloading", dockerBinaryName, "...") - - out, err := os.Create(filename) - if err != nil { - logrus.Fatal(err.Error()) - } - defer out.Close() - resp, err := http.Get("https://get.docker.com/builds/Linux/x86_64/docker-" + dockerDaemonVersion) - if err != nil { - logrus.Fatal(err.Error()) - } - defer resp.Body.Close() - - _, err = io.Copy(out, resp.Body) - if err != nil { - logrus.Fatal(err.Error()) - } - - err = os.Chmod(filename, 0700) - if err != nil { - logrus.Fatal(err.Error()) - } - } -} diff --git a/machines.go b/machines.go new file mode 100644 index 00000000..ef9a9df4 --- /dev/null +++ b/machines.go @@ -0,0 +1,183 @@ +package main + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "regexp" + "sync" + + "github.com/Jeffail/gabs" + log "github.com/Sirupsen/logrus" + "github.com/samalba/dockerclient" +) + +const machineDir = "/root/.docker/machine/machines/" + +// Machine contains information about a discovered Docker Machine +type Machine struct { + // Name is the name of the machine + Name string + // Client is a map of Machine names to DockerClient instances + Client *dockerclient.DockerClient + // URL is the url that the DockerClient should use for connecting to the daemon + URL string + // TLSConfig is the tls config + TLSConfig *tls.Config + // Version is the version of the Docker Daemon + Version string + // BinaryName is the name of the Docker Binary + BinaryName string + // previouscpustats is a map containing the previous cpu stats we got from the + // docker daemon through the docker remote api + previousCPUStats map[string]*CPUStats + previousCPUStatsLock sync.RWMutex + // the following are needed to provide the args for Docker CLI + caCertPath, serverCertPath, serverKeyPath string +} + +// NewMachine creates a new Machine object +func NewMachine(name string) *Machine { + return &Machine{Name: name, previousCPUStats: make(map[string]*CPUStats)} +} + +// GetDockerConfig generates the necessary config variables for using the Docker CLI +// This is a hack and should be replaced by Lua parsing the command in to the correct JSON format +// and the proxy should simply send this to the right daemon +func (m *Machine) GetDockerConfig() []string { + var config []string + if m.caCertPath != "" { + config = []string{ + "--tlsverify", + fmt.Sprintf("--tlscacert=\"%s\"", m.caCertPath), + fmt.Sprintf("--tlscert=\"%s\"", m.serverCertPath), + fmt.Sprintf("--tlskey=\"%s\"", m.serverKeyPath), + } + } + config = append(config, "-H", m.URL) + return config +} + +// GetMachines will populate a list of machines +func (d *Daemon) GetMachines() error { + if _, err := os.Stat("/var/run/docker.sock"); os.IsNotExist(err) { + log.Warn("No docker socket mounted") + } else { + machine := NewMachine("local") + machine.URL = "unix:///var/run/docker.sock" + d.Machines["local"] = machine + } + + log.Info("Looking for Docker Machine VMs...") + folders, err := ioutil.ReadDir(machineDir) + if err != nil { + return err + } + for _, f := range folders { + configFileName := filepath.Join(machineDir, f.Name(), "config.json") + configFile, err := ioutil.ReadFile(configFileName) + if err != nil { + log.Warnf("Failed to open %s", configFile) + continue + } + config, err := gabs.ParseJSON(configFile) + if err != nil { + log.Warnf("Error parsing file. %s", err) + continue + } + name, ok := config.Path("Name").Data().(string) + if !ok { + log.Warn("Couldn't get machine name") + continue + } + ip, ok := config.Path("Driver.IPAddress").Data().(string) + if !ok { + log.Warn("Couldn't get machine IP Address") + continue + } + caCertPath := config.Path("HostOptions.AuthOptions.CaCertPath").Data().(string) + if !ok { + log.Warn("Couldn't get machine CA Cert Path") + continue + } + serverCertPath := config.Path("HostOptions.AuthOptions.ServerCertPath").Data().(string) + if !ok { + log.Warn("Couldn't get machine Server Cert Path") + continue + } + serverKeyPath := config.Path("HostOptions.AuthOptions.ServerKeyPath").Data().(string) + if !ok { + log.Warn("Couldn't get machine Server Key") + continue + } + + // No IP == VM has stopped + if ip == "" { + continue + } + + url := fmt.Sprintf("tcp://%s:2376", ip) + + tlsConfig, err := configureTLS(caCertPath, serverCertPath, serverKeyPath) + if err != nil { + log.Warnf("Error setting up TLS: %s", err) + continue + } + + machine := NewMachine(name) + machine.URL = url + machine.TLSConfig = tlsConfig + machine.caCertPath = rewritePath(caCertPath) + machine.serverCertPath = rewritePath(serverCertPath) + machine.serverKeyPath = rewritePath(serverKeyPath) + d.Machines[name] = machine + log.Infof("Discovered Machine %s", name) + } + return nil +} + +func rewritePath(path string) string { + re := regexp.MustCompile("^/Users/[\\w ]+") + return re.ReplaceAllString(path, "/root") +} + +func configureTLS(caCertPath, serverCertPath, serverKeyPath string) (*tls.Config, error) { + var tlsConfig *tls.Config + + caCert, err := ioutil.ReadFile(rewritePath(caCertPath)) + if err != nil { + return tlsConfig, err + } + + serverCert, err := ioutil.ReadFile(rewritePath(serverCertPath)) + if err != nil { + return tlsConfig, err + } + + serverKey, err := ioutil.ReadFile(rewritePath(serverKeyPath)) + if err != nil { + return tlsConfig, err + } + + certPool := x509.NewCertPool() + ok := certPool.AppendCertsFromPEM(caCert) + if !ok { + return tlsConfig, err + } + + keypair, err := tls.X509KeyPair(serverCert, serverKey) + if err != nil { + return tlsConfig, err + } + + tlsConfig = &tls.Config{ + RootCAs: certPool, + InsecureSkipVerify: false, + Certificates: []tls.Certificate{keypair}, + } + + return tlsConfig, nil +} diff --git a/main.go b/main.go new file mode 100644 index 00000000..1e5ba862 --- /dev/null +++ b/main.go @@ -0,0 +1,201 @@ +package main + +import ( + "flag" + "fmt" + "io" + "net/http" + "os" + "os/exec" + "text/template" + + log "github.com/Sirupsen/logrus" +) + +var debugFlag = flag.Bool("debug", false, "enable debug logging") +var daemonFlag = flag.Bool("daemon", false, "run the daemon") + +func main() { + flag.Parse() + + if *debugFlag { + log.SetLevel(log.DebugLevel) + } + + // goproxy is executed as a short lived process to send a request to the + // goproxy daemon process + if !*daemonFlag { + if err := ProxyCmd(flag.Args()); err != nil { + log.Fatalf(err.Error()) + os.Exit(1) + } + return + } + + daemon := NewDaemon() + if err := daemon.Init(); err != nil { + log.Fatalf("Init error: %s", err.Error()) + os.Exit(1) + } + + if err := daemon.GetDockerBinaries(); err != nil { + log.Fatal(err.Error()) + os.Exit(1) + } + + type Config struct { + DefaultWorld string + Worlds []string + } + + config := Config{} + for name, machine := range daemon.Machines { + // start monitoring docker events + machine.Client.StartMonitorEvents(machine.eventCallback, nil) + // create a new world + log.Debug("Copying world") + if err := copyDir("/srv/world/world_template", fmt.Sprintf("/srv/world/%s", name)); err != nil { + log.Warn(err) + } + config.Worlds = append(config.Worlds, name) + } + + if len(config.Worlds) > 0 { + // Select default world + local := -1 + def := -1 + + for i := range config.Worlds { + if config.Worlds[i] == "local" { + local = i + } + if config.Worlds[i] == "default" { + def = i + } + } + + if local > -1 { + config.DefaultWorld = "local" + config.Worlds = append(config.Worlds[:local], config.Worlds[local+1:]...) + } else if def > -1 { + config.DefaultWorld = "default" + config.Worlds = append(config.Worlds[:def], config.Worlds[def+1:]...) + } else { + config.DefaultWorld = config.Worlds[0] + config.Worlds = append(config.Worlds[:0], config.Worlds[1:]...) + } + } else { + config.DefaultWorld = config.Worlds[0] + config.Worlds = nil + } + + log.Debug("Writing settings.ini") + t, err := template.ParseFiles("/srv/world/settings.ini.tmpl") + if err != nil { + log.Fatal(err) + os.Exit(1) + } + f, err := os.OpenFile("/srv/world/settings.ini", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0777) + if err != nil { + log.Fatal(err) + os.Exit(1) + } + err = t.Execute(f, config) + if err != nil { + log.Fatal(err) + os.Exit(1) + } + + // start a http server and listen on local port 8000 + go func() { + log.Info("Starting HTTP Server") + http.HandleFunc("/containers", daemon.listContainers) + http.HandleFunc("/exec", daemon.execCmd) + http.ListenAndServe(":8000", nil) + }() + + os.Chdir("/srv/world") + cmd := exec.Command("cuberite") + // cmd.Stdin = os.Stdin + + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + cmd.Stdin = os.Stdin + if err := cmd.Run(); err != nil { + log.Fatalf("Error: %s", err) + os.Exit(1) + } + + // wait for interruption + <-make(chan int) +} + +func copyDir(source string, dest string) (err error) { + + // get properties of source dir + sourceinfo, err := os.Stat(source) + if err != nil { + return err + } + + // create dest dir + + err = os.MkdirAll(dest, sourceinfo.Mode()) + if err != nil { + return err + } + + directory, _ := os.Open(source) + + objects, err := directory.Readdir(-1) + + for _, obj := range objects { + + sourcefilepointer := source + "/" + obj.Name() + + destinationfilepointer := dest + "/" + obj.Name() + + if obj.IsDir() { + // create sub-directories - recursively + err = copyDir(sourcefilepointer, destinationfilepointer) + if err != nil { + fmt.Println(err) + } + } else { + // perform copy + err = copyFile(sourcefilepointer, destinationfilepointer) + if err != nil { + fmt.Println(err) + } + } + + } + return +} + +func copyFile(source string, dest string) (err error) { + sourcefile, err := os.Open(source) + if err != nil { + return err + } + + defer sourcefile.Close() + + destfile, err := os.Create(dest) + if err != nil { + return err + } + + defer destfile.Close() + + _, err = io.Copy(destfile, sourcefile) + if err == nil { + sourceinfo, err := os.Stat(source) + if err != nil { + err = os.Chmod(dest, sourceinfo.Mode()) + } + + } + + return +} diff --git a/go/src/goproxy/main.go b/proxy.go similarity index 57% rename from go/src/goproxy/main.go rename to proxy.go index 7b11e026..6b5bc2e5 100644 --- a/go/src/goproxy/main.go +++ b/proxy.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "io" "net/http" "net/url" @@ -9,7 +10,7 @@ import ( "strconv" "strings" - "github.com/Sirupsen/logrus" + log "github.com/Sirupsen/logrus" "github.com/samalba/dockerclient" ) @@ -24,89 +25,91 @@ import ( // cuberite server, convert them into docker daemon remote API calls and send // them to the docker daemon. -// instance of DockerClient allowing for making calls to the docker daemon -// remote API -var dockerClient *dockerclient.DockerClient -// version of the docker daemon which is exposing the remote API -var dockerDaemonVersion string +// Daemon maintains state when the dockercraft daemon is running +type Daemon struct { + Machines map[string]*Machine +} + +// NewDaemon returns a new instance of Daemon +func NewDaemon() *Daemon { + return &Daemon{ + Machines: make(map[string]*Machine), + } +} +// CPUStats contains the Total and System CPU stats type CPUStats struct { TotalUsage uint64 SystemUsage uint64 } -// previousCPUStats is a map containing the previous CPU stats we got from the -// docker daemon through the docker remote API -var previousCPUStats map[string]*CPUStats = make(map[string]*CPUStats) - -func main() { - - // goproxy is executed as a short lived process to send a request to the - // goproxy daemon process - if len(os.Args) > 1 { - // If there's an argument - // It will be considered as a path for an HTTP GET request - // That's a way to communicate with goproxy daemon - if len(os.Args) == 2 { - reqPath := "http://127.0.0.1:8000/" + os.Args[1] - resp, err := http.Get(reqPath) - if err != nil { - logrus.Println("Error on request:", reqPath, "ERROR:", err.Error()) - } else { - logrus.Println("Request sent", reqPath, "StatusCode:", resp.StatusCode) - } - } - return +// ProxyCmd submits a command to a running Daemon +func ProxyCmd(args []string) error { + if len(args) != 1 { + return fmt.Errorf("One argument expected. Received %d", len(args)) } - - // init docker client object - var err error - dockerClient, err = dockerclient.NewDockerClient("unix:///var/run/docker.sock", nil) + reqPath := "http://127.0.0.1:8000/" + os.Args[1] + resp, err := http.Get(reqPath) if err != nil { - logrus.Fatal(err.Error()) + return fmt.Errorf("Error on request: %s, ERROR: %s", reqPath, err.Error()) } + log.Debugf("Request sent: %s, StatusCode: %d", reqPath, resp.StatusCode) + return nil +} - // get the version of the docker remote API - version, err := dockerClient.Version() - if err != nil { - logrus.Fatal(err.Error()) +// Init initializes a Daemon +func (d *Daemon) Init() error { + + if err := d.GetMachines(); err != nil { + return err } - dockerDaemonVersion = version.Version - // start monitoring docker events - dockerClient.StartMonitorEvents(eventCallback, nil) + if len(d.Machines) == 0 { + return fmt.Errorf("No Machines Found!") + } - // start a http server and listen on local port 8000 - go func() { - http.HandleFunc("/containers", listContainers) - http.HandleFunc("/exec", execCmd) - http.ListenAndServe(":8000", nil) - }() + for m, machine := range d.Machines { + var err error + machine.Client, err = dockerclient.NewDockerClient(machine.URL, machine.TLSConfig) + if err != nil { + log.Warn("Error connecting to machine %s: %s", m, err) + delete(d.Machines, m) + continue + } - // wait for interruption - <-make(chan int) + // get the version of the docker remote API + version, err := machine.Client.Version() + if err != nil { + log.Warn("Error connecting to machine %s: %s", m, err) + delete(d.Machines, m) + continue + + } + machine.Version = version.Version + } + return nil } // eventCallback receives and handles the docker events -func eventCallback(event *dockerclient.Event, ec chan error, args ...interface{}) { - logrus.Debugln("--\n%+v", *event) - - id := event.Id +func (m *Machine) eventCallback(event *dockerclient.Event, ec chan error, args ...interface{}) { + log.Debugf("--\n%+v", *event) + id := event.ID switch event.Status { case "create": - logrus.Debugln("create event") + log.Debug("create event") repo, tag := splitRepoAndTag(event.From) containerName := "" - containerInfo, err := dockerClient.InspectContainer(id) + containerInfo, err := m.Client.InspectContainer(id) if err != nil { - logrus.Print("InspectContainer error:", err.Error()) + log.Errorf("InspectContainer error: %s", err.Error()) } else { containerName = containerInfo.Name } data := url.Values{ + "world": {m.Name}, "action": {"createContainer"}, "id": {id}, "name": {containerName}, @@ -116,18 +119,19 @@ func eventCallback(event *dockerclient.Event, ec chan error, args ...interface{} CuberiteServerRequest(data) case "start": - logrus.Debugln("start event") + log.Debugln("start event") repo, tag := splitRepoAndTag(event.From) containerName := "" - containerInfo, err := dockerClient.InspectContainer(id) + containerInfo, err := m.Client.InspectContainer(id) if err != nil { - logrus.Print("InspectContainer error:", err.Error()) + log.Errorf("InspectContainer error: %s", err.Error()) } else { containerName = containerInfo.Name } data := url.Values{ + "world": {m.Name}, "action": {"startContainer"}, "id": {id}, "name": {containerName}, @@ -135,7 +139,7 @@ func eventCallback(event *dockerclient.Event, ec chan error, args ...interface{} "imageTag": {tag}} // Monitor stats - dockerClient.StartMonitorStats(id, statCallback, nil) + m.Client.StartMonitorStats(id, m.statCallback, nil) CuberiteServerRequest(data) case "stop": @@ -151,19 +155,20 @@ func eventCallback(event *dockerclient.Event, ec chan error, args ...interface{} // http://docs.docker.com/reference/api/docker_remote_api/#docker-events case "die": - logrus.Debugln("die event") + log.Debugln("die event") // same as stop event repo, tag := splitRepoAndTag(event.From) containerName := "" - containerInfo, err := dockerClient.InspectContainer(id) + containerInfo, err := m.Client.InspectContainer(id) if err != nil { - logrus.Print("InspectContainer error:", err.Error()) + log.Errorf("InspectContainer error: %s", err.Error()) } else { containerName = containerInfo.Name } data := url.Values{ + "world": {m.Name}, "action": {"stopContainer"}, "id": {id}, "name": {containerName}, @@ -173,9 +178,10 @@ func eventCallback(event *dockerclient.Event, ec chan error, args ...interface{} CuberiteServerRequest(data) case "destroy": - logrus.Debugln("destroy event") + log.Debug("destroy event") data := url.Values{ + "world": {m.Name}, "action": {"destroyContainer"}, "id": {id}, } @@ -186,22 +192,27 @@ func eventCallback(event *dockerclient.Event, ec chan error, args ...interface{} // statCallback receives the stats (cpu & ram) from containers and send them to // the cuberite server -func statCallback(id string, stat *dockerclient.Stats, ec chan error, args ...interface{}) { +func (m *Machine) statCallback(id string, stat *dockerclient.Stats, ec chan error, args ...interface{}) { - // logrus.Debugln("STATS", id, stat) - // logrus.Debugln("---") - // logrus.Debugln("cpu :", float64(stat.CpuStats.CpuUsage.TotalUsage)/float64(stat.CpuStats.SystemUsage)) - // logrus.Debugln("ram :", stat.MemoryStats.Usage) + // log.Debugln("STATS", id, stat) + // log.Debugln("---") + // log.Debugln("cpu :", float64(stat.CpuStats.CpuUsage.TotalUsage)/float64(stat.CpuStats.SystemUsage)) + // log.Debugln("ram :", stat.MemoryStats.Usage) memPercent := float64(stat.MemoryStats.Usage) / float64(stat.MemoryStats.Limit) * 100.0 - var cpuPercent float64 = 0.0 - if preCPUStats, exists := previousCPUStats[id]; exists { + var cpuPercent float64 + m.previousCPUStatsLock.RLock() + if preCPUStats, exists := m.previousCPUStats[id]; exists { cpuPercent = calculateCPUPercent(preCPUStats, &stat.CpuStats) } + m.previousCPUStatsLock.RUnlock() - previousCPUStats[id] = &CPUStats{TotalUsage: stat.CpuStats.CpuUsage.TotalUsage, SystemUsage: stat.CpuStats.SystemUsage} + m.previousCPUStatsLock.Lock() + m.previousCPUStats[id] = &CPUStats{TotalUsage: stat.CpuStats.CpuUsage.TotalUsage, SystemUsage: stat.CpuStats.SystemUsage} + m.previousCPUStatsLock.Unlock() data := url.Values{ + "world": {m.Name}, "action": {"stats"}, "id": {id}, "cpu": {strconv.FormatFloat(cpuPercent, 'f', 2, 64) + "%"}, @@ -211,20 +222,24 @@ func statCallback(id string, stat *dockerclient.Stats, ec chan error, args ...in } // execCmd handles http requests received for the path "/exec" -func execCmd(w http.ResponseWriter, r *http.Request) { +func (d *Daemon) execCmd(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "OK") go func() { + machine := r.URL.Query().Get("world") cmd := r.URL.Query().Get("cmd") cmd, _ = url.QueryUnescape(cmd) arr := strings.Split(cmd, " ") if len(arr) > 0 { - - if arr[0] == "docker" { - arr[0] = "docker-" + dockerDaemonVersion + if arr[0] != "docker" { + log.Errorf("Aborting. %s is not a supported command", arr[0]) } - + // switch "docker" for the binary name + arr[0] = d.Machines[machine].BinaryName + hostArgs := d.Machines[machine].GetDockerConfig() + arr = append(arr[:1], append(hostArgs, arr[1:]...)...) + log.Debugf("Command: %+v\n", arr) cmd := exec.Command(arr[0], arr[1:]...) // Stdout buffer // cmdOutput := &bytes.Buffer{} @@ -234,37 +249,43 @@ func execCmd(w http.ResponseWriter, r *http.Request) { // printCommand(cmd) err := cmd.Run() // will wait for command to return if err != nil { - logrus.Println("Error:", err.Error()) + log.Error(err.Error()) } } }() } // listContainers handles and reply to http requests having the path "/containers" -func listContainers(w http.ResponseWriter, r *http.Request) { +func (d *Daemon) listContainers(w http.ResponseWriter, r *http.Request) { // answer right away to avoid dead locks in LUA io.WriteString(w, "OK") - go func() { - containers, err := dockerClient.ListContainers(true, false, "") + go func(r *http.Request) { + log.Debug("Entering goroutine") + machine := r.URL.Query().Get("world") + log.Debugf("Machine is %s", machine) + log.Debugf("Getting containers") + containers, err := d.Machines[machine].Client.ListContainers(true, false, "") if err != nil { - logrus.Println(err.Error()) + log.Error(err.Error()) return } - images, err := dockerClient.ListImages(true) + log.Debugf("Getting images") + images, err := d.Machines[machine].Client.ListImages(true) if err != nil { - logrus.Println(err.Error()) + log.Error(err.Error()) return } + log.Debugf("%d containers found in %s", len(containers), machine) for i := 0; i < len(containers); i++ { - id := containers[i].Id - info, _ := dockerClient.InspectContainer(id) + log.Debugf("Looking at container %s", id) + info, _ := d.Machines[machine].Client.InspectContainer(id) name := info.Name[1:] imageRepo := "" imageTag := "" @@ -279,6 +300,7 @@ func listContainers(w http.ResponseWriter, r *http.Request) { } data := url.Values{ + "world": {machine}, "action": {"containerInfos"}, "id": {id}, "name": {name}, @@ -287,14 +309,15 @@ func listContainers(w http.ResponseWriter, r *http.Request) { "running": {strconv.FormatBool(info.State.Running)}, } + log.Debugf("Sending %+v", data) CuberiteServerRequest(data) if info.State.Running { // Monitor stats - dockerClient.StartMonitorStats(id, statCallback, nil) + d.Machines[machine].Client.StartMonitorStats(id, d.Machines[machine].statCallback, nil) } } - }() + }(r) } // Utility functions diff --git a/setup.go b/setup.go new file mode 100644 index 00000000..328d55d7 --- /dev/null +++ b/setup.go @@ -0,0 +1,65 @@ +package main + +import ( + "fmt" + "io" + "net/http" + "os" + "path" + + log "github.com/Sirupsen/logrus" +) + +// GetDockerBinaries gets the binaries for every registered machine +func (d *Daemon) GetDockerBinaries() error { + if len(d.Machines) == 0 { + return fmt.Errorf("No machines found") + } + + for _, machine := range d.Machines { + if err := machine.GetDockerBinary(); err != nil { + return err + } + } + return nil +} + +// GetDockerBinary ensures that we have the right version docker client +// for communicating with the Docker Daemon +func (m *Machine) GetDockerBinary() error { + // name of docker binary that is needed + m.BinaryName = "docker-" + m.Version + log.Infof("looking for docker binary named: %s", m.BinaryName) + + filename := path.Join("/bin", m.BinaryName) + + if _, err := os.Stat(filename); os.IsNotExist(err) { + + log.Infof("docker binary (version %s) not found.", m.Version) + log.Infof("downloading %s...", m.BinaryName) + + out, err := os.Create(filename) + if err != nil { + return err + } + defer out.Close() + resp, err := http.Get("https://get.docker.com/builds/Linux/x86_64/" + m.BinaryName) + if err != nil { + return err + } + defer resp.Body.Close() + + _, err = io.Copy(out, resp.Body) + if err != nil { + return err + } + + err = os.Chmod(filename, 0700) + if err != nil { + return err + } + } else { + log.Infof("docker binary (version %s) found!", m.Version) + } + return nil +} diff --git a/start.sh b/start.sh deleted file mode 100755 index 73c9632b..00000000 --- a/start.sh +++ /dev/null @@ -1,9 +0,0 @@ -# Download the version of the docker client that matches the docker daemon present -gosetup - -# Start goproxy -goproxy &> /srv/world/goproxy_out & - -# start Minecraft C++ server -cd /srv/world -../cuberite_server/Cuberite diff --git a/vendor/github.com/Jeffail/gabs/LICENSE b/vendor/github.com/Jeffail/gabs/LICENSE new file mode 100644 index 00000000..99a62c62 --- /dev/null +++ b/vendor/github.com/Jeffail/gabs/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 Ashley Jeffs + +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. diff --git a/vendor/github.com/Jeffail/gabs/gabs.go b/vendor/github.com/Jeffail/gabs/gabs.go new file mode 100644 index 00000000..9c8861fb --- /dev/null +++ b/vendor/github.com/Jeffail/gabs/gabs.go @@ -0,0 +1,645 @@ +/* +Copyright (c) 2014 Ashley Jeffs + +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. +*/ + +// Package gabs implements a simplified wrapper around creating and parsing JSON. +package gabs + +import ( + "encoding/json" + "errors" + "io" + "io/ioutil" + "strings" +) + +/*--------------------------------------------------------------------------------------------------- + */ + +var ( + // ErrOutOfBounds - Index out of bounds. + ErrOutOfBounds = errors.New("out of bounds") + + // ErrNotObjOrArray - The target is not an object or array type. + ErrNotObjOrArray = errors.New("not an object or array") + + // ErrNotObj - The target is not an object type. + ErrNotObj = errors.New("not an object") + + // ErrNotArray - The target is not an array type. + ErrNotArray = errors.New("not an array") + + // ErrPathCollision - Creating a path failed because an element collided with an existing value. + ErrPathCollision = errors.New("encountered value collision whilst building path") + + // ErrInvalidInputObj - The input value was not a map[string]interface{}. + ErrInvalidInputObj = errors.New("invalid input object") + + // ErrInvalidInputText - The input data could not be parsed. + ErrInvalidInputText = errors.New("input text could not be parsed") + + // ErrInvalidPath - The filepath was not valid. + ErrInvalidPath = errors.New("invalid file path") + + // ErrInvalidBuffer - The input buffer contained an invalid JSON string + ErrInvalidBuffer = errors.New("input buffer contained invalid JSON") +) + +/*--------------------------------------------------------------------------------------------------- + */ + +/* +Container - an internal structure that holds a reference to the core interface map of the parsed +json. Use this container to move context. +*/ +type Container struct { + object interface{} +} + +/* +Data - Return the contained data as an interface{}. +*/ +func (g *Container) Data() interface{} { + return g.object +} + +/*--------------------------------------------------------------------------------------------------- + */ + +/* +Path - Search for a value using dot notation. +*/ +func (g *Container) Path(path string) *Container { + return g.Search(strings.Split(path, ".")...) +} + +/* +Search - Attempt to find and return an object within the JSON structure by specifying the hierarchy +of field names to locate the target. If the search encounters an array and has not reached the end +target then it will iterate each object of the array for the target and return all of the results in +a JSON array. +*/ +func (g *Container) Search(hierarchy ...string) *Container { + var object interface{} + + object = g.object + for target := 0; target < len(hierarchy); target++ { + if mmap, ok := object.(map[string]interface{}); ok { + object = mmap[hierarchy[target]] + } else if marray, ok := object.([]interface{}); ok { + tmpArray := []interface{}{} + for _, val := range marray { + tmpGabs := &Container{val} + res := tmpGabs.Search(hierarchy[target:]...).Data() + if res != nil { + tmpArray = append(tmpArray, res) + } + } + if len(tmpArray) == 0 { + return &Container{nil} + } + return &Container{tmpArray} + } else { + return &Container{nil} + } + } + return &Container{object} +} + +/* +S - Shorthand method, does the same thing as Search. +*/ +func (g *Container) S(hierarchy ...string) *Container { + return g.Search(hierarchy...) +} + +/* +Exists - Checks whether a path exists. +*/ +func (g *Container) Exists(hierarchy ...string) bool { + var object interface{} + + object = g.object + for target := 0; target < len(hierarchy); target++ { + if mmap, ok := object.(map[string]interface{}); ok { + object, ok = mmap[hierarchy[target]] + if !ok { + return false + } + } else { + return false + } + } + return true +} + +/* +ExistsP - Checks whether a dot notation path exists. +*/ +func (g *Container) ExistsP(path string) bool { + return g.Exists(strings.Split(path, ".")...) +} + +/* +Index - Attempt to find and return an object with a JSON array by specifying the index of the +target. +*/ +func (g *Container) Index(index int) *Container { + if array, ok := g.Data().([]interface{}); ok { + if index >= len(array) { + return &Container{nil} + } + return &Container{array[index]} + } + return &Container{nil} +} + +/* +Children - Return a slice of all the children of the array. This also works for objects, however, +the children returned for an object will NOT be in order and you lose the names of the returned +objects this way. +*/ +func (g *Container) Children() ([]*Container, error) { + if array, ok := g.Data().([]interface{}); ok { + children := make([]*Container, len(array)) + for i := 0; i < len(array); i++ { + children[i] = &Container{array[i]} + } + return children, nil + } + if mmap, ok := g.Data().(map[string]interface{}); ok { + children := []*Container{} + for _, obj := range mmap { + children = append(children, &Container{obj}) + } + return children, nil + } + return nil, ErrNotObjOrArray +} + +/* +ChildrenMap - Return a map of all the children of an object. +*/ +func (g *Container) ChildrenMap() (map[string]*Container, error) { + if mmap, ok := g.Data().(map[string]interface{}); ok { + children := map[string]*Container{} + for name, obj := range mmap { + children[name] = &Container{obj} + } + return children, nil + } + return nil, ErrNotObj +} + +/*--------------------------------------------------------------------------------------------------- + */ + +/* +Set - Set the value of a field at a JSON path, any parts of the path that do not exist will be +constructed, and if a collision occurs with a non object type whilst iterating the path an error is +returned. +*/ +func (g *Container) Set(value interface{}, path ...string) (*Container, error) { + if len(path) == 0 { + g.object = value + return g, nil + } + var object interface{} + if g.object == nil { + g.object = map[string]interface{}{} + } + object = g.object + for target := 0; target < len(path); target++ { + if mmap, ok := object.(map[string]interface{}); ok { + if target == len(path)-1 { + mmap[path[target]] = value + } else if mmap[path[target]] == nil { + mmap[path[target]] = map[string]interface{}{} + } + object = mmap[path[target]] + } else { + return &Container{nil}, ErrPathCollision + } + } + return &Container{object}, nil +} + +/* +SetP - Does the same as Set, but using a dot notation JSON path. +*/ +func (g *Container) SetP(value interface{}, path string) (*Container, error) { + return g.Set(value, strings.Split(path, ".")...) +} + +/* +SetIndex - Set a value of an array element based on the index. +*/ +func (g *Container) SetIndex(value interface{}, index int) (*Container, error) { + if array, ok := g.Data().([]interface{}); ok { + if index >= len(array) { + return &Container{nil}, ErrOutOfBounds + } + array[index] = value + return &Container{array[index]}, nil + } + return &Container{nil}, ErrNotArray +} + +/* +Object - Create a new JSON object at a path. Returns an error if the path contains a collision with +a non object type. +*/ +func (g *Container) Object(path ...string) (*Container, error) { + return g.Set(map[string]interface{}{}, path...) +} + +/* +ObjectP - Does the same as Object, but using a dot notation JSON path. +*/ +func (g *Container) ObjectP(path string) (*Container, error) { + return g.Object(strings.Split(path, ".")...) +} + +/* +ObjectI - Create a new JSON object at an array index. Returns an error if the object is not an array +or the index is out of bounds. +*/ +func (g *Container) ObjectI(index int) (*Container, error) { + return g.SetIndex(map[string]interface{}{}, index) +} + +/* +Array - Create a new JSON array at a path. Returns an error if the path contains a collision with +a non object type. +*/ +func (g *Container) Array(path ...string) (*Container, error) { + return g.Set([]interface{}{}, path...) +} + +/* +ArrayP - Does the same as Array, but using a dot notation JSON path. +*/ +func (g *Container) ArrayP(path string) (*Container, error) { + return g.Array(strings.Split(path, ".")...) +} + +/* +ArrayI - Create a new JSON array at an array index. Returns an error if the object is not an array +or the index is out of bounds. +*/ +func (g *Container) ArrayI(index int) (*Container, error) { + return g.SetIndex([]interface{}{}, index) +} + +/* +ArrayOfSize - Create a new JSON array of a particular size at a path. Returns an error if the path +contains a collision with a non object type. +*/ +func (g *Container) ArrayOfSize(size int, path ...string) (*Container, error) { + a := make([]interface{}, size) + return g.Set(a, path...) +} + +/* +ArrayOfSizeP - Does the same as ArrayOfSize, but using a dot notation JSON path. +*/ +func (g *Container) ArrayOfSizeP(size int, path string) (*Container, error) { + return g.ArrayOfSize(size, strings.Split(path, ".")...) +} + +/* +ArrayOfSizeI - Create a new JSON array of a particular size at an array index. Returns an error if +the object is not an array or the index is out of bounds. +*/ +func (g *Container) ArrayOfSizeI(size, index int) (*Container, error) { + a := make([]interface{}, size) + return g.SetIndex(a, index) +} + +/* +Delete - Delete an element at a JSON path, an error is returned if the element does not exist. +*/ +func (g *Container) Delete(path ...string) error { + var object interface{} + + if g.object == nil { + return ErrNotObj + } + object = g.object + for target := 0; target < len(path); target++ { + if mmap, ok := object.(map[string]interface{}); ok { + if target == len(path)-1 { + delete(mmap, path[target]) + } else if mmap[path[target]] == nil { + return ErrNotObj + } + object = mmap[path[target]] + } else { + return ErrNotObj + } + } + return nil +} + +/* +DeleteP - Does the same as Delete, but using a dot notation JSON path. +*/ +func (g *Container) DeleteP(path string) error { + return g.Delete(strings.Split(path, ".")...) +} + +/*--------------------------------------------------------------------------------------------------- + */ + +/* +Array modification/search - Keeping these options simple right now, no need for anything more +complicated since you can just cast to []interface{}, modify and then reassign with Set. +*/ + +/* +ArrayAppend - Append a value onto a JSON array. +*/ +func (g *Container) ArrayAppend(value interface{}, path ...string) error { + array, ok := g.Search(path...).Data().([]interface{}) + if !ok { + return ErrNotArray + } + array = append(array, value) + _, err := g.Set(array, path...) + return err +} + +/* +ArrayAppendP - Append a value onto a JSON array using a dot notation JSON path. +*/ +func (g *Container) ArrayAppendP(value interface{}, path string) error { + return g.ArrayAppend(value, strings.Split(path, ".")...) +} + +/* +ArrayRemove - Remove an element from a JSON array. +*/ +func (g *Container) ArrayRemove(index int, path ...string) error { + if index < 0 { + return ErrOutOfBounds + } + array, ok := g.Search(path...).Data().([]interface{}) + if !ok { + return ErrNotArray + } + if index < len(array) { + array = append(array[:index], array[index+1:]...) + } else { + return ErrOutOfBounds + } + _, err := g.Set(array, path...) + return err +} + +/* +ArrayRemoveP - Remove an element from a JSON array using a dot notation JSON path. +*/ +func (g *Container) ArrayRemoveP(index int, path string) error { + return g.ArrayRemove(index, strings.Split(path, ".")...) +} + +/* +ArrayElement - Access an element from a JSON array. +*/ +func (g *Container) ArrayElement(index int, path ...string) (*Container, error) { + if index < 0 { + return &Container{nil}, ErrOutOfBounds + } + array, ok := g.Search(path...).Data().([]interface{}) + if !ok { + return &Container{nil}, ErrNotArray + } + if index < len(array) { + return &Container{array[index]}, nil + } + return &Container{nil}, ErrOutOfBounds +} + +/* +ArrayElementP - Access an element from a JSON array using a dot notation JSON path. +*/ +func (g *Container) ArrayElementP(index int, path string) (*Container, error) { + return g.ArrayElement(index, strings.Split(path, ".")...) +} + +/* +ArrayCount - Count the number of elements in a JSON array. +*/ +func (g *Container) ArrayCount(path ...string) (int, error) { + if array, ok := g.Search(path...).Data().([]interface{}); ok { + return len(array), nil + } + return 0, ErrNotArray +} + +/* +ArrayCountP - Count the number of elements in a JSON array using a dot notation JSON path. +*/ +func (g *Container) ArrayCountP(path string) (int, error) { + return g.ArrayCount(strings.Split(path, ".")...) +} + +/*--------------------------------------------------------------------------------------------------- + */ + +/* +Bytes - Converts the contained object back to a JSON []byte blob. +*/ +func (g *Container) Bytes() []byte { + if g.object != nil { + if bytes, err := json.Marshal(g.object); err == nil { + return bytes + } + } + return []byte("{}") +} + +/* +BytesIndent - Converts the contained object back to a JSON []byte blob formatted with prefix and indent. +*/ +func (g *Container) BytesIndent(prefix string, indent string) []byte { + if g.object != nil { + if bytes, err := json.MarshalIndent(g.object, prefix, indent); err == nil { + return bytes + } + } + return []byte("{}") +} + +/* +String - Converts the contained object back to a JSON formatted string. +*/ +func (g *Container) String() string { + return string(g.Bytes()) +} + +/* +StringIndent - Converts the contained object back to a JSON formatted string with prefix and indent. +*/ +func (g *Container) StringIndent(prefix string, indent string) string { + return string(g.BytesIndent(prefix, indent)) +} + +/* +New - Create a new gabs JSON object. +*/ +func New() *Container { + return &Container{map[string]interface{}{}} +} + +/* +Consume - Gobble up an already converted JSON object, or a fresh map[string]interface{} object. +*/ +func Consume(root interface{}) (*Container, error) { + return &Container{root}, nil +} + +/* +ParseJSON - Convert a string into a representation of the parsed JSON. +*/ +func ParseJSON(sample []byte) (*Container, error) { + var gabs Container + + if err := json.Unmarshal(sample, &gabs.object); err != nil { + return nil, err + } + + return &gabs, nil +} + +/* +ParseJSONFile - Read a file and convert into a representation of the parsed JSON. +*/ +func ParseJSONFile(path string) (*Container, error) { + if len(path) > 0 { + cBytes, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + container, err := ParseJSON(cBytes) + if err != nil { + return nil, err + } + + return container, nil + } + return nil, ErrInvalidPath +} + +/* +ParseJSONBuffer - Read the contents of a buffer into a representation of the parsed JSON. +*/ +func ParseJSONBuffer(buffer io.Reader) (*Container, error) { + var gabs Container + jsonDecoder := json.NewDecoder(buffer) + if err := jsonDecoder.Decode(&gabs.object); err != nil { + return nil, err + } + + return &gabs, nil +} + +/*--------------------------------------------------------------------------------------------------- + */ + +// DEPRECATED METHODS + +/* +Push - DEPRECATED: Push a value onto a JSON array. +*/ +func (g *Container) Push(target string, value interface{}) error { + if mmap, ok := g.Data().(map[string]interface{}); ok { + arrayTarget := mmap[target] + if array, ok := arrayTarget.([]interface{}); ok { + mmap[target] = append(array, value) + } else { + return ErrNotArray + } + } else { + return ErrNotObj + } + return nil +} + +/* +RemoveElement - DEPRECATED: Remove a value from a JSON array. +*/ +func (g *Container) RemoveElement(target string, index int) error { + if index < 0 { + return ErrOutOfBounds + } + if mmap, ok := g.Data().(map[string]interface{}); ok { + arrayTarget := mmap[target] + if array, ok := arrayTarget.([]interface{}); ok { + if index < len(array) { + mmap[target] = append(array[:index], array[index+1:]...) + } else { + return ErrOutOfBounds + } + } else { + return ErrNotArray + } + } else { + return ErrNotObj + } + return nil +} + +/* +GetElement - DEPRECATED: Get the desired element from a JSON array +*/ +func (g *Container) GetElement(target string, index int) *Container { + if index < 0 { + return &Container{nil} + } + if mmap, ok := g.Data().(map[string]interface{}); ok { + arrayTarget := mmap[target] + if array, ok := arrayTarget.([]interface{}); ok { + if index < len(array) { + return &Container{array[index]} + } + } + } + return &Container{nil} +} + +/* +CountElements - DEPRECATED: Count the elements of a JSON array, returns -1 if the target is not an +array +*/ +func (g *Container) CountElements(target string) int { + if mmap, ok := g.Data().(map[string]interface{}); ok { + arrayTarget := mmap[target] + if array, ok := arrayTarget.([]interface{}); ok { + return len(array) + } + } + return -1 +} + +/*--------------------------------------------------------------------------------------------------- + */ diff --git a/go/src/github.com/Sirupsen/logrus/LICENSE b/vendor/github.com/Sirupsen/logrus/LICENSE similarity index 100% rename from go/src/github.com/Sirupsen/logrus/LICENSE rename to vendor/github.com/Sirupsen/logrus/LICENSE diff --git a/go/src/github.com/Sirupsen/logrus/doc.go b/vendor/github.com/Sirupsen/logrus/doc.go similarity index 100% rename from go/src/github.com/Sirupsen/logrus/doc.go rename to vendor/github.com/Sirupsen/logrus/doc.go diff --git a/go/src/github.com/Sirupsen/logrus/entry.go b/vendor/github.com/Sirupsen/logrus/entry.go similarity index 99% rename from go/src/github.com/Sirupsen/logrus/entry.go rename to vendor/github.com/Sirupsen/logrus/entry.go index 9ae900bc..89e966e7 100644 --- a/go/src/github.com/Sirupsen/logrus/entry.go +++ b/vendor/github.com/Sirupsen/logrus/entry.go @@ -68,7 +68,7 @@ func (entry *Entry) WithField(key string, value interface{}) *Entry { // Add a map of fields to the Entry. func (entry *Entry) WithFields(fields Fields) *Entry { - data := Fields{} + data := make(Fields, len(entry.Data)+len(fields)) for k, v := range entry.Data { data[k] = v } diff --git a/go/src/github.com/Sirupsen/logrus/examples/basic/basic.go b/vendor/github.com/Sirupsen/logrus/examples/basic/basic.go similarity index 100% rename from go/src/github.com/Sirupsen/logrus/examples/basic/basic.go rename to vendor/github.com/Sirupsen/logrus/examples/basic/basic.go diff --git a/go/src/github.com/Sirupsen/logrus/examples/hook/hook.go b/vendor/github.com/Sirupsen/logrus/examples/hook/hook.go similarity index 100% rename from go/src/github.com/Sirupsen/logrus/examples/hook/hook.go rename to vendor/github.com/Sirupsen/logrus/examples/hook/hook.go diff --git a/go/src/github.com/Sirupsen/logrus/exported.go b/vendor/github.com/Sirupsen/logrus/exported.go similarity index 100% rename from go/src/github.com/Sirupsen/logrus/exported.go rename to vendor/github.com/Sirupsen/logrus/exported.go diff --git a/go/src/github.com/Sirupsen/logrus/formatter.go b/vendor/github.com/Sirupsen/logrus/formatter.go similarity index 85% rename from go/src/github.com/Sirupsen/logrus/formatter.go rename to vendor/github.com/Sirupsen/logrus/formatter.go index 104d689f..b5fbe934 100644 --- a/go/src/github.com/Sirupsen/logrus/formatter.go +++ b/vendor/github.com/Sirupsen/logrus/formatter.go @@ -31,18 +31,15 @@ type Formatter interface { // 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) { - _, ok := data["time"] - if ok { - data["fields.time"] = data["time"] + if t, ok := data["time"]; ok { + data["fields.time"] = t } - _, ok = data["msg"] - if ok { - data["fields.msg"] = data["msg"] + if m, ok := data["msg"]; ok { + data["fields.msg"] = m } - _, ok = data["level"] - if ok { - data["fields.level"] = data["level"] + if l, ok := data["level"]; ok { + data["fields.level"] = l } } diff --git a/go/src/github.com/Sirupsen/logrus/formatters/logstash/logstash.go b/vendor/github.com/Sirupsen/logrus/formatters/logstash/logstash.go similarity index 60% rename from go/src/github.com/Sirupsen/logrus/formatters/logstash/logstash.go rename to vendor/github.com/Sirupsen/logrus/formatters/logstash/logstash.go index 8ea93ddf..2793af83 100644 --- a/go/src/github.com/Sirupsen/logrus/formatters/logstash/logstash.go +++ b/vendor/github.com/Sirupsen/logrus/formatters/logstash/logstash.go @@ -17,38 +17,45 @@ type LogstashFormatter struct { } func (f *LogstashFormatter) Format(entry *logrus.Entry) ([]byte, error) { - entry.Data["@version"] = 1 + fields := make(logrus.Fields) + for k, v := range entry.Data { + fields[k] = v + } + + fields["@version"] = 1 + + timeStampFormat := f.TimestampFormat - if f.TimestampFormat == "" { - f.TimestampFormat = logrus.DefaultTimestampFormat + if timeStampFormat == "" { + timeStampFormat = logrus.DefaultTimestampFormat } - entry.Data["@timestamp"] = entry.Time.Format(f.TimestampFormat) + fields["@timestamp"] = entry.Time.Format(timeStampFormat) // set message field v, ok := entry.Data["message"] if ok { - entry.Data["fields.message"] = v + fields["fields.message"] = v } - entry.Data["message"] = entry.Message + fields["message"] = entry.Message // set level field v, ok = entry.Data["level"] if ok { - entry.Data["fields.level"] = v + fields["fields.level"] = v } - entry.Data["level"] = entry.Level.String() + fields["level"] = entry.Level.String() // set type field if f.Type != "" { v, ok = entry.Data["type"] if ok { - entry.Data["fields.type"] = v + fields["fields.type"] = v } - entry.Data["type"] = f.Type + fields["type"] = f.Type } - serialized, err := json.Marshal(entry.Data) + serialized, err := json.Marshal(fields) if err != nil { return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err) } diff --git a/go/src/github.com/Sirupsen/logrus/hooks.go b/vendor/github.com/Sirupsen/logrus/hooks.go similarity index 100% rename from go/src/github.com/Sirupsen/logrus/hooks.go rename to vendor/github.com/Sirupsen/logrus/hooks.go diff --git a/go/src/github.com/Sirupsen/logrus/hooks/syslog/syslog.go b/vendor/github.com/Sirupsen/logrus/hooks/syslog/syslog.go similarity index 89% rename from go/src/github.com/Sirupsen/logrus/hooks/syslog/syslog.go rename to vendor/github.com/Sirupsen/logrus/hooks/syslog/syslog.go index b6fa3746..a36e2003 100644 --- a/go/src/github.com/Sirupsen/logrus/hooks/syslog/syslog.go +++ b/vendor/github.com/Sirupsen/logrus/hooks/syslog/syslog.go @@ -1,3 +1,5 @@ +// +build !windows,!nacl,!plan9 + package logrus_syslog import ( @@ -48,12 +50,5 @@ func (hook *SyslogHook) Fire(entry *logrus.Entry) error { } func (hook *SyslogHook) Levels() []logrus.Level { - return []logrus.Level{ - logrus.PanicLevel, - logrus.FatalLevel, - logrus.ErrorLevel, - logrus.WarnLevel, - logrus.InfoLevel, - logrus.DebugLevel, - } + return logrus.AllLevels } diff --git a/vendor/github.com/Sirupsen/logrus/hooks/test/test.go b/vendor/github.com/Sirupsen/logrus/hooks/test/test.go new file mode 100644 index 00000000..06881253 --- /dev/null +++ b/vendor/github.com/Sirupsen/logrus/hooks/test/test.go @@ -0,0 +1,67 @@ +package test + +import ( + "io/ioutil" + + "github.com/Sirupsen/logrus" +) + +// test.Hook is a hook designed for dealing with logs in test scenarios. +type Hook struct { + Entries []*logrus.Entry +} + +// Installs a test hook for the global logger. +func NewGlobal() *Hook { + + hook := new(Hook) + logrus.AddHook(hook) + + return hook + +} + +// Installs a test hook for a given local logger. +func NewLocal(logger *logrus.Logger) *Hook { + + hook := new(Hook) + logger.Hooks.Add(hook) + + return hook + +} + +// Creates a discarding logger and installs the test hook. +func NewNullLogger() (*logrus.Logger, *Hook) { + + logger := logrus.New() + logger.Out = ioutil.Discard + + return logger, NewLocal(logger) + +} + +func (t *Hook) Fire(e *logrus.Entry) error { + t.Entries = append(t.Entries, e) + return nil +} + +func (t *Hook) Levels() []logrus.Level { + return logrus.AllLevels +} + +// LastEntry returns the last entry that was logged or nil. +func (t *Hook) LastEntry() (l *logrus.Entry) { + + if i := len(t.Entries) - 1; i < 0 { + return nil + } else { + return t.Entries[i] + } + +} + +// Reset removes all Entries from this test hook. +func (t *Hook) Reset() { + t.Entries = make([]*logrus.Entry, 0) +} diff --git a/go/src/github.com/Sirupsen/logrus/json_formatter.go b/vendor/github.com/Sirupsen/logrus/json_formatter.go similarity index 100% rename from go/src/github.com/Sirupsen/logrus/json_formatter.go rename to vendor/github.com/Sirupsen/logrus/json_formatter.go diff --git a/go/src/github.com/Sirupsen/logrus/logger.go b/vendor/github.com/Sirupsen/logrus/logger.go similarity index 96% rename from go/src/github.com/Sirupsen/logrus/logger.go rename to vendor/github.com/Sirupsen/logrus/logger.go index fd9804c6..2fdb2317 100644 --- a/go/src/github.com/Sirupsen/logrus/logger.go +++ b/vendor/github.com/Sirupsen/logrus/logger.go @@ -64,6 +64,12 @@ func (logger *Logger) WithFields(fields Fields) *Entry { return NewEntry(logger).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 { + return NewEntry(logger).WithError(err) +} + func (logger *Logger) Debugf(format string, args ...interface{}) { if logger.Level >= DebugLevel { NewEntry(logger).Debugf(format, args...) diff --git a/go/src/github.com/Sirupsen/logrus/logrus.go b/vendor/github.com/Sirupsen/logrus/logrus.go similarity index 66% rename from go/src/github.com/Sirupsen/logrus/logrus.go rename to vendor/github.com/Sirupsen/logrus/logrus.go index 0c09fbc2..e5966911 100644 --- a/go/src/github.com/Sirupsen/logrus/logrus.go +++ b/vendor/github.com/Sirupsen/logrus/logrus.go @@ -3,6 +3,7 @@ package logrus import ( "fmt" "log" + "strings" ) // Fields type, used to pass to `WithFields`. @@ -33,7 +34,7 @@ func (level Level) String() string { // ParseLevel takes a string level and returns the Logrus log level constant. func ParseLevel(lvl string) (Level, error) { - switch lvl { + switch strings.ToLower(lvl) { case "panic": return PanicLevel, nil case "fatal": @@ -52,6 +53,16 @@ func ParseLevel(lvl string) (Level, error) { 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 ( @@ -96,3 +107,37 @@ type StdLogger 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{}) +} diff --git a/go/src/github.com/Sirupsen/logrus/terminal_bsd.go b/vendor/github.com/Sirupsen/logrus/terminal_bsd.go similarity index 100% rename from go/src/github.com/Sirupsen/logrus/terminal_bsd.go rename to vendor/github.com/Sirupsen/logrus/terminal_bsd.go diff --git a/go/src/github.com/Sirupsen/logrus/terminal_linux.go b/vendor/github.com/Sirupsen/logrus/terminal_linux.go similarity index 100% rename from go/src/github.com/Sirupsen/logrus/terminal_linux.go rename to vendor/github.com/Sirupsen/logrus/terminal_linux.go diff --git a/go/src/github.com/Sirupsen/logrus/terminal_notwindows.go b/vendor/github.com/Sirupsen/logrus/terminal_notwindows.go similarity index 83% rename from go/src/github.com/Sirupsen/logrus/terminal_notwindows.go rename to vendor/github.com/Sirupsen/logrus/terminal_notwindows.go index 4bb53760..b343b3a3 100644 --- a/go/src/github.com/Sirupsen/logrus/terminal_notwindows.go +++ b/vendor/github.com/Sirupsen/logrus/terminal_notwindows.go @@ -12,9 +12,9 @@ import ( "unsafe" ) -// IsTerminal returns true if the given file descriptor is a terminal. +// IsTerminal returns true if stderr's file descriptor is a terminal. func IsTerminal() bool { - fd := syscall.Stdout + fd := syscall.Stderr var termios Termios _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) return err == 0 diff --git a/vendor/github.com/Sirupsen/logrus/terminal_solaris.go b/vendor/github.com/Sirupsen/logrus/terminal_solaris.go new file mode 100644 index 00000000..3e70bf7b --- /dev/null +++ b/vendor/github.com/Sirupsen/logrus/terminal_solaris.go @@ -0,0 +1,15 @@ +// +build solaris + +package logrus + +import ( + "os" + + "golang.org/x/sys/unix" +) + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal() bool { + _, err := unix.IoctlGetTermios(int(os.Stdout.Fd()), unix.TCGETA) + return err == nil +} diff --git a/go/src/github.com/Sirupsen/logrus/terminal_windows.go b/vendor/github.com/Sirupsen/logrus/terminal_windows.go similarity index 85% rename from go/src/github.com/Sirupsen/logrus/terminal_windows.go rename to vendor/github.com/Sirupsen/logrus/terminal_windows.go index 2e09f6f7..0146845d 100644 --- a/go/src/github.com/Sirupsen/logrus/terminal_windows.go +++ b/vendor/github.com/Sirupsen/logrus/terminal_windows.go @@ -18,9 +18,9 @@ var ( procGetConsoleMode = kernel32.NewProc("GetConsoleMode") ) -// IsTerminal returns true if the given file descriptor is a terminal. +// IsTerminal returns true if stderr's file descriptor is a terminal. func IsTerminal() bool { - fd := syscall.Stdout + fd := syscall.Stderr var st uint32 r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) return r != 0 && e == 0 diff --git a/go/src/github.com/Sirupsen/logrus/text_formatter.go b/vendor/github.com/Sirupsen/logrus/text_formatter.go similarity index 97% rename from go/src/github.com/Sirupsen/logrus/text_formatter.go rename to vendor/github.com/Sirupsen/logrus/text_formatter.go index 06ef2023..6afd0e03 100644 --- a/go/src/github.com/Sirupsen/logrus/text_formatter.go +++ b/vendor/github.com/Sirupsen/logrus/text_formatter.go @@ -128,10 +128,10 @@ func needsQuoting(text string) bool { (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '-' || ch == '.') { - return false + return true } } - return true + return false } func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) { @@ -141,14 +141,14 @@ func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interf switch value := value.(type) { case string: - if needsQuoting(value) { + if !needsQuoting(value) { b.WriteString(value) } else { fmt.Fprintf(b, "%q", value) } case error: errmsg := value.Error() - if needsQuoting(errmsg) { + if !needsQuoting(errmsg) { b.WriteString(errmsg) } else { fmt.Fprintf(b, "%q", value) diff --git a/vendor/github.com/Sirupsen/logrus/writer.go b/vendor/github.com/Sirupsen/logrus/writer.go new file mode 100644 index 00000000..f74d2aa5 --- /dev/null +++ b/vendor/github.com/Sirupsen/logrus/writer.go @@ -0,0 +1,53 @@ +package logrus + +import ( + "bufio" + "io" + "runtime" +) + +func (logger *Logger) Writer() *io.PipeWriter { + return logger.WriterLevel(InfoLevel) +} + +func (logger *Logger) WriterLevel(level Level) *io.PipeWriter { + reader, writer := io.Pipe() + + var printFunc func(args ...interface{}) + switch level { + case DebugLevel: + printFunc = logger.Debug + case InfoLevel: + printFunc = logger.Info + case WarnLevel: + printFunc = logger.Warn + case ErrorLevel: + printFunc = logger.Error + case FatalLevel: + printFunc = logger.Fatal + case PanicLevel: + printFunc = logger.Panic + default: + printFunc = logger.Print + } + + go logger.writerScanner(reader, printFunc) + runtime.SetFinalizer(writer, writerFinalizer) + + return writer +} + +func (logger *Logger) writerScanner(reader *io.PipeReader, printFunc func(args ...interface{})) { + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + printFunc(scanner.Text()) + } + if err := scanner.Err(); err != nil { + logger.Errorf("Error while reading from Writer: %s", err) + } + reader.Close() +} + +func writerFinalizer(writer *io.PipeWriter) { + writer.Close() +} diff --git a/vendor/github.com/davecgh/go-spew/spew/LICENSE b/vendor/github.com/davecgh/go-spew/spew/LICENSE new file mode 100644 index 00000000..2a7cfd2b --- /dev/null +++ b/vendor/github.com/davecgh/go-spew/spew/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2012-2013 Dave Collins + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/vendor/github.com/davecgh/go-spew/spew/bypass.go b/vendor/github.com/davecgh/go-spew/spew/bypass.go new file mode 100644 index 00000000..565bf589 --- /dev/null +++ b/vendor/github.com/davecgh/go-spew/spew/bypass.go @@ -0,0 +1,151 @@ +// Copyright (c) 2015 Dave Collins +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +// NOTE: Due to the following build constraints, this file will only be compiled +// when the code is not running on Google App Engine and "-tags disableunsafe" +// is not added to the go build command line. +// +build !appengine,!disableunsafe + +package spew + +import ( + "reflect" + "unsafe" +) + +const ( + // UnsafeDisabled is a build-time constant which specifies whether or + // not access to the unsafe package is available. + UnsafeDisabled = false + + // ptrSize is the size of a pointer on the current arch. + ptrSize = unsafe.Sizeof((*byte)(nil)) +) + +var ( + // offsetPtr, offsetScalar, and offsetFlag are the offsets for the + // internal reflect.Value fields. These values are valid before golang + // commit ecccf07e7f9d which changed the format. The are also valid + // after commit 82f48826c6c7 which changed the format again to mirror + // the original format. Code in the init function updates these offsets + // as necessary. + offsetPtr = uintptr(ptrSize) + offsetScalar = uintptr(0) + offsetFlag = uintptr(ptrSize * 2) + + // flagKindWidth and flagKindShift indicate various bits that the + // reflect package uses internally to track kind information. + // + // flagRO indicates whether or not the value field of a reflect.Value is + // read-only. + // + // flagIndir indicates whether the value field of a reflect.Value is + // the actual data or a pointer to the data. + // + // These values are valid before golang commit 90a7c3c86944 which + // changed their positions. Code in the init function updates these + // flags as necessary. + flagKindWidth = uintptr(5) + flagKindShift = uintptr(flagKindWidth - 1) + flagRO = uintptr(1 << 0) + flagIndir = uintptr(1 << 1) +) + +func init() { + // Older versions of reflect.Value stored small integers directly in the + // ptr field (which is named val in the older versions). Versions + // between commits ecccf07e7f9d and 82f48826c6c7 added a new field named + // scalar for this purpose which unfortunately came before the flag + // field, so the offset of the flag field is different for those + // versions. + // + // This code constructs a new reflect.Value from a known small integer + // and checks if the size of the reflect.Value struct indicates it has + // the scalar field. When it does, the offsets are updated accordingly. + vv := reflect.ValueOf(0xf00) + if unsafe.Sizeof(vv) == (ptrSize * 4) { + offsetScalar = ptrSize * 2 + offsetFlag = ptrSize * 3 + } + + // Commit 90a7c3c86944 changed the flag positions such that the low + // order bits are the kind. This code extracts the kind from the flags + // field and ensures it's the correct type. When it's not, the flag + // order has been changed to the newer format, so the flags are updated + // accordingly. + upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag) + upfv := *(*uintptr)(upf) + flagKindMask := uintptr((1<>flagKindShift != uintptr(reflect.Int) { + flagKindShift = 0 + flagRO = 1 << 5 + flagIndir = 1 << 6 + + // Commit adf9b30e5594 modified the flags to separate the + // flagRO flag into two bits which specifies whether or not the + // field is embedded. This causes flagIndir to move over a bit + // and means that flagRO is the combination of either of the + // original flagRO bit and the new bit. + // + // This code detects the change by extracting what used to be + // the indirect bit to ensure it's set. When it's not, the flag + // order has been changed to the newer format, so the flags are + // updated accordingly. + if upfv&flagIndir == 0 { + flagRO = 3 << 5 + flagIndir = 1 << 7 + } + } +} + +// unsafeReflectValue converts the passed reflect.Value into a one that bypasses +// the typical safety restrictions preventing access to unaddressable and +// unexported data. It works by digging the raw pointer to the underlying +// value out of the protected value and generating a new unprotected (unsafe) +// reflect.Value to it. +// +// This allows us to check for implementations of the Stringer and error +// interfaces to be used for pretty printing ordinarily unaddressable and +// inaccessible values such as unexported struct fields. +func unsafeReflectValue(v reflect.Value) (rv reflect.Value) { + indirects := 1 + vt := v.Type() + upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr) + rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag)) + if rvf&flagIndir != 0 { + vt = reflect.PtrTo(v.Type()) + indirects++ + } else if offsetScalar != 0 { + // The value is in the scalar field when it's not one of the + // reference types. + switch vt.Kind() { + case reflect.Uintptr: + case reflect.Chan: + case reflect.Func: + case reflect.Map: + case reflect.Ptr: + case reflect.UnsafePointer: + default: + upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + + offsetScalar) + } + } + + pv := reflect.NewAt(vt, upv) + rv = pv + for i := 0; i < indirects; i++ { + rv = rv.Elem() + } + return rv +} diff --git a/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go b/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go new file mode 100644 index 00000000..457e4123 --- /dev/null +++ b/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go @@ -0,0 +1,37 @@ +// Copyright (c) 2015 Dave Collins +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +// NOTE: Due to the following build constraints, this file will only be compiled +// when either the code is running on Google App Engine or "-tags disableunsafe" +// is added to the go build command line. +// +build appengine disableunsafe + +package spew + +import "reflect" + +const ( + // UnsafeDisabled is a build-time constant which specifies whether or + // not access to the unsafe package is available. + UnsafeDisabled = true +) + +// unsafeReflectValue typically converts the passed reflect.Value into a one +// that bypasses the typical safety restrictions preventing access to +// unaddressable and unexported data. However, doing this relies on access to +// the unsafe package. This is a stub version which simply returns the passed +// reflect.Value when the unsafe package is not available. +func unsafeReflectValue(v reflect.Value) reflect.Value { + return v +} diff --git a/vendor/github.com/davecgh/go-spew/spew/common.go b/vendor/github.com/davecgh/go-spew/spew/common.go new file mode 100644 index 00000000..14f02dc1 --- /dev/null +++ b/vendor/github.com/davecgh/go-spew/spew/common.go @@ -0,0 +1,341 @@ +/* + * Copyright (c) 2013 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package spew + +import ( + "bytes" + "fmt" + "io" + "reflect" + "sort" + "strconv" +) + +// Some constants in the form of bytes to avoid string overhead. This mirrors +// the technique used in the fmt package. +var ( + panicBytes = []byte("(PANIC=") + plusBytes = []byte("+") + iBytes = []byte("i") + trueBytes = []byte("true") + falseBytes = []byte("false") + interfaceBytes = []byte("(interface {})") + commaNewlineBytes = []byte(",\n") + newlineBytes = []byte("\n") + openBraceBytes = []byte("{") + openBraceNewlineBytes = []byte("{\n") + closeBraceBytes = []byte("}") + asteriskBytes = []byte("*") + colonBytes = []byte(":") + colonSpaceBytes = []byte(": ") + openParenBytes = []byte("(") + closeParenBytes = []byte(")") + spaceBytes = []byte(" ") + pointerChainBytes = []byte("->") + nilAngleBytes = []byte("") + maxNewlineBytes = []byte("\n") + maxShortBytes = []byte("") + circularBytes = []byte("") + circularShortBytes = []byte("") + invalidAngleBytes = []byte("") + openBracketBytes = []byte("[") + closeBracketBytes = []byte("]") + percentBytes = []byte("%") + precisionBytes = []byte(".") + openAngleBytes = []byte("<") + closeAngleBytes = []byte(">") + openMapBytes = []byte("map[") + closeMapBytes = []byte("]") + lenEqualsBytes = []byte("len=") + capEqualsBytes = []byte("cap=") +) + +// hexDigits is used to map a decimal value to a hex digit. +var hexDigits = "0123456789abcdef" + +// catchPanic handles any panics that might occur during the handleMethods +// calls. +func catchPanic(w io.Writer, v reflect.Value) { + if err := recover(); err != nil { + w.Write(panicBytes) + fmt.Fprintf(w, "%v", err) + w.Write(closeParenBytes) + } +} + +// handleMethods attempts to call the Error and String methods on the underlying +// type the passed reflect.Value represents and outputes the result to Writer w. +// +// It handles panics in any called methods by catching and displaying the error +// as the formatted value. +func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) { + // We need an interface to check if the type implements the error or + // Stringer interface. However, the reflect package won't give us an + // interface on certain things like unexported struct fields in order + // to enforce visibility rules. We use unsafe, when it's available, + // to bypass these restrictions since this package does not mutate the + // values. + if !v.CanInterface() { + if UnsafeDisabled { + return false + } + + v = unsafeReflectValue(v) + } + + // Choose whether or not to do error and Stringer interface lookups against + // the base type or a pointer to the base type depending on settings. + // Technically calling one of these methods with a pointer receiver can + // mutate the value, however, types which choose to satisify an error or + // Stringer interface with a pointer receiver should not be mutating their + // state inside these interface methods. + if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() { + v = unsafeReflectValue(v) + } + if v.CanAddr() { + v = v.Addr() + } + + // Is it an error or Stringer? + switch iface := v.Interface().(type) { + case error: + defer catchPanic(w, v) + if cs.ContinueOnMethod { + w.Write(openParenBytes) + w.Write([]byte(iface.Error())) + w.Write(closeParenBytes) + w.Write(spaceBytes) + return false + } + + w.Write([]byte(iface.Error())) + return true + + case fmt.Stringer: + defer catchPanic(w, v) + if cs.ContinueOnMethod { + w.Write(openParenBytes) + w.Write([]byte(iface.String())) + w.Write(closeParenBytes) + w.Write(spaceBytes) + return false + } + w.Write([]byte(iface.String())) + return true + } + return false +} + +// printBool outputs a boolean value as true or false to Writer w. +func printBool(w io.Writer, val bool) { + if val { + w.Write(trueBytes) + } else { + w.Write(falseBytes) + } +} + +// printInt outputs a signed integer value to Writer w. +func printInt(w io.Writer, val int64, base int) { + w.Write([]byte(strconv.FormatInt(val, base))) +} + +// printUint outputs an unsigned integer value to Writer w. +func printUint(w io.Writer, val uint64, base int) { + w.Write([]byte(strconv.FormatUint(val, base))) +} + +// printFloat outputs a floating point value using the specified precision, +// which is expected to be 32 or 64bit, to Writer w. +func printFloat(w io.Writer, val float64, precision int) { + w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision))) +} + +// printComplex outputs a complex value using the specified float precision +// for the real and imaginary parts to Writer w. +func printComplex(w io.Writer, c complex128, floatPrecision int) { + r := real(c) + w.Write(openParenBytes) + w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision))) + i := imag(c) + if i >= 0 { + w.Write(plusBytes) + } + w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision))) + w.Write(iBytes) + w.Write(closeParenBytes) +} + +// printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x' +// prefix to Writer w. +func printHexPtr(w io.Writer, p uintptr) { + // Null pointer. + num := uint64(p) + if num == 0 { + w.Write(nilAngleBytes) + return + } + + // Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix + buf := make([]byte, 18) + + // It's simpler to construct the hex string right to left. + base := uint64(16) + i := len(buf) - 1 + for num >= base { + buf[i] = hexDigits[num%base] + num /= base + i-- + } + buf[i] = hexDigits[num] + + // Add '0x' prefix. + i-- + buf[i] = 'x' + i-- + buf[i] = '0' + + // Strip unused leading bytes. + buf = buf[i:] + w.Write(buf) +} + +// valuesSorter implements sort.Interface to allow a slice of reflect.Value +// elements to be sorted. +type valuesSorter struct { + values []reflect.Value + strings []string // either nil or same len and values + cs *ConfigState +} + +// newValuesSorter initializes a valuesSorter instance, which holds a set of +// surrogate keys on which the data should be sorted. It uses flags in +// ConfigState to decide if and how to populate those surrogate keys. +func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface { + vs := &valuesSorter{values: values, cs: cs} + if canSortSimply(vs.values[0].Kind()) { + return vs + } + if !cs.DisableMethods { + vs.strings = make([]string, len(values)) + for i := range vs.values { + b := bytes.Buffer{} + if !handleMethods(cs, &b, vs.values[i]) { + vs.strings = nil + break + } + vs.strings[i] = b.String() + } + } + if vs.strings == nil && cs.SpewKeys { + vs.strings = make([]string, len(values)) + for i := range vs.values { + vs.strings[i] = Sprintf("%#v", vs.values[i].Interface()) + } + } + return vs +} + +// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted +// directly, or whether it should be considered for sorting by surrogate keys +// (if the ConfigState allows it). +func canSortSimply(kind reflect.Kind) bool { + // This switch parallels valueSortLess, except for the default case. + switch kind { + case reflect.Bool: + return true + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: + return true + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + return true + case reflect.Float32, reflect.Float64: + return true + case reflect.String: + return true + case reflect.Uintptr: + return true + case reflect.Array: + return true + } + return false +} + +// Len returns the number of values in the slice. It is part of the +// sort.Interface implementation. +func (s *valuesSorter) Len() int { + return len(s.values) +} + +// Swap swaps the values at the passed indices. It is part of the +// sort.Interface implementation. +func (s *valuesSorter) Swap(i, j int) { + s.values[i], s.values[j] = s.values[j], s.values[i] + if s.strings != nil { + s.strings[i], s.strings[j] = s.strings[j], s.strings[i] + } +} + +// valueSortLess returns whether the first value should sort before the second +// value. It is used by valueSorter.Less as part of the sort.Interface +// implementation. +func valueSortLess(a, b reflect.Value) bool { + switch a.Kind() { + case reflect.Bool: + return !a.Bool() && b.Bool() + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: + return a.Int() < b.Int() + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + return a.Uint() < b.Uint() + case reflect.Float32, reflect.Float64: + return a.Float() < b.Float() + case reflect.String: + return a.String() < b.String() + case reflect.Uintptr: + return a.Uint() < b.Uint() + case reflect.Array: + // Compare the contents of both arrays. + l := a.Len() + for i := 0; i < l; i++ { + av := a.Index(i) + bv := b.Index(i) + if av.Interface() == bv.Interface() { + continue + } + return valueSortLess(av, bv) + } + } + return a.String() < b.String() +} + +// Less returns whether the value at index i should sort before the +// value at index j. It is part of the sort.Interface implementation. +func (s *valuesSorter) Less(i, j int) bool { + if s.strings == nil { + return valueSortLess(s.values[i], s.values[j]) + } + return s.strings[i] < s.strings[j] +} + +// sortValues is a sort function that handles both native types and any type that +// can be converted to error or Stringer. Other inputs are sorted according to +// their Value.String() value to ensure display stability. +func sortValues(values []reflect.Value, cs *ConfigState) { + if len(values) == 0 { + return + } + sort.Sort(newValuesSorter(values, cs)) +} diff --git a/vendor/github.com/davecgh/go-spew/spew/config.go b/vendor/github.com/davecgh/go-spew/spew/config.go new file mode 100644 index 00000000..ee1ab07b --- /dev/null +++ b/vendor/github.com/davecgh/go-spew/spew/config.go @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2013 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package spew + +import ( + "bytes" + "fmt" + "io" + "os" +) + +// ConfigState houses the configuration options used by spew to format and +// display values. There is a global instance, Config, that is used to control +// all top-level Formatter and Dump functionality. Each ConfigState instance +// provides methods equivalent to the top-level functions. +// +// The zero value for ConfigState provides no indentation. You would typically +// want to set it to a space or a tab. +// +// Alternatively, you can use NewDefaultConfig to get a ConfigState instance +// with default settings. See the documentation of NewDefaultConfig for default +// values. +type ConfigState struct { + // Indent specifies the string to use for each indentation level. The + // global config instance that all top-level functions use set this to a + // single space by default. If you would like more indentation, you might + // set this to a tab with "\t" or perhaps two spaces with " ". + Indent string + + // MaxDepth controls the maximum number of levels to descend into nested + // data structures. The default, 0, means there is no limit. + // + // NOTE: Circular data structures are properly detected, so it is not + // necessary to set this value unless you specifically want to limit deeply + // nested data structures. + MaxDepth int + + // DisableMethods specifies whether or not error and Stringer interfaces are + // invoked for types that implement them. + DisableMethods bool + + // DisablePointerMethods specifies whether or not to check for and invoke + // error and Stringer interfaces on types which only accept a pointer + // receiver when the current type is not a pointer. + // + // NOTE: This might be an unsafe action since calling one of these methods + // with a pointer receiver could technically mutate the value, however, + // in practice, types which choose to satisify an error or Stringer + // interface with a pointer receiver should not be mutating their state + // inside these interface methods. As a result, this option relies on + // access to the unsafe package, so it will not have any effect when + // running in environments without access to the unsafe package such as + // Google App Engine or with the "disableunsafe" build tag specified. + DisablePointerMethods bool + + // ContinueOnMethod specifies whether or not recursion should continue once + // a custom error or Stringer interface is invoked. The default, false, + // means it will print the results of invoking the custom error or Stringer + // interface and return immediately instead of continuing to recurse into + // the internals of the data type. + // + // NOTE: This flag does not have any effect if method invocation is disabled + // via the DisableMethods or DisablePointerMethods options. + ContinueOnMethod bool + + // SortKeys specifies map keys should be sorted before being printed. Use + // this to have a more deterministic, diffable output. Note that only + // native types (bool, int, uint, floats, uintptr and string) and types + // that support the error or Stringer interfaces (if methods are + // enabled) are supported, with other types sorted according to the + // reflect.Value.String() output which guarantees display stability. + SortKeys bool + + // SpewKeys specifies that, as a last resort attempt, map keys should + // be spewed to strings and sorted by those strings. This is only + // considered if SortKeys is true. + SpewKeys bool +} + +// Config is the active configuration of the top-level functions. +// The configuration can be changed by modifying the contents of spew.Config. +var Config = ConfigState{Indent: " "} + +// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the formatted string as a value that satisfies error. See NewFormatter +// for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) { + return fmt.Errorf(format, c.convertArgs(a)...) +} + +// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) { + return fmt.Fprint(w, c.convertArgs(a)...) +} + +// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { + return fmt.Fprintf(w, format, c.convertArgs(a)...) +} + +// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it +// passed with a Formatter interface returned by c.NewFormatter. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) { + return fmt.Fprintln(w, c.convertArgs(a)...) +} + +// Print is a wrapper for fmt.Print that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Print(c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Print(a ...interface{}) (n int, err error) { + return fmt.Print(c.convertArgs(a)...) +} + +// Printf is a wrapper for fmt.Printf that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) { + return fmt.Printf(format, c.convertArgs(a)...) +} + +// Println is a wrapper for fmt.Println that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Println(c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Println(a ...interface{}) (n int, err error) { + return fmt.Println(c.convertArgs(a)...) +} + +// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the resulting string. See NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Sprint(a ...interface{}) string { + return fmt.Sprint(c.convertArgs(a)...) +} + +// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the resulting string. See NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Sprintf(format string, a ...interface{}) string { + return fmt.Sprintf(format, c.convertArgs(a)...) +} + +// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it +// were passed with a Formatter interface returned by c.NewFormatter. It +// returns the resulting string. See NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Sprintln(a ...interface{}) string { + return fmt.Sprintln(c.convertArgs(a)...) +} + +/* +NewFormatter returns a custom formatter that satisfies the fmt.Formatter +interface. As a result, it integrates cleanly with standard fmt package +printing functions. The formatter is useful for inline printing of smaller data +types similar to the standard %v format specifier. + +The custom formatter only responds to the %v (most compact), %+v (adds pointer +addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb +combinations. Any other verbs such as %x and %q will be sent to the the +standard fmt package for formatting. In addition, the custom formatter ignores +the width and precision arguments (however they will still work on the format +specifiers not handled by the custom formatter). + +Typically this function shouldn't be called directly. It is much easier to make +use of the custom formatter by calling one of the convenience functions such as +c.Printf, c.Println, or c.Printf. +*/ +func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter { + return newFormatter(c, v) +} + +// Fdump formats and displays the passed arguments to io.Writer w. It formats +// exactly the same as Dump. +func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) { + fdump(c, w, a...) +} + +/* +Dump displays the passed parameters to standard out with newlines, customizable +indentation, and additional debug information such as complete types and all +pointer addresses used to indirect to the final value. It provides the +following features over the built-in printing facilities provided by the fmt +package: + + * Pointers are dereferenced and followed + * Circular data structures are detected and handled properly + * Custom Stringer/error interfaces are optionally invoked, including + on unexported types + * Custom types which only implement the Stringer/error interfaces via + a pointer receiver are optionally invoked when passing non-pointer + variables + * Byte arrays and slices are dumped like the hexdump -C command which + includes offsets, byte values in hex, and ASCII output + +The configuration options are controlled by modifying the public members +of c. See ConfigState for options documentation. + +See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to +get the formatted result as a string. +*/ +func (c *ConfigState) Dump(a ...interface{}) { + fdump(c, os.Stdout, a...) +} + +// Sdump returns a string with the passed arguments formatted exactly the same +// as Dump. +func (c *ConfigState) Sdump(a ...interface{}) string { + var buf bytes.Buffer + fdump(c, &buf, a...) + return buf.String() +} + +// convertArgs accepts a slice of arguments and returns a slice of the same +// length with each argument converted to a spew Formatter interface using +// the ConfigState associated with s. +func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) { + formatters = make([]interface{}, len(args)) + for index, arg := range args { + formatters[index] = newFormatter(c, arg) + } + return formatters +} + +// NewDefaultConfig returns a ConfigState with the following default settings. +// +// Indent: " " +// MaxDepth: 0 +// DisableMethods: false +// DisablePointerMethods: false +// ContinueOnMethod: false +// SortKeys: false +func NewDefaultConfig() *ConfigState { + return &ConfigState{Indent: " "} +} diff --git a/vendor/github.com/davecgh/go-spew/spew/doc.go b/vendor/github.com/davecgh/go-spew/spew/doc.go new file mode 100644 index 00000000..5be0c406 --- /dev/null +++ b/vendor/github.com/davecgh/go-spew/spew/doc.go @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2013 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* +Package spew implements a deep pretty printer for Go data structures to aid in +debugging. + +A quick overview of the additional features spew provides over the built-in +printing facilities for Go data types are as follows: + + * Pointers are dereferenced and followed + * Circular data structures are detected and handled properly + * Custom Stringer/error interfaces are optionally invoked, including + on unexported types + * Custom types which only implement the Stringer/error interfaces via + a pointer receiver are optionally invoked when passing non-pointer + variables + * Byte arrays and slices are dumped like the hexdump -C command which + includes offsets, byte values in hex, and ASCII output (only when using + Dump style) + +There are two different approaches spew allows for dumping Go data structures: + + * Dump style which prints with newlines, customizable indentation, + and additional debug information such as types and all pointer addresses + used to indirect to the final value + * A custom Formatter interface that integrates cleanly with the standard fmt + package and replaces %v, %+v, %#v, and %#+v to provide inline printing + similar to the default %v while providing the additional functionality + outlined above and passing unsupported format verbs such as %x and %q + along to fmt + +Quick Start + +This section demonstrates how to quickly get started with spew. See the +sections below for further details on formatting and configuration options. + +To dump a variable with full newlines, indentation, type, and pointer +information use Dump, Fdump, or Sdump: + spew.Dump(myVar1, myVar2, ...) + spew.Fdump(someWriter, myVar1, myVar2, ...) + str := spew.Sdump(myVar1, myVar2, ...) + +Alternatively, if you would prefer to use format strings with a compacted inline +printing style, use the convenience wrappers Printf, Fprintf, etc with +%v (most compact), %+v (adds pointer addresses), %#v (adds types), or +%#+v (adds types and pointer addresses): + spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2) + spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) + spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2) + spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) + +Configuration Options + +Configuration of spew is handled by fields in the ConfigState type. For +convenience, all of the top-level functions use a global state available +via the spew.Config global. + +It is also possible to create a ConfigState instance that provides methods +equivalent to the top-level functions. This allows concurrent configuration +options. See the ConfigState documentation for more details. + +The following configuration options are available: + * Indent + String to use for each indentation level for Dump functions. + It is a single space by default. A popular alternative is "\t". + + * MaxDepth + Maximum number of levels to descend into nested data structures. + There is no limit by default. + + * DisableMethods + Disables invocation of error and Stringer interface methods. + Method invocation is enabled by default. + + * DisablePointerMethods + Disables invocation of error and Stringer interface methods on types + which only accept pointer receivers from non-pointer variables. + Pointer method invocation is enabled by default. + + * ContinueOnMethod + Enables recursion into types after invoking error and Stringer interface + methods. Recursion after method invocation is disabled by default. + + * SortKeys + Specifies map keys should be sorted before being printed. Use + this to have a more deterministic, diffable output. Note that + only native types (bool, int, uint, floats, uintptr and string) + and types which implement error or Stringer interfaces are + supported with other types sorted according to the + reflect.Value.String() output which guarantees display + stability. Natural map order is used by default. + + * SpewKeys + Specifies that, as a last resort attempt, map keys should be + spewed to strings and sorted by those strings. This is only + considered if SortKeys is true. + +Dump Usage + +Simply call spew.Dump with a list of variables you want to dump: + + spew.Dump(myVar1, myVar2, ...) + +You may also call spew.Fdump if you would prefer to output to an arbitrary +io.Writer. For example, to dump to standard error: + + spew.Fdump(os.Stderr, myVar1, myVar2, ...) + +A third option is to call spew.Sdump to get the formatted output as a string: + + str := spew.Sdump(myVar1, myVar2, ...) + +Sample Dump Output + +See the Dump example for details on the setup of the types and variables being +shown here. + + (main.Foo) { + unexportedField: (*main.Bar)(0xf84002e210)({ + flag: (main.Flag) flagTwo, + data: (uintptr) + }), + ExportedField: (map[interface {}]interface {}) (len=1) { + (string) (len=3) "one": (bool) true + } + } + +Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C +command as shown. + ([]uint8) (len=32 cap=32) { + 00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... | + 00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0| + 00000020 31 32 |12| + } + +Custom Formatter + +Spew provides a custom formatter that implements the fmt.Formatter interface +so that it integrates cleanly with standard fmt package printing functions. The +formatter is useful for inline printing of smaller data types similar to the +standard %v format specifier. + +The custom formatter only responds to the %v (most compact), %+v (adds pointer +addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb +combinations. Any other verbs such as %x and %q will be sent to the the +standard fmt package for formatting. In addition, the custom formatter ignores +the width and precision arguments (however they will still work on the format +specifiers not handled by the custom formatter). + +Custom Formatter Usage + +The simplest way to make use of the spew custom formatter is to call one of the +convenience functions such as spew.Printf, spew.Println, or spew.Printf. The +functions have syntax you are most likely already familiar with: + + spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2) + spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) + spew.Println(myVar, myVar2) + spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2) + spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) + +See the Index for the full list convenience functions. + +Sample Formatter Output + +Double pointer to a uint8: + %v: <**>5 + %+v: <**>(0xf8400420d0->0xf8400420c8)5 + %#v: (**uint8)5 + %#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5 + +Pointer to circular struct with a uint8 field and a pointer to itself: + %v: <*>{1 <*>} + %+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)} + %#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)} + %#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)} + +See the Printf example for details on the setup of variables being shown +here. + +Errors + +Since it is possible for custom Stringer/error interfaces to panic, spew +detects them and handles them internally by printing the panic information +inline with the output. Since spew is intended to provide deep pretty printing +capabilities on structures, it intentionally does not return any errors. +*/ +package spew diff --git a/vendor/github.com/davecgh/go-spew/spew/dump.go b/vendor/github.com/davecgh/go-spew/spew/dump.go new file mode 100644 index 00000000..a0ff95e2 --- /dev/null +++ b/vendor/github.com/davecgh/go-spew/spew/dump.go @@ -0,0 +1,509 @@ +/* + * Copyright (c) 2013 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package spew + +import ( + "bytes" + "encoding/hex" + "fmt" + "io" + "os" + "reflect" + "regexp" + "strconv" + "strings" +) + +var ( + // uint8Type is a reflect.Type representing a uint8. It is used to + // convert cgo types to uint8 slices for hexdumping. + uint8Type = reflect.TypeOf(uint8(0)) + + // cCharRE is a regular expression that matches a cgo char. + // It is used to detect character arrays to hexdump them. + cCharRE = regexp.MustCompile("^.*\\._Ctype_char$") + + // cUnsignedCharRE is a regular expression that matches a cgo unsigned + // char. It is used to detect unsigned character arrays to hexdump + // them. + cUnsignedCharRE = regexp.MustCompile("^.*\\._Ctype_unsignedchar$") + + // cUint8tCharRE is a regular expression that matches a cgo uint8_t. + // It is used to detect uint8_t arrays to hexdump them. + cUint8tCharRE = regexp.MustCompile("^.*\\._Ctype_uint8_t$") +) + +// dumpState contains information about the state of a dump operation. +type dumpState struct { + w io.Writer + depth int + pointers map[uintptr]int + ignoreNextType bool + ignoreNextIndent bool + cs *ConfigState +} + +// indent performs indentation according to the depth level and cs.Indent +// option. +func (d *dumpState) indent() { + if d.ignoreNextIndent { + d.ignoreNextIndent = false + return + } + d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth)) +} + +// unpackValue returns values inside of non-nil interfaces when possible. +// This is useful for data types like structs, arrays, slices, and maps which +// can contain varying types packed inside an interface. +func (d *dumpState) unpackValue(v reflect.Value) reflect.Value { + if v.Kind() == reflect.Interface && !v.IsNil() { + v = v.Elem() + } + return v +} + +// dumpPtr handles formatting of pointers by indirecting them as necessary. +func (d *dumpState) dumpPtr(v reflect.Value) { + // Remove pointers at or below the current depth from map used to detect + // circular refs. + for k, depth := range d.pointers { + if depth >= d.depth { + delete(d.pointers, k) + } + } + + // Keep list of all dereferenced pointers to show later. + pointerChain := make([]uintptr, 0) + + // Figure out how many levels of indirection there are by dereferencing + // pointers and unpacking interfaces down the chain while detecting circular + // references. + nilFound := false + cycleFound := false + indirects := 0 + ve := v + for ve.Kind() == reflect.Ptr { + if ve.IsNil() { + nilFound = true + break + } + indirects++ + addr := ve.Pointer() + pointerChain = append(pointerChain, addr) + if pd, ok := d.pointers[addr]; ok && pd < d.depth { + cycleFound = true + indirects-- + break + } + d.pointers[addr] = d.depth + + ve = ve.Elem() + if ve.Kind() == reflect.Interface { + if ve.IsNil() { + nilFound = true + break + } + ve = ve.Elem() + } + } + + // Display type information. + d.w.Write(openParenBytes) + d.w.Write(bytes.Repeat(asteriskBytes, indirects)) + d.w.Write([]byte(ve.Type().String())) + d.w.Write(closeParenBytes) + + // Display pointer information. + if len(pointerChain) > 0 { + d.w.Write(openParenBytes) + for i, addr := range pointerChain { + if i > 0 { + d.w.Write(pointerChainBytes) + } + printHexPtr(d.w, addr) + } + d.w.Write(closeParenBytes) + } + + // Display dereferenced value. + d.w.Write(openParenBytes) + switch { + case nilFound == true: + d.w.Write(nilAngleBytes) + + case cycleFound == true: + d.w.Write(circularBytes) + + default: + d.ignoreNextType = true + d.dump(ve) + } + d.w.Write(closeParenBytes) +} + +// dumpSlice handles formatting of arrays and slices. Byte (uint8 under +// reflection) arrays and slices are dumped in hexdump -C fashion. +func (d *dumpState) dumpSlice(v reflect.Value) { + // Determine whether this type should be hex dumped or not. Also, + // for types which should be hexdumped, try to use the underlying data + // first, then fall back to trying to convert them to a uint8 slice. + var buf []uint8 + doConvert := false + doHexDump := false + numEntries := v.Len() + if numEntries > 0 { + vt := v.Index(0).Type() + vts := vt.String() + switch { + // C types that need to be converted. + case cCharRE.MatchString(vts): + fallthrough + case cUnsignedCharRE.MatchString(vts): + fallthrough + case cUint8tCharRE.MatchString(vts): + doConvert = true + + // Try to use existing uint8 slices and fall back to converting + // and copying if that fails. + case vt.Kind() == reflect.Uint8: + // We need an addressable interface to convert the type + // to a byte slice. However, the reflect package won't + // give us an interface on certain things like + // unexported struct fields in order to enforce + // visibility rules. We use unsafe, when available, to + // bypass these restrictions since this package does not + // mutate the values. + vs := v + if !vs.CanInterface() || !vs.CanAddr() { + vs = unsafeReflectValue(vs) + } + if !UnsafeDisabled { + vs = vs.Slice(0, numEntries) + + // Use the existing uint8 slice if it can be + // type asserted. + iface := vs.Interface() + if slice, ok := iface.([]uint8); ok { + buf = slice + doHexDump = true + break + } + } + + // The underlying data needs to be converted if it can't + // be type asserted to a uint8 slice. + doConvert = true + } + + // Copy and convert the underlying type if needed. + if doConvert && vt.ConvertibleTo(uint8Type) { + // Convert and copy each element into a uint8 byte + // slice. + buf = make([]uint8, numEntries) + for i := 0; i < numEntries; i++ { + vv := v.Index(i) + buf[i] = uint8(vv.Convert(uint8Type).Uint()) + } + doHexDump = true + } + } + + // Hexdump the entire slice as needed. + if doHexDump { + indent := strings.Repeat(d.cs.Indent, d.depth) + str := indent + hex.Dump(buf) + str = strings.Replace(str, "\n", "\n"+indent, -1) + str = strings.TrimRight(str, d.cs.Indent) + d.w.Write([]byte(str)) + return + } + + // Recursively call dump for each item. + for i := 0; i < numEntries; i++ { + d.dump(d.unpackValue(v.Index(i))) + if i < (numEntries - 1) { + d.w.Write(commaNewlineBytes) + } else { + d.w.Write(newlineBytes) + } + } +} + +// dump is the main workhorse for dumping a value. It uses the passed reflect +// value to figure out what kind of object we are dealing with and formats it +// appropriately. It is a recursive function, however circular data structures +// are detected and handled properly. +func (d *dumpState) dump(v reflect.Value) { + // Handle invalid reflect values immediately. + kind := v.Kind() + if kind == reflect.Invalid { + d.w.Write(invalidAngleBytes) + return + } + + // Handle pointers specially. + if kind == reflect.Ptr { + d.indent() + d.dumpPtr(v) + return + } + + // Print type information unless already handled elsewhere. + if !d.ignoreNextType { + d.indent() + d.w.Write(openParenBytes) + d.w.Write([]byte(v.Type().String())) + d.w.Write(closeParenBytes) + d.w.Write(spaceBytes) + } + d.ignoreNextType = false + + // Display length and capacity if the built-in len and cap functions + // work with the value's kind and the len/cap itself is non-zero. + valueLen, valueCap := 0, 0 + switch v.Kind() { + case reflect.Array, reflect.Slice, reflect.Chan: + valueLen, valueCap = v.Len(), v.Cap() + case reflect.Map, reflect.String: + valueLen = v.Len() + } + if valueLen != 0 || valueCap != 0 { + d.w.Write(openParenBytes) + if valueLen != 0 { + d.w.Write(lenEqualsBytes) + printInt(d.w, int64(valueLen), 10) + } + if valueCap != 0 { + if valueLen != 0 { + d.w.Write(spaceBytes) + } + d.w.Write(capEqualsBytes) + printInt(d.w, int64(valueCap), 10) + } + d.w.Write(closeParenBytes) + d.w.Write(spaceBytes) + } + + // Call Stringer/error interfaces if they exist and the handle methods flag + // is enabled + if !d.cs.DisableMethods { + if (kind != reflect.Invalid) && (kind != reflect.Interface) { + if handled := handleMethods(d.cs, d.w, v); handled { + return + } + } + } + + switch kind { + case reflect.Invalid: + // Do nothing. We should never get here since invalid has already + // been handled above. + + case reflect.Bool: + printBool(d.w, v.Bool()) + + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: + printInt(d.w, v.Int(), 10) + + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + printUint(d.w, v.Uint(), 10) + + case reflect.Float32: + printFloat(d.w, v.Float(), 32) + + case reflect.Float64: + printFloat(d.w, v.Float(), 64) + + case reflect.Complex64: + printComplex(d.w, v.Complex(), 32) + + case reflect.Complex128: + printComplex(d.w, v.Complex(), 64) + + case reflect.Slice: + if v.IsNil() { + d.w.Write(nilAngleBytes) + break + } + fallthrough + + case reflect.Array: + d.w.Write(openBraceNewlineBytes) + d.depth++ + if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) { + d.indent() + d.w.Write(maxNewlineBytes) + } else { + d.dumpSlice(v) + } + d.depth-- + d.indent() + d.w.Write(closeBraceBytes) + + case reflect.String: + d.w.Write([]byte(strconv.Quote(v.String()))) + + case reflect.Interface: + // The only time we should get here is for nil interfaces due to + // unpackValue calls. + if v.IsNil() { + d.w.Write(nilAngleBytes) + } + + case reflect.Ptr: + // Do nothing. We should never get here since pointers have already + // been handled above. + + case reflect.Map: + // nil maps should be indicated as different than empty maps + if v.IsNil() { + d.w.Write(nilAngleBytes) + break + } + + d.w.Write(openBraceNewlineBytes) + d.depth++ + if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) { + d.indent() + d.w.Write(maxNewlineBytes) + } else { + numEntries := v.Len() + keys := v.MapKeys() + if d.cs.SortKeys { + sortValues(keys, d.cs) + } + for i, key := range keys { + d.dump(d.unpackValue(key)) + d.w.Write(colonSpaceBytes) + d.ignoreNextIndent = true + d.dump(d.unpackValue(v.MapIndex(key))) + if i < (numEntries - 1) { + d.w.Write(commaNewlineBytes) + } else { + d.w.Write(newlineBytes) + } + } + } + d.depth-- + d.indent() + d.w.Write(closeBraceBytes) + + case reflect.Struct: + d.w.Write(openBraceNewlineBytes) + d.depth++ + if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) { + d.indent() + d.w.Write(maxNewlineBytes) + } else { + vt := v.Type() + numFields := v.NumField() + for i := 0; i < numFields; i++ { + d.indent() + vtf := vt.Field(i) + d.w.Write([]byte(vtf.Name)) + d.w.Write(colonSpaceBytes) + d.ignoreNextIndent = true + d.dump(d.unpackValue(v.Field(i))) + if i < (numFields - 1) { + d.w.Write(commaNewlineBytes) + } else { + d.w.Write(newlineBytes) + } + } + } + d.depth-- + d.indent() + d.w.Write(closeBraceBytes) + + case reflect.Uintptr: + printHexPtr(d.w, uintptr(v.Uint())) + + case reflect.UnsafePointer, reflect.Chan, reflect.Func: + printHexPtr(d.w, v.Pointer()) + + // There were not any other types at the time this code was written, but + // fall back to letting the default fmt package handle it in case any new + // types are added. + default: + if v.CanInterface() { + fmt.Fprintf(d.w, "%v", v.Interface()) + } else { + fmt.Fprintf(d.w, "%v", v.String()) + } + } +} + +// fdump is a helper function to consolidate the logic from the various public +// methods which take varying writers and config states. +func fdump(cs *ConfigState, w io.Writer, a ...interface{}) { + for _, arg := range a { + if arg == nil { + w.Write(interfaceBytes) + w.Write(spaceBytes) + w.Write(nilAngleBytes) + w.Write(newlineBytes) + continue + } + + d := dumpState{w: w, cs: cs} + d.pointers = make(map[uintptr]int) + d.dump(reflect.ValueOf(arg)) + d.w.Write(newlineBytes) + } +} + +// Fdump formats and displays the passed arguments to io.Writer w. It formats +// exactly the same as Dump. +func Fdump(w io.Writer, a ...interface{}) { + fdump(&Config, w, a...) +} + +// Sdump returns a string with the passed arguments formatted exactly the same +// as Dump. +func Sdump(a ...interface{}) string { + var buf bytes.Buffer + fdump(&Config, &buf, a...) + return buf.String() +} + +/* +Dump displays the passed parameters to standard out with newlines, customizable +indentation, and additional debug information such as complete types and all +pointer addresses used to indirect to the final value. It provides the +following features over the built-in printing facilities provided by the fmt +package: + + * Pointers are dereferenced and followed + * Circular data structures are detected and handled properly + * Custom Stringer/error interfaces are optionally invoked, including + on unexported types + * Custom types which only implement the Stringer/error interfaces via + a pointer receiver are optionally invoked when passing non-pointer + variables + * Byte arrays and slices are dumped like the hexdump -C command which + includes offsets, byte values in hex, and ASCII output + +The configuration options are controlled by an exported package global, +spew.Config. See ConfigState for options documentation. + +See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to +get the formatted result as a string. +*/ +func Dump(a ...interface{}) { + fdump(&Config, os.Stdout, a...) +} diff --git a/vendor/github.com/davecgh/go-spew/spew/format.go b/vendor/github.com/davecgh/go-spew/spew/format.go new file mode 100644 index 00000000..ecf3b80e --- /dev/null +++ b/vendor/github.com/davecgh/go-spew/spew/format.go @@ -0,0 +1,419 @@ +/* + * Copyright (c) 2013 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package spew + +import ( + "bytes" + "fmt" + "reflect" + "strconv" + "strings" +) + +// supportedFlags is a list of all the character flags supported by fmt package. +const supportedFlags = "0-+# " + +// formatState implements the fmt.Formatter interface and contains information +// about the state of a formatting operation. The NewFormatter function can +// be used to get a new Formatter which can be used directly as arguments +// in standard fmt package printing calls. +type formatState struct { + value interface{} + fs fmt.State + depth int + pointers map[uintptr]int + ignoreNextType bool + cs *ConfigState +} + +// buildDefaultFormat recreates the original format string without precision +// and width information to pass in to fmt.Sprintf in the case of an +// unrecognized type. Unless new types are added to the language, this +// function won't ever be called. +func (f *formatState) buildDefaultFormat() (format string) { + buf := bytes.NewBuffer(percentBytes) + + for _, flag := range supportedFlags { + if f.fs.Flag(int(flag)) { + buf.WriteRune(flag) + } + } + + buf.WriteRune('v') + + format = buf.String() + return format +} + +// constructOrigFormat recreates the original format string including precision +// and width information to pass along to the standard fmt package. This allows +// automatic deferral of all format strings this package doesn't support. +func (f *formatState) constructOrigFormat(verb rune) (format string) { + buf := bytes.NewBuffer(percentBytes) + + for _, flag := range supportedFlags { + if f.fs.Flag(int(flag)) { + buf.WriteRune(flag) + } + } + + if width, ok := f.fs.Width(); ok { + buf.WriteString(strconv.Itoa(width)) + } + + if precision, ok := f.fs.Precision(); ok { + buf.Write(precisionBytes) + buf.WriteString(strconv.Itoa(precision)) + } + + buf.WriteRune(verb) + + format = buf.String() + return format +} + +// unpackValue returns values inside of non-nil interfaces when possible and +// ensures that types for values which have been unpacked from an interface +// are displayed when the show types flag is also set. +// This is useful for data types like structs, arrays, slices, and maps which +// can contain varying types packed inside an interface. +func (f *formatState) unpackValue(v reflect.Value) reflect.Value { + if v.Kind() == reflect.Interface { + f.ignoreNextType = false + if !v.IsNil() { + v = v.Elem() + } + } + return v +} + +// formatPtr handles formatting of pointers by indirecting them as necessary. +func (f *formatState) formatPtr(v reflect.Value) { + // Display nil if top level pointer is nil. + showTypes := f.fs.Flag('#') + if v.IsNil() && (!showTypes || f.ignoreNextType) { + f.fs.Write(nilAngleBytes) + return + } + + // Remove pointers at or below the current depth from map used to detect + // circular refs. + for k, depth := range f.pointers { + if depth >= f.depth { + delete(f.pointers, k) + } + } + + // Keep list of all dereferenced pointers to possibly show later. + pointerChain := make([]uintptr, 0) + + // Figure out how many levels of indirection there are by derferencing + // pointers and unpacking interfaces down the chain while detecting circular + // references. + nilFound := false + cycleFound := false + indirects := 0 + ve := v + for ve.Kind() == reflect.Ptr { + if ve.IsNil() { + nilFound = true + break + } + indirects++ + addr := ve.Pointer() + pointerChain = append(pointerChain, addr) + if pd, ok := f.pointers[addr]; ok && pd < f.depth { + cycleFound = true + indirects-- + break + } + f.pointers[addr] = f.depth + + ve = ve.Elem() + if ve.Kind() == reflect.Interface { + if ve.IsNil() { + nilFound = true + break + } + ve = ve.Elem() + } + } + + // Display type or indirection level depending on flags. + if showTypes && !f.ignoreNextType { + f.fs.Write(openParenBytes) + f.fs.Write(bytes.Repeat(asteriskBytes, indirects)) + f.fs.Write([]byte(ve.Type().String())) + f.fs.Write(closeParenBytes) + } else { + if nilFound || cycleFound { + indirects += strings.Count(ve.Type().String(), "*") + } + f.fs.Write(openAngleBytes) + f.fs.Write([]byte(strings.Repeat("*", indirects))) + f.fs.Write(closeAngleBytes) + } + + // Display pointer information depending on flags. + if f.fs.Flag('+') && (len(pointerChain) > 0) { + f.fs.Write(openParenBytes) + for i, addr := range pointerChain { + if i > 0 { + f.fs.Write(pointerChainBytes) + } + printHexPtr(f.fs, addr) + } + f.fs.Write(closeParenBytes) + } + + // Display dereferenced value. + switch { + case nilFound == true: + f.fs.Write(nilAngleBytes) + + case cycleFound == true: + f.fs.Write(circularShortBytes) + + default: + f.ignoreNextType = true + f.format(ve) + } +} + +// format is the main workhorse for providing the Formatter interface. It +// uses the passed reflect value to figure out what kind of object we are +// dealing with and formats it appropriately. It is a recursive function, +// however circular data structures are detected and handled properly. +func (f *formatState) format(v reflect.Value) { + // Handle invalid reflect values immediately. + kind := v.Kind() + if kind == reflect.Invalid { + f.fs.Write(invalidAngleBytes) + return + } + + // Handle pointers specially. + if kind == reflect.Ptr { + f.formatPtr(v) + return + } + + // Print type information unless already handled elsewhere. + if !f.ignoreNextType && f.fs.Flag('#') { + f.fs.Write(openParenBytes) + f.fs.Write([]byte(v.Type().String())) + f.fs.Write(closeParenBytes) + } + f.ignoreNextType = false + + // Call Stringer/error interfaces if they exist and the handle methods + // flag is enabled. + if !f.cs.DisableMethods { + if (kind != reflect.Invalid) && (kind != reflect.Interface) { + if handled := handleMethods(f.cs, f.fs, v); handled { + return + } + } + } + + switch kind { + case reflect.Invalid: + // Do nothing. We should never get here since invalid has already + // been handled above. + + case reflect.Bool: + printBool(f.fs, v.Bool()) + + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: + printInt(f.fs, v.Int(), 10) + + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + printUint(f.fs, v.Uint(), 10) + + case reflect.Float32: + printFloat(f.fs, v.Float(), 32) + + case reflect.Float64: + printFloat(f.fs, v.Float(), 64) + + case reflect.Complex64: + printComplex(f.fs, v.Complex(), 32) + + case reflect.Complex128: + printComplex(f.fs, v.Complex(), 64) + + case reflect.Slice: + if v.IsNil() { + f.fs.Write(nilAngleBytes) + break + } + fallthrough + + case reflect.Array: + f.fs.Write(openBracketBytes) + f.depth++ + if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) { + f.fs.Write(maxShortBytes) + } else { + numEntries := v.Len() + for i := 0; i < numEntries; i++ { + if i > 0 { + f.fs.Write(spaceBytes) + } + f.ignoreNextType = true + f.format(f.unpackValue(v.Index(i))) + } + } + f.depth-- + f.fs.Write(closeBracketBytes) + + case reflect.String: + f.fs.Write([]byte(v.String())) + + case reflect.Interface: + // The only time we should get here is for nil interfaces due to + // unpackValue calls. + if v.IsNil() { + f.fs.Write(nilAngleBytes) + } + + case reflect.Ptr: + // Do nothing. We should never get here since pointers have already + // been handled above. + + case reflect.Map: + // nil maps should be indicated as different than empty maps + if v.IsNil() { + f.fs.Write(nilAngleBytes) + break + } + + f.fs.Write(openMapBytes) + f.depth++ + if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) { + f.fs.Write(maxShortBytes) + } else { + keys := v.MapKeys() + if f.cs.SortKeys { + sortValues(keys, f.cs) + } + for i, key := range keys { + if i > 0 { + f.fs.Write(spaceBytes) + } + f.ignoreNextType = true + f.format(f.unpackValue(key)) + f.fs.Write(colonBytes) + f.ignoreNextType = true + f.format(f.unpackValue(v.MapIndex(key))) + } + } + f.depth-- + f.fs.Write(closeMapBytes) + + case reflect.Struct: + numFields := v.NumField() + f.fs.Write(openBraceBytes) + f.depth++ + if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) { + f.fs.Write(maxShortBytes) + } else { + vt := v.Type() + for i := 0; i < numFields; i++ { + if i > 0 { + f.fs.Write(spaceBytes) + } + vtf := vt.Field(i) + if f.fs.Flag('+') || f.fs.Flag('#') { + f.fs.Write([]byte(vtf.Name)) + f.fs.Write(colonBytes) + } + f.format(f.unpackValue(v.Field(i))) + } + } + f.depth-- + f.fs.Write(closeBraceBytes) + + case reflect.Uintptr: + printHexPtr(f.fs, uintptr(v.Uint())) + + case reflect.UnsafePointer, reflect.Chan, reflect.Func: + printHexPtr(f.fs, v.Pointer()) + + // There were not any other types at the time this code was written, but + // fall back to letting the default fmt package handle it if any get added. + default: + format := f.buildDefaultFormat() + if v.CanInterface() { + fmt.Fprintf(f.fs, format, v.Interface()) + } else { + fmt.Fprintf(f.fs, format, v.String()) + } + } +} + +// Format satisfies the fmt.Formatter interface. See NewFormatter for usage +// details. +func (f *formatState) Format(fs fmt.State, verb rune) { + f.fs = fs + + // Use standard formatting for verbs that are not v. + if verb != 'v' { + format := f.constructOrigFormat(verb) + fmt.Fprintf(fs, format, f.value) + return + } + + if f.value == nil { + if fs.Flag('#') { + fs.Write(interfaceBytes) + } + fs.Write(nilAngleBytes) + return + } + + f.format(reflect.ValueOf(f.value)) +} + +// newFormatter is a helper function to consolidate the logic from the various +// public methods which take varying config states. +func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter { + fs := &formatState{value: v, cs: cs} + fs.pointers = make(map[uintptr]int) + return fs +} + +/* +NewFormatter returns a custom formatter that satisfies the fmt.Formatter +interface. As a result, it integrates cleanly with standard fmt package +printing functions. The formatter is useful for inline printing of smaller data +types similar to the standard %v format specifier. + +The custom formatter only responds to the %v (most compact), %+v (adds pointer +addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb +combinations. Any other verbs such as %x and %q will be sent to the the +standard fmt package for formatting. In addition, the custom formatter ignores +the width and precision arguments (however they will still work on the format +specifiers not handled by the custom formatter). + +Typically this function shouldn't be called directly. It is much easier to make +use of the custom formatter by calling one of the convenience functions such as +Printf, Println, or Fprintf. +*/ +func NewFormatter(v interface{}) fmt.Formatter { + return newFormatter(&Config, v) +} diff --git a/vendor/github.com/davecgh/go-spew/spew/spew.go b/vendor/github.com/davecgh/go-spew/spew/spew.go new file mode 100644 index 00000000..d8233f54 --- /dev/null +++ b/vendor/github.com/davecgh/go-spew/spew/spew.go @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2013 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package spew + +import ( + "fmt" + "io" +) + +// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the formatted string as a value that satisfies error. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b)) +func Errorf(format string, a ...interface{}) (err error) { + return fmt.Errorf(format, convertArgs(a)...) +} + +// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b)) +func Fprint(w io.Writer, a ...interface{}) (n int, err error) { + return fmt.Fprint(w, convertArgs(a)...) +} + +// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b)) +func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { + return fmt.Fprintf(w, format, convertArgs(a)...) +} + +// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it +// passed with a default Formatter interface returned by NewFormatter. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b)) +func Fprintln(w io.Writer, a ...interface{}) (n int, err error) { + return fmt.Fprintln(w, convertArgs(a)...) +} + +// Print is a wrapper for fmt.Print that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b)) +func Print(a ...interface{}) (n int, err error) { + return fmt.Print(convertArgs(a)...) +} + +// Printf is a wrapper for fmt.Printf that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b)) +func Printf(format string, a ...interface{}) (n int, err error) { + return fmt.Printf(format, convertArgs(a)...) +} + +// Println is a wrapper for fmt.Println that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b)) +func Println(a ...interface{}) (n int, err error) { + return fmt.Println(convertArgs(a)...) +} + +// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the resulting string. See NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b)) +func Sprint(a ...interface{}) string { + return fmt.Sprint(convertArgs(a)...) +} + +// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the resulting string. See NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b)) +func Sprintf(format string, a ...interface{}) string { + return fmt.Sprintf(format, convertArgs(a)...) +} + +// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it +// were passed with a default Formatter interface returned by NewFormatter. It +// returns the resulting string. See NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b)) +func Sprintln(a ...interface{}) string { + return fmt.Sprintln(convertArgs(a)...) +} + +// convertArgs accepts a slice of arguments and returns a slice of the same +// length with each argument converted to a default spew Formatter interface. +func convertArgs(args []interface{}) (formatters []interface{}) { + formatters = make([]interface{}, len(args)) + for index, arg := range args { + formatters[index] = NewFormatter(arg) + } + return formatters +} diff --git a/go/src/github.com/docker/docker/pkg/units/duration.go b/vendor/github.com/docker/go-units/duration.go old mode 100755 new mode 100644 similarity index 100% rename from go/src/github.com/docker/docker/pkg/units/duration.go rename to vendor/github.com/docker/go-units/duration.go diff --git a/go/src/github.com/docker/docker/pkg/units/size.go b/vendor/github.com/docker/go-units/size.go old mode 100755 new mode 100644 similarity index 90% rename from go/src/github.com/docker/docker/pkg/units/size.go rename to vendor/github.com/docker/go-units/size.go index 3b59daff..989edd29 --- a/go/src/github.com/docker/docker/pkg/units/size.go +++ b/vendor/github.com/docker/go-units/size.go @@ -31,7 +31,7 @@ type unitMap map[string]int64 var ( decimalMap = unitMap{"k": KB, "m": MB, "g": GB, "t": TB, "p": PB} binaryMap = unitMap{"k": KiB, "m": MiB, "g": GiB, "t": TiB, "p": PiB} - sizeRegex = regexp.MustCompile(`^(\d+)([kKmMgGtTpP])?[bB]?$`) + sizeRegex = regexp.MustCompile(`^(\d+(\.\d+)*) ?([kKmMgGtTpP])?[bB]?$`) ) var decimapAbbrs = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"} @@ -77,19 +77,19 @@ func RAMInBytes(size string) (int64, error) { // Parses the human-readable size string into the amount it represents. func parseSize(sizeStr string, uMap unitMap) (int64, error) { matches := sizeRegex.FindStringSubmatch(sizeStr) - if len(matches) != 3 { + if len(matches) != 4 { return -1, fmt.Errorf("invalid size: '%s'", sizeStr) } - size, err := strconv.ParseInt(matches[1], 10, 0) + size, err := strconv.ParseFloat(matches[1], 64) if err != nil { return -1, err } - unitPrefix := strings.ToLower(matches[2]) + unitPrefix := strings.ToLower(matches[3]) if mul, ok := uMap[unitPrefix]; ok { - size *= mul + size *= float64(mul) } - return size, nil + return int64(size), nil } diff --git a/vendor/github.com/docker/go-units/ulimit.go b/vendor/github.com/docker/go-units/ulimit.go new file mode 100644 index 00000000..5ac7fd82 --- /dev/null +++ b/vendor/github.com/docker/go-units/ulimit.go @@ -0,0 +1,118 @@ +package units + +import ( + "fmt" + "strconv" + "strings" +) + +// Ulimit is a human friendly version of Rlimit. +type Ulimit struct { + Name string + Hard int64 + Soft int64 +} + +// Rlimit specifies the resource limits, such as max open files. +type Rlimit struct { + Type int `json:"type,omitempty"` + Hard uint64 `json:"hard,omitempty"` + Soft uint64 `json:"soft,omitempty"` +} + +const ( + // magic numbers for making the syscall + // some of these are defined in the syscall package, but not all. + // Also since Windows client doesn't get access to the syscall package, need to + // define these here + rlimitAs = 9 + rlimitCore = 4 + rlimitCPU = 0 + rlimitData = 2 + rlimitFsize = 1 + rlimitLocks = 10 + rlimitMemlock = 8 + rlimitMsgqueue = 12 + rlimitNice = 13 + rlimitNofile = 7 + rlimitNproc = 6 + rlimitRss = 5 + rlimitRtprio = 14 + rlimitRttime = 15 + rlimitSigpending = 11 + rlimitStack = 3 +) + +var ulimitNameMapping = map[string]int{ + //"as": rlimitAs, // Disabled since this doesn't seem usable with the way Docker inits a container. + "core": rlimitCore, + "cpu": rlimitCPU, + "data": rlimitData, + "fsize": rlimitFsize, + "locks": rlimitLocks, + "memlock": rlimitMemlock, + "msgqueue": rlimitMsgqueue, + "nice": rlimitNice, + "nofile": rlimitNofile, + "nproc": rlimitNproc, + "rss": rlimitRss, + "rtprio": rlimitRtprio, + "rttime": rlimitRttime, + "sigpending": rlimitSigpending, + "stack": rlimitStack, +} + +// ParseUlimit parses and returns a Ulimit from the specified string. +func ParseUlimit(val string) (*Ulimit, error) { + parts := strings.SplitN(val, "=", 2) + if len(parts) != 2 { + return nil, fmt.Errorf("invalid ulimit argument: %s", val) + } + + if _, exists := ulimitNameMapping[parts[0]]; !exists { + return nil, fmt.Errorf("invalid ulimit type: %s", parts[0]) + } + + var ( + soft int64 + hard = &soft // default to soft in case no hard was set + temp int64 + err error + ) + switch limitVals := strings.Split(parts[1], ":"); len(limitVals) { + case 2: + temp, err = strconv.ParseInt(limitVals[1], 10, 64) + if err != nil { + return nil, err + } + hard = &temp + fallthrough + case 1: + soft, err = strconv.ParseInt(limitVals[0], 10, 64) + if err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("too many limit value arguments - %s, can only have up to two, `soft[:hard]`", parts[1]) + } + + if soft > *hard { + return nil, fmt.Errorf("ulimit soft limit must be less than or equal to hard limit: %d > %d", soft, *hard) + } + + return &Ulimit{Name: parts[0], Soft: soft, Hard: *hard}, nil +} + +// GetRlimit returns the RLimit corresponding to Ulimit. +func (u *Ulimit) GetRlimit() (*Rlimit, error) { + t, exists := ulimitNameMapping[u.Name] + if !exists { + return nil, fmt.Errorf("invalid ulimit name %s", u.Name) + } + + return &Rlimit{Type: t, Soft: uint64(u.Soft), Hard: uint64(u.Hard)}, nil +} + +func (u *Ulimit) String() string { + return fmt.Sprintf("%s=%d:%d", u.Name, u.Soft, u.Hard) +} diff --git a/vendor/github.com/pmezard/go-difflib/difflib/LICENSE b/vendor/github.com/pmezard/go-difflib/difflib/LICENSE new file mode 100644 index 00000000..c67dad61 --- /dev/null +++ b/vendor/github.com/pmezard/go-difflib/difflib/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2013, Patrick Mezard +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. + The names of its contributors may not 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 +HOLDER 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. diff --git a/vendor/github.com/pmezard/go-difflib/difflib/difflib.go b/vendor/github.com/pmezard/go-difflib/difflib/difflib.go new file mode 100644 index 00000000..003e99fa --- /dev/null +++ b/vendor/github.com/pmezard/go-difflib/difflib/difflib.go @@ -0,0 +1,772 @@ +// Package difflib is a partial port of Python difflib module. +// +// It provides tools to compare sequences of strings and generate textual diffs. +// +// The following class and functions have been ported: +// +// - SequenceMatcher +// +// - unified_diff +// +// - context_diff +// +// Getting unified diffs was the main goal of the port. Keep in mind this code +// is mostly suitable to output text differences in a human friendly way, there +// are no guarantees generated diffs are consumable by patch(1). +package difflib + +import ( + "bufio" + "bytes" + "fmt" + "io" + "strings" +) + +func min(a, b int) int { + if a < b { + return a + } + return b +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} + +func calculateRatio(matches, length int) float64 { + if length > 0 { + return 2.0 * float64(matches) / float64(length) + } + return 1.0 +} + +type Match struct { + A int + B int + Size int +} + +type OpCode struct { + Tag byte + I1 int + I2 int + J1 int + J2 int +} + +// SequenceMatcher compares sequence of strings. The basic +// algorithm predates, and is a little fancier than, an algorithm +// published in the late 1980's by Ratcliff and Obershelp under the +// hyperbolic name "gestalt pattern matching". The basic idea is to find +// the longest contiguous matching subsequence that contains no "junk" +// elements (R-O doesn't address junk). The same idea is then applied +// recursively to the pieces of the sequences to the left and to the right +// of the matching subsequence. This does not yield minimal edit +// sequences, but does tend to yield matches that "look right" to people. +// +// SequenceMatcher tries to compute a "human-friendly diff" between two +// sequences. Unlike e.g. UNIX(tm) diff, the fundamental notion is the +// longest *contiguous* & junk-free matching subsequence. That's what +// catches peoples' eyes. The Windows(tm) windiff has another interesting +// notion, pairing up elements that appear uniquely in each sequence. +// That, and the method here, appear to yield more intuitive difference +// reports than does diff. This method appears to be the least vulnerable +// to synching up on blocks of "junk lines", though (like blank lines in +// ordinary text files, or maybe "

" lines in HTML files). That may be +// because this is the only method of the 3 that has a *concept* of +// "junk" . +// +// Timing: Basic R-O is cubic time worst case and quadratic time expected +// case. SequenceMatcher is quadratic time for the worst case and has +// expected-case behavior dependent in a complicated way on how many +// elements the sequences have in common; best case time is linear. +type SequenceMatcher struct { + a []string + b []string + b2j map[string][]int + IsJunk func(string) bool + autoJunk bool + bJunk map[string]struct{} + matchingBlocks []Match + fullBCount map[string]int + bPopular map[string]struct{} + opCodes []OpCode +} + +func NewMatcher(a, b []string) *SequenceMatcher { + m := SequenceMatcher{autoJunk: true} + m.SetSeqs(a, b) + return &m +} + +func NewMatcherWithJunk(a, b []string, autoJunk bool, + isJunk func(string) bool) *SequenceMatcher { + + m := SequenceMatcher{IsJunk: isJunk, autoJunk: autoJunk} + m.SetSeqs(a, b) + return &m +} + +// Set two sequences to be compared. +func (m *SequenceMatcher) SetSeqs(a, b []string) { + m.SetSeq1(a) + m.SetSeq2(b) +} + +// Set the first sequence to be compared. The second sequence to be compared is +// not changed. +// +// SequenceMatcher computes and caches detailed information about the second +// sequence, so if you want to compare one sequence S against many sequences, +// use .SetSeq2(s) once and call .SetSeq1(x) repeatedly for each of the other +// sequences. +// +// See also SetSeqs() and SetSeq2(). +func (m *SequenceMatcher) SetSeq1(a []string) { + if &a == &m.a { + return + } + m.a = a + m.matchingBlocks = nil + m.opCodes = nil +} + +// Set the second sequence to be compared. The first sequence to be compared is +// not changed. +func (m *SequenceMatcher) SetSeq2(b []string) { + if &b == &m.b { + return + } + m.b = b + m.matchingBlocks = nil + m.opCodes = nil + m.fullBCount = nil + m.chainB() +} + +func (m *SequenceMatcher) chainB() { + // Populate line -> index mapping + b2j := map[string][]int{} + for i, s := range m.b { + indices := b2j[s] + indices = append(indices, i) + b2j[s] = indices + } + + // Purge junk elements + m.bJunk = map[string]struct{}{} + if m.IsJunk != nil { + junk := m.bJunk + for s, _ := range b2j { + if m.IsJunk(s) { + junk[s] = struct{}{} + } + } + for s, _ := range junk { + delete(b2j, s) + } + } + + // Purge remaining popular elements + popular := map[string]struct{}{} + n := len(m.b) + if m.autoJunk && n >= 200 { + ntest := n/100 + 1 + for s, indices := range b2j { + if len(indices) > ntest { + popular[s] = struct{}{} + } + } + for s, _ := range popular { + delete(b2j, s) + } + } + m.bPopular = popular + m.b2j = b2j +} + +func (m *SequenceMatcher) isBJunk(s string) bool { + _, ok := m.bJunk[s] + return ok +} + +// Find longest matching block in a[alo:ahi] and b[blo:bhi]. +// +// If IsJunk is not defined: +// +// Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where +// alo <= i <= i+k <= ahi +// blo <= j <= j+k <= bhi +// and for all (i',j',k') meeting those conditions, +// k >= k' +// i <= i' +// and if i == i', j <= j' +// +// In other words, of all maximal matching blocks, return one that +// starts earliest in a, and of all those maximal matching blocks that +// start earliest in a, return the one that starts earliest in b. +// +// If IsJunk is defined, first the longest matching block is +// determined as above, but with the additional restriction that no +// junk element appears in the block. Then that block is extended as +// far as possible by matching (only) junk elements on both sides. So +// the resulting block never matches on junk except as identical junk +// happens to be adjacent to an "interesting" match. +// +// If no blocks match, return (alo, blo, 0). +func (m *SequenceMatcher) findLongestMatch(alo, ahi, blo, bhi int) Match { + // CAUTION: stripping common prefix or suffix would be incorrect. + // E.g., + // ab + // acab + // Longest matching block is "ab", but if common prefix is + // stripped, it's "a" (tied with "b"). UNIX(tm) diff does so + // strip, so ends up claiming that ab is changed to acab by + // inserting "ca" in the middle. That's minimal but unintuitive: + // "it's obvious" that someone inserted "ac" at the front. + // Windiff ends up at the same place as diff, but by pairing up + // the unique 'b's and then matching the first two 'a's. + besti, bestj, bestsize := alo, blo, 0 + + // find longest junk-free match + // during an iteration of the loop, j2len[j] = length of longest + // junk-free match ending with a[i-1] and b[j] + j2len := map[int]int{} + for i := alo; i != ahi; i++ { + // look at all instances of a[i] in b; note that because + // b2j has no junk keys, the loop is skipped if a[i] is junk + newj2len := map[int]int{} + for _, j := range m.b2j[m.a[i]] { + // a[i] matches b[j] + if j < blo { + continue + } + if j >= bhi { + break + } + k := j2len[j-1] + 1 + newj2len[j] = k + if k > bestsize { + besti, bestj, bestsize = i-k+1, j-k+1, k + } + } + j2len = newj2len + } + + // Extend the best by non-junk elements on each end. In particular, + // "popular" non-junk elements aren't in b2j, which greatly speeds + // the inner loop above, but also means "the best" match so far + // doesn't contain any junk *or* popular non-junk elements. + for besti > alo && bestj > blo && !m.isBJunk(m.b[bestj-1]) && + m.a[besti-1] == m.b[bestj-1] { + besti, bestj, bestsize = besti-1, bestj-1, bestsize+1 + } + for besti+bestsize < ahi && bestj+bestsize < bhi && + !m.isBJunk(m.b[bestj+bestsize]) && + m.a[besti+bestsize] == m.b[bestj+bestsize] { + bestsize += 1 + } + + // Now that we have a wholly interesting match (albeit possibly + // empty!), we may as well suck up the matching junk on each + // side of it too. Can't think of a good reason not to, and it + // saves post-processing the (possibly considerable) expense of + // figuring out what to do with it. In the case of an empty + // interesting match, this is clearly the right thing to do, + // because no other kind of match is possible in the regions. + for besti > alo && bestj > blo && m.isBJunk(m.b[bestj-1]) && + m.a[besti-1] == m.b[bestj-1] { + besti, bestj, bestsize = besti-1, bestj-1, bestsize+1 + } + for besti+bestsize < ahi && bestj+bestsize < bhi && + m.isBJunk(m.b[bestj+bestsize]) && + m.a[besti+bestsize] == m.b[bestj+bestsize] { + bestsize += 1 + } + + return Match{A: besti, B: bestj, Size: bestsize} +} + +// Return list of triples describing matching subsequences. +// +// Each triple is of the form (i, j, n), and means that +// a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in +// i and in j. It's also guaranteed that if (i, j, n) and (i', j', n') are +// adjacent triples in the list, and the second is not the last triple in the +// list, then i+n != i' or j+n != j'. IOW, adjacent triples never describe +// adjacent equal blocks. +// +// The last triple is a dummy, (len(a), len(b), 0), and is the only +// triple with n==0. +func (m *SequenceMatcher) GetMatchingBlocks() []Match { + if m.matchingBlocks != nil { + return m.matchingBlocks + } + + var matchBlocks func(alo, ahi, blo, bhi int, matched []Match) []Match + matchBlocks = func(alo, ahi, blo, bhi int, matched []Match) []Match { + match := m.findLongestMatch(alo, ahi, blo, bhi) + i, j, k := match.A, match.B, match.Size + if match.Size > 0 { + if alo < i && blo < j { + matched = matchBlocks(alo, i, blo, j, matched) + } + matched = append(matched, match) + if i+k < ahi && j+k < bhi { + matched = matchBlocks(i+k, ahi, j+k, bhi, matched) + } + } + return matched + } + matched := matchBlocks(0, len(m.a), 0, len(m.b), nil) + + // It's possible that we have adjacent equal blocks in the + // matching_blocks list now. + nonAdjacent := []Match{} + i1, j1, k1 := 0, 0, 0 + for _, b := range matched { + // Is this block adjacent to i1, j1, k1? + i2, j2, k2 := b.A, b.B, b.Size + if i1+k1 == i2 && j1+k1 == j2 { + // Yes, so collapse them -- this just increases the length of + // the first block by the length of the second, and the first + // block so lengthened remains the block to compare against. + k1 += k2 + } else { + // Not adjacent. Remember the first block (k1==0 means it's + // the dummy we started with), and make the second block the + // new block to compare against. + if k1 > 0 { + nonAdjacent = append(nonAdjacent, Match{i1, j1, k1}) + } + i1, j1, k1 = i2, j2, k2 + } + } + if k1 > 0 { + nonAdjacent = append(nonAdjacent, Match{i1, j1, k1}) + } + + nonAdjacent = append(nonAdjacent, Match{len(m.a), len(m.b), 0}) + m.matchingBlocks = nonAdjacent + return m.matchingBlocks +} + +// Return list of 5-tuples describing how to turn a into b. +// +// Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple +// has i1 == j1 == 0, and remaining tuples have i1 == the i2 from the +// tuple preceding it, and likewise for j1 == the previous j2. +// +// The tags are characters, with these meanings: +// +// 'r' (replace): a[i1:i2] should be replaced by b[j1:j2] +// +// 'd' (delete): a[i1:i2] should be deleted, j1==j2 in this case. +// +// 'i' (insert): b[j1:j2] should be inserted at a[i1:i1], i1==i2 in this case. +// +// 'e' (equal): a[i1:i2] == b[j1:j2] +func (m *SequenceMatcher) GetOpCodes() []OpCode { + if m.opCodes != nil { + return m.opCodes + } + i, j := 0, 0 + matching := m.GetMatchingBlocks() + opCodes := make([]OpCode, 0, len(matching)) + for _, m := range matching { + // invariant: we've pumped out correct diffs to change + // a[:i] into b[:j], and the next matching block is + // a[ai:ai+size] == b[bj:bj+size]. So we need to pump + // out a diff to change a[i:ai] into b[j:bj], pump out + // the matching block, and move (i,j) beyond the match + ai, bj, size := m.A, m.B, m.Size + tag := byte(0) + if i < ai && j < bj { + tag = 'r' + } else if i < ai { + tag = 'd' + } else if j < bj { + tag = 'i' + } + if tag > 0 { + opCodes = append(opCodes, OpCode{tag, i, ai, j, bj}) + } + i, j = ai+size, bj+size + // the list of matching blocks is terminated by a + // sentinel with size 0 + if size > 0 { + opCodes = append(opCodes, OpCode{'e', ai, i, bj, j}) + } + } + m.opCodes = opCodes + return m.opCodes +} + +// Isolate change clusters by eliminating ranges with no changes. +// +// Return a generator of groups with up to n lines of context. +// Each group is in the same format as returned by GetOpCodes(). +func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode { + if n < 0 { + n = 3 + } + codes := m.GetOpCodes() + if len(codes) == 0 { + codes = []OpCode{OpCode{'e', 0, 1, 0, 1}} + } + // Fixup leading and trailing groups if they show no changes. + if codes[0].Tag == 'e' { + c := codes[0] + i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2 + codes[0] = OpCode{c.Tag, max(i1, i2-n), i2, max(j1, j2-n), j2} + } + if codes[len(codes)-1].Tag == 'e' { + c := codes[len(codes)-1] + i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2 + codes[len(codes)-1] = OpCode{c.Tag, i1, min(i2, i1+n), j1, min(j2, j1+n)} + } + nn := n + n + groups := [][]OpCode{} + group := []OpCode{} + for _, c := range codes { + i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2 + // End the current group and start a new one whenever + // there is a large range with no changes. + if c.Tag == 'e' && i2-i1 > nn { + group = append(group, OpCode{c.Tag, i1, min(i2, i1+n), + j1, min(j2, j1+n)}) + groups = append(groups, group) + group = []OpCode{} + i1, j1 = max(i1, i2-n), max(j1, j2-n) + } + group = append(group, OpCode{c.Tag, i1, i2, j1, j2}) + } + if len(group) > 0 && !(len(group) == 1 && group[0].Tag == 'e') { + groups = append(groups, group) + } + return groups +} + +// Return a measure of the sequences' similarity (float in [0,1]). +// +// Where T is the total number of elements in both sequences, and +// M is the number of matches, this is 2.0*M / T. +// Note that this is 1 if the sequences are identical, and 0 if +// they have nothing in common. +// +// .Ratio() is expensive to compute if you haven't already computed +// .GetMatchingBlocks() or .GetOpCodes(), in which case you may +// want to try .QuickRatio() or .RealQuickRation() first to get an +// upper bound. +func (m *SequenceMatcher) Ratio() float64 { + matches := 0 + for _, m := range m.GetMatchingBlocks() { + matches += m.Size + } + return calculateRatio(matches, len(m.a)+len(m.b)) +} + +// Return an upper bound on ratio() relatively quickly. +// +// This isn't defined beyond that it is an upper bound on .Ratio(), and +// is faster to compute. +func (m *SequenceMatcher) QuickRatio() float64 { + // viewing a and b as multisets, set matches to the cardinality + // of their intersection; this counts the number of matches + // without regard to order, so is clearly an upper bound + if m.fullBCount == nil { + m.fullBCount = map[string]int{} + for _, s := range m.b { + m.fullBCount[s] = m.fullBCount[s] + 1 + } + } + + // avail[x] is the number of times x appears in 'b' less the + // number of times we've seen it in 'a' so far ... kinda + avail := map[string]int{} + matches := 0 + for _, s := range m.a { + n, ok := avail[s] + if !ok { + n = m.fullBCount[s] + } + avail[s] = n - 1 + if n > 0 { + matches += 1 + } + } + return calculateRatio(matches, len(m.a)+len(m.b)) +} + +// Return an upper bound on ratio() very quickly. +// +// This isn't defined beyond that it is an upper bound on .Ratio(), and +// is faster to compute than either .Ratio() or .QuickRatio(). +func (m *SequenceMatcher) RealQuickRatio() float64 { + la, lb := len(m.a), len(m.b) + return calculateRatio(min(la, lb), la+lb) +} + +// Convert range to the "ed" format +func formatRangeUnified(start, stop int) string { + // Per the diff spec at http://www.unix.org/single_unix_specification/ + beginning := start + 1 // lines start numbering with one + length := stop - start + if length == 1 { + return fmt.Sprintf("%d", beginning) + } + if length == 0 { + beginning -= 1 // empty ranges begin at line just before the range + } + return fmt.Sprintf("%d,%d", beginning, length) +} + +// Unified diff parameters +type UnifiedDiff struct { + A []string // First sequence lines + FromFile string // First file name + FromDate string // First file time + B []string // Second sequence lines + ToFile string // Second file name + ToDate string // Second file time + Eol string // Headers end of line, defaults to LF + Context int // Number of context lines +} + +// Compare two sequences of lines; generate the delta as a unified diff. +// +// Unified diffs are a compact way of showing line changes and a few +// lines of context. The number of context lines is set by 'n' which +// defaults to three. +// +// By default, the diff control lines (those with ---, +++, or @@) are +// created with a trailing newline. This is helpful so that inputs +// created from file.readlines() result in diffs that are suitable for +// file.writelines() since both the inputs and outputs have trailing +// newlines. +// +// For inputs that do not have trailing newlines, set the lineterm +// argument to "" so that the output will be uniformly newline free. +// +// The unidiff format normally has a header for filenames and modification +// times. Any or all of these may be specified using strings for +// 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'. +// The modification times are normally expressed in the ISO 8601 format. +func WriteUnifiedDiff(writer io.Writer, diff UnifiedDiff) error { + buf := bufio.NewWriter(writer) + defer buf.Flush() + wf := func(format string, args ...interface{}) error { + _, err := buf.WriteString(fmt.Sprintf(format, args...)) + return err + } + ws := func(s string) error { + _, err := buf.WriteString(s) + return err + } + + if len(diff.Eol) == 0 { + diff.Eol = "\n" + } + + started := false + m := NewMatcher(diff.A, diff.B) + for _, g := range m.GetGroupedOpCodes(diff.Context) { + if !started { + started = true + fromDate := "" + if len(diff.FromDate) > 0 { + fromDate = "\t" + diff.FromDate + } + toDate := "" + if len(diff.ToDate) > 0 { + toDate = "\t" + diff.ToDate + } + if diff.FromFile != "" || diff.ToFile != "" { + err := wf("--- %s%s%s", diff.FromFile, fromDate, diff.Eol) + if err != nil { + return err + } + err = wf("+++ %s%s%s", diff.ToFile, toDate, diff.Eol) + if err != nil { + return err + } + } + } + first, last := g[0], g[len(g)-1] + range1 := formatRangeUnified(first.I1, last.I2) + range2 := formatRangeUnified(first.J1, last.J2) + if err := wf("@@ -%s +%s @@%s", range1, range2, diff.Eol); err != nil { + return err + } + for _, c := range g { + i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2 + if c.Tag == 'e' { + for _, line := range diff.A[i1:i2] { + if err := ws(" " + line); err != nil { + return err + } + } + continue + } + if c.Tag == 'r' || c.Tag == 'd' { + for _, line := range diff.A[i1:i2] { + if err := ws("-" + line); err != nil { + return err + } + } + } + if c.Tag == 'r' || c.Tag == 'i' { + for _, line := range diff.B[j1:j2] { + if err := ws("+" + line); err != nil { + return err + } + } + } + } + } + return nil +} + +// Like WriteUnifiedDiff but returns the diff a string. +func GetUnifiedDiffString(diff UnifiedDiff) (string, error) { + w := &bytes.Buffer{} + err := WriteUnifiedDiff(w, diff) + return string(w.Bytes()), err +} + +// Convert range to the "ed" format. +func formatRangeContext(start, stop int) string { + // Per the diff spec at http://www.unix.org/single_unix_specification/ + beginning := start + 1 // lines start numbering with one + length := stop - start + if length == 0 { + beginning -= 1 // empty ranges begin at line just before the range + } + if length <= 1 { + return fmt.Sprintf("%d", beginning) + } + return fmt.Sprintf("%d,%d", beginning, beginning+length-1) +} + +type ContextDiff UnifiedDiff + +// Compare two sequences of lines; generate the delta as a context diff. +// +// Context diffs are a compact way of showing line changes and a few +// lines of context. The number of context lines is set by diff.Context +// which defaults to three. +// +// By default, the diff control lines (those with *** or ---) are +// created with a trailing newline. +// +// For inputs that do not have trailing newlines, set the diff.Eol +// argument to "" so that the output will be uniformly newline free. +// +// The context diff format normally has a header for filenames and +// modification times. Any or all of these may be specified using +// strings for diff.FromFile, diff.ToFile, diff.FromDate, diff.ToDate. +// The modification times are normally expressed in the ISO 8601 format. +// If not specified, the strings default to blanks. +func WriteContextDiff(writer io.Writer, diff ContextDiff) error { + buf := bufio.NewWriter(writer) + defer buf.Flush() + var diffErr error + wf := func(format string, args ...interface{}) { + _, err := buf.WriteString(fmt.Sprintf(format, args...)) + if diffErr == nil && err != nil { + diffErr = err + } + } + ws := func(s string) { + _, err := buf.WriteString(s) + if diffErr == nil && err != nil { + diffErr = err + } + } + + if len(diff.Eol) == 0 { + diff.Eol = "\n" + } + + prefix := map[byte]string{ + 'i': "+ ", + 'd': "- ", + 'r': "! ", + 'e': " ", + } + + started := false + m := NewMatcher(diff.A, diff.B) + for _, g := range m.GetGroupedOpCodes(diff.Context) { + if !started { + started = true + fromDate := "" + if len(diff.FromDate) > 0 { + fromDate = "\t" + diff.FromDate + } + toDate := "" + if len(diff.ToDate) > 0 { + toDate = "\t" + diff.ToDate + } + if diff.FromFile != "" || diff.ToFile != "" { + wf("*** %s%s%s", diff.FromFile, fromDate, diff.Eol) + wf("--- %s%s%s", diff.ToFile, toDate, diff.Eol) + } + } + + first, last := g[0], g[len(g)-1] + ws("***************" + diff.Eol) + + range1 := formatRangeContext(first.I1, last.I2) + wf("*** %s ****%s", range1, diff.Eol) + for _, c := range g { + if c.Tag == 'r' || c.Tag == 'd' { + for _, cc := range g { + if cc.Tag == 'i' { + continue + } + for _, line := range diff.A[cc.I1:cc.I2] { + ws(prefix[cc.Tag] + line) + } + } + break + } + } + + range2 := formatRangeContext(first.J1, last.J2) + wf("--- %s ----%s", range2, diff.Eol) + for _, c := range g { + if c.Tag == 'r' || c.Tag == 'i' { + for _, cc := range g { + if cc.Tag == 'd' { + continue + } + for _, line := range diff.B[cc.J1:cc.J2] { + ws(prefix[cc.Tag] + line) + } + } + break + } + } + } + return diffErr +} + +// Like WriteContextDiff but returns the diff a string. +func GetContextDiffString(diff ContextDiff) (string, error) { + w := &bytes.Buffer{} + err := WriteContextDiff(w, diff) + return string(w.Bytes()), err +} + +// Split a string on "\n" while preserving them. The output can be used +// as input for UnifiedDiff and ContextDiff structures. +func SplitLines(s string) []string { + lines := strings.SplitAfter(s, "\n") + lines[len(lines)-1] += "\n" + return lines +} diff --git a/go/src/github.com/samalba/dockerclient/LICENSE b/vendor/github.com/samalba/dockerclient/LICENSE old mode 100755 new mode 100644 similarity index 100% rename from go/src/github.com/samalba/dockerclient/LICENSE rename to vendor/github.com/samalba/dockerclient/LICENSE diff --git a/go/src/github.com/samalba/dockerclient/auth.go b/vendor/github.com/samalba/dockerclient/auth.go old mode 100755 new mode 100644 similarity index 82% rename from go/src/github.com/samalba/dockerclient/auth.go rename to vendor/github.com/samalba/dockerclient/auth.go index 48f5f90b..7c0a67f7 --- a/go/src/github.com/samalba/dockerclient/auth.go +++ b/vendor/github.com/samalba/dockerclient/auth.go @@ -8,9 +8,10 @@ import ( // AuthConfig hold parameters for authenticating with the docker registry type AuthConfig struct { - Username string `json:"username,omitempty"` - Password string `json:"password,omitempty"` - Email string `json:"email,omitempty"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + Email string `json:"email,omitempty"` + RegistryToken string `json:"registrytoken,omitempty"` } // encode the auth configuration struct into base64 for the X-Registry-Auth header diff --git a/go/src/github.com/samalba/dockerclient/dockerclient.go b/vendor/github.com/samalba/dockerclient/dockerclient.go old mode 100755 new mode 100644 similarity index 86% rename from go/src/github.com/samalba/dockerclient/dockerclient.go rename to vendor/github.com/samalba/dockerclient/dockerclient.go index 96ea37fe..51a00b29 --- a/go/src/github.com/samalba/dockerclient/dockerclient.go +++ b/vendor/github.com/samalba/dockerclient/dockerclient.go @@ -16,13 +16,23 @@ import ( "time" ) +var _ Client = (*DockerClient)(nil) + const ( + // APIVersion is currently hardcoded to v1.15 + // TODO: bump the API version or allow users to choose which API version to + // use the client with. The current value does not make sense for many + // methods, such as ContainerStats, StartMonitorStats, and StopAllMonitorStats + // (v1.17) and + // ListVolumes, {Remove,Create}Volume, ListNetworks, + // {Inspect,Create,Connect,Disconnect,Remove}Network (v1.21) APIVersion = "v1.15" ) var ( - ErrImageNotFound = errors.New("Image not found") - ErrNotFound = errors.New("Not found") + ErrImageNotFound = errors.New("Image not found") + ErrNotFound = errors.New("Not found") + ErrConnectionRefused = errors.New("Cannot connect to the docker engine endpoint") defaultTimeout = 30 * time.Second ) @@ -46,10 +56,10 @@ func (e Error) Error() string { } func NewDockerClient(daemonUrl string, tlsConfig *tls.Config) (*DockerClient, error) { - return NewDockerClientTimeout(daemonUrl, tlsConfig, time.Duration(defaultTimeout)) + return NewDockerClientTimeout(daemonUrl, tlsConfig, time.Duration(defaultTimeout), nil) } -func NewDockerClientTimeout(daemonUrl string, tlsConfig *tls.Config, timeout time.Duration) (*DockerClient, error) { +func NewDockerClientTimeout(daemonUrl string, tlsConfig *tls.Config, timeout time.Duration, setUserTimeout tcpFunc) (*DockerClient, error) { u, err := url.Parse(daemonUrl) if err != nil { return nil, err @@ -61,7 +71,7 @@ func NewDockerClientTimeout(daemonUrl string, tlsConfig *tls.Config, timeout tim u.Scheme = "https" } } - httpClient := newHTTPClient(u, tlsConfig, timeout) + httpClient := newHTTPClient(u, tlsConfig, timeout, setUserTimeout) return &DockerClient{u, httpClient, tlsConfig, 0, nil}, nil } @@ -100,6 +110,9 @@ func (client *DockerClient) doStreamRequest(method string, path string, in io.Re if !strings.Contains(err.Error(), "connection refused") && client.TLSConfig == nil { return nil, fmt.Errorf("%v. Are you trying to connect to a TLS-enabled daemon without TLS?", err) } + if strings.Contains(err.Error(), "connection refused") { + return nil, ErrConnectionRefused + } return nil, err } if resp.StatusCode == 404 { @@ -184,7 +197,7 @@ func (client *DockerClient) InspectContainer(id string) (*ContainerInfo, error) return info, nil } -func (client *DockerClient) CreateContainer(config *ContainerConfig, name string) (string, error) { +func (client *DockerClient) CreateContainer(config *ContainerConfig, name string, auth *AuthConfig) (string, error) { data, err := json.Marshal(config) if err != nil { return "", err @@ -195,14 +208,22 @@ func (client *DockerClient) CreateContainer(config *ContainerConfig, name string v.Set("name", name) uri = fmt.Sprintf("%s?%s", uri, v.Encode()) } - data, err = client.doRequest("POST", uri, data, nil) + headers := map[string]string{} + if auth != nil { + encoded_auth, err := auth.encode() + if err != nil { + return "", err + } + headers["X-Registry-Auth"] = encoded_auth + } + data, err = client.doRequest("POST", uri, data, headers) if err != nil { return "", err } result := &RespContainersCreate{} err = json.Unmarshal(data, result) if err != nil { - return "", err + return "", fmt.Errorf(string(data)) } return result.Id, nil } @@ -244,6 +265,36 @@ func (client *DockerClient) ContainerChanges(id string) ([]*ContainerChanges, er return changes, nil } +func (client *DockerClient) ContainerStats(id string, stopChan <-chan struct{}) (<-chan StatsOrError, error) { + uri := fmt.Sprintf("/%s/containers/%s/stats", APIVersion, id) + resp, err := client.HTTPClient.Get(client.URL.String() + uri) + if err != nil { + return nil, err + } + + decode := func(decoder *json.Decoder) decodingResult { + var containerStats Stats + if err := decoder.Decode(&containerStats); err != nil { + return decodingResult{err: err} + } else { + return decodingResult{result: containerStats} + } + } + decodingResultChan := client.readJSONStream(resp.Body, decode, stopChan) + statsOrErrorChan := make(chan StatsOrError) + go func() { + for decodingResult := range decodingResultChan { + stats, _ := decodingResult.result.(Stats) + statsOrErrorChan <- StatsOrError{ + Stats: stats, + Error: decodingResult.err, + } + } + close(statsOrErrorChan) + }() + return statsOrErrorChan, nil +} + func (client *DockerClient) readJSONStream(stream io.ReadCloser, decode func(*json.Decoder) decodingResult, stopChan <-chan struct{}) <-chan decodingResult { resultChan := make(chan decodingResult) @@ -335,6 +386,29 @@ func (client *DockerClient) ExecResize(id string, width, height int) error { return nil } +func (client *DockerClient) AttachContainer(id string, options *AttachOptions) (io.ReadCloser, error) { + v := url.Values{} + if options != nil { + if options.Logs { + v.Set("logs", "1") + } + if options.Stream { + v.Set("stream", "1") + } + if options.Stdin { + v.Set("stdin", "1") + } + if options.Stdout { + v.Set("stdout", "1") + } + if options.Stderr { + v.Set("stderr", "1") + } + } + uri := fmt.Sprintf("/%s/containers/%s/attach?%s", APIVersion, id, v.Encode()) + return client.doStreamRequest("POST", uri, nil, nil) +} + func (client *DockerClient) StartContainer(id string, config *HostConfig) error { data, err := json.Marshal(config) if err != nil { @@ -468,7 +542,7 @@ func (client *DockerClient) StartMonitorEvents(cb Callback, ec chan error, args for e := range eventErrChan { if e.Error != nil { if ec != nil { - ec <- err + ec <- e.Error } return } @@ -478,6 +552,9 @@ func (client *DockerClient) StartMonitorEvents(cb Callback, ec chan error, args } func (client *DockerClient) StopAllMonitorEvents() { + if client.eventStopChan == nil { + return + } close(client.eventStopChan) } @@ -625,17 +702,9 @@ func (client *DockerClient) InspectImage(id string) (*ImageInfo, error) { } func (client *DockerClient) LoadImage(reader io.Reader) error { - data, err := ioutil.ReadAll(reader) - if err != nil { - return err - } - uri := fmt.Sprintf("/%s/images/load", APIVersion) - _, err = client.doRequest("POST", uri, data, nil) - if err != nil { - return err - } - return nil + _, err := client.doStreamRequest("POST", uri, reader, nil) + return err } func (client *DockerClient) RemoveContainer(id string, force, volumes bool) error { @@ -689,6 +758,31 @@ func (client *DockerClient) RemoveImage(name string, force bool) ([]*ImageDelete return imageDelete, nil } +func (client *DockerClient) SearchImages(query, registry string, auth *AuthConfig) ([]ImageSearch, error) { + term := query + if registry != "" { + term = registry + "/" + term + } + uri := fmt.Sprintf("/%s/images/search?term=%s", APIVersion, term) + headers := map[string]string{} + if auth != nil { + if encodedAuth, err := auth.encode(); err != nil { + return nil, err + } else { + headers["X-Registry-Auth"] = encodedAuth + } + } + data, err := client.doRequest("GET", uri, nil, headers) + if err != nil { + return nil, err + } + var imageSearches []ImageSearch + if err := json.Unmarshal(data, &imageSearches); err != nil { + return nil, err + } + return imageSearches, nil +} + func (client *DockerClient) PauseContainer(id string) error { uri := fmt.Sprintf("/%s/containers/%s/pause", APIVersion, id) _, err := client.doRequest("POST", uri, nil, nil) @@ -890,8 +984,8 @@ func (client *DockerClient) ConnectNetwork(id, container string) error { return err } -func (client *DockerClient) DisconnectNetwork(id, container string) error { - data, err := json.Marshal(NetworkDisconnect{Container: container}) +func (client *DockerClient) DisconnectNetwork(id, container string, force bool) error { + data, err := json.Marshal(NetworkDisconnect{Container: container, Force: force}) if err != nil { return err } diff --git a/go/src/github.com/samalba/dockerclient/example_responses.go b/vendor/github.com/samalba/dockerclient/example_responses.go old mode 100755 new mode 100644 similarity index 66% rename from go/src/github.com/samalba/dockerclient/example_responses.go rename to vendor/github.com/samalba/dockerclient/example_responses.go index 670508c0..e4c890dc --- a/go/src/github.com/samalba/dockerclient/example_responses.go +++ b/vendor/github.com/samalba/dockerclient/example_responses.go @@ -10,4 +10,6 @@ var haproxyPullOutput = `{"status":"The image you are pulling has been verified" {"status":"Already exists","progressDetail":{},"id":"511136ea3c5a"}{"status":"Already exists","progressDetail":{},"id":"1aeada447715"}{"status":"Already exists","progressDetail":{},"id":"479215127fa7"}{"status":"Already exists","progressDetail":{},"id":"66301eb54a7d"}{"status":"Already exists","progressDetail":{},"id":"e3990b07573f"}{"status":"Already exists","progressDetail":{},"id":"ecb4b23ca7ce"}{"status":"Already exists","progressDetail":{},"id":"f453e940c177"}{"status":"Already exists","progressDetail":{},"id":"fc5ea1bc05ab"}{"status":"Already exists","progressDetail":{},"id":"380557f8f7b3"}{"status":"Status: Image is up to date for haproxy"} ` +var statsResp = `{"read":"2015-02-02T17:06:08.187833376-05:00","network":{"rx_bytes":99988,"rx_packets":928,"rx_errors":0,"rx_dropped":0,"tx_bytes":1786548,"tx_packets":877,"tx_errors":0,"tx_dropped":0},"cpu_stats":{"cpu_usage":{"total_usage":170018598,"percpu_usage":[170018598],"usage_in_kernelmode":30000000,"usage_in_usermode":70000000},"system_cpu_usage":9020930000000,"throttling_data":{"periods":0,"throttled_periods":0,"throttled_time":0}},"memory_stats":{"usage":18022400,"max_usage":20541440,"stats":{"active_anon":6213632,"active_file":176128,"cache":11808768,"hierarchical_memory_limit":9223372036854775807,"hierarchical_memsw_limit":9223372036854775807,"inactive_anon":0,"inactive_file":11632640,"mapped_file":5165056,"pgfault":2535,"pgmajfault":13,"pgpgin":4293,"pgpgout":1937,"rss":6213632,"rss_huge":2097152,"swap":0,"total_active_anon":6213632,"total_active_file":176128,"total_cache":11808768,"total_inactive_anon":0,"total_inactive_file":11632640,"total_mapped_file":5165056,"total_pgfault":2535,"total_pgmajfault":13,"total_pgpgin":4293,"total_pgpgout":1937,"total_rss":6213632,"total_rss_huge":2097152,"total_swap":0,"total_unevictable":0,"unevictable":0},"failcnt":0,"limit":1041051648},"blkio_stats":{"io_service_bytes_recursive":[{"major":7,"minor":0,"op":"Read","value":28672},{"major":7,"minor":0,"op":"Write","value":0},{"major":7,"minor":0,"op":"Sync","value":0},{"major":7,"minor":0,"op":"Async","value":28672},{"major":7,"minor":0,"op":"Total","value":28672},{"major":253,"minor":0,"op":"Read","value":28672},{"major":253,"minor":0,"op":"Write","value":0},{"major":253,"minor":0,"op":"Sync","value":0},{"major":253,"minor":0,"op":"Async","value":28672},{"major":253,"minor":0,"op":"Total","value":28672},{"major":253,"minor":7,"op":"Read","value":11718656},{"major":253,"minor":7,"op":"Write","value":0},{"major":253,"minor":7,"op":"Sync","value":0},{"major":253,"minor":7,"op":"Async","value":11718656},{"major":253,"minor":7,"op":"Total","value":11718656},{"major":202,"minor":0,"op":"Read","value":0},{"major":202,"minor":0,"op":"Write","value":0},{"major":202,"minor":0,"op":"Sync","value":0},{"major":202,"minor":0,"op":"Async","value":0},{"major":202,"minor":0,"op":"Total","value":0}],"io_serviced_recursive":[{"major":7,"minor":0,"op":"Read","value":7},{"major":7,"minor":0,"op":"Write","value":0},{"major":7,"minor":0,"op":"Sync","value":0},{"major":7,"minor":0,"op":"Async","value":7},{"major":7,"minor":0,"op":"Total","value":7},{"major":253,"minor":0,"op":"Read","value":7},{"major":253,"minor":0,"op":"Write","value":0},{"major":253,"minor":0,"op":"Sync","value":0},{"major":253,"minor":0,"op":"Async","value":7},{"major":253,"minor":0,"op":"Total","value":7},{"major":253,"minor":7,"op":"Read","value":312},{"major":253,"minor":7,"op":"Write","value":0},{"major":253,"minor":7,"op":"Sync","value":0},{"major":253,"minor":7,"op":"Async","value":312},{"major":253,"minor":7,"op":"Total","value":312},{"major":202,"minor":0,"op":"Read","value":0},{"major":202,"minor":0,"op":"Write","value":0},{"major":202,"minor":0,"op":"Sync","value":0},{"major":202,"minor":0,"op":"Async","value":0},{"major":202,"minor":0,"op":"Total","value":0}],"io_queue_recursive":[],"io_service_time_recursive":[],"io_wait_time_recursive":[],"io_merged_recursive":[],"io_time_recursive":[],"sectors_recursive":[]}}` + var eventsResp = `{"status":"pull","id":"nginx:latest","time":1428620433}{"status":"create","id":"9b818c3b8291708fdcecd7c4086b75c222cb503be10a93d9c11040886032a48b","from":"nginx:latest","time":1428620433}{"status":"start","id":"9b818c3b8291708fdcecd7c4086b75c222cb503be10a93d9c11040886032a48b","from":"nginx:latest","time":1428620433}{"status":"die","id":"9b818c3b8291708fdcecd7c4086b75c222cb503be10a93d9c11040886032a48b","from":"nginx:latest","time":1428620442}{"status":"create","id":"352d0b412aae5a5d2b14ae9d88be59dc276602d9edb9dcc33e138e475b3e4720","from":"52.11.96.81/foobar/ubuntu:latest","time":1428620444}{"status":"start","id":"352d0b412aae5a5d2b14ae9d88be59dc276602d9edb9dcc33e138e475b3e4720","from":"52.11.96.81/foobar/ubuntu:latest","time":1428620444}{"status":"die","id":"352d0b412aae5a5d2b14ae9d88be59dc276602d9edb9dcc33e138e475b3e4720","from":"52.11.96.81/foobar/ubuntu:latest","time":1428620444}{"status":"pull","id":"debian:latest","time":1428620453}{"status":"create","id":"668887b5729946546b3072655dc6da08f0e3210111b68b704eb842adfce53f6c","from":"debian:latest","time":1428620453}{"status":"start","id":"668887b5729946546b3072655dc6da08f0e3210111b68b704eb842adfce53f6c","from":"debian:latest","time":1428620453}{"status":"die","id":"668887b5729946546b3072655dc6da08f0e3210111b68b704eb842adfce53f6c","from":"debian:latest","time":1428620453}{"status":"create","id":"eb4a19ec21ab29bbbffbf3ee2e2df9d99cb749780e1eff06a591cee5ba505180","from":"nginx:latest","time":1428620458}{"status":"start","id":"eb4a19ec21ab29bbbffbf3ee2e2df9d99cb749780e1eff06a591cee5ba505180","from":"nginx:latest","time":1428620458}{"status":"pause","id":"eb4a19ec21ab29bbbffbf3ee2e2df9d99cb749780e1eff06a591cee5ba505180","from":"nginx:latest","time":1428620462}{"status":"unpause","id":"eb4a19ec21ab29bbbffbf3ee2e2df9d99cb749780e1eff06a591cee5ba505180","from":"nginx:latest","time":1428620466}{"status":"die","id":"eb4a19ec21ab29bbbffbf3ee2e2df9d99cb749780e1eff06a591cee5ba505180","from":"nginx:latest","time":1428620469}` diff --git a/go/src/github.com/samalba/dockerclient/examples/events.go b/vendor/github.com/samalba/dockerclient/examples/events.go old mode 100755 new mode 100644 similarity index 100% rename from go/src/github.com/samalba/dockerclient/examples/events.go rename to vendor/github.com/samalba/dockerclient/examples/events.go diff --git a/go/src/github.com/samalba/dockerclient/examples/stats/stats.go b/vendor/github.com/samalba/dockerclient/examples/stats/stats.go old mode 100755 new mode 100644 similarity index 92% rename from go/src/github.com/samalba/dockerclient/examples/stats/stats.go rename to vendor/github.com/samalba/dockerclient/examples/stats/stats.go index 9027069d..81ee2fb4 --- a/go/src/github.com/samalba/dockerclient/examples/stats/stats.go +++ b/vendor/github.com/samalba/dockerclient/examples/stats/stats.go @@ -27,7 +27,7 @@ func main() { } containerConfig := &dockerclient.ContainerConfig{Image: "busybox", Cmd: []string{"sh"}} - containerId, err := docker.CreateContainer(containerConfig, "") + containerId, err := docker.CreateContainer(containerConfig, "", nil) if err != nil { log.Fatal(err) } diff --git a/go/src/github.com/samalba/dockerclient/interface.go b/vendor/github.com/samalba/dockerclient/interface.go old mode 100755 new mode 100644 similarity index 78% rename from go/src/github.com/samalba/dockerclient/interface.go rename to vendor/github.com/samalba/dockerclient/interface.go index cb7a8b79..b373055e --- a/go/src/github.com/samalba/dockerclient/interface.go +++ b/vendor/github.com/samalba/dockerclient/interface.go @@ -13,13 +13,20 @@ type Client interface { ListContainers(all, size bool, filters string) ([]Container, error) InspectContainer(id string) (*ContainerInfo, error) InspectImage(id string) (*ImageInfo, error) - CreateContainer(config *ContainerConfig, name string) (string, error) + CreateContainer(config *ContainerConfig, name string, authConfig *AuthConfig) (string, error) ContainerLogs(id string, options *LogOptions) (io.ReadCloser, error) ContainerChanges(id string) ([]*ContainerChanges, error) + // ContainerStats takes a container ID and an optional stop channel and + // returns a StatsOrError channel. If an error is ever sent, then no + // more stats will be sent on that channel. If a stop channel is + // provided, events will stop being monitored after the stop channel is + // closed. + ContainerStats(id string, stopChan <-chan struct{}) (<-chan StatsOrError, error) ExecCreate(config *ExecConfig) (string, error) ExecStart(id string, config *ExecConfig) error ExecResize(id string, width, height int) error StartContainer(id string, config *HostConfig) error + AttachContainer(id string, options *AttachOptions) (io.ReadCloser, error) StopContainer(id string, timeout int) error RestartContainer(id string, timeout int) error KillContainer(id, signal string) error @@ -41,6 +48,7 @@ type Client interface { RemoveContainer(id string, force, volumes bool) error ListImages(all bool) ([]*Image, error) RemoveImage(name string, force bool) ([]*ImageDelete, error) + SearchImages(query, registry string, auth *AuthConfig) ([]ImageSearch, error) PauseContainer(name string) error UnpauseContainer(name string) error RenameContainer(oldName string, newName string) error @@ -53,6 +61,6 @@ type Client interface { InspectNetwork(id string) (*NetworkResource, error) CreateNetwork(config *NetworkCreate) (*NetworkCreateResponse, error) ConnectNetwork(id, container string) error - DisconnectNetwork(id, container string) error + DisconnectNetwork(id, container string, force bool) error RemoveNetwork(id string) error } diff --git a/go/src/github.com/samalba/dockerclient/mockclient/mock.go b/vendor/github.com/samalba/dockerclient/mockclient/mock.go old mode 100755 new mode 100644 similarity index 88% rename from go/src/github.com/samalba/dockerclient/mockclient/mock.go rename to vendor/github.com/samalba/dockerclient/mockclient/mock.go index a54ee575..9da36b87 --- a/go/src/github.com/samalba/dockerclient/mockclient/mock.go +++ b/vendor/github.com/samalba/dockerclient/mockclient/mock.go @@ -35,8 +35,8 @@ func (client *MockClient) InspectImage(id string) (*dockerclient.ImageInfo, erro return args.Get(0).(*dockerclient.ImageInfo), args.Error(1) } -func (client *MockClient) CreateContainer(config *dockerclient.ContainerConfig, name string) (string, error) { - args := client.Mock.Called(config, name) +func (client *MockClient) CreateContainer(config *dockerclient.ContainerConfig, name string, authConfig *dockerclient.AuthConfig) (string, error) { + args := client.Mock.Called(config, name, authConfig) return args.String(0), args.Error(1) } @@ -50,6 +50,16 @@ func (client *MockClient) ContainerChanges(id string) ([]*dockerclient.Container return args.Get(0).([]*dockerclient.ContainerChanges), args.Error(1) } +func (client *MockClient) ContainerStats(id string, stopChan <-chan struct{}) (<-chan dockerclient.StatsOrError, error) { + args := client.Mock.Called(id, stopChan) + return args.Get(0).(<-chan dockerclient.StatsOrError), args.Error(1) +} + +func (client *MockClient) AttachContainer(id string, options *dockerclient.AttachOptions) (io.ReadCloser, error) { + args := client.Mock.Called(id, options) + return args.Get(0).(io.ReadCloser), args.Error(1) +} + func (client *MockClient) StartContainer(id string, config *dockerclient.HostConfig) error { args := client.Mock.Called(id, config) return args.Error(0) @@ -136,6 +146,11 @@ func (client *MockClient) RemoveImage(name string, force bool) ([]*dockerclient. return args.Get(0).([]*dockerclient.ImageDelete), args.Error(1) } +func (client *MockClient) SearchImages(query, registry string, authConfig *dockerclient.AuthConfig) ([]dockerclient.ImageSearch, error) { + args := client.Mock.Called(query, registry, authConfig) + return args.Get(0).([]dockerclient.ImageSearch), args.Error(1) +} + func (client *MockClient) PauseContainer(name string) error { args := client.Mock.Called(name) return args.Error(0) @@ -211,8 +226,8 @@ func (client *MockClient) ConnectNetwork(id, container string) error { return args.Error(0) } -func (client *MockClient) DisconnectNetwork(id, container string) error { - args := client.Mock.Called(id, container) +func (client *MockClient) DisconnectNetwork(id, container string, force bool) error { + args := client.Mock.Called(id, container, force) return args.Error(0) } diff --git a/go/src/github.com/samalba/dockerclient/nopclient/nop.go b/vendor/github.com/samalba/dockerclient/nopclient/nop.go old mode 100755 new mode 100644 similarity index 88% rename from go/src/github.com/samalba/dockerclient/nopclient/nop.go rename to vendor/github.com/samalba/dockerclient/nopclient/nop.go index 03dfbab1..659b57b7 --- a/go/src/github.com/samalba/dockerclient/nopclient/nop.go +++ b/vendor/github.com/samalba/dockerclient/nopclient/nop.go @@ -34,7 +34,7 @@ func (client *NopClient) InspectImage(id string) (*dockerclient.ImageInfo, error return nil, ErrNoEngine } -func (client *NopClient) CreateContainer(config *dockerclient.ContainerConfig, name string) (string, error) { +func (client *NopClient) CreateContainer(config *dockerclient.ContainerConfig, name string, authConfig *dockerclient.AuthConfig) (string, error) { return "", ErrNoEngine } @@ -46,6 +46,14 @@ func (client *NopClient) ContainerChanges(id string) ([]*dockerclient.ContainerC return nil, ErrNoEngine } +func (client *NopClient) ContainerStats(id string, stopChan <-chan struct{}) (<-chan dockerclient.StatsOrError, error) { + return nil, ErrNoEngine +} + +func (client *NopClient) AttachContainer(id string, options *dockerclient.AttachOptions) (io.ReadCloser, error) { + return nil, ErrNoEngine +} + func (client *NopClient) StartContainer(id string, config *dockerclient.HostConfig) error { return ErrNoEngine } @@ -118,6 +126,10 @@ func (client *NopClient) RemoveImage(name string, force bool) ([]*dockerclient.I return nil, ErrNoEngine } +func (client *NopClient) SearchImages(query, registry string, authConfig *dockerclient.AuthConfig) ([]dockerclient.ImageSearch, error) { + return nil, ErrNoEngine +} + func (client *NopClient) PauseContainer(name string) error { return ErrNoEngine } @@ -178,7 +190,7 @@ func (client *NopClient) ConnectNetwork(id, container string) error { return ErrNoEngine } -func (client *NopClient) DisconnectNetwork(id, container string) error { +func (client *NopClient) DisconnectNetwork(id, container string, force bool) error { return ErrNoEngine } diff --git a/vendor/github.com/samalba/dockerclient/tls.go b/vendor/github.com/samalba/dockerclient/tls.go new file mode 100644 index 00000000..80ae6f1e --- /dev/null +++ b/vendor/github.com/samalba/dockerclient/tls.go @@ -0,0 +1,38 @@ +package dockerclient + +import ( + "crypto/tls" + "crypto/x509" + "errors" + "io/ioutil" + "path/filepath" +) + +// TLSConfigFromCertPath returns a configuration based on PEM files in the directory +// +// path is usually what is set by the environment variable `DOCKER_CERT_PATH`, +// or `$HOME/.docker`. +func TLSConfigFromCertPath(path string) (*tls.Config, error) { + cert, err := ioutil.ReadFile(filepath.Join(path, "cert.pem")) + if err != nil { + return nil, err + } + key, err := ioutil.ReadFile(filepath.Join(path, "key.pem")) + if err != nil { + return nil, err + } + ca, err := ioutil.ReadFile(filepath.Join(path, "ca.pem")) + if err != nil { + return nil, err + } + tlsCert, err := tls.X509KeyPair(cert, key) + if err != nil { + return nil, err + } + tlsConfig := &tls.Config{Certificates: []tls.Certificate{tlsCert}} + tlsConfig.RootCAs = x509.NewCertPool() + if !tlsConfig.RootCAs.AppendCertsFromPEM(ca) { + return nil, errors.New("Could not add RootCA pem") + } + return tlsConfig, nil +} diff --git a/go/src/github.com/samalba/dockerclient/types.go b/vendor/github.com/samalba/dockerclient/types.go old mode 100755 new mode 100644 similarity index 68% rename from go/src/github.com/samalba/dockerclient/types.go rename to vendor/github.com/samalba/dockerclient/types.go index 7b64c89d..193c72c5 --- a/go/src/github.com/samalba/dockerclient/types.go +++ b/vendor/github.com/samalba/dockerclient/types.go @@ -5,7 +5,7 @@ import ( "io" "time" - "github.com/docker/docker/pkg/units" + "github.com/docker/go-units" ) type ContainerConfig struct { @@ -43,49 +43,70 @@ type ContainerConfig struct { // This is used only by the create command HostConfig HostConfig + + // Network configuration support + NetworkingConfig NetworkingConfig } type HostConfig struct { - Binds []string - ContainerIDFile string - LxcConf []map[string]string - Memory int64 - MemoryReservation int64 - MemorySwap int64 - KernelMemory int64 - CpuShares int64 - CpuPeriod int64 - CpusetCpus string - CpusetMems string - CpuQuota int64 - BlkioWeight int64 - OomKillDisable bool - MemorySwappiness int64 - Privileged bool - PortBindings map[string][]PortBinding - Links []string - PublishAllPorts bool - Dns []string - DNSOptions []string - DnsSearch []string - ExtraHosts []string - VolumesFrom []string - Devices []DeviceMapping - NetworkMode string - IpcMode string - PidMode string - UTSMode string - CapAdd []string - CapDrop []string - GroupAdd []string - RestartPolicy RestartPolicy - SecurityOpt []string - ReadonlyRootfs bool - Ulimits []Ulimit - LogConfig LogConfig - CgroupParent string - ConsoleSize [2]int - VolumeDriver string + Binds []string + ContainerIDFile string + LxcConf []map[string]string + Memory int64 + MemoryReservation int64 + MemorySwap int64 + KernelMemory int64 + CpuShares int64 + CpuPeriod int64 + CpusetCpus string + CpusetMems string + CpuQuota int64 + BlkioWeight int64 + OomKillDisable bool + MemorySwappiness int64 + Privileged bool + PortBindings map[string][]PortBinding + Links []string + PublishAllPorts bool + Dns []string + DNSOptions []string + DnsSearch []string + ExtraHosts []string + VolumesFrom []string + Devices []DeviceMapping + NetworkMode string + IpcMode string + PidMode string + UTSMode string + CapAdd []string + CapDrop []string + GroupAdd []string + RestartPolicy RestartPolicy + SecurityOpt []string + ReadonlyRootfs bool + Ulimits []Ulimit + LogConfig LogConfig + CgroupParent string + ConsoleSize [2]int + VolumeDriver string + OomScoreAdj int + Tmpfs map[string]string + ShmSize int64 `json:"omitempty"` + BlkioWeightDevice []WeightDevice + BlkioDeviceReadBps []ThrottleDevice + BlkioDeviceWriteBps []ThrottleDevice + BlkioDeviceReadIOps []ThrottleDevice + BlkioDeviceWriteIOps []ThrottleDevice +} + +type WeightDevice struct { + Path string + Weight uint16 +} + +type ThrottleDevice struct { + Path string + Rate uint64 } type DeviceMapping struct { @@ -112,6 +133,14 @@ type LogOptions struct { Tail int64 } +type AttachOptions struct { + Logs bool + Stream bool + Stdin bool + Stdout bool + Stderr bool +} + type MonitorEventsFilters struct { Event string `json:",omitempty"` Image string `json:",omitempty"` @@ -166,6 +195,10 @@ func (s *State) String() string { return "Dead" } + if s.StartedAt.IsZero() { + return "Created" + } + if s.FinishedAt.IsZero() { return "" } @@ -190,6 +223,10 @@ func (s *State) StateString() string { return "dead" } + if s.StartedAt.IsZero() { + return "created" + } + return "exited" } @@ -209,6 +246,14 @@ type ImageInfo struct { VirtualSize int64 } +type ImageSearch struct { + Description string `json:"description,omitempty" yaml:"description,omitempty"` + IsOfficial bool `json:"is_official,omitempty" yaml:"is_official,omitempty"` + IsAutomated bool `json:"is_automated,omitempty" yaml:"is_automated,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + StarCount int `json:"star_count,omitempty" yaml:"star_count,omitempty"` +} + type ContainerInfo struct { Id string Created string @@ -225,6 +270,7 @@ type ContainerInfo struct { Gateway string Bridge string Ports map[string][]PortBinding + Networks map[string]*EndpointSettings } SysInitPath string ResolvConfPath string @@ -244,24 +290,62 @@ type Port struct { Type string } +// EndpointSettings stores the network endpoint details +type EndpointSettings struct { + // Configurations + IPAMConfig *EndpointIPAMConfig + Links []string + Aliases []string + // Operational data + NetworkID string + EndpointID string + Gateway string + IPAddress string + IPPrefixLen int + IPv6Gateway string + GlobalIPv6Address string + GlobalIPv6PrefixLen int + MacAddress string +} + +// NetworkingConfig represents the container's networking configuration for each of its interfaces +// Carries the networink configs specified in the `docker run` and `docker network connect` commands +type NetworkingConfig struct { + EndpointsConfig map[string]*EndpointSettings // Endpoint configs for each conencting network +} + type Container struct { - Id string - Names []string - Image string - Command string - Created int64 - Status string - Ports []Port - SizeRw int64 - SizeRootFs int64 - Labels map[string]string + Id string + Names []string + Image string + Command string + Created int64 + Status string + Ports []Port + SizeRw int64 + SizeRootFs int64 + Labels map[string]string + NetworkSettings struct { + Networks map[string]EndpointSettings + } +} + +type Actor struct { + ID string + Attributes map[string]string } type Event struct { - Id string - Status string - From string - Time int64 + Status string `json:"status,omitempty"` + ID string `json:"id,omitempty"` + From string `json:"from,omitempty"` + + Type string + Action string + Actor Actor + + Time int64 `json:"time,omitempty"` + TimeNano int64 `json:"timeNano,omitempty"` } type Version struct { @@ -331,6 +415,11 @@ type ImageDelete struct { Untagged string } +type StatsOrError struct { + Stats + Error error +} + type EventOrError struct { Event Error error @@ -356,6 +445,7 @@ type ThrottlingData struct { ThrottledTime uint64 `json:"throttled_time"` } +// All CPU stats are aggregated since container inception. type CpuUsage struct { // Total CPU time consumed. // Units: nanoseconds. @@ -454,12 +544,14 @@ type BuildImage struct { CpuSetMems string CgroupParent string BuildArgs map[string]string + Labels map[string]string // Labels hold metadata about the image } type Volume struct { - Name string // Name is the name of the volume - Driver string // Driver is the Driver name used to create the volume - Mountpoint string // Mountpoint is the location on disk of the volume + Name string // Name is the name of the volume + Driver string // Driver is the Driver name used to create the volume + Mountpoint string // Mountpoint is the location on disk of the volume + Labels map[string]string // Labels hold metadata about the volume } type VolumesListResponse struct { @@ -470,12 +562,14 @@ type VolumeCreateRequest struct { Name string // Name is the requested name of the volume Driver string // Driver is the name of the driver that should be used to create the volume DriverOpts map[string]string // DriverOpts holds the driver specific options to use for when creating the volume. + Labels map[string]string // Labels hold metadata about the volume } // IPAM represents IP Address Management type IPAM struct { - Driver string - Config []IPAMConfig + Driver string + Options map[string]string //Per network IPAM driver options + Config []IPAMConfig } // IPAMConfig represents IPAM configurations @@ -486,18 +580,28 @@ type IPAMConfig struct { AuxAddress map[string]string `json:"AuxiliaryAddresses,omitempty"` } +// EndpointIPAMConfig represents IPAM configurations for the endpoint +type EndpointIPAMConfig struct { + IPv4Address string `json:",omitempty"` + IPv6Address string `json:",omitempty"` +} + // NetworkResource is the body of the "get network" http response message type NetworkResource struct { - Name string - ID string `json:"Id"` - Scope string - Driver string - IPAM IPAM + Name string + ID string `json:"Id"` + Scope string + Driver string + IPAM IPAM + //Internal bool Containers map[string]EndpointResource + Options map[string]string + Labels map[string]string // Labels hold metadata about the network } -//EndpointResource contains network resources allocated and usd for a container in a network +// EndpointResource contains network resources allocated and used for a container in a network type EndpointResource struct { + Name string EndpointID string MacAddress string IPv4Address string @@ -510,6 +614,9 @@ type NetworkCreate struct { CheckDuplicate bool Driver string IPAM IPAM + Internal bool + Options map[string]string + Labels map[string]string // Labels hold metadata about the network } // NetworkCreateResponse is the response message sent by the server for network create call @@ -526,4 +633,5 @@ type NetworkConnect struct { // NetworkDisconnect represents the data to be used to disconnect a container from the network type NetworkDisconnect struct { Container string + Force bool } diff --git a/go/src/github.com/samalba/dockerclient/utils.go b/vendor/github.com/samalba/dockerclient/utils.go old mode 100755 new mode 100644 similarity index 62% rename from go/src/github.com/samalba/dockerclient/utils.go rename to vendor/github.com/samalba/dockerclient/utils.go index 806f1b3e..8a6b0d6e --- a/go/src/github.com/samalba/dockerclient/utils.go +++ b/vendor/github.com/samalba/dockerclient/utils.go @@ -8,7 +8,9 @@ import ( "time" ) -func newHTTPClient(u *url.URL, tlsConfig *tls.Config, timeout time.Duration) *http.Client { +type tcpFunc func(*net.TCPConn, time.Duration) error + +func newHTTPClient(u *url.URL, tlsConfig *tls.Config, timeout time.Duration, setUserTimeout tcpFunc) *http.Client { httpTransport := &http.Transport{ TLSClientConfig: tlsConfig, } @@ -16,7 +18,13 @@ func newHTTPClient(u *url.URL, tlsConfig *tls.Config, timeout time.Duration) *ht switch u.Scheme { default: httpTransport.Dial = func(proto, addr string) (net.Conn, error) { - return net.DialTimeout(proto, addr, timeout) + conn, err := net.DialTimeout(proto, addr, timeout) + if tcpConn, ok := conn.(*net.TCPConn); ok && setUserTimeout != nil { + // Sender can break TCP connection if the remote side doesn't + // acknowledge packets within timeout + setUserTimeout(tcpConn, timeout) + } + return conn, err } case "unix": socketPath := u.Path diff --git a/vendor/github.com/stretchr/objx/LICENSE.md b/vendor/github.com/stretchr/objx/LICENSE.md new file mode 100644 index 00000000..21999458 --- /dev/null +++ b/vendor/github.com/stretchr/objx/LICENSE.md @@ -0,0 +1,23 @@ +objx - by Mat Ryer and Tyler Bunnell + +The MIT License (MIT) + +Copyright (c) 2014 Stretchr, Inc. + +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. diff --git a/vendor/github.com/stretchr/objx/accessors.go b/vendor/github.com/stretchr/objx/accessors.go new file mode 100644 index 00000000..721bcac7 --- /dev/null +++ b/vendor/github.com/stretchr/objx/accessors.go @@ -0,0 +1,179 @@ +package objx + +import ( + "fmt" + "regexp" + "strconv" + "strings" +) + +// arrayAccesRegexString is the regex used to extract the array number +// from the access path +const arrayAccesRegexString = `^(.+)\[([0-9]+)\]$` + +// arrayAccesRegex is the compiled arrayAccesRegexString +var arrayAccesRegex = regexp.MustCompile(arrayAccesRegexString) + +// Get gets the value using the specified selector and +// returns it inside a new Obj object. +// +// If it cannot find the value, Get will return a nil +// value inside an instance of Obj. +// +// Get can only operate directly on map[string]interface{} and []interface. +// +// Example +// +// To access the title of the third chapter of the second book, do: +// +// o.Get("books[1].chapters[2].title") +func (m Map) Get(selector string) *Value { + rawObj := access(m, selector, nil, false, false) + return &Value{data: rawObj} +} + +// Set sets the value using the specified selector and +// returns the object on which Set was called. +// +// Set can only operate directly on map[string]interface{} and []interface +// +// Example +// +// To set the title of the third chapter of the second book, do: +// +// o.Set("books[1].chapters[2].title","Time to Go") +func (m Map) Set(selector string, value interface{}) Map { + access(m, selector, value, true, false) + return m +} + +// access accesses the object using the selector and performs the +// appropriate action. +func access(current, selector, value interface{}, isSet, panics bool) interface{} { + + switch selector.(type) { + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + + if array, ok := current.([]interface{}); ok { + index := intFromInterface(selector) + + if index >= len(array) { + if panics { + panic(fmt.Sprintf("objx: Index %d is out of range. Slice only contains %d items.", index, len(array))) + } + return nil + } + + return array[index] + } + + return nil + + case string: + + selStr := selector.(string) + selSegs := strings.SplitN(selStr, PathSeparator, 2) + thisSel := selSegs[0] + index := -1 + var err error + + // https://github.com/stretchr/objx/issues/12 + if strings.Contains(thisSel, "[") { + + arrayMatches := arrayAccesRegex.FindStringSubmatch(thisSel) + + if len(arrayMatches) > 0 { + + // Get the key into the map + thisSel = arrayMatches[1] + + // Get the index into the array at the key + index, err = strconv.Atoi(arrayMatches[2]) + + if err != nil { + // This should never happen. If it does, something has gone + // seriously wrong. Panic. + panic("objx: Array index is not an integer. Must use array[int].") + } + + } + } + + if curMap, ok := current.(Map); ok { + current = map[string]interface{}(curMap) + } + + // get the object in question + switch current.(type) { + case map[string]interface{}: + curMSI := current.(map[string]interface{}) + if len(selSegs) <= 1 && isSet { + curMSI[thisSel] = value + return nil + } else { + current = curMSI[thisSel] + } + default: + current = nil + } + + if current == nil && panics { + panic(fmt.Sprintf("objx: '%v' invalid on object.", selector)) + } + + // do we need to access the item of an array? + if index > -1 { + if array, ok := current.([]interface{}); ok { + if index < len(array) { + current = array[index] + } else { + if panics { + panic(fmt.Sprintf("objx: Index %d is out of range. Slice only contains %d items.", index, len(array))) + } + current = nil + } + } + } + + if len(selSegs) > 1 { + current = access(current, selSegs[1], value, isSet, panics) + } + + } + + return current + +} + +// intFromInterface converts an interface object to the largest +// representation of an unsigned integer using a type switch and +// assertions +func intFromInterface(selector interface{}) int { + var value int + switch selector.(type) { + case int: + value = selector.(int) + case int8: + value = int(selector.(int8)) + case int16: + value = int(selector.(int16)) + case int32: + value = int(selector.(int32)) + case int64: + value = int(selector.(int64)) + case uint: + value = int(selector.(uint)) + case uint8: + value = int(selector.(uint8)) + case uint16: + value = int(selector.(uint16)) + case uint32: + value = int(selector.(uint32)) + case uint64: + value = int(selector.(uint64)) + default: + panic("objx: array access argument is not an integer type (this should never happen)") + } + + return value +} diff --git a/vendor/github.com/stretchr/objx/constants.go b/vendor/github.com/stretchr/objx/constants.go new file mode 100644 index 00000000..f9eb42a2 --- /dev/null +++ b/vendor/github.com/stretchr/objx/constants.go @@ -0,0 +1,13 @@ +package objx + +const ( + // PathSeparator is the character used to separate the elements + // of the keypath. + // + // For example, `location.address.city` + PathSeparator string = "." + + // SignatureSeparator is the character that is used to + // separate the Base64 string from the security signature. + SignatureSeparator = "_" +) diff --git a/vendor/github.com/stretchr/objx/conversions.go b/vendor/github.com/stretchr/objx/conversions.go new file mode 100644 index 00000000..9cdfa9f9 --- /dev/null +++ b/vendor/github.com/stretchr/objx/conversions.go @@ -0,0 +1,117 @@ +package objx + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "net/url" +) + +// JSON converts the contained object to a JSON string +// representation +func (m Map) JSON() (string, error) { + + result, err := json.Marshal(m) + + if err != nil { + err = errors.New("objx: JSON encode failed with: " + err.Error()) + } + + return string(result), err + +} + +// MustJSON converts the contained object to a JSON string +// representation and panics if there is an error +func (m Map) MustJSON() string { + result, err := m.JSON() + if err != nil { + panic(err.Error()) + } + return result +} + +// Base64 converts the contained object to a Base64 string +// representation of the JSON string representation +func (m Map) Base64() (string, error) { + + var buf bytes.Buffer + + jsonData, err := m.JSON() + if err != nil { + return "", err + } + + encoder := base64.NewEncoder(base64.StdEncoding, &buf) + encoder.Write([]byte(jsonData)) + encoder.Close() + + return buf.String(), nil + +} + +// MustBase64 converts the contained object to a Base64 string +// representation of the JSON string representation and panics +// if there is an error +func (m Map) MustBase64() string { + result, err := m.Base64() + if err != nil { + panic(err.Error()) + } + return result +} + +// SignedBase64 converts the contained object to a Base64 string +// representation of the JSON string representation and signs it +// using the provided key. +func (m Map) SignedBase64(key string) (string, error) { + + base64, err := m.Base64() + if err != nil { + return "", err + } + + sig := HashWithKey(base64, key) + + return base64 + SignatureSeparator + sig, nil + +} + +// MustSignedBase64 converts the contained object to a Base64 string +// representation of the JSON string representation and signs it +// using the provided key and panics if there is an error +func (m Map) MustSignedBase64(key string) string { + result, err := m.SignedBase64(key) + if err != nil { + panic(err.Error()) + } + return result +} + +/* + URL Query + ------------------------------------------------ +*/ + +// URLValues creates a url.Values object from an Obj. This +// function requires that the wrapped object be a map[string]interface{} +func (m Map) URLValues() url.Values { + + vals := make(url.Values) + + for k, v := range m { + //TODO: can this be done without sprintf? + vals.Set(k, fmt.Sprintf("%v", v)) + } + + return vals +} + +// URLQuery gets an encoded URL query representing the given +// Obj. This function requires that the wrapped object be a +// map[string]interface{} +func (m Map) URLQuery() (string, error) { + return m.URLValues().Encode(), nil +} diff --git a/vendor/github.com/stretchr/objx/doc.go b/vendor/github.com/stretchr/objx/doc.go new file mode 100644 index 00000000..47bf85e4 --- /dev/null +++ b/vendor/github.com/stretchr/objx/doc.go @@ -0,0 +1,72 @@ +// objx - Go package for dealing with maps, slices, JSON and other data. +// +// Overview +// +// Objx provides the `objx.Map` type, which is a `map[string]interface{}` that exposes +// a powerful `Get` method (among others) that allows you to easily and quickly get +// access to data within the map, without having to worry too much about type assertions, +// missing data, default values etc. +// +// Pattern +// +// Objx uses a preditable pattern to make access data from within `map[string]interface{}'s +// easy. +// +// Call one of the `objx.` functions to create your `objx.Map` to get going: +// +// m, err := objx.FromJSON(json) +// +// NOTE: Any methods or functions with the `Must` prefix will panic if something goes wrong, +// the rest will be optimistic and try to figure things out without panicking. +// +// Use `Get` to access the value you're interested in. You can use dot and array +// notation too: +// +// m.Get("places[0].latlng") +// +// Once you have saught the `Value` you're interested in, you can use the `Is*` methods +// to determine its type. +// +// if m.Get("code").IsStr() { /* ... */ } +// +// Or you can just assume the type, and use one of the strong type methods to +// extract the real value: +// +// m.Get("code").Int() +// +// If there's no value there (or if it's the wrong type) then a default value +// will be returned, or you can be explicit about the default value. +// +// Get("code").Int(-1) +// +// If you're dealing with a slice of data as a value, Objx provides many useful +// methods for iterating, manipulating and selecting that data. You can find out more +// by exploring the index below. +// +// Reading data +// +// A simple example of how to use Objx: +// +// // use MustFromJSON to make an objx.Map from some JSON +// m := objx.MustFromJSON(`{"name": "Mat", "age": 30}`) +// +// // get the details +// name := m.Get("name").Str() +// age := m.Get("age").Int() +// +// // get their nickname (or use their name if they +// // don't have one) +// nickname := m.Get("nickname").Str(name) +// +// Ranging +// +// Since `objx.Map` is a `map[string]interface{}` you can treat it as such. For +// example, to `range` the data, do what you would expect: +// +// m := objx.MustFromJSON(json) +// for key, value := range m { +// +// /* ... do your magic ... */ +// +// } +package objx diff --git a/vendor/github.com/stretchr/objx/map.go b/vendor/github.com/stretchr/objx/map.go new file mode 100644 index 00000000..eb6ed8e2 --- /dev/null +++ b/vendor/github.com/stretchr/objx/map.go @@ -0,0 +1,222 @@ +package objx + +import ( + "encoding/base64" + "encoding/json" + "errors" + "io/ioutil" + "net/url" + "strings" +) + +// MSIConvertable is an interface that defines methods for converting your +// custom types to a map[string]interface{} representation. +type MSIConvertable interface { + // MSI gets a map[string]interface{} (msi) representing the + // object. + MSI() map[string]interface{} +} + +// Map provides extended functionality for working with +// untyped data, in particular map[string]interface (msi). +type Map map[string]interface{} + +// Value returns the internal value instance +func (m Map) Value() *Value { + return &Value{data: m} +} + +// Nil represents a nil Map. +var Nil Map = New(nil) + +// New creates a new Map containing the map[string]interface{} in the data argument. +// If the data argument is not a map[string]interface, New attempts to call the +// MSI() method on the MSIConvertable interface to create one. +func New(data interface{}) Map { + if _, ok := data.(map[string]interface{}); !ok { + if converter, ok := data.(MSIConvertable); ok { + data = converter.MSI() + } else { + return nil + } + } + return Map(data.(map[string]interface{})) +} + +// MSI creates a map[string]interface{} and puts it inside a new Map. +// +// The arguments follow a key, value pattern. +// +// Panics +// +// Panics if any key arugment is non-string or if there are an odd number of arguments. +// +// Example +// +// To easily create Maps: +// +// m := objx.MSI("name", "Mat", "age", 29, "subobj", objx.MSI("active", true)) +// +// // creates an Map equivalent to +// m := objx.New(map[string]interface{}{"name": "Mat", "age": 29, "subobj": map[string]interface{}{"active": true}}) +func MSI(keyAndValuePairs ...interface{}) Map { + + newMap := make(map[string]interface{}) + keyAndValuePairsLen := len(keyAndValuePairs) + + if keyAndValuePairsLen%2 != 0 { + panic("objx: MSI must have an even number of arguments following the 'key, value' pattern.") + } + + for i := 0; i < keyAndValuePairsLen; i = i + 2 { + + key := keyAndValuePairs[i] + value := keyAndValuePairs[i+1] + + // make sure the key is a string + keyString, keyStringOK := key.(string) + if !keyStringOK { + panic("objx: MSI must follow 'string, interface{}' pattern. " + keyString + " is not a valid key.") + } + + newMap[keyString] = value + + } + + return New(newMap) +} + +// ****** Conversion Constructors + +// MustFromJSON creates a new Map containing the data specified in the +// jsonString. +// +// Panics if the JSON is invalid. +func MustFromJSON(jsonString string) Map { + o, err := FromJSON(jsonString) + + if err != nil { + panic("objx: MustFromJSON failed with error: " + err.Error()) + } + + return o +} + +// FromJSON creates a new Map containing the data specified in the +// jsonString. +// +// Returns an error if the JSON is invalid. +func FromJSON(jsonString string) (Map, error) { + + var data interface{} + err := json.Unmarshal([]byte(jsonString), &data) + + if err != nil { + return Nil, err + } + + return New(data), nil + +} + +// FromBase64 creates a new Obj containing the data specified +// in the Base64 string. +// +// The string is an encoded JSON string returned by Base64 +func FromBase64(base64String string) (Map, error) { + + decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(base64String)) + + decoded, err := ioutil.ReadAll(decoder) + if err != nil { + return nil, err + } + + return FromJSON(string(decoded)) +} + +// MustFromBase64 creates a new Obj containing the data specified +// in the Base64 string and panics if there is an error. +// +// The string is an encoded JSON string returned by Base64 +func MustFromBase64(base64String string) Map { + + result, err := FromBase64(base64String) + + if err != nil { + panic("objx: MustFromBase64 failed with error: " + err.Error()) + } + + return result +} + +// FromSignedBase64 creates a new Obj containing the data specified +// in the Base64 string. +// +// The string is an encoded JSON string returned by SignedBase64 +func FromSignedBase64(base64String, key string) (Map, error) { + parts := strings.Split(base64String, SignatureSeparator) + if len(parts) != 2 { + return nil, errors.New("objx: Signed base64 string is malformed.") + } + + sig := HashWithKey(parts[0], key) + if parts[1] != sig { + return nil, errors.New("objx: Signature for base64 data does not match.") + } + + return FromBase64(parts[0]) +} + +// MustFromSignedBase64 creates a new Obj containing the data specified +// in the Base64 string and panics if there is an error. +// +// The string is an encoded JSON string returned by Base64 +func MustFromSignedBase64(base64String, key string) Map { + + result, err := FromSignedBase64(base64String, key) + + if err != nil { + panic("objx: MustFromSignedBase64 failed with error: " + err.Error()) + } + + return result +} + +// FromURLQuery generates a new Obj by parsing the specified +// query. +// +// For queries with multiple values, the first value is selected. +func FromURLQuery(query string) (Map, error) { + + vals, err := url.ParseQuery(query) + + if err != nil { + return nil, err + } + + m := make(map[string]interface{}) + for k, vals := range vals { + m[k] = vals[0] + } + + return New(m), nil +} + +// MustFromURLQuery generates a new Obj by parsing the specified +// query. +// +// For queries with multiple values, the first value is selected. +// +// Panics if it encounters an error +func MustFromURLQuery(query string) Map { + + o, err := FromURLQuery(query) + + if err != nil { + panic("objx: MustFromURLQuery failed with error: " + err.Error()) + } + + return o + +} diff --git a/vendor/github.com/stretchr/objx/mutations.go b/vendor/github.com/stretchr/objx/mutations.go new file mode 100644 index 00000000..b35c8639 --- /dev/null +++ b/vendor/github.com/stretchr/objx/mutations.go @@ -0,0 +1,81 @@ +package objx + +// Exclude returns a new Map with the keys in the specified []string +// excluded. +func (d Map) Exclude(exclude []string) Map { + + excluded := make(Map) + for k, v := range d { + var shouldInclude bool = true + for _, toExclude := range exclude { + if k == toExclude { + shouldInclude = false + break + } + } + if shouldInclude { + excluded[k] = v + } + } + + return excluded +} + +// Copy creates a shallow copy of the Obj. +func (m Map) Copy() Map { + copied := make(map[string]interface{}) + for k, v := range m { + copied[k] = v + } + return New(copied) +} + +// Merge blends the specified map with a copy of this map and returns the result. +// +// Keys that appear in both will be selected from the specified map. +// This method requires that the wrapped object be a map[string]interface{} +func (m Map) Merge(merge Map) Map { + return m.Copy().MergeHere(merge) +} + +// Merge blends the specified map with this map and returns the current map. +// +// Keys that appear in both will be selected from the specified map. The original map +// will be modified. This method requires that +// the wrapped object be a map[string]interface{} +func (m Map) MergeHere(merge Map) Map { + + for k, v := range merge { + m[k] = v + } + + return m + +} + +// Transform builds a new Obj giving the transformer a chance +// to change the keys and values as it goes. This method requires that +// the wrapped object be a map[string]interface{} +func (m Map) Transform(transformer func(key string, value interface{}) (string, interface{})) Map { + newMap := make(map[string]interface{}) + for k, v := range m { + modifiedKey, modifiedVal := transformer(k, v) + newMap[modifiedKey] = modifiedVal + } + return New(newMap) +} + +// TransformKeys builds a new map using the specified key mapping. +// +// Unspecified keys will be unaltered. +// This method requires that the wrapped object be a map[string]interface{} +func (m Map) TransformKeys(mapping map[string]string) Map { + return m.Transform(func(key string, value interface{}) (string, interface{}) { + + if newKey, ok := mapping[key]; ok { + return newKey, value + } + + return key, value + }) +} diff --git a/vendor/github.com/stretchr/objx/security.go b/vendor/github.com/stretchr/objx/security.go new file mode 100644 index 00000000..fdd6be9c --- /dev/null +++ b/vendor/github.com/stretchr/objx/security.go @@ -0,0 +1,14 @@ +package objx + +import ( + "crypto/sha1" + "encoding/hex" +) + +// HashWithKey hashes the specified string using the security +// key. +func HashWithKey(data, key string) string { + hash := sha1.New() + hash.Write([]byte(data + ":" + key)) + return hex.EncodeToString(hash.Sum(nil)) +} diff --git a/vendor/github.com/stretchr/objx/tests.go b/vendor/github.com/stretchr/objx/tests.go new file mode 100644 index 00000000..d9e0b479 --- /dev/null +++ b/vendor/github.com/stretchr/objx/tests.go @@ -0,0 +1,17 @@ +package objx + +// Has gets whether there is something at the specified selector +// or not. +// +// If m is nil, Has will always return false. +func (m Map) Has(selector string) bool { + if m == nil { + return false + } + return !m.Get(selector).IsNil() +} + +// IsNil gets whether the data is nil or not. +func (v *Value) IsNil() bool { + return v == nil || v.data == nil +} diff --git a/vendor/github.com/stretchr/objx/type_specific_codegen.go b/vendor/github.com/stretchr/objx/type_specific_codegen.go new file mode 100644 index 00000000..f3ecb29b --- /dev/null +++ b/vendor/github.com/stretchr/objx/type_specific_codegen.go @@ -0,0 +1,2881 @@ +package objx + +/* + Inter (interface{} and []interface{}) + -------------------------------------------------- +*/ + +// Inter gets the value as a interface{}, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Inter(optionalDefault ...interface{}) interface{} { + if s, ok := v.data.(interface{}); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInter gets the value as a interface{}. +// +// Panics if the object is not a interface{}. +func (v *Value) MustInter() interface{} { + return v.data.(interface{}) +} + +// InterSlice gets the value as a []interface{}, returns the optionalDefault +// value or nil if the value is not a []interface{}. +func (v *Value) InterSlice(optionalDefault ...[]interface{}) []interface{} { + if s, ok := v.data.([]interface{}); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInterSlice gets the value as a []interface{}. +// +// Panics if the object is not a []interface{}. +func (v *Value) MustInterSlice() []interface{} { + return v.data.([]interface{}) +} + +// IsInter gets whether the object contained is a interface{} or not. +func (v *Value) IsInter() bool { + _, ok := v.data.(interface{}) + return ok +} + +// IsInterSlice gets whether the object contained is a []interface{} or not. +func (v *Value) IsInterSlice() bool { + _, ok := v.data.([]interface{}) + return ok +} + +// EachInter calls the specified callback for each object +// in the []interface{}. +// +// Panics if the object is the wrong type. +func (v *Value) EachInter(callback func(int, interface{}) bool) *Value { + + for index, val := range v.MustInterSlice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereInter uses the specified decider function to select items +// from the []interface{}. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInter(decider func(int, interface{}) bool) *Value { + + var selected []interface{} + + v.EachInter(func(index int, val interface{}) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupInter uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]interface{}. +func (v *Value) GroupInter(grouper func(int, interface{}) string) *Value { + + groups := make(map[string][]interface{}) + + v.EachInter(func(index int, val interface{}) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]interface{}, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceInter uses the specified function to replace each interface{}s +// by iterating each item. The data in the returned result will be a +// []interface{} containing the replaced items. +func (v *Value) ReplaceInter(replacer func(int, interface{}) interface{}) *Value { + + arr := v.MustInterSlice() + replaced := make([]interface{}, len(arr)) + + v.EachInter(func(index int, val interface{}) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectInter uses the specified collector function to collect a value +// for each of the interface{}s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInter(collector func(int, interface{}) interface{}) *Value { + + arr := v.MustInterSlice() + collected := make([]interface{}, len(arr)) + + v.EachInter(func(index int, val interface{}) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + MSI (map[string]interface{} and []map[string]interface{}) + -------------------------------------------------- +*/ + +// MSI gets the value as a map[string]interface{}, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) MSI(optionalDefault ...map[string]interface{}) map[string]interface{} { + if s, ok := v.data.(map[string]interface{}); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustMSI gets the value as a map[string]interface{}. +// +// Panics if the object is not a map[string]interface{}. +func (v *Value) MustMSI() map[string]interface{} { + return v.data.(map[string]interface{}) +} + +// MSISlice gets the value as a []map[string]interface{}, returns the optionalDefault +// value or nil if the value is not a []map[string]interface{}. +func (v *Value) MSISlice(optionalDefault ...[]map[string]interface{}) []map[string]interface{} { + if s, ok := v.data.([]map[string]interface{}); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustMSISlice gets the value as a []map[string]interface{}. +// +// Panics if the object is not a []map[string]interface{}. +func (v *Value) MustMSISlice() []map[string]interface{} { + return v.data.([]map[string]interface{}) +} + +// IsMSI gets whether the object contained is a map[string]interface{} or not. +func (v *Value) IsMSI() bool { + _, ok := v.data.(map[string]interface{}) + return ok +} + +// IsMSISlice gets whether the object contained is a []map[string]interface{} or not. +func (v *Value) IsMSISlice() bool { + _, ok := v.data.([]map[string]interface{}) + return ok +} + +// EachMSI calls the specified callback for each object +// in the []map[string]interface{}. +// +// Panics if the object is the wrong type. +func (v *Value) EachMSI(callback func(int, map[string]interface{}) bool) *Value { + + for index, val := range v.MustMSISlice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereMSI uses the specified decider function to select items +// from the []map[string]interface{}. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereMSI(decider func(int, map[string]interface{}) bool) *Value { + + var selected []map[string]interface{} + + v.EachMSI(func(index int, val map[string]interface{}) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupMSI uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]map[string]interface{}. +func (v *Value) GroupMSI(grouper func(int, map[string]interface{}) string) *Value { + + groups := make(map[string][]map[string]interface{}) + + v.EachMSI(func(index int, val map[string]interface{}) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]map[string]interface{}, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceMSI uses the specified function to replace each map[string]interface{}s +// by iterating each item. The data in the returned result will be a +// []map[string]interface{} containing the replaced items. +func (v *Value) ReplaceMSI(replacer func(int, map[string]interface{}) map[string]interface{}) *Value { + + arr := v.MustMSISlice() + replaced := make([]map[string]interface{}, len(arr)) + + v.EachMSI(func(index int, val map[string]interface{}) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectMSI uses the specified collector function to collect a value +// for each of the map[string]interface{}s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectMSI(collector func(int, map[string]interface{}) interface{}) *Value { + + arr := v.MustMSISlice() + collected := make([]interface{}, len(arr)) + + v.EachMSI(func(index int, val map[string]interface{}) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + ObjxMap ((Map) and [](Map)) + -------------------------------------------------- +*/ + +// ObjxMap gets the value as a (Map), returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) ObjxMap(optionalDefault ...(Map)) Map { + if s, ok := v.data.((Map)); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return New(nil) +} + +// MustObjxMap gets the value as a (Map). +// +// Panics if the object is not a (Map). +func (v *Value) MustObjxMap() Map { + return v.data.((Map)) +} + +// ObjxMapSlice gets the value as a [](Map), returns the optionalDefault +// value or nil if the value is not a [](Map). +func (v *Value) ObjxMapSlice(optionalDefault ...[](Map)) [](Map) { + if s, ok := v.data.([](Map)); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustObjxMapSlice gets the value as a [](Map). +// +// Panics if the object is not a [](Map). +func (v *Value) MustObjxMapSlice() [](Map) { + return v.data.([](Map)) +} + +// IsObjxMap gets whether the object contained is a (Map) or not. +func (v *Value) IsObjxMap() bool { + _, ok := v.data.((Map)) + return ok +} + +// IsObjxMapSlice gets whether the object contained is a [](Map) or not. +func (v *Value) IsObjxMapSlice() bool { + _, ok := v.data.([](Map)) + return ok +} + +// EachObjxMap calls the specified callback for each object +// in the [](Map). +// +// Panics if the object is the wrong type. +func (v *Value) EachObjxMap(callback func(int, Map) bool) *Value { + + for index, val := range v.MustObjxMapSlice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereObjxMap uses the specified decider function to select items +// from the [](Map). The object contained in the result will contain +// only the selected items. +func (v *Value) WhereObjxMap(decider func(int, Map) bool) *Value { + + var selected [](Map) + + v.EachObjxMap(func(index int, val Map) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupObjxMap uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][](Map). +func (v *Value) GroupObjxMap(grouper func(int, Map) string) *Value { + + groups := make(map[string][](Map)) + + v.EachObjxMap(func(index int, val Map) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([](Map), 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceObjxMap uses the specified function to replace each (Map)s +// by iterating each item. The data in the returned result will be a +// [](Map) containing the replaced items. +func (v *Value) ReplaceObjxMap(replacer func(int, Map) Map) *Value { + + arr := v.MustObjxMapSlice() + replaced := make([](Map), len(arr)) + + v.EachObjxMap(func(index int, val Map) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectObjxMap uses the specified collector function to collect a value +// for each of the (Map)s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectObjxMap(collector func(int, Map) interface{}) *Value { + + arr := v.MustObjxMapSlice() + collected := make([]interface{}, len(arr)) + + v.EachObjxMap(func(index int, val Map) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Bool (bool and []bool) + -------------------------------------------------- +*/ + +// Bool gets the value as a bool, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Bool(optionalDefault ...bool) bool { + if s, ok := v.data.(bool); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return false +} + +// MustBool gets the value as a bool. +// +// Panics if the object is not a bool. +func (v *Value) MustBool() bool { + return v.data.(bool) +} + +// BoolSlice gets the value as a []bool, returns the optionalDefault +// value or nil if the value is not a []bool. +func (v *Value) BoolSlice(optionalDefault ...[]bool) []bool { + if s, ok := v.data.([]bool); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustBoolSlice gets the value as a []bool. +// +// Panics if the object is not a []bool. +func (v *Value) MustBoolSlice() []bool { + return v.data.([]bool) +} + +// IsBool gets whether the object contained is a bool or not. +func (v *Value) IsBool() bool { + _, ok := v.data.(bool) + return ok +} + +// IsBoolSlice gets whether the object contained is a []bool or not. +func (v *Value) IsBoolSlice() bool { + _, ok := v.data.([]bool) + return ok +} + +// EachBool calls the specified callback for each object +// in the []bool. +// +// Panics if the object is the wrong type. +func (v *Value) EachBool(callback func(int, bool) bool) *Value { + + for index, val := range v.MustBoolSlice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereBool uses the specified decider function to select items +// from the []bool. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereBool(decider func(int, bool) bool) *Value { + + var selected []bool + + v.EachBool(func(index int, val bool) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupBool uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]bool. +func (v *Value) GroupBool(grouper func(int, bool) string) *Value { + + groups := make(map[string][]bool) + + v.EachBool(func(index int, val bool) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]bool, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceBool uses the specified function to replace each bools +// by iterating each item. The data in the returned result will be a +// []bool containing the replaced items. +func (v *Value) ReplaceBool(replacer func(int, bool) bool) *Value { + + arr := v.MustBoolSlice() + replaced := make([]bool, len(arr)) + + v.EachBool(func(index int, val bool) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectBool uses the specified collector function to collect a value +// for each of the bools in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectBool(collector func(int, bool) interface{}) *Value { + + arr := v.MustBoolSlice() + collected := make([]interface{}, len(arr)) + + v.EachBool(func(index int, val bool) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Str (string and []string) + -------------------------------------------------- +*/ + +// Str gets the value as a string, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Str(optionalDefault ...string) string { + if s, ok := v.data.(string); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return "" +} + +// MustStr gets the value as a string. +// +// Panics if the object is not a string. +func (v *Value) MustStr() string { + return v.data.(string) +} + +// StrSlice gets the value as a []string, returns the optionalDefault +// value or nil if the value is not a []string. +func (v *Value) StrSlice(optionalDefault ...[]string) []string { + if s, ok := v.data.([]string); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustStrSlice gets the value as a []string. +// +// Panics if the object is not a []string. +func (v *Value) MustStrSlice() []string { + return v.data.([]string) +} + +// IsStr gets whether the object contained is a string or not. +func (v *Value) IsStr() bool { + _, ok := v.data.(string) + return ok +} + +// IsStrSlice gets whether the object contained is a []string or not. +func (v *Value) IsStrSlice() bool { + _, ok := v.data.([]string) + return ok +} + +// EachStr calls the specified callback for each object +// in the []string. +// +// Panics if the object is the wrong type. +func (v *Value) EachStr(callback func(int, string) bool) *Value { + + for index, val := range v.MustStrSlice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereStr uses the specified decider function to select items +// from the []string. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereStr(decider func(int, string) bool) *Value { + + var selected []string + + v.EachStr(func(index int, val string) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupStr uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]string. +func (v *Value) GroupStr(grouper func(int, string) string) *Value { + + groups := make(map[string][]string) + + v.EachStr(func(index int, val string) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]string, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceStr uses the specified function to replace each strings +// by iterating each item. The data in the returned result will be a +// []string containing the replaced items. +func (v *Value) ReplaceStr(replacer func(int, string) string) *Value { + + arr := v.MustStrSlice() + replaced := make([]string, len(arr)) + + v.EachStr(func(index int, val string) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectStr uses the specified collector function to collect a value +// for each of the strings in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectStr(collector func(int, string) interface{}) *Value { + + arr := v.MustStrSlice() + collected := make([]interface{}, len(arr)) + + v.EachStr(func(index int, val string) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Int (int and []int) + -------------------------------------------------- +*/ + +// Int gets the value as a int, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int(optionalDefault ...int) int { + if s, ok := v.data.(int); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt gets the value as a int. +// +// Panics if the object is not a int. +func (v *Value) MustInt() int { + return v.data.(int) +} + +// IntSlice gets the value as a []int, returns the optionalDefault +// value or nil if the value is not a []int. +func (v *Value) IntSlice(optionalDefault ...[]int) []int { + if s, ok := v.data.([]int); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustIntSlice gets the value as a []int. +// +// Panics if the object is not a []int. +func (v *Value) MustIntSlice() []int { + return v.data.([]int) +} + +// IsInt gets whether the object contained is a int or not. +func (v *Value) IsInt() bool { + _, ok := v.data.(int) + return ok +} + +// IsIntSlice gets whether the object contained is a []int or not. +func (v *Value) IsIntSlice() bool { + _, ok := v.data.([]int) + return ok +} + +// EachInt calls the specified callback for each object +// in the []int. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt(callback func(int, int) bool) *Value { + + for index, val := range v.MustIntSlice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereInt uses the specified decider function to select items +// from the []int. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt(decider func(int, int) bool) *Value { + + var selected []int + + v.EachInt(func(index int, val int) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupInt uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int. +func (v *Value) GroupInt(grouper func(int, int) string) *Value { + + groups := make(map[string][]int) + + v.EachInt(func(index int, val int) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceInt uses the specified function to replace each ints +// by iterating each item. The data in the returned result will be a +// []int containing the replaced items. +func (v *Value) ReplaceInt(replacer func(int, int) int) *Value { + + arr := v.MustIntSlice() + replaced := make([]int, len(arr)) + + v.EachInt(func(index int, val int) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectInt uses the specified collector function to collect a value +// for each of the ints in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt(collector func(int, int) interface{}) *Value { + + arr := v.MustIntSlice() + collected := make([]interface{}, len(arr)) + + v.EachInt(func(index int, val int) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Int8 (int8 and []int8) + -------------------------------------------------- +*/ + +// Int8 gets the value as a int8, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int8(optionalDefault ...int8) int8 { + if s, ok := v.data.(int8); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt8 gets the value as a int8. +// +// Panics if the object is not a int8. +func (v *Value) MustInt8() int8 { + return v.data.(int8) +} + +// Int8Slice gets the value as a []int8, returns the optionalDefault +// value or nil if the value is not a []int8. +func (v *Value) Int8Slice(optionalDefault ...[]int8) []int8 { + if s, ok := v.data.([]int8); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInt8Slice gets the value as a []int8. +// +// Panics if the object is not a []int8. +func (v *Value) MustInt8Slice() []int8 { + return v.data.([]int8) +} + +// IsInt8 gets whether the object contained is a int8 or not. +func (v *Value) IsInt8() bool { + _, ok := v.data.(int8) + return ok +} + +// IsInt8Slice gets whether the object contained is a []int8 or not. +func (v *Value) IsInt8Slice() bool { + _, ok := v.data.([]int8) + return ok +} + +// EachInt8 calls the specified callback for each object +// in the []int8. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt8(callback func(int, int8) bool) *Value { + + for index, val := range v.MustInt8Slice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereInt8 uses the specified decider function to select items +// from the []int8. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt8(decider func(int, int8) bool) *Value { + + var selected []int8 + + v.EachInt8(func(index int, val int8) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupInt8 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int8. +func (v *Value) GroupInt8(grouper func(int, int8) string) *Value { + + groups := make(map[string][]int8) + + v.EachInt8(func(index int, val int8) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int8, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceInt8 uses the specified function to replace each int8s +// by iterating each item. The data in the returned result will be a +// []int8 containing the replaced items. +func (v *Value) ReplaceInt8(replacer func(int, int8) int8) *Value { + + arr := v.MustInt8Slice() + replaced := make([]int8, len(arr)) + + v.EachInt8(func(index int, val int8) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectInt8 uses the specified collector function to collect a value +// for each of the int8s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt8(collector func(int, int8) interface{}) *Value { + + arr := v.MustInt8Slice() + collected := make([]interface{}, len(arr)) + + v.EachInt8(func(index int, val int8) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Int16 (int16 and []int16) + -------------------------------------------------- +*/ + +// Int16 gets the value as a int16, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int16(optionalDefault ...int16) int16 { + if s, ok := v.data.(int16); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt16 gets the value as a int16. +// +// Panics if the object is not a int16. +func (v *Value) MustInt16() int16 { + return v.data.(int16) +} + +// Int16Slice gets the value as a []int16, returns the optionalDefault +// value or nil if the value is not a []int16. +func (v *Value) Int16Slice(optionalDefault ...[]int16) []int16 { + if s, ok := v.data.([]int16); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInt16Slice gets the value as a []int16. +// +// Panics if the object is not a []int16. +func (v *Value) MustInt16Slice() []int16 { + return v.data.([]int16) +} + +// IsInt16 gets whether the object contained is a int16 or not. +func (v *Value) IsInt16() bool { + _, ok := v.data.(int16) + return ok +} + +// IsInt16Slice gets whether the object contained is a []int16 or not. +func (v *Value) IsInt16Slice() bool { + _, ok := v.data.([]int16) + return ok +} + +// EachInt16 calls the specified callback for each object +// in the []int16. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt16(callback func(int, int16) bool) *Value { + + for index, val := range v.MustInt16Slice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereInt16 uses the specified decider function to select items +// from the []int16. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt16(decider func(int, int16) bool) *Value { + + var selected []int16 + + v.EachInt16(func(index int, val int16) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupInt16 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int16. +func (v *Value) GroupInt16(grouper func(int, int16) string) *Value { + + groups := make(map[string][]int16) + + v.EachInt16(func(index int, val int16) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int16, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceInt16 uses the specified function to replace each int16s +// by iterating each item. The data in the returned result will be a +// []int16 containing the replaced items. +func (v *Value) ReplaceInt16(replacer func(int, int16) int16) *Value { + + arr := v.MustInt16Slice() + replaced := make([]int16, len(arr)) + + v.EachInt16(func(index int, val int16) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectInt16 uses the specified collector function to collect a value +// for each of the int16s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt16(collector func(int, int16) interface{}) *Value { + + arr := v.MustInt16Slice() + collected := make([]interface{}, len(arr)) + + v.EachInt16(func(index int, val int16) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Int32 (int32 and []int32) + -------------------------------------------------- +*/ + +// Int32 gets the value as a int32, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int32(optionalDefault ...int32) int32 { + if s, ok := v.data.(int32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt32 gets the value as a int32. +// +// Panics if the object is not a int32. +func (v *Value) MustInt32() int32 { + return v.data.(int32) +} + +// Int32Slice gets the value as a []int32, returns the optionalDefault +// value or nil if the value is not a []int32. +func (v *Value) Int32Slice(optionalDefault ...[]int32) []int32 { + if s, ok := v.data.([]int32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInt32Slice gets the value as a []int32. +// +// Panics if the object is not a []int32. +func (v *Value) MustInt32Slice() []int32 { + return v.data.([]int32) +} + +// IsInt32 gets whether the object contained is a int32 or not. +func (v *Value) IsInt32() bool { + _, ok := v.data.(int32) + return ok +} + +// IsInt32Slice gets whether the object contained is a []int32 or not. +func (v *Value) IsInt32Slice() bool { + _, ok := v.data.([]int32) + return ok +} + +// EachInt32 calls the specified callback for each object +// in the []int32. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt32(callback func(int, int32) bool) *Value { + + for index, val := range v.MustInt32Slice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereInt32 uses the specified decider function to select items +// from the []int32. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt32(decider func(int, int32) bool) *Value { + + var selected []int32 + + v.EachInt32(func(index int, val int32) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupInt32 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int32. +func (v *Value) GroupInt32(grouper func(int, int32) string) *Value { + + groups := make(map[string][]int32) + + v.EachInt32(func(index int, val int32) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int32, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceInt32 uses the specified function to replace each int32s +// by iterating each item. The data in the returned result will be a +// []int32 containing the replaced items. +func (v *Value) ReplaceInt32(replacer func(int, int32) int32) *Value { + + arr := v.MustInt32Slice() + replaced := make([]int32, len(arr)) + + v.EachInt32(func(index int, val int32) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectInt32 uses the specified collector function to collect a value +// for each of the int32s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt32(collector func(int, int32) interface{}) *Value { + + arr := v.MustInt32Slice() + collected := make([]interface{}, len(arr)) + + v.EachInt32(func(index int, val int32) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Int64 (int64 and []int64) + -------------------------------------------------- +*/ + +// Int64 gets the value as a int64, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int64(optionalDefault ...int64) int64 { + if s, ok := v.data.(int64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt64 gets the value as a int64. +// +// Panics if the object is not a int64. +func (v *Value) MustInt64() int64 { + return v.data.(int64) +} + +// Int64Slice gets the value as a []int64, returns the optionalDefault +// value or nil if the value is not a []int64. +func (v *Value) Int64Slice(optionalDefault ...[]int64) []int64 { + if s, ok := v.data.([]int64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInt64Slice gets the value as a []int64. +// +// Panics if the object is not a []int64. +func (v *Value) MustInt64Slice() []int64 { + return v.data.([]int64) +} + +// IsInt64 gets whether the object contained is a int64 or not. +func (v *Value) IsInt64() bool { + _, ok := v.data.(int64) + return ok +} + +// IsInt64Slice gets whether the object contained is a []int64 or not. +func (v *Value) IsInt64Slice() bool { + _, ok := v.data.([]int64) + return ok +} + +// EachInt64 calls the specified callback for each object +// in the []int64. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt64(callback func(int, int64) bool) *Value { + + for index, val := range v.MustInt64Slice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereInt64 uses the specified decider function to select items +// from the []int64. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt64(decider func(int, int64) bool) *Value { + + var selected []int64 + + v.EachInt64(func(index int, val int64) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupInt64 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int64. +func (v *Value) GroupInt64(grouper func(int, int64) string) *Value { + + groups := make(map[string][]int64) + + v.EachInt64(func(index int, val int64) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int64, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceInt64 uses the specified function to replace each int64s +// by iterating each item. The data in the returned result will be a +// []int64 containing the replaced items. +func (v *Value) ReplaceInt64(replacer func(int, int64) int64) *Value { + + arr := v.MustInt64Slice() + replaced := make([]int64, len(arr)) + + v.EachInt64(func(index int, val int64) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectInt64 uses the specified collector function to collect a value +// for each of the int64s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt64(collector func(int, int64) interface{}) *Value { + + arr := v.MustInt64Slice() + collected := make([]interface{}, len(arr)) + + v.EachInt64(func(index int, val int64) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Uint (uint and []uint) + -------------------------------------------------- +*/ + +// Uint gets the value as a uint, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint(optionalDefault ...uint) uint { + if s, ok := v.data.(uint); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint gets the value as a uint. +// +// Panics if the object is not a uint. +func (v *Value) MustUint() uint { + return v.data.(uint) +} + +// UintSlice gets the value as a []uint, returns the optionalDefault +// value or nil if the value is not a []uint. +func (v *Value) UintSlice(optionalDefault ...[]uint) []uint { + if s, ok := v.data.([]uint); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUintSlice gets the value as a []uint. +// +// Panics if the object is not a []uint. +func (v *Value) MustUintSlice() []uint { + return v.data.([]uint) +} + +// IsUint gets whether the object contained is a uint or not. +func (v *Value) IsUint() bool { + _, ok := v.data.(uint) + return ok +} + +// IsUintSlice gets whether the object contained is a []uint or not. +func (v *Value) IsUintSlice() bool { + _, ok := v.data.([]uint) + return ok +} + +// EachUint calls the specified callback for each object +// in the []uint. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint(callback func(int, uint) bool) *Value { + + for index, val := range v.MustUintSlice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereUint uses the specified decider function to select items +// from the []uint. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint(decider func(int, uint) bool) *Value { + + var selected []uint + + v.EachUint(func(index int, val uint) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupUint uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint. +func (v *Value) GroupUint(grouper func(int, uint) string) *Value { + + groups := make(map[string][]uint) + + v.EachUint(func(index int, val uint) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceUint uses the specified function to replace each uints +// by iterating each item. The data in the returned result will be a +// []uint containing the replaced items. +func (v *Value) ReplaceUint(replacer func(int, uint) uint) *Value { + + arr := v.MustUintSlice() + replaced := make([]uint, len(arr)) + + v.EachUint(func(index int, val uint) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectUint uses the specified collector function to collect a value +// for each of the uints in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint(collector func(int, uint) interface{}) *Value { + + arr := v.MustUintSlice() + collected := make([]interface{}, len(arr)) + + v.EachUint(func(index int, val uint) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Uint8 (uint8 and []uint8) + -------------------------------------------------- +*/ + +// Uint8 gets the value as a uint8, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint8(optionalDefault ...uint8) uint8 { + if s, ok := v.data.(uint8); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint8 gets the value as a uint8. +// +// Panics if the object is not a uint8. +func (v *Value) MustUint8() uint8 { + return v.data.(uint8) +} + +// Uint8Slice gets the value as a []uint8, returns the optionalDefault +// value or nil if the value is not a []uint8. +func (v *Value) Uint8Slice(optionalDefault ...[]uint8) []uint8 { + if s, ok := v.data.([]uint8); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUint8Slice gets the value as a []uint8. +// +// Panics if the object is not a []uint8. +func (v *Value) MustUint8Slice() []uint8 { + return v.data.([]uint8) +} + +// IsUint8 gets whether the object contained is a uint8 or not. +func (v *Value) IsUint8() bool { + _, ok := v.data.(uint8) + return ok +} + +// IsUint8Slice gets whether the object contained is a []uint8 or not. +func (v *Value) IsUint8Slice() bool { + _, ok := v.data.([]uint8) + return ok +} + +// EachUint8 calls the specified callback for each object +// in the []uint8. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint8(callback func(int, uint8) bool) *Value { + + for index, val := range v.MustUint8Slice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereUint8 uses the specified decider function to select items +// from the []uint8. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint8(decider func(int, uint8) bool) *Value { + + var selected []uint8 + + v.EachUint8(func(index int, val uint8) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupUint8 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint8. +func (v *Value) GroupUint8(grouper func(int, uint8) string) *Value { + + groups := make(map[string][]uint8) + + v.EachUint8(func(index int, val uint8) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint8, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceUint8 uses the specified function to replace each uint8s +// by iterating each item. The data in the returned result will be a +// []uint8 containing the replaced items. +func (v *Value) ReplaceUint8(replacer func(int, uint8) uint8) *Value { + + arr := v.MustUint8Slice() + replaced := make([]uint8, len(arr)) + + v.EachUint8(func(index int, val uint8) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectUint8 uses the specified collector function to collect a value +// for each of the uint8s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint8(collector func(int, uint8) interface{}) *Value { + + arr := v.MustUint8Slice() + collected := make([]interface{}, len(arr)) + + v.EachUint8(func(index int, val uint8) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Uint16 (uint16 and []uint16) + -------------------------------------------------- +*/ + +// Uint16 gets the value as a uint16, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint16(optionalDefault ...uint16) uint16 { + if s, ok := v.data.(uint16); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint16 gets the value as a uint16. +// +// Panics if the object is not a uint16. +func (v *Value) MustUint16() uint16 { + return v.data.(uint16) +} + +// Uint16Slice gets the value as a []uint16, returns the optionalDefault +// value or nil if the value is not a []uint16. +func (v *Value) Uint16Slice(optionalDefault ...[]uint16) []uint16 { + if s, ok := v.data.([]uint16); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUint16Slice gets the value as a []uint16. +// +// Panics if the object is not a []uint16. +func (v *Value) MustUint16Slice() []uint16 { + return v.data.([]uint16) +} + +// IsUint16 gets whether the object contained is a uint16 or not. +func (v *Value) IsUint16() bool { + _, ok := v.data.(uint16) + return ok +} + +// IsUint16Slice gets whether the object contained is a []uint16 or not. +func (v *Value) IsUint16Slice() bool { + _, ok := v.data.([]uint16) + return ok +} + +// EachUint16 calls the specified callback for each object +// in the []uint16. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint16(callback func(int, uint16) bool) *Value { + + for index, val := range v.MustUint16Slice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereUint16 uses the specified decider function to select items +// from the []uint16. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint16(decider func(int, uint16) bool) *Value { + + var selected []uint16 + + v.EachUint16(func(index int, val uint16) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupUint16 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint16. +func (v *Value) GroupUint16(grouper func(int, uint16) string) *Value { + + groups := make(map[string][]uint16) + + v.EachUint16(func(index int, val uint16) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint16, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceUint16 uses the specified function to replace each uint16s +// by iterating each item. The data in the returned result will be a +// []uint16 containing the replaced items. +func (v *Value) ReplaceUint16(replacer func(int, uint16) uint16) *Value { + + arr := v.MustUint16Slice() + replaced := make([]uint16, len(arr)) + + v.EachUint16(func(index int, val uint16) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectUint16 uses the specified collector function to collect a value +// for each of the uint16s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint16(collector func(int, uint16) interface{}) *Value { + + arr := v.MustUint16Slice() + collected := make([]interface{}, len(arr)) + + v.EachUint16(func(index int, val uint16) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Uint32 (uint32 and []uint32) + -------------------------------------------------- +*/ + +// Uint32 gets the value as a uint32, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint32(optionalDefault ...uint32) uint32 { + if s, ok := v.data.(uint32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint32 gets the value as a uint32. +// +// Panics if the object is not a uint32. +func (v *Value) MustUint32() uint32 { + return v.data.(uint32) +} + +// Uint32Slice gets the value as a []uint32, returns the optionalDefault +// value or nil if the value is not a []uint32. +func (v *Value) Uint32Slice(optionalDefault ...[]uint32) []uint32 { + if s, ok := v.data.([]uint32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUint32Slice gets the value as a []uint32. +// +// Panics if the object is not a []uint32. +func (v *Value) MustUint32Slice() []uint32 { + return v.data.([]uint32) +} + +// IsUint32 gets whether the object contained is a uint32 or not. +func (v *Value) IsUint32() bool { + _, ok := v.data.(uint32) + return ok +} + +// IsUint32Slice gets whether the object contained is a []uint32 or not. +func (v *Value) IsUint32Slice() bool { + _, ok := v.data.([]uint32) + return ok +} + +// EachUint32 calls the specified callback for each object +// in the []uint32. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint32(callback func(int, uint32) bool) *Value { + + for index, val := range v.MustUint32Slice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereUint32 uses the specified decider function to select items +// from the []uint32. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint32(decider func(int, uint32) bool) *Value { + + var selected []uint32 + + v.EachUint32(func(index int, val uint32) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupUint32 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint32. +func (v *Value) GroupUint32(grouper func(int, uint32) string) *Value { + + groups := make(map[string][]uint32) + + v.EachUint32(func(index int, val uint32) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint32, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceUint32 uses the specified function to replace each uint32s +// by iterating each item. The data in the returned result will be a +// []uint32 containing the replaced items. +func (v *Value) ReplaceUint32(replacer func(int, uint32) uint32) *Value { + + arr := v.MustUint32Slice() + replaced := make([]uint32, len(arr)) + + v.EachUint32(func(index int, val uint32) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectUint32 uses the specified collector function to collect a value +// for each of the uint32s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint32(collector func(int, uint32) interface{}) *Value { + + arr := v.MustUint32Slice() + collected := make([]interface{}, len(arr)) + + v.EachUint32(func(index int, val uint32) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Uint64 (uint64 and []uint64) + -------------------------------------------------- +*/ + +// Uint64 gets the value as a uint64, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint64(optionalDefault ...uint64) uint64 { + if s, ok := v.data.(uint64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint64 gets the value as a uint64. +// +// Panics if the object is not a uint64. +func (v *Value) MustUint64() uint64 { + return v.data.(uint64) +} + +// Uint64Slice gets the value as a []uint64, returns the optionalDefault +// value or nil if the value is not a []uint64. +func (v *Value) Uint64Slice(optionalDefault ...[]uint64) []uint64 { + if s, ok := v.data.([]uint64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUint64Slice gets the value as a []uint64. +// +// Panics if the object is not a []uint64. +func (v *Value) MustUint64Slice() []uint64 { + return v.data.([]uint64) +} + +// IsUint64 gets whether the object contained is a uint64 or not. +func (v *Value) IsUint64() bool { + _, ok := v.data.(uint64) + return ok +} + +// IsUint64Slice gets whether the object contained is a []uint64 or not. +func (v *Value) IsUint64Slice() bool { + _, ok := v.data.([]uint64) + return ok +} + +// EachUint64 calls the specified callback for each object +// in the []uint64. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint64(callback func(int, uint64) bool) *Value { + + for index, val := range v.MustUint64Slice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereUint64 uses the specified decider function to select items +// from the []uint64. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint64(decider func(int, uint64) bool) *Value { + + var selected []uint64 + + v.EachUint64(func(index int, val uint64) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupUint64 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint64. +func (v *Value) GroupUint64(grouper func(int, uint64) string) *Value { + + groups := make(map[string][]uint64) + + v.EachUint64(func(index int, val uint64) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint64, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceUint64 uses the specified function to replace each uint64s +// by iterating each item. The data in the returned result will be a +// []uint64 containing the replaced items. +func (v *Value) ReplaceUint64(replacer func(int, uint64) uint64) *Value { + + arr := v.MustUint64Slice() + replaced := make([]uint64, len(arr)) + + v.EachUint64(func(index int, val uint64) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectUint64 uses the specified collector function to collect a value +// for each of the uint64s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint64(collector func(int, uint64) interface{}) *Value { + + arr := v.MustUint64Slice() + collected := make([]interface{}, len(arr)) + + v.EachUint64(func(index int, val uint64) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Uintptr (uintptr and []uintptr) + -------------------------------------------------- +*/ + +// Uintptr gets the value as a uintptr, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uintptr(optionalDefault ...uintptr) uintptr { + if s, ok := v.data.(uintptr); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUintptr gets the value as a uintptr. +// +// Panics if the object is not a uintptr. +func (v *Value) MustUintptr() uintptr { + return v.data.(uintptr) +} + +// UintptrSlice gets the value as a []uintptr, returns the optionalDefault +// value or nil if the value is not a []uintptr. +func (v *Value) UintptrSlice(optionalDefault ...[]uintptr) []uintptr { + if s, ok := v.data.([]uintptr); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUintptrSlice gets the value as a []uintptr. +// +// Panics if the object is not a []uintptr. +func (v *Value) MustUintptrSlice() []uintptr { + return v.data.([]uintptr) +} + +// IsUintptr gets whether the object contained is a uintptr or not. +func (v *Value) IsUintptr() bool { + _, ok := v.data.(uintptr) + return ok +} + +// IsUintptrSlice gets whether the object contained is a []uintptr or not. +func (v *Value) IsUintptrSlice() bool { + _, ok := v.data.([]uintptr) + return ok +} + +// EachUintptr calls the specified callback for each object +// in the []uintptr. +// +// Panics if the object is the wrong type. +func (v *Value) EachUintptr(callback func(int, uintptr) bool) *Value { + + for index, val := range v.MustUintptrSlice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereUintptr uses the specified decider function to select items +// from the []uintptr. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUintptr(decider func(int, uintptr) bool) *Value { + + var selected []uintptr + + v.EachUintptr(func(index int, val uintptr) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupUintptr uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uintptr. +func (v *Value) GroupUintptr(grouper func(int, uintptr) string) *Value { + + groups := make(map[string][]uintptr) + + v.EachUintptr(func(index int, val uintptr) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uintptr, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceUintptr uses the specified function to replace each uintptrs +// by iterating each item. The data in the returned result will be a +// []uintptr containing the replaced items. +func (v *Value) ReplaceUintptr(replacer func(int, uintptr) uintptr) *Value { + + arr := v.MustUintptrSlice() + replaced := make([]uintptr, len(arr)) + + v.EachUintptr(func(index int, val uintptr) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectUintptr uses the specified collector function to collect a value +// for each of the uintptrs in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUintptr(collector func(int, uintptr) interface{}) *Value { + + arr := v.MustUintptrSlice() + collected := make([]interface{}, len(arr)) + + v.EachUintptr(func(index int, val uintptr) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Float32 (float32 and []float32) + -------------------------------------------------- +*/ + +// Float32 gets the value as a float32, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Float32(optionalDefault ...float32) float32 { + if s, ok := v.data.(float32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustFloat32 gets the value as a float32. +// +// Panics if the object is not a float32. +func (v *Value) MustFloat32() float32 { + return v.data.(float32) +} + +// Float32Slice gets the value as a []float32, returns the optionalDefault +// value or nil if the value is not a []float32. +func (v *Value) Float32Slice(optionalDefault ...[]float32) []float32 { + if s, ok := v.data.([]float32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustFloat32Slice gets the value as a []float32. +// +// Panics if the object is not a []float32. +func (v *Value) MustFloat32Slice() []float32 { + return v.data.([]float32) +} + +// IsFloat32 gets whether the object contained is a float32 or not. +func (v *Value) IsFloat32() bool { + _, ok := v.data.(float32) + return ok +} + +// IsFloat32Slice gets whether the object contained is a []float32 or not. +func (v *Value) IsFloat32Slice() bool { + _, ok := v.data.([]float32) + return ok +} + +// EachFloat32 calls the specified callback for each object +// in the []float32. +// +// Panics if the object is the wrong type. +func (v *Value) EachFloat32(callback func(int, float32) bool) *Value { + + for index, val := range v.MustFloat32Slice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereFloat32 uses the specified decider function to select items +// from the []float32. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereFloat32(decider func(int, float32) bool) *Value { + + var selected []float32 + + v.EachFloat32(func(index int, val float32) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupFloat32 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]float32. +func (v *Value) GroupFloat32(grouper func(int, float32) string) *Value { + + groups := make(map[string][]float32) + + v.EachFloat32(func(index int, val float32) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]float32, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceFloat32 uses the specified function to replace each float32s +// by iterating each item. The data in the returned result will be a +// []float32 containing the replaced items. +func (v *Value) ReplaceFloat32(replacer func(int, float32) float32) *Value { + + arr := v.MustFloat32Slice() + replaced := make([]float32, len(arr)) + + v.EachFloat32(func(index int, val float32) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectFloat32 uses the specified collector function to collect a value +// for each of the float32s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectFloat32(collector func(int, float32) interface{}) *Value { + + arr := v.MustFloat32Slice() + collected := make([]interface{}, len(arr)) + + v.EachFloat32(func(index int, val float32) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Float64 (float64 and []float64) + -------------------------------------------------- +*/ + +// Float64 gets the value as a float64, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Float64(optionalDefault ...float64) float64 { + if s, ok := v.data.(float64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustFloat64 gets the value as a float64. +// +// Panics if the object is not a float64. +func (v *Value) MustFloat64() float64 { + return v.data.(float64) +} + +// Float64Slice gets the value as a []float64, returns the optionalDefault +// value or nil if the value is not a []float64. +func (v *Value) Float64Slice(optionalDefault ...[]float64) []float64 { + if s, ok := v.data.([]float64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustFloat64Slice gets the value as a []float64. +// +// Panics if the object is not a []float64. +func (v *Value) MustFloat64Slice() []float64 { + return v.data.([]float64) +} + +// IsFloat64 gets whether the object contained is a float64 or not. +func (v *Value) IsFloat64() bool { + _, ok := v.data.(float64) + return ok +} + +// IsFloat64Slice gets whether the object contained is a []float64 or not. +func (v *Value) IsFloat64Slice() bool { + _, ok := v.data.([]float64) + return ok +} + +// EachFloat64 calls the specified callback for each object +// in the []float64. +// +// Panics if the object is the wrong type. +func (v *Value) EachFloat64(callback func(int, float64) bool) *Value { + + for index, val := range v.MustFloat64Slice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereFloat64 uses the specified decider function to select items +// from the []float64. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereFloat64(decider func(int, float64) bool) *Value { + + var selected []float64 + + v.EachFloat64(func(index int, val float64) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupFloat64 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]float64. +func (v *Value) GroupFloat64(grouper func(int, float64) string) *Value { + + groups := make(map[string][]float64) + + v.EachFloat64(func(index int, val float64) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]float64, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceFloat64 uses the specified function to replace each float64s +// by iterating each item. The data in the returned result will be a +// []float64 containing the replaced items. +func (v *Value) ReplaceFloat64(replacer func(int, float64) float64) *Value { + + arr := v.MustFloat64Slice() + replaced := make([]float64, len(arr)) + + v.EachFloat64(func(index int, val float64) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectFloat64 uses the specified collector function to collect a value +// for each of the float64s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectFloat64(collector func(int, float64) interface{}) *Value { + + arr := v.MustFloat64Slice() + collected := make([]interface{}, len(arr)) + + v.EachFloat64(func(index int, val float64) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Complex64 (complex64 and []complex64) + -------------------------------------------------- +*/ + +// Complex64 gets the value as a complex64, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Complex64(optionalDefault ...complex64) complex64 { + if s, ok := v.data.(complex64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustComplex64 gets the value as a complex64. +// +// Panics if the object is not a complex64. +func (v *Value) MustComplex64() complex64 { + return v.data.(complex64) +} + +// Complex64Slice gets the value as a []complex64, returns the optionalDefault +// value or nil if the value is not a []complex64. +func (v *Value) Complex64Slice(optionalDefault ...[]complex64) []complex64 { + if s, ok := v.data.([]complex64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustComplex64Slice gets the value as a []complex64. +// +// Panics if the object is not a []complex64. +func (v *Value) MustComplex64Slice() []complex64 { + return v.data.([]complex64) +} + +// IsComplex64 gets whether the object contained is a complex64 or not. +func (v *Value) IsComplex64() bool { + _, ok := v.data.(complex64) + return ok +} + +// IsComplex64Slice gets whether the object contained is a []complex64 or not. +func (v *Value) IsComplex64Slice() bool { + _, ok := v.data.([]complex64) + return ok +} + +// EachComplex64 calls the specified callback for each object +// in the []complex64. +// +// Panics if the object is the wrong type. +func (v *Value) EachComplex64(callback func(int, complex64) bool) *Value { + + for index, val := range v.MustComplex64Slice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereComplex64 uses the specified decider function to select items +// from the []complex64. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereComplex64(decider func(int, complex64) bool) *Value { + + var selected []complex64 + + v.EachComplex64(func(index int, val complex64) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupComplex64 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]complex64. +func (v *Value) GroupComplex64(grouper func(int, complex64) string) *Value { + + groups := make(map[string][]complex64) + + v.EachComplex64(func(index int, val complex64) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]complex64, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceComplex64 uses the specified function to replace each complex64s +// by iterating each item. The data in the returned result will be a +// []complex64 containing the replaced items. +func (v *Value) ReplaceComplex64(replacer func(int, complex64) complex64) *Value { + + arr := v.MustComplex64Slice() + replaced := make([]complex64, len(arr)) + + v.EachComplex64(func(index int, val complex64) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectComplex64 uses the specified collector function to collect a value +// for each of the complex64s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectComplex64(collector func(int, complex64) interface{}) *Value { + + arr := v.MustComplex64Slice() + collected := make([]interface{}, len(arr)) + + v.EachComplex64(func(index int, val complex64) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Complex128 (complex128 and []complex128) + -------------------------------------------------- +*/ + +// Complex128 gets the value as a complex128, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Complex128(optionalDefault ...complex128) complex128 { + if s, ok := v.data.(complex128); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustComplex128 gets the value as a complex128. +// +// Panics if the object is not a complex128. +func (v *Value) MustComplex128() complex128 { + return v.data.(complex128) +} + +// Complex128Slice gets the value as a []complex128, returns the optionalDefault +// value or nil if the value is not a []complex128. +func (v *Value) Complex128Slice(optionalDefault ...[]complex128) []complex128 { + if s, ok := v.data.([]complex128); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustComplex128Slice gets the value as a []complex128. +// +// Panics if the object is not a []complex128. +func (v *Value) MustComplex128Slice() []complex128 { + return v.data.([]complex128) +} + +// IsComplex128 gets whether the object contained is a complex128 or not. +func (v *Value) IsComplex128() bool { + _, ok := v.data.(complex128) + return ok +} + +// IsComplex128Slice gets whether the object contained is a []complex128 or not. +func (v *Value) IsComplex128Slice() bool { + _, ok := v.data.([]complex128) + return ok +} + +// EachComplex128 calls the specified callback for each object +// in the []complex128. +// +// Panics if the object is the wrong type. +func (v *Value) EachComplex128(callback func(int, complex128) bool) *Value { + + for index, val := range v.MustComplex128Slice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereComplex128 uses the specified decider function to select items +// from the []complex128. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereComplex128(decider func(int, complex128) bool) *Value { + + var selected []complex128 + + v.EachComplex128(func(index int, val complex128) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupComplex128 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]complex128. +func (v *Value) GroupComplex128(grouper func(int, complex128) string) *Value { + + groups := make(map[string][]complex128) + + v.EachComplex128(func(index int, val complex128) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]complex128, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceComplex128 uses the specified function to replace each complex128s +// by iterating each item. The data in the returned result will be a +// []complex128 containing the replaced items. +func (v *Value) ReplaceComplex128(replacer func(int, complex128) complex128) *Value { + + arr := v.MustComplex128Slice() + replaced := make([]complex128, len(arr)) + + v.EachComplex128(func(index int, val complex128) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectComplex128 uses the specified collector function to collect a value +// for each of the complex128s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectComplex128(collector func(int, complex128) interface{}) *Value { + + arr := v.MustComplex128Slice() + collected := make([]interface{}, len(arr)) + + v.EachComplex128(func(index int, val complex128) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} diff --git a/vendor/github.com/stretchr/objx/value.go b/vendor/github.com/stretchr/objx/value.go new file mode 100644 index 00000000..956a2211 --- /dev/null +++ b/vendor/github.com/stretchr/objx/value.go @@ -0,0 +1,56 @@ +package objx + +import ( + "fmt" + "strconv" +) + +// Value provides methods for extracting interface{} data in various +// types. +type Value struct { + // data contains the raw data being managed by this Value + data interface{} +} + +// Data returns the raw data contained by this Value +func (v *Value) Data() interface{} { + return v.data +} + +// String returns the value always as a string +func (v *Value) String() string { + switch { + case v.IsStr(): + return v.Str() + case v.IsBool(): + return strconv.FormatBool(v.Bool()) + case v.IsFloat32(): + return strconv.FormatFloat(float64(v.Float32()), 'f', -1, 32) + case v.IsFloat64(): + return strconv.FormatFloat(v.Float64(), 'f', -1, 64) + case v.IsInt(): + return strconv.FormatInt(int64(v.Int()), 10) + case v.IsInt(): + return strconv.FormatInt(int64(v.Int()), 10) + case v.IsInt8(): + return strconv.FormatInt(int64(v.Int8()), 10) + case v.IsInt16(): + return strconv.FormatInt(int64(v.Int16()), 10) + case v.IsInt32(): + return strconv.FormatInt(int64(v.Int32()), 10) + case v.IsInt64(): + return strconv.FormatInt(v.Int64(), 10) + case v.IsUint(): + return strconv.FormatUint(uint64(v.Uint()), 10) + case v.IsUint8(): + return strconv.FormatUint(uint64(v.Uint8()), 10) + case v.IsUint16(): + return strconv.FormatUint(uint64(v.Uint16()), 10) + case v.IsUint32(): + return strconv.FormatUint(uint64(v.Uint32()), 10) + case v.IsUint64(): + return strconv.FormatUint(v.Uint64(), 10) + } + + return fmt.Sprintf("%#v", v.Data()) +} diff --git a/vendor/github.com/stretchr/testify/assert/LICENCE.txt b/vendor/github.com/stretchr/testify/assert/LICENCE.txt new file mode 100644 index 00000000..473b670a --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/LICENCE.txt @@ -0,0 +1,22 @@ +Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell + +Please consider promoting this project if you find it useful. + +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. diff --git a/vendor/github.com/stretchr/testify/assert/LICENSE b/vendor/github.com/stretchr/testify/assert/LICENSE new file mode 100644 index 00000000..473b670a --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell + +Please consider promoting this project if you find it useful. + +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. diff --git a/vendor/github.com/stretchr/testify/assert/assertion_forward.go b/vendor/github.com/stretchr/testify/assert/assertion_forward.go new file mode 100644 index 00000000..e6a79604 --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/assertion_forward.go @@ -0,0 +1,387 @@ +/* +* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen +* THIS FILE MUST NOT BE EDITED BY HAND +*/ + +package assert + +import ( + + http "net/http" + url "net/url" + time "time" +) + + +// Condition uses a Comparison to assert a complex condition. +func (a *Assertions) Condition(comp Comparison, msgAndArgs ...interface{}) bool { + return Condition(a.t, comp, msgAndArgs...) +} + + +// Contains asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// a.Contains("Hello World", "World", "But 'Hello World' does contain 'World'") +// a.Contains(["Hello", "World"], "World", "But ["Hello", "World"] does contain 'World'") +// a.Contains({"Hello": "World"}, "Hello", "But {'Hello': 'World'} does contain 'Hello'") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool { + return Contains(a.t, s, contains, msgAndArgs...) +} + + +// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// a.Empty(obj) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool { + return Empty(a.t, object, msgAndArgs...) +} + + +// Equal asserts that two objects are equal. +// +// a.Equal(123, 123, "123 and 123 should be equal") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { + return Equal(a.t, expected, actual, msgAndArgs...) +} + + +// EqualError asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// if assert.Error(t, err, "An error was expected") { +// assert.Equal(t, err, expectedError) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) bool { + return EqualError(a.t, theError, errString, msgAndArgs...) +} + + +// EqualValues asserts that two objects are equal or convertable to the same types +// and equal. +// +// a.EqualValues(uint32(123), int32(123), "123 and 123 should be equal") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { + return EqualValues(a.t, expected, actual, msgAndArgs...) +} + + +// Error asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if a.Error(err, "An error was expected") { +// assert.Equal(t, err, expectedError) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Error(err error, msgAndArgs ...interface{}) bool { + return Error(a.t, err, msgAndArgs...) +} + + +// Exactly asserts that two objects are equal is value and type. +// +// a.Exactly(int32(123), int64(123), "123 and 123 should NOT be equal") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { + return Exactly(a.t, expected, actual, msgAndArgs...) +} + + +// Fail reports a failure through +func (a *Assertions) Fail(failureMessage string, msgAndArgs ...interface{}) bool { + return Fail(a.t, failureMessage, msgAndArgs...) +} + + +// FailNow fails test +func (a *Assertions) FailNow(failureMessage string, msgAndArgs ...interface{}) bool { + return FailNow(a.t, failureMessage, msgAndArgs...) +} + + +// False asserts that the specified value is false. +// +// a.False(myBool, "myBool should be false") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) False(value bool, msgAndArgs ...interface{}) bool { + return False(a.t, value, msgAndArgs...) +} + + +// HTTPBodyContains asserts that a specified handler returns a +// body that contains a string. +// +// a.HTTPBodyContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool { + return HTTPBodyContains(a.t, handler, method, url, values, str) +} + + +// HTTPBodyNotContains asserts that a specified handler returns a +// body that does not contain a string. +// +// a.HTTPBodyNotContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool { + return HTTPBodyNotContains(a.t, handler, method, url, values, str) +} + + +// HTTPError asserts that a specified handler returns an error status code. +// +// a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values) bool { + return HTTPError(a.t, handler, method, url, values) +} + + +// HTTPRedirect asserts that a specified handler returns a redirect status code. +// +// a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values) bool { + return HTTPRedirect(a.t, handler, method, url, values) +} + + +// HTTPSuccess asserts that a specified handler returns a success status code. +// +// a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values) bool { + return HTTPSuccess(a.t, handler, method, url, values) +} + + +// Implements asserts that an object is implemented by the specified interface. +// +// a.Implements((*MyInterface)(nil), new(MyObject), "MyObject") +func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool { + return Implements(a.t, interfaceObject, object, msgAndArgs...) +} + + +// InDelta asserts that the two numerals are within delta of each other. +// +// a.InDelta(math.Pi, (22 / 7.0), 0.01) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + return InDelta(a.t, expected, actual, delta, msgAndArgs...) +} + + +// InDeltaSlice is the same as InDelta, except it compares two slices. +func (a *Assertions) InDeltaSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + return InDeltaSlice(a.t, expected, actual, delta, msgAndArgs...) +} + + +// InEpsilon asserts that expected and actual have a relative error less than epsilon +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) InEpsilon(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool { + return InEpsilon(a.t, expected, actual, epsilon, msgAndArgs...) +} + + +// InEpsilonSlice is the same as InEpsilon, except it compares two slices. +func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + return InEpsilonSlice(a.t, expected, actual, delta, msgAndArgs...) +} + + +// IsType asserts that the specified objects are of the same type. +func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool { + return IsType(a.t, expectedType, object, msgAndArgs...) +} + + +// JSONEq asserts that two JSON strings are equivalent. +// +// a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) bool { + return JSONEq(a.t, expected, actual, msgAndArgs...) +} + + +// Len asserts that the specified object has specific length. +// Len also fails if the object has a type that len() not accept. +// +// a.Len(mySlice, 3, "The size of slice is not 3") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) bool { + return Len(a.t, object, length, msgAndArgs...) +} + + +// Nil asserts that the specified object is nil. +// +// a.Nil(err, "err should be nothing") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) bool { + return Nil(a.t, object, msgAndArgs...) +} + + +// NoError asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if a.NoError(err) { +// assert.Equal(t, actualObj, expectedObj) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) bool { + return NoError(a.t, err, msgAndArgs...) +} + + +// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// a.NotContains("Hello World", "Earth", "But 'Hello World' does NOT contain 'Earth'") +// a.NotContains(["Hello", "World"], "Earth", "But ['Hello', 'World'] does NOT contain 'Earth'") +// a.NotContains({"Hello": "World"}, "Earth", "But {'Hello': 'World'} does NOT contain 'Earth'") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool { + return NotContains(a.t, s, contains, msgAndArgs...) +} + + +// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if a.NotEmpty(obj) { +// assert.Equal(t, "two", obj[1]) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) bool { + return NotEmpty(a.t, object, msgAndArgs...) +} + + +// NotEqual asserts that the specified values are NOT equal. +// +// a.NotEqual(obj1, obj2, "two objects shouldn't be equal") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { + return NotEqual(a.t, expected, actual, msgAndArgs...) +} + + +// NotNil asserts that the specified object is not nil. +// +// a.NotNil(err, "err should be something") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) bool { + return NotNil(a.t, object, msgAndArgs...) +} + + +// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// a.NotPanics(func(){ +// RemainCalm() +// }, "Calling RemainCalm() should NOT panic") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) NotPanics(f PanicTestFunc, msgAndArgs ...interface{}) bool { + return NotPanics(a.t, f, msgAndArgs...) +} + + +// NotRegexp asserts that a specified regexp does not match a string. +// +// a.NotRegexp(regexp.MustCompile("starts"), "it's starting") +// a.NotRegexp("^start", "it's not starting") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { + return NotRegexp(a.t, rx, str, msgAndArgs...) +} + + +// NotZero asserts that i is not the zero value for its type and returns the truth. +func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) bool { + return NotZero(a.t, i, msgAndArgs...) +} + + +// Panics asserts that the code inside the specified PanicTestFunc panics. +// +// a.Panics(func(){ +// GoCrazy() +// }, "Calling GoCrazy() should panic") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Panics(f PanicTestFunc, msgAndArgs ...interface{}) bool { + return Panics(a.t, f, msgAndArgs...) +} + + +// Regexp asserts that a specified regexp matches a string. +// +// a.Regexp(regexp.MustCompile("start"), "it's starting") +// a.Regexp("start...$", "it's not starting") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { + return Regexp(a.t, rx, str, msgAndArgs...) +} + + +// True asserts that the specified value is true. +// +// a.True(myBool, "myBool should be true") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) True(value bool, msgAndArgs ...interface{}) bool { + return True(a.t, value, msgAndArgs...) +} + + +// WithinDuration asserts that the two times are within duration delta of each other. +// +// a.WithinDuration(time.Now(), time.Now(), 10*time.Second, "The difference should not be more than 10s") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool { + return WithinDuration(a.t, expected, actual, delta, msgAndArgs...) +} + + +// Zero asserts that i is the zero value for its type and returns the truth. +func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) bool { + return Zero(a.t, i, msgAndArgs...) +} diff --git a/vendor/github.com/stretchr/testify/assert/assertions.go b/vendor/github.com/stretchr/testify/assert/assertions.go new file mode 100644 index 00000000..d7c16c59 --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/assertions.go @@ -0,0 +1,1004 @@ +package assert + +import ( + "bufio" + "bytes" + "encoding/json" + "fmt" + "math" + "reflect" + "regexp" + "runtime" + "strings" + "time" + "unicode" + "unicode/utf8" + + "github.com/davecgh/go-spew/spew" + "github.com/pmezard/go-difflib/difflib" +) + +// TestingT is an interface wrapper around *testing.T +type TestingT interface { + Errorf(format string, args ...interface{}) +} + +// Comparison a custom function that returns true on success and false on failure +type Comparison func() (success bool) + +/* + Helper functions +*/ + +// ObjectsAreEqual determines if two objects are considered equal. +// +// This function does no assertion of any kind. +func ObjectsAreEqual(expected, actual interface{}) bool { + + if expected == nil || actual == nil { + return expected == actual + } + + return reflect.DeepEqual(expected, actual) + +} + +// ObjectsAreEqualValues gets whether two objects are equal, or if their +// values are equal. +func ObjectsAreEqualValues(expected, actual interface{}) bool { + if ObjectsAreEqual(expected, actual) { + return true + } + + actualType := reflect.TypeOf(actual) + if actualType == nil { + return false + } + expectedValue := reflect.ValueOf(expected) + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + // Attempt comparison after type conversion + return reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + + return false +} + +/* CallerInfo is necessary because the assert functions use the testing object +internally, causing it to print the file:line of the assert method, rather than where +the problem actually occured in calling code.*/ + +// CallerInfo returns an array of strings containing the file and line number +// of each stack frame leading from the current test to the assert call that +// failed. +func CallerInfo() []string { + + pc := uintptr(0) + file := "" + line := 0 + ok := false + name := "" + + callers := []string{} + for i := 0; ; i++ { + pc, file, line, ok = runtime.Caller(i) + if !ok { + return nil + } + + // This is a huge edge case, but it will panic if this is the case, see #180 + if file == "" { + break + } + + parts := strings.Split(file, "/") + dir := parts[len(parts)-2] + file = parts[len(parts)-1] + if (dir != "assert" && dir != "mock" && dir != "require") || file == "mock_test.go" { + callers = append(callers, fmt.Sprintf("%s:%d", file, line)) + } + + f := runtime.FuncForPC(pc) + if f == nil { + break + } + name = f.Name() + // Drop the package + segments := strings.Split(name, ".") + name = segments[len(segments)-1] + if isTest(name, "Test") || + isTest(name, "Benchmark") || + isTest(name, "Example") { + break + } + } + + return callers +} + +// Stolen from the `go test` tool. +// isTest tells whether name looks like a test (or benchmark, according to prefix). +// It is a Test (say) if there is a character after Test that is not a lower-case letter. +// We don't want TesticularCancer. +func isTest(name, prefix string) bool { + if !strings.HasPrefix(name, prefix) { + return false + } + if len(name) == len(prefix) { // "Test" is ok + return true + } + rune, _ := utf8.DecodeRuneInString(name[len(prefix):]) + return !unicode.IsLower(rune) +} + +// getWhitespaceString returns a string that is long enough to overwrite the default +// output from the go testing framework. +func getWhitespaceString() string { + + _, file, line, ok := runtime.Caller(1) + if !ok { + return "" + } + parts := strings.Split(file, "/") + file = parts[len(parts)-1] + + return strings.Repeat(" ", len(fmt.Sprintf("%s:%d: ", file, line))) + +} + +func messageFromMsgAndArgs(msgAndArgs ...interface{}) string { + if len(msgAndArgs) == 0 || msgAndArgs == nil { + return "" + } + if len(msgAndArgs) == 1 { + return msgAndArgs[0].(string) + } + if len(msgAndArgs) > 1 { + return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...) + } + return "" +} + +// Indents all lines of the message by appending a number of tabs to each line, in an output format compatible with Go's +// test printing (see inner comment for specifics) +func indentMessageLines(message string, tabs int) string { + outBuf := new(bytes.Buffer) + + for i, scanner := 0, bufio.NewScanner(strings.NewReader(message)); scanner.Scan(); i++ { + if i != 0 { + outBuf.WriteRune('\n') + } + for ii := 0; ii < tabs; ii++ { + outBuf.WriteRune('\t') + // Bizarrely, all lines except the first need one fewer tabs prepended, so deliberately advance the counter + // by 1 prematurely. + if ii == 0 && i > 0 { + ii++ + } + } + outBuf.WriteString(scanner.Text()) + } + + return outBuf.String() +} + +type failNower interface { + FailNow() +} + +// FailNow fails test +func FailNow(t TestingT, failureMessage string, msgAndArgs ...interface{}) bool { + Fail(t, failureMessage, msgAndArgs...) + + // We cannot extend TestingT with FailNow() and + // maintain backwards compatibility, so we fallback + // to panicking when FailNow is not available in + // TestingT. + // See issue #263 + + if t, ok := t.(failNower); ok { + t.FailNow() + } else { + panic("test failed and t is missing `FailNow()`") + } + return false +} + +// Fail reports a failure through +func Fail(t TestingT, failureMessage string, msgAndArgs ...interface{}) bool { + + message := messageFromMsgAndArgs(msgAndArgs...) + + errorTrace := strings.Join(CallerInfo(), "\n\r\t\t\t") + if len(message) > 0 { + t.Errorf("\r%s\r\tError Trace:\t%s\n"+ + "\r\tError:%s\n"+ + "\r\tMessages:\t%s\n\r", + getWhitespaceString(), + errorTrace, + indentMessageLines(failureMessage, 2), + message) + } else { + t.Errorf("\r%s\r\tError Trace:\t%s\n"+ + "\r\tError:%s\n\r", + getWhitespaceString(), + errorTrace, + indentMessageLines(failureMessage, 2)) + } + + return false +} + +// Implements asserts that an object is implemented by the specified interface. +// +// assert.Implements(t, (*MyInterface)(nil), new(MyObject), "MyObject") +func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool { + + interfaceType := reflect.TypeOf(interfaceObject).Elem() + + if !reflect.TypeOf(object).Implements(interfaceType) { + return Fail(t, fmt.Sprintf("%T must implement %v", object, interfaceType), msgAndArgs...) + } + + return true + +} + +// IsType asserts that the specified objects are of the same type. +func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool { + + if !ObjectsAreEqual(reflect.TypeOf(object), reflect.TypeOf(expectedType)) { + return Fail(t, fmt.Sprintf("Object expected to be of type %v, but was %v", reflect.TypeOf(expectedType), reflect.TypeOf(object)), msgAndArgs...) + } + + return true +} + +// Equal asserts that two objects are equal. +// +// assert.Equal(t, 123, 123, "123 and 123 should be equal") +// +// Returns whether the assertion was successful (true) or not (false). +func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { + + if !ObjectsAreEqual(expected, actual) { + diff := diff(expected, actual) + return Fail(t, fmt.Sprintf("Not equal: %#v (expected)\n"+ + " != %#v (actual)%s", expected, actual, diff), msgAndArgs...) + } + + return true + +} + +// EqualValues asserts that two objects are equal or convertable to the same types +// and equal. +// +// assert.EqualValues(t, uint32(123), int32(123), "123 and 123 should be equal") +// +// Returns whether the assertion was successful (true) or not (false). +func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { + + if !ObjectsAreEqualValues(expected, actual) { + return Fail(t, fmt.Sprintf("Not equal: %#v (expected)\n"+ + " != %#v (actual)", expected, actual), msgAndArgs...) + } + + return true + +} + +// Exactly asserts that two objects are equal is value and type. +// +// assert.Exactly(t, int32(123), int64(123), "123 and 123 should NOT be equal") +// +// Returns whether the assertion was successful (true) or not (false). +func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { + + aType := reflect.TypeOf(expected) + bType := reflect.TypeOf(actual) + + if aType != bType { + return Fail(t, fmt.Sprintf("Types expected to match exactly\n\r\t%v != %v", aType, bType), msgAndArgs...) + } + + return Equal(t, expected, actual, msgAndArgs...) + +} + +// NotNil asserts that the specified object is not nil. +// +// assert.NotNil(t, err, "err should be something") +// +// Returns whether the assertion was successful (true) or not (false). +func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { + if !isNil(object) { + return true + } + return Fail(t, "Expected value not to be nil.", msgAndArgs...) +} + +// isNil checks if a specified object is nil or not, without Failing. +func isNil(object interface{}) bool { + if object == nil { + return true + } + + value := reflect.ValueOf(object) + kind := value.Kind() + if kind >= reflect.Chan && kind <= reflect.Slice && value.IsNil() { + return true + } + + return false +} + +// Nil asserts that the specified object is nil. +// +// assert.Nil(t, err, "err should be nothing") +// +// Returns whether the assertion was successful (true) or not (false). +func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { + if isNil(object) { + return true + } + return Fail(t, fmt.Sprintf("Expected nil, but got: %#v", object), msgAndArgs...) +} + +var numericZeros = []interface{}{ + int(0), + int8(0), + int16(0), + int32(0), + int64(0), + uint(0), + uint8(0), + uint16(0), + uint32(0), + uint64(0), + float32(0), + float64(0), +} + +// isEmpty gets whether the specified object is considered empty or not. +func isEmpty(object interface{}) bool { + + if object == nil { + return true + } else if object == "" { + return true + } else if object == false { + return true + } + + for _, v := range numericZeros { + if object == v { + return true + } + } + + objValue := reflect.ValueOf(object) + + switch objValue.Kind() { + case reflect.Map: + fallthrough + case reflect.Slice, reflect.Chan: + { + return (objValue.Len() == 0) + } + case reflect.Struct: + switch object.(type) { + case time.Time: + return object.(time.Time).IsZero() + } + case reflect.Ptr: + { + if objValue.IsNil() { + return true + } + switch object.(type) { + case *time.Time: + return object.(*time.Time).IsZero() + default: + return false + } + } + } + return false +} + +// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// assert.Empty(t, obj) +// +// Returns whether the assertion was successful (true) or not (false). +func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { + + pass := isEmpty(object) + if !pass { + Fail(t, fmt.Sprintf("Should be empty, but was %v", object), msgAndArgs...) + } + + return pass + +} + +// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if assert.NotEmpty(t, obj) { +// assert.Equal(t, "two", obj[1]) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { + + pass := !isEmpty(object) + if !pass { + Fail(t, fmt.Sprintf("Should NOT be empty, but was %v", object), msgAndArgs...) + } + + return pass + +} + +// getLen try to get length of object. +// return (false, 0) if impossible. +func getLen(x interface{}) (ok bool, length int) { + v := reflect.ValueOf(x) + defer func() { + if e := recover(); e != nil { + ok = false + } + }() + return true, v.Len() +} + +// Len asserts that the specified object has specific length. +// Len also fails if the object has a type that len() not accept. +// +// assert.Len(t, mySlice, 3, "The size of slice is not 3") +// +// Returns whether the assertion was successful (true) or not (false). +func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) bool { + ok, l := getLen(object) + if !ok { + return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", object), msgAndArgs...) + } + + if l != length { + return Fail(t, fmt.Sprintf("\"%s\" should have %d item(s), but has %d", object, length, l), msgAndArgs...) + } + return true +} + +// True asserts that the specified value is true. +// +// assert.True(t, myBool, "myBool should be true") +// +// Returns whether the assertion was successful (true) or not (false). +func True(t TestingT, value bool, msgAndArgs ...interface{}) bool { + + if value != true { + return Fail(t, "Should be true", msgAndArgs...) + } + + return true + +} + +// False asserts that the specified value is false. +// +// assert.False(t, myBool, "myBool should be false") +// +// Returns whether the assertion was successful (true) or not (false). +func False(t TestingT, value bool, msgAndArgs ...interface{}) bool { + + if value != false { + return Fail(t, "Should be false", msgAndArgs...) + } + + return true + +} + +// NotEqual asserts that the specified values are NOT equal. +// +// assert.NotEqual(t, obj1, obj2, "two objects shouldn't be equal") +// +// Returns whether the assertion was successful (true) or not (false). +func NotEqual(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { + + if ObjectsAreEqual(expected, actual) { + return Fail(t, fmt.Sprintf("Should not be: %#v\n", actual), msgAndArgs...) + } + + return true + +} + +// containsElement try loop over the list check if the list includes the element. +// return (false, false) if impossible. +// return (true, false) if element was not found. +// return (true, true) if element was found. +func includeElement(list interface{}, element interface{}) (ok, found bool) { + + listValue := reflect.ValueOf(list) + elementValue := reflect.ValueOf(element) + defer func() { + if e := recover(); e != nil { + ok = false + found = false + } + }() + + if reflect.TypeOf(list).Kind() == reflect.String { + return true, strings.Contains(listValue.String(), elementValue.String()) + } + + if reflect.TypeOf(list).Kind() == reflect.Map { + mapKeys := listValue.MapKeys() + for i := 0; i < len(mapKeys); i++ { + if ObjectsAreEqual(mapKeys[i].Interface(), element) { + return true, true + } + } + return true, false + } + + for i := 0; i < listValue.Len(); i++ { + if ObjectsAreEqual(listValue.Index(i).Interface(), element) { + return true, true + } + } + return true, false + +} + +// Contains asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// assert.Contains(t, "Hello World", "World", "But 'Hello World' does contain 'World'") +// assert.Contains(t, ["Hello", "World"], "World", "But ["Hello", "World"] does contain 'World'") +// assert.Contains(t, {"Hello": "World"}, "Hello", "But {'Hello': 'World'} does contain 'Hello'") +// +// Returns whether the assertion was successful (true) or not (false). +func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool { + + ok, found := includeElement(s, contains) + if !ok { + return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", s), msgAndArgs...) + } + if !found { + return Fail(t, fmt.Sprintf("\"%s\" does not contain \"%s\"", s, contains), msgAndArgs...) + } + + return true + +} + +// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// assert.NotContains(t, "Hello World", "Earth", "But 'Hello World' does NOT contain 'Earth'") +// assert.NotContains(t, ["Hello", "World"], "Earth", "But ['Hello', 'World'] does NOT contain 'Earth'") +// assert.NotContains(t, {"Hello": "World"}, "Earth", "But {'Hello': 'World'} does NOT contain 'Earth'") +// +// Returns whether the assertion was successful (true) or not (false). +func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool { + + ok, found := includeElement(s, contains) + if !ok { + return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", s), msgAndArgs...) + } + if found { + return Fail(t, fmt.Sprintf("\"%s\" should not contain \"%s\"", s, contains), msgAndArgs...) + } + + return true + +} + +// Condition uses a Comparison to assert a complex condition. +func Condition(t TestingT, comp Comparison, msgAndArgs ...interface{}) bool { + result := comp() + if !result { + Fail(t, "Condition failed!", msgAndArgs...) + } + return result +} + +// PanicTestFunc defines a func that should be passed to the assert.Panics and assert.NotPanics +// methods, and represents a simple func that takes no arguments, and returns nothing. +type PanicTestFunc func() + +// didPanic returns true if the function passed to it panics. Otherwise, it returns false. +func didPanic(f PanicTestFunc) (bool, interface{}) { + + didPanic := false + var message interface{} + func() { + + defer func() { + if message = recover(); message != nil { + didPanic = true + } + }() + + // call the target function + f() + + }() + + return didPanic, message + +} + +// Panics asserts that the code inside the specified PanicTestFunc panics. +// +// assert.Panics(t, func(){ +// GoCrazy() +// }, "Calling GoCrazy() should panic") +// +// Returns whether the assertion was successful (true) or not (false). +func Panics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { + + if funcDidPanic, panicValue := didPanic(f); !funcDidPanic { + return Fail(t, fmt.Sprintf("func %#v should panic\n\r\tPanic value:\t%v", f, panicValue), msgAndArgs...) + } + + return true +} + +// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// assert.NotPanics(t, func(){ +// RemainCalm() +// }, "Calling RemainCalm() should NOT panic") +// +// Returns whether the assertion was successful (true) or not (false). +func NotPanics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { + + if funcDidPanic, panicValue := didPanic(f); funcDidPanic { + return Fail(t, fmt.Sprintf("func %#v should not panic\n\r\tPanic value:\t%v", f, panicValue), msgAndArgs...) + } + + return true +} + +// WithinDuration asserts that the two times are within duration delta of each other. +// +// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second, "The difference should not be more than 10s") +// +// Returns whether the assertion was successful (true) or not (false). +func WithinDuration(t TestingT, expected, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool { + + dt := expected.Sub(actual) + if dt < -delta || dt > delta { + return Fail(t, fmt.Sprintf("Max difference between %v and %v allowed is %v, but difference was %v", expected, actual, delta, dt), msgAndArgs...) + } + + return true +} + +func toFloat(x interface{}) (float64, bool) { + var xf float64 + xok := true + + switch xn := x.(type) { + case uint8: + xf = float64(xn) + case uint16: + xf = float64(xn) + case uint32: + xf = float64(xn) + case uint64: + xf = float64(xn) + case int: + xf = float64(xn) + case int8: + xf = float64(xn) + case int16: + xf = float64(xn) + case int32: + xf = float64(xn) + case int64: + xf = float64(xn) + case float32: + xf = float64(xn) + case float64: + xf = float64(xn) + default: + xok = false + } + + return xf, xok +} + +// InDelta asserts that the two numerals are within delta of each other. +// +// assert.InDelta(t, math.Pi, (22 / 7.0), 0.01) +// +// Returns whether the assertion was successful (true) or not (false). +func InDelta(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + + af, aok := toFloat(expected) + bf, bok := toFloat(actual) + + if !aok || !bok { + return Fail(t, fmt.Sprintf("Parameters must be numerical"), msgAndArgs...) + } + + if math.IsNaN(af) { + return Fail(t, fmt.Sprintf("Actual must not be NaN"), msgAndArgs...) + } + + if math.IsNaN(bf) { + return Fail(t, fmt.Sprintf("Expected %v with delta %v, but was NaN", expected, delta), msgAndArgs...) + } + + dt := af - bf + if dt < -delta || dt > delta { + return Fail(t, fmt.Sprintf("Max difference between %v and %v allowed is %v, but difference was %v", expected, actual, delta, dt), msgAndArgs...) + } + + return true +} + +// InDeltaSlice is the same as InDelta, except it compares two slices. +func InDeltaSlice(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + if expected == nil || actual == nil || + reflect.TypeOf(actual).Kind() != reflect.Slice || + reflect.TypeOf(expected).Kind() != reflect.Slice { + return Fail(t, fmt.Sprintf("Parameters must be slice"), msgAndArgs...) + } + + actualSlice := reflect.ValueOf(actual) + expectedSlice := reflect.ValueOf(expected) + + for i := 0; i < actualSlice.Len(); i++ { + result := InDelta(t, actualSlice.Index(i).Interface(), expectedSlice.Index(i).Interface(), delta) + if !result { + return result + } + } + + return true +} + +func calcRelativeError(expected, actual interface{}) (float64, error) { + af, aok := toFloat(expected) + if !aok { + return 0, fmt.Errorf("expected value %q cannot be converted to float", expected) + } + if af == 0 { + return 0, fmt.Errorf("expected value must have a value other than zero to calculate the relative error") + } + bf, bok := toFloat(actual) + if !bok { + return 0, fmt.Errorf("expected value %q cannot be converted to float", actual) + } + + return math.Abs(af-bf) / math.Abs(af), nil +} + +// InEpsilon asserts that expected and actual have a relative error less than epsilon +// +// Returns whether the assertion was successful (true) or not (false). +func InEpsilon(t TestingT, expected, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool { + actualEpsilon, err := calcRelativeError(expected, actual) + if err != nil { + return Fail(t, err.Error(), msgAndArgs...) + } + if actualEpsilon > epsilon { + return Fail(t, fmt.Sprintf("Relative error is too high: %#v (expected)\n"+ + " < %#v (actual)", actualEpsilon, epsilon), msgAndArgs...) + } + + return true +} + +// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices. +func InEpsilonSlice(t TestingT, expected, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool { + if expected == nil || actual == nil || + reflect.TypeOf(actual).Kind() != reflect.Slice || + reflect.TypeOf(expected).Kind() != reflect.Slice { + return Fail(t, fmt.Sprintf("Parameters must be slice"), msgAndArgs...) + } + + actualSlice := reflect.ValueOf(actual) + expectedSlice := reflect.ValueOf(expected) + + for i := 0; i < actualSlice.Len(); i++ { + result := InEpsilon(t, actualSlice.Index(i).Interface(), expectedSlice.Index(i).Interface(), epsilon) + if !result { + return result + } + } + + return true +} + +/* + Errors +*/ + +// NoError asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if assert.NoError(t, err) { +// assert.Equal(t, actualObj, expectedObj) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool { + if isNil(err) { + return true + } + + return Fail(t, fmt.Sprintf("Received unexpected error %q", err), msgAndArgs...) +} + +// Error asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if assert.Error(t, err, "An error was expected") { +// assert.Equal(t, err, expectedError) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func Error(t TestingT, err error, msgAndArgs ...interface{}) bool { + + message := messageFromMsgAndArgs(msgAndArgs...) + return NotNil(t, err, "An error is expected but got nil. %s", message) + +} + +// EqualError asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// if assert.Error(t, err, "An error was expected") { +// assert.Equal(t, err, expectedError) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) bool { + + message := messageFromMsgAndArgs(msgAndArgs...) + if !NotNil(t, theError, "An error is expected but got nil. %s", message) { + return false + } + s := "An error with value \"%s\" is expected but got \"%s\". %s" + return Equal(t, errString, theError.Error(), + s, errString, theError.Error(), message) +} + +// matchRegexp return true if a specified regexp matches a string. +func matchRegexp(rx interface{}, str interface{}) bool { + + var r *regexp.Regexp + if rr, ok := rx.(*regexp.Regexp); ok { + r = rr + } else { + r = regexp.MustCompile(fmt.Sprint(rx)) + } + + return (r.FindStringIndex(fmt.Sprint(str)) != nil) + +} + +// Regexp asserts that a specified regexp matches a string. +// +// assert.Regexp(t, regexp.MustCompile("start"), "it's starting") +// assert.Regexp(t, "start...$", "it's not starting") +// +// Returns whether the assertion was successful (true) or not (false). +func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { + + match := matchRegexp(rx, str) + + if !match { + Fail(t, fmt.Sprintf("Expect \"%v\" to match \"%v\"", str, rx), msgAndArgs...) + } + + return match +} + +// NotRegexp asserts that a specified regexp does not match a string. +// +// assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting") +// assert.NotRegexp(t, "^start", "it's not starting") +// +// Returns whether the assertion was successful (true) or not (false). +func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { + match := matchRegexp(rx, str) + + if match { + Fail(t, fmt.Sprintf("Expect \"%v\" to NOT match \"%v\"", str, rx), msgAndArgs...) + } + + return !match + +} + +// Zero asserts that i is the zero value for its type and returns the truth. +func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool { + if i != nil && !reflect.DeepEqual(i, reflect.Zero(reflect.TypeOf(i)).Interface()) { + return Fail(t, fmt.Sprintf("Should be zero, but was %v", i), msgAndArgs...) + } + return true +} + +// NotZero asserts that i is not the zero value for its type and returns the truth. +func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool { + if i == nil || reflect.DeepEqual(i, reflect.Zero(reflect.TypeOf(i)).Interface()) { + return Fail(t, fmt.Sprintf("Should not be zero, but was %v", i), msgAndArgs...) + } + return true +} + +// JSONEq asserts that two JSON strings are equivalent. +// +// assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +// +// Returns whether the assertion was successful (true) or not (false). +func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) bool { + var expectedJSONAsInterface, actualJSONAsInterface interface{} + + if err := json.Unmarshal([]byte(expected), &expectedJSONAsInterface); err != nil { + return Fail(t, fmt.Sprintf("Expected value ('%s') is not valid json.\nJSON parsing error: '%s'", expected, err.Error()), msgAndArgs...) + } + + if err := json.Unmarshal([]byte(actual), &actualJSONAsInterface); err != nil { + return Fail(t, fmt.Sprintf("Input ('%s') needs to be valid json.\nJSON parsing error: '%s'", actual, err.Error()), msgAndArgs...) + } + + return Equal(t, expectedJSONAsInterface, actualJSONAsInterface, msgAndArgs...) +} + +func typeAndKind(v interface{}) (reflect.Type, reflect.Kind) { + t := reflect.TypeOf(v) + k := t.Kind() + + if k == reflect.Ptr { + t = t.Elem() + k = t.Kind() + } + return t, k +} + +// diff returns a diff of both values as long as both are of the same type and +// are a struct, map, slice or array. Otherwise it returns an empty string. +func diff(expected interface{}, actual interface{}) string { + if expected == nil || actual == nil { + return "" + } + + et, ek := typeAndKind(expected) + at, _ := typeAndKind(actual) + + if et != at { + return "" + } + + if ek != reflect.Struct && ek != reflect.Map && ek != reflect.Slice && ek != reflect.Array { + return "" + } + + spew.Config.SortKeys = true + e := spew.Sdump(expected) + a := spew.Sdump(actual) + + diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ + A: difflib.SplitLines(e), + B: difflib.SplitLines(a), + FromFile: "Expected", + FromDate: "", + ToFile: "Actual", + ToDate: "", + Context: 1, + }) + + return "\n\nDiff:\n" + diff +} diff --git a/vendor/github.com/stretchr/testify/assert/doc.go b/vendor/github.com/stretchr/testify/assert/doc.go new file mode 100644 index 00000000..c9dccc4d --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/doc.go @@ -0,0 +1,45 @@ +// Package assert provides a set of comprehensive testing tools for use with the normal Go testing system. +// +// Example Usage +// +// The following is a complete example using assert in a standard test function: +// import ( +// "testing" +// "github.com/stretchr/testify/assert" +// ) +// +// func TestSomething(t *testing.T) { +// +// var a string = "Hello" +// var b string = "Hello" +// +// assert.Equal(t, a, b, "The two words should be the same.") +// +// } +// +// if you assert many times, use the format below: +// +// import ( +// "testing" +// "github.com/stretchr/testify/assert" +// ) +// +// func TestSomething(t *testing.T) { +// assert := assert.New(t) +// +// var a string = "Hello" +// var b string = "Hello" +// +// assert.Equal(a, b, "The two words should be the same.") +// } +// +// Assertions +// +// Assertions allow you to easily write test code, and are global funcs in the `assert` package. +// All assertion functions take, as the first argument, the `*testing.T` object provided by the +// testing framework. This allows the assertion funcs to write the failings and other details to +// the correct place. +// +// Every assertion function also takes an optional string message as the final argument, +// allowing custom error messages to be appended to the message the assertion method outputs. +package assert diff --git a/vendor/github.com/stretchr/testify/assert/errors.go b/vendor/github.com/stretchr/testify/assert/errors.go new file mode 100644 index 00000000..ac9dc9d1 --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/errors.go @@ -0,0 +1,10 @@ +package assert + +import ( + "errors" +) + +// AnError is an error instance useful for testing. If the code does not care +// about error specifics, and only needs to return the error for example, this +// error should be used to make the test code more readable. +var AnError = errors.New("assert.AnError general error for testing") diff --git a/vendor/github.com/stretchr/testify/assert/forward_assertions.go b/vendor/github.com/stretchr/testify/assert/forward_assertions.go new file mode 100644 index 00000000..b867e95e --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/forward_assertions.go @@ -0,0 +1,16 @@ +package assert + +// Assertions provides assertion methods around the +// TestingT interface. +type Assertions struct { + t TestingT +} + +// New makes a new Assertions object for the specified TestingT. +func New(t TestingT) *Assertions { + return &Assertions{ + t: t, + } +} + +//go:generate go run ../_codegen/main.go -output-package=assert -template=assertion_forward.go.tmpl diff --git a/vendor/github.com/stretchr/testify/assert/http_assertions.go b/vendor/github.com/stretchr/testify/assert/http_assertions.go new file mode 100644 index 00000000..e1b9442b --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/http_assertions.go @@ -0,0 +1,106 @@ +package assert + +import ( + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "strings" +) + +// httpCode is a helper that returns HTTP code of the response. It returns -1 +// if building a new request fails. +func httpCode(handler http.HandlerFunc, method, url string, values url.Values) int { + w := httptest.NewRecorder() + req, err := http.NewRequest(method, url+"?"+values.Encode(), nil) + if err != nil { + return -1 + } + handler(w, req) + return w.Code +} + +// HTTPSuccess asserts that a specified handler returns a success status code. +// +// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool { + code := httpCode(handler, method, url, values) + if code == -1 { + return false + } + return code >= http.StatusOK && code <= http.StatusPartialContent +} + +// HTTPRedirect asserts that a specified handler returns a redirect status code. +// +// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool { + code := httpCode(handler, method, url, values) + if code == -1 { + return false + } + return code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect +} + +// HTTPError asserts that a specified handler returns an error status code. +// +// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool { + code := httpCode(handler, method, url, values) + if code == -1 { + return false + } + return code >= http.StatusBadRequest +} + +// HTTPBody is a helper that returns HTTP body of the response. It returns +// empty string if building a new request fails. +func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) string { + w := httptest.NewRecorder() + req, err := http.NewRequest(method, url+"?"+values.Encode(), nil) + if err != nil { + return "" + } + handler(w, req) + return w.Body.String() +} + +// HTTPBodyContains asserts that a specified handler returns a +// body that contains a string. +// +// assert.HTTPBodyContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}) bool { + body := HTTPBody(handler, method, url, values) + + contains := strings.Contains(body, fmt.Sprint(str)) + if !contains { + Fail(t, fmt.Sprintf("Expected response body for \"%s\" to contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body)) + } + + return contains +} + +// HTTPBodyNotContains asserts that a specified handler returns a +// body that does not contain a string. +// +// assert.HTTPBodyNotContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}) bool { + body := HTTPBody(handler, method, url, values) + + contains := strings.Contains(body, fmt.Sprint(str)) + if contains { + Fail(t, "Expected response body for %s to NOT contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body) + } + + return !contains +} diff --git a/vendor/github.com/stretchr/testify/mock/LICENCE.txt b/vendor/github.com/stretchr/testify/mock/LICENCE.txt new file mode 100644 index 00000000..473b670a --- /dev/null +++ b/vendor/github.com/stretchr/testify/mock/LICENCE.txt @@ -0,0 +1,22 @@ +Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell + +Please consider promoting this project if you find it useful. + +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. diff --git a/vendor/github.com/stretchr/testify/mock/LICENSE b/vendor/github.com/stretchr/testify/mock/LICENSE new file mode 100644 index 00000000..473b670a --- /dev/null +++ b/vendor/github.com/stretchr/testify/mock/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell + +Please consider promoting this project if you find it useful. + +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. diff --git a/vendor/github.com/stretchr/testify/mock/doc.go b/vendor/github.com/stretchr/testify/mock/doc.go new file mode 100644 index 00000000..7324128e --- /dev/null +++ b/vendor/github.com/stretchr/testify/mock/doc.go @@ -0,0 +1,44 @@ +// Package mock provides a system by which it is possible to mock your objects +// and verify calls are happening as expected. +// +// Example Usage +// +// The mock package provides an object, Mock, that tracks activity on another object. It is usually +// embedded into a test object as shown below: +// +// type MyTestObject struct { +// // add a Mock object instance +// mock.Mock +// +// // other fields go here as normal +// } +// +// When implementing the methods of an interface, you wire your functions up +// to call the Mock.Called(args...) method, and return the appropriate values. +// +// For example, to mock a method that saves the name and age of a person and returns +// the year of their birth or an error, you might write this: +// +// func (o *MyTestObject) SavePersonDetails(firstname, lastname string, age int) (int, error) { +// args := o.Called(firstname, lastname, age) +// return args.Int(0), args.Error(1) +// } +// +// The Int, Error and Bool methods are examples of strongly typed getters that take the argument +// index position. Given this argument list: +// +// (12, true, "Something") +// +// You could read them out strongly typed like this: +// +// args.Int(0) +// args.Bool(1) +// args.String(2) +// +// For objects of your own type, use the generic Arguments.Get(index) method and make a type assertion: +// +// return args.Get(0).(*MyObject), args.Get(1).(*AnotherObjectOfMine) +// +// This may cause a panic if the object you are getting is nil (the type assertion will fail), in those +// cases you should check for nil first. +package mock diff --git a/vendor/github.com/stretchr/testify/mock/mock.go b/vendor/github.com/stretchr/testify/mock/mock.go new file mode 100644 index 00000000..e3abfd5a --- /dev/null +++ b/vendor/github.com/stretchr/testify/mock/mock.go @@ -0,0 +1,693 @@ +package mock + +import ( + "fmt" + "reflect" + "regexp" + "runtime" + "strings" + "sync" + "time" + + "github.com/stretchr/objx" + "github.com/stretchr/testify/assert" +) + +// TestingT is an interface wrapper around *testing.T +type TestingT interface { + Logf(format string, args ...interface{}) + Errorf(format string, args ...interface{}) + FailNow() +} + +/* + Call +*/ + +// Call represents a method call and is used for setting expectations, +// as well as recording activity. +type Call struct { + Parent *Mock + + // The name of the method that was or will be called. + Method string + + // Holds the arguments of the method. + Arguments Arguments + + // Holds the arguments that should be returned when + // this method is called. + ReturnArguments Arguments + + // The number of times to return the return arguments when setting + // expectations. 0 means to always return the value. + Repeatability int + + // Amount of times this call has been called + totalCalls int + + // Holds a channel that will be used to block the Return until it either + // recieves a message or is closed. nil means it returns immediately. + WaitFor <-chan time.Time + + // Holds a handler used to manipulate arguments content that are passed by + // reference. It's useful when mocking methods such as unmarshalers or + // decoders. + RunFn func(Arguments) +} + +func newCall(parent *Mock, methodName string, methodArguments ...interface{}) *Call { + return &Call{ + Parent: parent, + Method: methodName, + Arguments: methodArguments, + ReturnArguments: make([]interface{}, 0), + Repeatability: 0, + WaitFor: nil, + RunFn: nil, + } +} + +func (c *Call) lock() { + c.Parent.mutex.Lock() +} + +func (c *Call) unlock() { + c.Parent.mutex.Unlock() +} + +// Return specifies the return arguments for the expectation. +// +// Mock.On("DoSomething").Return(errors.New("failed")) +func (c *Call) Return(returnArguments ...interface{}) *Call { + c.lock() + defer c.unlock() + + c.ReturnArguments = returnArguments + + return c +} + +// Once indicates that that the mock should only return the value once. +// +// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Once() +func (c *Call) Once() *Call { + return c.Times(1) +} + +// Twice indicates that that the mock should only return the value twice. +// +// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Twice() +func (c *Call) Twice() *Call { + return c.Times(2) +} + +// Times indicates that that the mock should only return the indicated number +// of times. +// +// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Times(5) +func (c *Call) Times(i int) *Call { + c.lock() + defer c.unlock() + c.Repeatability = i + return c +} + +// WaitUntil sets the channel that will block the mock's return until its closed +// or a message is received. +// +// Mock.On("MyMethod", arg1, arg2).WaitUntil(time.After(time.Second)) +func (c *Call) WaitUntil(w <-chan time.Time) *Call { + c.lock() + defer c.unlock() + c.WaitFor = w + return c +} + +// After sets how long to block until the call returns +// +// Mock.On("MyMethod", arg1, arg2).After(time.Second) +func (c *Call) After(d time.Duration) *Call { + return c.WaitUntil(time.After(d)) +} + +// Run sets a handler to be called before returning. It can be used when +// mocking a method such as unmarshalers that takes a pointer to a struct and +// sets properties in such struct +// +// Mock.On("Unmarshal", AnythingOfType("*map[string]interface{}").Return().Run(func(args Arguments) { +// arg := args.Get(0).(*map[string]interface{}) +// arg["foo"] = "bar" +// }) +func (c *Call) Run(fn func(Arguments)) *Call { + c.lock() + defer c.unlock() + c.RunFn = fn + return c +} + +// On chains a new expectation description onto the mocked interface. This +// allows syntax like. +// +// Mock. +// On("MyMethod", 1).Return(nil). +// On("MyOtherMethod", 'a', 'b', 'c').Return(errors.New("Some Error")) +func (c *Call) On(methodName string, arguments ...interface{}) *Call { + return c.Parent.On(methodName, arguments...) +} + +// Mock is the workhorse used to track activity on another object. +// For an example of its usage, refer to the "Example Usage" section at the top +// of this document. +type Mock struct { + // Represents the calls that are expected of + // an object. + ExpectedCalls []*Call + + // Holds the calls that were made to this mocked object. + Calls []Call + + // TestData holds any data that might be useful for testing. Testify ignores + // this data completely allowing you to do whatever you like with it. + testData objx.Map + + mutex sync.Mutex +} + +// TestData holds any data that might be useful for testing. Testify ignores +// this data completely allowing you to do whatever you like with it. +func (m *Mock) TestData() objx.Map { + + if m.testData == nil { + m.testData = make(objx.Map) + } + + return m.testData +} + +/* + Setting expectations +*/ + +// On starts a description of an expectation of the specified method +// being called. +// +// Mock.On("MyMethod", arg1, arg2) +func (m *Mock) On(methodName string, arguments ...interface{}) *Call { + for _, arg := range arguments { + if v := reflect.ValueOf(arg); v.Kind() == reflect.Func { + panic(fmt.Sprintf("cannot use Func in expectations. Use mock.AnythingOfType(\"%T\")", arg)) + } + } + + m.mutex.Lock() + defer m.mutex.Unlock() + c := newCall(m, methodName, arguments...) + m.ExpectedCalls = append(m.ExpectedCalls, c) + return c +} + +// /* +// Recording and responding to activity +// */ + +func (m *Mock) findExpectedCall(method string, arguments ...interface{}) (int, *Call) { + m.mutex.Lock() + defer m.mutex.Unlock() + for i, call := range m.ExpectedCalls { + if call.Method == method && call.Repeatability > -1 { + + _, diffCount := call.Arguments.Diff(arguments) + if diffCount == 0 { + return i, call + } + + } + } + return -1, nil +} + +func (m *Mock) findClosestCall(method string, arguments ...interface{}) (bool, *Call) { + diffCount := 0 + var closestCall *Call + + for _, call := range m.expectedCalls() { + if call.Method == method { + + _, tempDiffCount := call.Arguments.Diff(arguments) + if tempDiffCount < diffCount || diffCount == 0 { + diffCount = tempDiffCount + closestCall = call + } + + } + } + + if closestCall == nil { + return false, nil + } + + return true, closestCall +} + +func callString(method string, arguments Arguments, includeArgumentValues bool) string { + + var argValsString string + if includeArgumentValues { + var argVals []string + for argIndex, arg := range arguments { + argVals = append(argVals, fmt.Sprintf("%d: %#v", argIndex, arg)) + } + argValsString = fmt.Sprintf("\n\t\t%s", strings.Join(argVals, "\n\t\t")) + } + + return fmt.Sprintf("%s(%s)%s", method, arguments.String(), argValsString) +} + +// Called tells the mock object that a method has been called, and gets an array +// of arguments to return. Panics if the call is unexpected (i.e. not preceeded by +// appropriate .On .Return() calls) +// If Call.WaitFor is set, blocks until the channel is closed or receives a message. +func (m *Mock) Called(arguments ...interface{}) Arguments { + // get the calling function's name + pc, _, _, ok := runtime.Caller(1) + if !ok { + panic("Couldn't get the caller information") + } + functionPath := runtime.FuncForPC(pc).Name() + //Next four lines are required to use GCCGO function naming conventions. + //For Ex: github_com_docker_libkv_store_mock.WatchTree.pN39_github_com_docker_libkv_store_mock.Mock + //uses inteface information unlike golang github.com/docker/libkv/store/mock.(*Mock).WatchTree + //With GCCGO we need to remove interface information starting from pN

. + re := regexp.MustCompile("\\.pN\\d+_") + if re.MatchString(functionPath) { + functionPath = re.Split(functionPath, -1)[0] + } + parts := strings.Split(functionPath, ".") + functionName := parts[len(parts)-1] + + found, call := m.findExpectedCall(functionName, arguments...) + + if found < 0 { + // we have to fail here - because we don't know what to do + // as the return arguments. This is because: + // + // a) this is a totally unexpected call to this method, + // b) the arguments are not what was expected, or + // c) the developer has forgotten to add an accompanying On...Return pair. + + closestFound, closestCall := m.findClosestCall(functionName, arguments...) + + if closestFound { + panic(fmt.Sprintf("\n\nmock: Unexpected Method Call\n-----------------------------\n\n%s\n\nThe closest call I have is: \n\n%s\n", callString(functionName, arguments, true), callString(functionName, closestCall.Arguments, true))) + } else { + panic(fmt.Sprintf("\nassert: mock: I don't know what to return because the method call was unexpected.\n\tEither do Mock.On(\"%s\").Return(...) first, or remove the %s() call.\n\tThis method was unexpected:\n\t\t%s\n\tat: %s", functionName, functionName, callString(functionName, arguments, true), assert.CallerInfo())) + } + } else { + m.mutex.Lock() + switch { + case call.Repeatability == 1: + call.Repeatability = -1 + call.totalCalls++ + + case call.Repeatability > 1: + call.Repeatability-- + call.totalCalls++ + + case call.Repeatability == 0: + call.totalCalls++ + } + m.mutex.Unlock() + } + + // add the call + m.mutex.Lock() + m.Calls = append(m.Calls, *newCall(m, functionName, arguments...)) + m.mutex.Unlock() + + // block if specified + if call.WaitFor != nil { + <-call.WaitFor + } + + if call.RunFn != nil { + call.RunFn(arguments) + } + + return call.ReturnArguments +} + +/* + Assertions +*/ + +// AssertExpectationsForObjects asserts that everything specified with On and Return +// of the specified objects was in fact called as expected. +// +// Calls may have occurred in any order. +func AssertExpectationsForObjects(t TestingT, testObjects ...interface{}) bool { + var success = true + for _, obj := range testObjects { + mockObj := obj.(Mock) + success = success && mockObj.AssertExpectations(t) + } + return success +} + +// AssertExpectations asserts that everything specified with On and Return was +// in fact called as expected. Calls may have occurred in any order. +func (m *Mock) AssertExpectations(t TestingT) bool { + var somethingMissing bool + var failedExpectations int + + // iterate through each expectation + expectedCalls := m.expectedCalls() + for _, expectedCall := range expectedCalls { + if !m.methodWasCalled(expectedCall.Method, expectedCall.Arguments) && expectedCall.totalCalls == 0 { + somethingMissing = true + failedExpectations++ + t.Logf("\u274C\t%s(%s)", expectedCall.Method, expectedCall.Arguments.String()) + } else { + m.mutex.Lock() + if expectedCall.Repeatability > 0 { + somethingMissing = true + failedExpectations++ + } else { + t.Logf("\u2705\t%s(%s)", expectedCall.Method, expectedCall.Arguments.String()) + } + m.mutex.Unlock() + } + } + + if somethingMissing { + t.Errorf("FAIL: %d out of %d expectation(s) were met.\n\tThe code you are testing needs to make %d more call(s).\n\tat: %s", len(expectedCalls)-failedExpectations, len(expectedCalls), failedExpectations, assert.CallerInfo()) + } + + return !somethingMissing +} + +// AssertNumberOfCalls asserts that the method was called expectedCalls times. +func (m *Mock) AssertNumberOfCalls(t TestingT, methodName string, expectedCalls int) bool { + var actualCalls int + for _, call := range m.calls() { + if call.Method == methodName { + actualCalls++ + } + } + return assert.Equal(t, expectedCalls, actualCalls, fmt.Sprintf("Expected number of calls (%d) does not match the actual number of calls (%d).", expectedCalls, actualCalls)) +} + +// AssertCalled asserts that the method was called. +// It can produce a false result when an arugment is a pointer type and the underyling value changed after calling the mocked method. +func (m *Mock) AssertCalled(t TestingT, methodName string, arguments ...interface{}) bool { + if !assert.True(t, m.methodWasCalled(methodName, arguments), fmt.Sprintf("The \"%s\" method should have been called with %d argument(s), but was not.", methodName, len(arguments))) { + t.Logf("%v", m.expectedCalls()) + return false + } + return true +} + +// AssertNotCalled asserts that the method was not called. +// It can produce a false result when an arugment is a pointer type and the underyling value changed after calling the mocked method. +func (m *Mock) AssertNotCalled(t TestingT, methodName string, arguments ...interface{}) bool { + if !assert.False(t, m.methodWasCalled(methodName, arguments), fmt.Sprintf("The \"%s\" method was called with %d argument(s), but should NOT have been.", methodName, len(arguments))) { + t.Logf("%v", m.expectedCalls()) + return false + } + return true +} + +func (m *Mock) methodWasCalled(methodName string, expected []interface{}) bool { + for _, call := range m.calls() { + if call.Method == methodName { + + _, differences := Arguments(expected).Diff(call.Arguments) + + if differences == 0 { + // found the expected call + return true + } + + } + } + // we didn't find the expected call + return false +} + +func (m *Mock) expectedCalls() []*Call { + m.mutex.Lock() + defer m.mutex.Unlock() + return append([]*Call{}, m.ExpectedCalls...) +} + +func (m *Mock) calls() []Call { + m.mutex.Lock() + defer m.mutex.Unlock() + return append([]Call{}, m.Calls...) +} + +/* + Arguments +*/ + +// Arguments holds an array of method arguments or return values. +type Arguments []interface{} + +const ( + // Anything is used in Diff and Assert when the argument being tested + // shouldn't be taken into consideration. + Anything string = "mock.Anything" +) + +// AnythingOfTypeArgument is a string that contains the type of an argument +// for use when type checking. Used in Diff and Assert. +type AnythingOfTypeArgument string + +// AnythingOfType returns an AnythingOfTypeArgument object containing the +// name of the type to check for. Used in Diff and Assert. +// +// For example: +// Assert(t, AnythingOfType("string"), AnythingOfType("int")) +func AnythingOfType(t string) AnythingOfTypeArgument { + return AnythingOfTypeArgument(t) +} + +// argumentMatcher performs custom argument matching, returning whether or +// not the argument is matched by the expectation fixture function. +type argumentMatcher struct { + // fn is a function which accepts one argument, and returns a bool. + fn reflect.Value +} + +func (f argumentMatcher) Matches(argument interface{}) bool { + expectType := f.fn.Type().In(0) + + if reflect.TypeOf(argument).AssignableTo(expectType) { + result := f.fn.Call([]reflect.Value{reflect.ValueOf(argument)}) + return result[0].Bool() + } + return false +} + +func (f argumentMatcher) String() string { + return fmt.Sprintf("func(%s) bool", f.fn.Type().In(0).Name()) +} + +// MatchedBy can be used to match a mock call based on only certain properties +// from a complex struct or some calculation. It takes a function that will be +// evaluated with the called argument and will return true when there's a match +// and false otherwise. +// +// Example: +// m.On("Do", MatchedBy(func(req *http.Request) bool { return req.Host == "example.com" })) +// +// |fn|, must be a function accepting a single argument (of the expected type) +// which returns a bool. If |fn| doesn't match the required signature, +// MathedBy() panics. +func MatchedBy(fn interface{}) argumentMatcher { + fnType := reflect.TypeOf(fn) + + if fnType.Kind() != reflect.Func { + panic(fmt.Sprintf("assert: arguments: %s is not a func", fn)) + } + if fnType.NumIn() != 1 { + panic(fmt.Sprintf("assert: arguments: %s does not take exactly one argument", fn)) + } + if fnType.NumOut() != 1 || fnType.Out(0).Kind() != reflect.Bool { + panic(fmt.Sprintf("assert: arguments: %s does not return a bool", fn)) + } + + return argumentMatcher{fn: reflect.ValueOf(fn)} +} + +// Get Returns the argument at the specified index. +func (args Arguments) Get(index int) interface{} { + if index+1 > len(args) { + panic(fmt.Sprintf("assert: arguments: Cannot call Get(%d) because there are %d argument(s).", index, len(args))) + } + return args[index] +} + +// Is gets whether the objects match the arguments specified. +func (args Arguments) Is(objects ...interface{}) bool { + for i, obj := range args { + if obj != objects[i] { + return false + } + } + return true +} + +// Diff gets a string describing the differences between the arguments +// and the specified objects. +// +// Returns the diff string and number of differences found. +func (args Arguments) Diff(objects []interface{}) (string, int) { + + var output = "\n" + var differences int + + var maxArgCount = len(args) + if len(objects) > maxArgCount { + maxArgCount = len(objects) + } + + for i := 0; i < maxArgCount; i++ { + var actual, expected interface{} + + if len(objects) <= i { + actual = "(Missing)" + } else { + actual = objects[i] + } + + if len(args) <= i { + expected = "(Missing)" + } else { + expected = args[i] + } + + if matcher, ok := expected.(argumentMatcher); ok { + if matcher.Matches(actual) { + output = fmt.Sprintf("%s\t%d: \u2705 %s matched by %s\n", output, i, actual, matcher) + } else { + differences++ + output = fmt.Sprintf("%s\t%d: \u2705 %s not matched by %s\n", output, i, actual, matcher) + } + } else if reflect.TypeOf(expected) == reflect.TypeOf((*AnythingOfTypeArgument)(nil)).Elem() { + + // type checking + if reflect.TypeOf(actual).Name() != string(expected.(AnythingOfTypeArgument)) && reflect.TypeOf(actual).String() != string(expected.(AnythingOfTypeArgument)) { + // not match + differences++ + output = fmt.Sprintf("%s\t%d: \u274C type %s != type %s - %s\n", output, i, expected, reflect.TypeOf(actual).Name(), actual) + } + + } else { + + // normal checking + + if assert.ObjectsAreEqual(expected, Anything) || assert.ObjectsAreEqual(actual, Anything) || assert.ObjectsAreEqual(actual, expected) { + // match + output = fmt.Sprintf("%s\t%d: \u2705 %s == %s\n", output, i, actual, expected) + } else { + // not match + differences++ + output = fmt.Sprintf("%s\t%d: \u274C %s != %s\n", output, i, actual, expected) + } + } + + } + + if differences == 0 { + return "No differences.", differences + } + + return output, differences + +} + +// Assert compares the arguments with the specified objects and fails if +// they do not exactly match. +func (args Arguments) Assert(t TestingT, objects ...interface{}) bool { + + // get the differences + diff, diffCount := args.Diff(objects) + + if diffCount == 0 { + return true + } + + // there are differences... report them... + t.Logf(diff) + t.Errorf("%sArguments do not match.", assert.CallerInfo()) + + return false + +} + +// String gets the argument at the specified index. Panics if there is no argument, or +// if the argument is of the wrong type. +// +// If no index is provided, String() returns a complete string representation +// of the arguments. +func (args Arguments) String(indexOrNil ...int) string { + + if len(indexOrNil) == 0 { + // normal String() method - return a string representation of the args + var argsStr []string + for _, arg := range args { + argsStr = append(argsStr, fmt.Sprintf("%s", reflect.TypeOf(arg))) + } + return strings.Join(argsStr, ",") + } else if len(indexOrNil) == 1 { + // Index has been specified - get the argument at that index + var index = indexOrNil[0] + var s string + var ok bool + if s, ok = args.Get(index).(string); !ok { + panic(fmt.Sprintf("assert: arguments: String(%d) failed because object wasn't correct type: %s", index, args.Get(index))) + } + return s + } + + panic(fmt.Sprintf("assert: arguments: Wrong number of arguments passed to String. Must be 0 or 1, not %d", len(indexOrNil))) + +} + +// Int gets the argument at the specified index. Panics if there is no argument, or +// if the argument is of the wrong type. +func (args Arguments) Int(index int) int { + var s int + var ok bool + if s, ok = args.Get(index).(int); !ok { + panic(fmt.Sprintf("assert: arguments: Int(%d) failed because object wasn't correct type: %v", index, args.Get(index))) + } + return s +} + +// Error gets the argument at the specified index. Panics if there is no argument, or +// if the argument is of the wrong type. +func (args Arguments) Error(index int) error { + obj := args.Get(index) + var s error + var ok bool + if obj == nil { + return nil + } + if s, ok = obj.(error); !ok { + panic(fmt.Sprintf("assert: arguments: Error(%d) failed because object wasn't correct type: %v", index, args.Get(index))) + } + return s +} + +// Bool gets the argument at the specified index. Panics if there is no argument, or +// if the argument is of the wrong type. +func (args Arguments) Bool(index int) bool { + var s bool + var ok bool + if s, ok = args.Get(index).(bool); !ok { + panic(fmt.Sprintf("assert: arguments: Bool(%d) failed because object wasn't correct type: %v", index, args.Get(index))) + } + return s +} diff --git a/vendor/gopkg.in/airbrake/gobrake.v2/LICENSE b/vendor/gopkg.in/airbrake/gobrake.v2/LICENSE new file mode 100644 index 00000000..d64c10ef --- /dev/null +++ b/vendor/gopkg.in/airbrake/gobrake.v2/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2014 The Gobrake 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. diff --git a/vendor/gopkg.in/airbrake/gobrake.v2/gobrake.go b/vendor/gopkg.in/airbrake/gobrake.v2/gobrake.go new file mode 100644 index 00000000..23efe414 --- /dev/null +++ b/vendor/gopkg.in/airbrake/gobrake.v2/gobrake.go @@ -0,0 +1,16 @@ +package gobrake + +import ( + "log" + "os" +) + +var logger *log.Logger + +func init() { + SetLogger(log.New(os.Stderr, "gobrake: ", log.LstdFlags)) +} + +func SetLogger(l *log.Logger) { + logger = l +} diff --git a/vendor/gopkg.in/airbrake/gobrake.v2/notice.go b/vendor/gopkg.in/airbrake/gobrake.v2/notice.go new file mode 100644 index 00000000..7152c329 --- /dev/null +++ b/vendor/gopkg.in/airbrake/gobrake.v2/notice.go @@ -0,0 +1,78 @@ +package gobrake + +import ( + "fmt" + "net/http" +) + +type Error struct { + Type string `json:"type"` + Message string `json:"message"` + Backtrace []StackFrame `json:"backtrace"` +} + +type Notice struct { + Errors []Error `json:"errors"` + Context map[string]interface{} `json:"context"` + Env map[string]interface{} `json:"environment"` + Session map[string]interface{} `json:"session"` + Params map[string]interface{} `json:"params"` +} + +func (n *Notice) String() string { + if len(n.Errors) == 0 { + return fmt.Sprint(n) + } + e := n.Errors[0] + return fmt.Sprintf("%s: %s", e.Type, e.Message) +} + +func NewNotice(e interface{}, req *http.Request, depth int) *Notice { + stack := stack(depth) + notice := &Notice{ + Errors: []Error{ + { + Type: fmt.Sprintf("%T", e), + Message: fmt.Sprint(e), + Backtrace: stack, + }, + }, + Context: map[string]interface{}{ + "notifier": map[string]interface{}{ + "name": "gobrake", + "version": "2.0.3", + "url": "https://github.com/airbrake/gobrake", + }, + }, + Env: map[string]interface{}{}, + Session: map[string]interface{}{}, + Params: map[string]interface{}{}, + } + + if req != nil { + notice.Context["url"] = req.URL.String() + if ua := req.Header.Get("User-Agent"); ua != "" { + notice.Context["userAgent"] = ua + } + + for k, v := range req.Header { + if len(v) == 1 { + notice.Env[k] = v[0] + } else { + notice.Env[k] = v + } + } + + if err := req.ParseForm(); err == nil { + for k, v := range req.Form { + if len(v) == 1 { + notice.Params[k] = v[0] + } else { + notice.Params[k] = v + } + } + } + } + + return notice +} diff --git a/vendor/gopkg.in/airbrake/gobrake.v2/notifier.go b/vendor/gopkg.in/airbrake/gobrake.v2/notifier.go new file mode 100644 index 00000000..0ca22e35 --- /dev/null +++ b/vendor/gopkg.in/airbrake/gobrake.v2/notifier.go @@ -0,0 +1,238 @@ +package gobrake // import "gopkg.in/airbrake/gobrake.v2" + +import ( + "bytes" + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "net" + "net/http" + "os" + "runtime" + "sync" + "time" +) + +const defaultAirbrakeHost = "https://airbrake.io" + +const statusTooManyRequests = 429 + +var ( + errClosed = errors.New("gobrake: notifier is closed") + errRateLimited = errors.New("gobrake: you are rate limited") +) + +var httpClient = &http.Client{ + Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, + Dial: (&net.Dialer{ + Timeout: 15 * time.Second, + KeepAlive: 30 * time.Second, + }).Dial, + TLSHandshakeTimeout: 10 * time.Second, + TLSClientConfig: &tls.Config{ + ClientSessionCache: tls.NewLRUClientSessionCache(1024), + }, + MaxIdleConnsPerHost: 10, + ResponseHeaderTimeout: 10 * time.Second, + }, + Timeout: 10 * time.Second, +} + +var buffers = sync.Pool{ + New: func() interface{} { + return new(bytes.Buffer) + }, +} + +type filter func(*Notice) *Notice + +type Notifier struct { + // http.Client that is used to interact with Airbrake API. + Client *http.Client + + projectId int64 + projectKey string + createNoticeURL string + + context map[string]string + filters []filter + + wg sync.WaitGroup + noticeCh chan *Notice + closed chan struct{} +} + +func NewNotifier(projectId int64, projectKey string) *Notifier { + n := &Notifier{ + projectId: projectId, + projectKey: projectKey, + createNoticeURL: getCreateNoticeURL(defaultAirbrakeHost, projectId, projectKey), + + Client: httpClient, + + context: map[string]string{ + "language": runtime.Version(), + "os": runtime.GOOS, + "architecture": runtime.GOARCH, + }, + + noticeCh: make(chan *Notice, 1000), + closed: make(chan struct{}), + } + if hostname, err := os.Hostname(); err == nil { + n.context["hostname"] = hostname + } + if wd, err := os.Getwd(); err == nil { + n.context["rootDirectory"] = wd + } + for i := 0; i < 10; i++ { + go n.worker() + } + return n +} + +// Sets Airbrake host name. Default is https://airbrake.io. +func (n *Notifier) SetHost(h string) { + n.createNoticeURL = getCreateNoticeURL(h, n.projectId, n.projectKey) +} + +// AddFilter adds filter that can modify or ignore notice. +func (n *Notifier) AddFilter(fn filter) { + n.filters = append(n.filters, fn) +} + +// Notify notifies Airbrake about the error. +func (n *Notifier) Notify(e interface{}, req *http.Request) { + notice := n.Notice(e, req, 1) + n.SendNoticeAsync(notice) +} + +// Notice returns Aibrake notice created from error and request. depth +// determines which call frame to use when constructing backtrace. +func (n *Notifier) Notice(err interface{}, req *http.Request, depth int) *Notice { + notice := NewNotice(err, req, depth+3) + for k, v := range n.context { + notice.Context[k] = v + } + return notice +} + +type sendResponse struct { + Id string `json:"id"` +} + +// SendNotice sends notice to Airbrake. +func (n *Notifier) SendNotice(notice *Notice) (string, error) { + for _, fn := range n.filters { + notice = fn(notice) + if notice == nil { + // Notice is ignored. + return "", nil + } + } + + buf := buffers.Get().(*bytes.Buffer) + defer buffers.Put(buf) + + buf.Reset() + if err := json.NewEncoder(buf).Encode(notice); err != nil { + return "", err + } + + resp, err := n.Client.Post(n.createNoticeURL, "application/json", buf) + if err != nil { + return "", err + } + defer resp.Body.Close() + + buf.Reset() + _, err = buf.ReadFrom(resp.Body) + if err != nil { + return "", err + } + + if resp.StatusCode != http.StatusCreated { + if resp.StatusCode == statusTooManyRequests { + return "", errRateLimited + } + err := fmt.Errorf("gobrake: got response code=%d, wanted 201 CREATED", resp.StatusCode) + return "", err + } + + var sendResp sendResponse + err = json.NewDecoder(buf).Decode(&sendResp) + if err != nil { + return "", err + } + + return sendResp.Id, nil +} + +// SendNoticeAsync acts as SendNotice, but sends notice asynchronously +// and pending notices can be flushed with Flush. +func (n *Notifier) SendNoticeAsync(notice *Notice) { + n.wg.Add(1) + select { + case n.noticeCh <- notice: + default: + n.wg.Done() + logger.Printf( + "notice=%q is ignored, because queue is full (len=%d)", + notice, len(n.noticeCh), + ) + } +} + +func (n *Notifier) worker() { + for { + select { + case notice := <-n.noticeCh: + if _, err := n.SendNotice(notice); err != nil && err != errRateLimited { + logger.Printf("gobrake failed reporting notice=%q: error=%q", notice, err) + } + n.wg.Done() + case <-n.closed: + return + } + } +} + +// NotifyOnPanic notifies Airbrake about the panic and should be used +// with defer statement. +func (n *Notifier) NotifyOnPanic() { + if v := recover(); v != nil { + notice := n.Notice(v, nil, 3) + n.SendNotice(notice) + panic(v) + } +} + +// Flush does nothing. +// +// Deprecated. Use CloseAndWait instead. +func (n *Notifier) Flush() {} + +// WaitAndClose waits for pending requests to finish and then closes the notifier. +func (n *Notifier) WaitAndClose(timeout time.Duration) error { + done := make(chan struct{}) + go func() { + n.wg.Wait() + close(done) + }() + select { + case <-done: + case <-time.After(timeout): + } + + close(n.closed) + return nil +} + +func getCreateNoticeURL(host string, projectId int64, key string) string { + return fmt.Sprintf( + "%s/api/v3/projects/%d/notices?key=%s", + host, projectId, key, + ) +} diff --git a/vendor/gopkg.in/airbrake/gobrake.v2/util.go b/vendor/gopkg.in/airbrake/gobrake.v2/util.go new file mode 100644 index 00000000..c05c0331 --- /dev/null +++ b/vendor/gopkg.in/airbrake/gobrake.v2/util.go @@ -0,0 +1,59 @@ +package gobrake + +import ( + "runtime" + "strings" +) + +func stackFilter(packageName, funcName string, file string, line int) bool { + return packageName == "runtime" && funcName == "panic" +} + +type StackFrame struct { + File string `json:"file"` + Line int `json:"line"` + Func string `json:"function"` +} + +func stack(depth int) []StackFrame { + stack := []StackFrame{} + for i := depth; ; i++ { + pc, file, line, ok := runtime.Caller(i) + if !ok { + break + } + packageName, funcName := packageFuncName(pc) + if stackFilter(packageName, funcName, file, line) { + stack = stack[:0] + continue + } + stack = append(stack, StackFrame{ + File: file, + Line: line, + Func: funcName, + }) + } + + return stack +} + +func packageFuncName(pc uintptr) (string, string) { + f := runtime.FuncForPC(pc) + if f == nil { + return "", "" + } + + packageName := "" + funcName := f.Name() + + if ind := strings.LastIndex(funcName, "/"); ind > 0 { + packageName += funcName[:ind+1] + funcName = funcName[ind+1:] + } + if ind := strings.Index(funcName, "."); ind > 0 { + packageName += funcName[:ind] + funcName = funcName[ind+1:] + } + + return packageName, funcName +} diff --git a/vendor/gopkg.in/gemnasium/logrus-airbrake-hook.v2/LICENSE b/vendor/gopkg.in/gemnasium/logrus-airbrake-hook.v2/LICENSE new file mode 100644 index 00000000..a4282b2a --- /dev/null +++ b/vendor/gopkg.in/gemnasium/logrus-airbrake-hook.v2/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Gemnasium + +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. diff --git a/vendor/gopkg.in/gemnasium/logrus-airbrake-hook.v2/airbrake.go b/vendor/gopkg.in/gemnasium/logrus-airbrake-hook.v2/airbrake.go new file mode 100644 index 00000000..c2cf9216 --- /dev/null +++ b/vendor/gopkg.in/gemnasium/logrus-airbrake-hook.v2/airbrake.go @@ -0,0 +1,80 @@ +package airbrake // import "gopkg.in/gemnasium/logrus-airbrake-hook.v2" + +import ( + "errors" + "fmt" + "os" + + "github.com/Sirupsen/logrus" + "gopkg.in/airbrake/gobrake.v2" +) + +// Set airbrake.BufSize = _before_ calling NewHook +var BufSize uint = 1024 + +// AirbrakeHook to send exceptions to an exception-tracking service compatible +// with the Airbrake API. +type airbrakeHook struct { + Airbrake *gobrake.Notifier + noticeChan chan *gobrake.Notice +} + +func NewHook(projectID int64, apiKey, env string) *airbrakeHook { + airbrake := gobrake.NewNotifier(projectID, apiKey) + airbrake.AddFilter(func(notice *gobrake.Notice) *gobrake.Notice { + if env == "development" { + return nil + } + notice.Context["environment"] = env + return notice + }) + hook := &airbrakeHook{ + Airbrake: airbrake, + noticeChan: make(chan *gobrake.Notice, BufSize), + } + go hook.fire() + return hook +} + +func (hook *airbrakeHook) Fire(entry *logrus.Entry) error { + var notifyErr error + err, ok := entry.Data["error"].(error) + if ok { + notifyErr = err + } else { + notifyErr = errors.New(entry.Message) + } + notice := hook.Airbrake.Notice(notifyErr, nil, 3) + for k, v := range entry.Data { + notice.Context[k] = fmt.Sprintf("%s", v) + } + // Don't exit before sending the exception + if entry.Level == logrus.ErrorLevel || entry.Level == logrus.PanicLevel { + hook.sendNotice(notice) + return nil + } + hook.noticeChan <- notice + return nil +} + +// fire sends errors to airbrake when an entry is available on entryChan +func (hook *airbrakeHook) fire() { + for { + notice := <-hook.noticeChan + hook.sendNotice(notice) + } +} + +func (hook *airbrakeHook) sendNotice(notice *gobrake.Notice) { + if _, err := hook.Airbrake.SendNotice(notice); err != nil { + fmt.Fprintf(os.Stderr, "Failed to send error to Airbrake: %v\n", err) + } +} + +func (hook *airbrakeHook) Levels() []logrus.Level { + return []logrus.Level{ + logrus.ErrorLevel, + logrus.FatalLevel, + logrus.PanicLevel, + } +} diff --git a/vendor/manifest b/vendor/manifest new file mode 100644 index 00000000..de40db4d --- /dev/null +++ b/vendor/manifest @@ -0,0 +1,97 @@ +{ + "version": 0, + "dependencies": [ + { + "importpath": "github.com/Jeffail/gabs", + "repository": "https://github.com/Jeffail/gabs", + "vcs": "git", + "revision": "ee1575a53249b51d636e62464ca43a13030afdb5", + "branch": "master", + "notests": true + }, + { + "importpath": "github.com/Sirupsen/logrus", + "repository": "https://github.com/Sirupsen/logrus", + "vcs": "git", + "revision": "cd7d1bbe41066b6c1f19780f895901052150a575", + "branch": "master", + "notests": true + }, + { + "importpath": "github.com/davecgh/go-spew/spew", + "repository": "https://github.com/davecgh/go-spew", + "vcs": "git", + "revision": "5215b55f46b2b919f50a1df0eaa5886afe4e3b3d", + "branch": "master", + "path": "/spew", + "notests": true + }, + { + "importpath": "github.com/docker/go-units", + "repository": "https://github.com/docker/go-units", + "vcs": "git", + "revision": "5d2041e26a699eaca682e2ea41c8f891e1060444", + "branch": "master", + "notests": true + }, + { + "importpath": "github.com/pmezard/go-difflib/difflib", + "repository": "https://github.com/pmezard/go-difflib", + "vcs": "git", + "revision": "792786c7400a136282c1664665ae0a8db921c6c2", + "branch": "master", + "path": "/difflib", + "notests": true + }, + { + "importpath": "github.com/samalba/dockerclient", + "repository": "https://github.com/samalba/dockerclient", + "vcs": "git", + "revision": "91d7393ff85980ba3a8966405871a3d446ca28f2", + "branch": "master", + "notests": true + }, + { + "importpath": "github.com/stretchr/objx", + "repository": "https://github.com/stretchr/objx", + "vcs": "git", + "revision": "1a9d0bb9f541897e62256577b352fdbc1fb4fd94", + "branch": "master", + "notests": true + }, + { + "importpath": "github.com/stretchr/testify/assert", + "repository": "https://github.com/stretchr/testify", + "vcs": "git", + "revision": "c5d7a69bf8a2c9c374798160849c071093e41dd1", + "branch": "master", + "path": "/assert", + "notests": true + }, + { + "importpath": "github.com/stretchr/testify/mock", + "repository": "https://github.com/stretchr/testify", + "vcs": "git", + "revision": "c5d7a69bf8a2c9c374798160849c071093e41dd1", + "branch": "master", + "path": "/mock", + "notests": true + }, + { + "importpath": "gopkg.in/airbrake/gobrake.v2", + "repository": "https://gopkg.in/airbrake/gobrake.v2", + "vcs": "git", + "revision": "31c8ff1fb8b79a6947e6565e9a6df535f98a6b94", + "branch": "master", + "notests": true + }, + { + "importpath": "gopkg.in/gemnasium/logrus-airbrake-hook.v2", + "repository": "https://gopkg.in/gemnasium/logrus-airbrake-hook.v2", + "vcs": "git", + "revision": "31e6fd4bd5a98d8ee7673d24bc54ec73c31810dd", + "branch": "master", + "notests": true + } + ] +} \ No newline at end of file diff --git a/world/Plugins/Docker/Info.lua b/world/Plugins/Docker/Info.lua new file mode 100644 index 00000000..31cd2efe --- /dev/null +++ b/world/Plugins/Docker/Info.lua @@ -0,0 +1,27 @@ +-- Info.lua + +-- Implements the g_PluginInfo standard plugin description + +g_PluginInfo = +{ + Name = "Docker", + Version = 1, + DisplayVersion = "0.1.0", + Date = "2016-09-16", -- yyyy-mm-dd + SourceLocation = "https://github.com/docker/dockercraft", + Description = [[This plugin, in combination with the Dockercraft proxy, turns minecraft in to a Docker client]], + Commands = + { + + ["/docker"] = + { + Permission = "dockercraft.docker", + Handler = DockerCommand, + HelpString = "Execute a Docker Command", + Category = "Scripting", + }, + }, -- Commands + ConsoleCommands = {}, + AdditionalInfo = {} +} + diff --git a/world/Plugins/Docker/config.lua b/world/Plugins/Docker/config.lua index c88b2229..c910e6d6 100644 --- a/world/Plugins/Docker/config.lua +++ b/world/Plugins/Docker/config.lua @@ -18,7 +18,7 @@ GROUND_MAX_X = CONTAINER_START_X + 5 GROUND_MIN_Z = -4 GROUND_MAX_Z = CONTAINER_START_Z + 6 --- block updates are queued, this defines the +-- block updates are queued, this defines the -- maximum of block updates that can be handled -- in one single tick, for performance issues. MAX_BLOCK_UPDATE_PER_TICK = 50 diff --git a/world/Plugins/Docker/container.lua b/world/Plugins/Docker/container.lua index 8bf8e689..03502138 100644 --- a/world/Plugins/Docker/container.lua +++ b/world/Plugins/Docker/container.lua @@ -10,206 +10,205 @@ CONTAINER_STOPPED = 2 -- representation of a Docker container in -- the Minecraft world function NewContainer() - c = { - displayed = false, - x = 0, - z = 0, - name="", - id="", - imageRepo="", - imageTag="", - running=false, - init=Container.init, - setInfos=Container.setInfos, - destroy=Container.destroy, - display=Container.display, - updateMemSign=Container.updateMemSign, - updateCPUSign=Container.updateCPUSign, - addGround=Container.addGround - } - return c + c = { + world = nil, + displayed = false, + x = 0, + z = 0, + name="", + id="", + imageRepo="", + imageTag="", + running=false, + init=Container.init, + setInfos=Container.setInfos, + destroy=Container.destroy, + display=Container.display, + updateMemSign=Container.updateMemSign, + updateCPUSign=Container.updateCPUSign, + addGround=Container.addGround + } + return c end -Container = {displayed = false, x = 0, z = 0, name="",id="",imageRepo="",imageTag="",running=false} +Container = {world=nil, displayed = false, x = 0, z = 0, name="",id="",imageRepo="",imageTag="",running=false} -- Container:init sets Container's position function Container:init(x,z) - self.x = x - self.z = z - self.displayed = false + self.x = x + self.z = z + self.displayed = false end --- Container:setInfos sets Container's id, name, imageRepo, +-- Container:setInfos sets Container's id, name, imageRepo, -- image tag and running state -function Container:setInfos(id,name,imageRepo,imageTag,running) - self.id = id - self.name = name - self.imageRepo = imageRepo - self.imageTag = imageTag - self.running = running +function Container:setInfos(world,id,name,imageRepo,imageTag,running) + self.world = world + self.id = id + self.name = name + self.imageRepo = imageRepo + self.imageTag = imageTag + self.running = running end --- Container:destroy removes all blocks of the +-- Container:destroy removes all blocks of the -- container, it won't be visible on the map anymore function Container:destroy(running) - X = self.x+2 - Y = GROUND_LEVEL+2 - Z = self.z+2 - LOG("Exploding at X:" .. X .. " Y:" .. Y .. " Z:" .. Z) - local World = cRoot:Get():GetDefaultWorld() - World:BroadcastSoundEffect("random.explode", X, Y, Z, 1, 1) - World:BroadcastParticleEffect("hugeexplosion",X, Y, Z, 0, 0, 0, 1, 1) - - -- if a block is removed before it's button/lever/sign, that object will drop - -- and the player can collect it. Remove these first - - -- lever - digBlock(UpdateQueue,self.x+1,GROUND_LEVEL+3,self.z+1) - -- signs - digBlock(UpdateQueue,self.x+3,GROUND_LEVEL+2,self.z-1) - digBlock(UpdateQueue,self.x,GROUND_LEVEL+2,self.z-1) - digBlock(UpdateQueue,self.x+1,GROUND_LEVEL+2,self.z-1) - -- torch - digBlock(UpdateQueue,self.x+1,GROUND_LEVEL+3,self.z+1) - --button - digBlock(UpdateQueue,self.x+2,GROUND_LEVEL+3,self.z+2) - - -- rest of the blocks - for py = GROUND_LEVEL+1, GROUND_LEVEL+4 - do - for px=self.x-1, self.x+4 - do - for pz=self.z-1, self.z+5 - do - digBlock(UpdateQueue,px,py,pz) - end - end - end + local x = self.x+2 + local y = GROUND_LEVEL+2 + local z = self.z+2 + local world = self.world:getWorld() + world:BroadcastSoundEffect("random.explode", x, y, z, 1, 1) + world:BroadcastParticleEffect("hugeexplosion",x, y, z, 0, 0, 0, 1, 1) + + -- if a block is removed before it's button/lever/sign, that object will drop + -- and the player can collect it. Remove these first + + -- lever + self.world:digBlock(self.x+1,GROUND_LEVEL+3,self.z+1) + -- signs + self.world:digBlock(self.x+3,GROUND_LEVEL+2,self.z-1) + self.world:digBlock(self.x,GROUND_LEVEL+2,self.z-1) + self.world:digBlock(self.x+1,GROUND_LEVEL+2,self.z-1) + -- torch + self.world:digBlock(self.x+1,GROUND_LEVEL+3,self.z+1) + --button + self.world:digBlock(self.x+2,GROUND_LEVEL+3,self.z+2) + + -- rest of the blocks + for py = GROUND_LEVEL+1, GROUND_LEVEL+4 + do + for px=self.x-1, self.x+4 + do + for pz=self.z-1, self.z+5 + do + self.world:digBlock(px,py,pz) + end + end + end end -- Container:display displays all Container's blocks --- Blocks will be blue if the container is running, +-- Blocks will be blue if the container is running, -- orange otherwise. function Container:display(running) + local metaPrimaryColor = E_META_WOOL_LIGHTBLUE + local metaSecondaryColor = E_META_WOOL_BLUE - metaPrimaryColor = E_META_WOOL_LIGHTBLUE - metaSecondaryColor = E_META_WOOL_BLUE + if running == false + then + metaPrimaryColor = E_META_WOOL_ORANGE + metaSecondaryColor = E_META_WOOL_RED + end - if running == false - then - metaPrimaryColor = E_META_WOOL_ORANGE - metaSecondaryColor = E_META_WOOL_RED - end + self.displayed = true - self.displayed = true - - for px=self.x, self.x+3 - do - for pz=self.z, self.z+4 - do - setBlock(UpdateQueue,px,GROUND_LEVEL + 1,pz,E_BLOCK_WOOL,metaPrimaryColor) - end - end + for px=self.x, self.x+3 + do + for pz=self.z, self.z+4 + do + self.world:setBlock(px,GROUND_LEVEL+1,pz,E_BLOCK_WOOL,metaPrimaryColor) + end + end - for py = GROUND_LEVEL+2, GROUND_LEVEL+3 - do - setBlock(UpdateQueue,self.x+1,py,self.z,E_BLOCK_WOOL,metaPrimaryColor) + for py=GROUND_LEVEL+2, GROUND_LEVEL+3 + do + self.world:setBlock(self.x+1,py,self.z,E_BLOCK_WOOL,metaPrimaryColor) - -- leave empty space for the door - -- setBlock(UpdateQueue,self.x+2,py,self.z,E_BLOCK_WOOL,metaPrimaryColor) - - setBlock(UpdateQueue,self.x,py,self.z,E_BLOCK_WOOL,metaPrimaryColor) - setBlock(UpdateQueue,self.x+3,py,self.z,E_BLOCK_WOOL,metaPrimaryColor) + -- leave empty space for the door + -- setBlock(self.x+2,py,self.z,E_BLOCK_WOOL,metaPrimaryColor) - setBlock(UpdateQueue,self.x,py,self.z+1,E_BLOCK_WOOL,metaSecondaryColor) - setBlock(UpdateQueue,self.x+3,py,self.z+1,E_BLOCK_WOOL,metaSecondaryColor) + self.world:setBlock(self.x,py,self.z,E_BLOCK_WOOL,metaPrimaryColor) + self.world:setBlock(self.x+3,py,self.z,E_BLOCK_WOOL,metaPrimaryColor) - setBlock(UpdateQueue,self.x,py,self.z+2,E_BLOCK_WOOL,metaPrimaryColor) - setBlock(UpdateQueue,self.x+3,py,self.z+2,E_BLOCK_WOOL,metaPrimaryColor) + self.world:setBlock(self.x,py,self.z+1,E_BLOCK_WOOL,metaSecondaryColor) + self.world:setBlock(self.x+3,py,self.z+1,E_BLOCK_WOOL,metaSecondaryColor) - setBlock(UpdateQueue,self.x,py,self.z+3,E_BLOCK_WOOL,metaSecondaryColor) - setBlock(UpdateQueue,self.x+3,py,self.z+3,E_BLOCK_WOOL,metaSecondaryColor) + self.world:setBlock(self.x,py,self.z+2,E_BLOCK_WOOL,metaPrimaryColor) + self.world:setBlock(self.x+3,py,self.z+2,E_BLOCK_WOOL,metaPrimaryColor) - setBlock(UpdateQueue,self.x,py,self.z+4,E_BLOCK_WOOL,metaPrimaryColor) - setBlock(UpdateQueue,self.x+3,py,self.z+4,E_BLOCK_WOOL,metaPrimaryColor) + self.world:setBlock(self.x,py,self.z+3,E_BLOCK_WOOL,metaSecondaryColor) + self.world:setBlock(self.x+3,py,self.z+3,E_BLOCK_WOOL,metaSecondaryColor) - setBlock(UpdateQueue,self.x+1,py,self.z+4,E_BLOCK_WOOL,metaPrimaryColor) - setBlock(UpdateQueue,self.x+2,py,self.z+4,E_BLOCK_WOOL,metaPrimaryColor) - end + self.world:setBlock(self.x,py,self.z+4,E_BLOCK_WOOL,metaPrimaryColor) + self.world:setBlock(self.x+3,py,self.z+4,E_BLOCK_WOOL,metaPrimaryColor) - -- torch - setBlock(UpdateQueue,self.x+1,GROUND_LEVEL+3,self.z+3,E_BLOCK_TORCH,E_META_TORCH_ZP) + self.world:setBlock(self.x+1,py,self.z+4,E_BLOCK_WOOL,metaPrimaryColor) + self.world:setBlock(self.x+2,py,self.z+4,E_BLOCK_WOOL,metaPrimaryColor) + end - -- start / stop lever - setBlock(UpdateQueue,self.x+1,GROUND_LEVEL + 3,self.z + 2,E_BLOCK_WALLSIGN,E_META_CHEST_FACING_XP) - updateSign(UpdateQueue,self.x+1,GROUND_LEVEL + 3,self.z + 2,"","START/STOP","---->","",2) + -- torch + self.world:setBlock(self.x+1,GROUND_LEVEL+3,self.z+3,E_BLOCK_TORCH,E_META_TORCH_ZP) + -- start / stop lever + self.world:setBlock(self.x+1,GROUND_LEVEL + 3,self.z + 2,E_BLOCK_WALLSIGN,E_META_CHEST_FACING_XP) + self.world:updateSign(self.x+1,GROUND_LEVEL + 3,self.z + 2,"","START/STOP","---->","",2) - if running - then - setBlock(UpdateQueue,self.x+1,GROUND_LEVEL+3,self.z+1,E_BLOCK_LEVER,1) - else - setBlock(UpdateQueue,self.x+1,GROUND_LEVEL+3,self.z+1,E_BLOCK_LEVER,9) - end + if running + then + self.world:setBlock(self.x+1,GROUND_LEVEL+3,self.z+1,E_BLOCK_LEVER,1) + else + self.world:setBlock(self.x+1,GROUND_LEVEL+3,self.z+1,E_BLOCK_LEVER,9) + end - -- remove button + -- remove button - setBlock(UpdateQueue,self.x+2,GROUND_LEVEL + 3,self.z + 2,E_BLOCK_WALLSIGN,E_META_CHEST_FACING_XM) - updateSign(UpdateQueue,self.x+2,GROUND_LEVEL + 3,self.z + 2,"","REMOVE","---->","",2) + self.world:setBlock(self.x+2,GROUND_LEVEL + 3,self.z + 2,E_BLOCK_WALLSIGN,E_META_CHEST_FACING_XM) + self.world:updateSign(self.x+2,GROUND_LEVEL + 3,self.z + 2,"","REMOVE","---->","",2) - setBlock(UpdateQueue,self.x+2,GROUND_LEVEL+3,self.z+3,E_BLOCK_STONE_BUTTON,E_BLOCK_BUTTON_XM) + self.world:setBlock(self.x+2,GROUND_LEVEL+3,self.z+3,E_BLOCK_STONE_BUTTON,E_BLOCK_BUTTON_XM) + -- door + -- Cuberite bug with Minecraft 1.8 apparently, doors are not displayed correctly + -- setBlock(UpdateQueue,self.x+2,GROUND_LEVEL+2,self.z,E_BLOCK_WOODEN_DOOR,E_META_CHEST_FACING_ZM) - -- door - -- Cuberite bug with Minecraft 1.8 apparently, doors are not displayed correctly - -- setBlock(UpdateQueue,self.x+2,GROUND_LEVEL+2,self.z,E_BLOCK_WOODEN_DOOR,E_META_CHEST_FACING_ZM) + for px=self.x, self.x+3 + do + for pz=self.z, self.z+4 + do + self.world:setBlock(px,GROUND_LEVEL + 4,pz,E_BLOCK_WOOL,metaPrimaryColor) + end + end + self.world:setBlock(self.x+3,GROUND_LEVEL + 2,self.z - 1,E_BLOCK_WALLSIGN,E_META_CHEST_FACING_ZM) + self.world:updateSign(self.x+3,GROUND_LEVEL + 2,self.z - 1,string.sub(self.id,1,8),self.name,self.imageRepo,self.imageTag,2) - for px=self.x, self.x+3 - do - for pz=self.z, self.z+4 - do - setBlock(UpdateQueue,px,GROUND_LEVEL + 4,pz,E_BLOCK_WOOL,metaPrimaryColor) - end - end + -- Mem sign + self.world:setBlock(self.x,GROUND_LEVEL + 2,self.z - 1,E_BLOCK_WALLSIGN,E_META_CHEST_FACING_ZM) - setBlock(UpdateQueue,self.x+3,GROUND_LEVEL + 2,self.z - 1,E_BLOCK_WALLSIGN,E_META_CHEST_FACING_ZM) - updateSign(UpdateQueue,self.x+3,GROUND_LEVEL + 2,self.z - 1,string.sub(self.id,1,8),self.name,self.imageRepo,self.imageTag,2) - - -- Mem sign - setBlock(UpdateQueue,self.x,GROUND_LEVEL + 2,self.z - 1,E_BLOCK_WALLSIGN,E_META_CHEST_FACING_ZM) - - -- CPU sign - setBlock(UpdateQueue,self.x+1,GROUND_LEVEL + 2,self.z - 1,E_BLOCK_WALLSIGN,E_META_CHEST_FACING_ZM) + -- CPU sign + self.world:setBlock(self.x+1,GROUND_LEVEL + 2,self.z - 1,E_BLOCK_WALLSIGN,E_META_CHEST_FACING_ZM) end -- Container:updateMemSign updates the mem usage -- value displayed on Container's sign function Container:updateMemSign(s) - updateSign(UpdateQueue,self.x,GROUND_LEVEL + 2,self.z - 1,"Mem usage","",s,"") + self.world:updateSign(self.x,GROUND_LEVEL + 2,self.z - 1,"Mem usage","",s,"") end -- Container:updateCPUSign updates the mem usage -- value displayed on Container's sign function Container:updateCPUSign(s) - updateSign(UpdateQueue,self.x+1,GROUND_LEVEL + 2,self.z - 1,"CPU usage","",s,"") + self.world:updateSign(self.x+1,GROUND_LEVEL + 2,self.z - 1,"CPU usage","",s,"") end -- Container:addGround creates ground blocks -- necessary to display the container function Container:addGround() - if GROUND_MIN_X > self.x - 2 - then - OLD_GROUND_MIN_X = GROUND_MIN_X - GROUND_MIN_X = self.x - 2 - for x= GROUND_MIN_X, OLD_GROUND_MIN_X - do - for z=GROUND_MIN_Z,GROUND_MAX_Z - do - setBlock(UpdateQueue,x,y,z,E_BLOCK_WOOL,E_META_WOOL_WHITE) - end - end - end + LOG("Drawing ground in " .. self.world.Name .. " for container " .. self.id) + if self.world.groundMinX > self.x - 2 + then + local groundMinX = self.world.groundMinX + local y = GROUND_LEVEL + self.world.groundMinX = self.x - 2 + for x=self.world.groundMinX, groundMinX + do + for z=GROUND_MIN_Z,GROUND_MAX_Z + do + self.world:setBlock(x,y,z,E_BLOCK_WOOL,E_META_WOOL_WHITE) + end + end + end end diff --git a/world/Plugins/Docker/docker.lua b/world/Plugins/Docker/docker.lua index a87e2d19..faed3e92 100644 --- a/world/Plugins/Docker/docker.lua +++ b/world/Plugins/Docker/docker.lua @@ -1,422 +1,300 @@ +PLUGIN = nil --- queue containing the updates that need to be applied to the minecraft world -UpdateQueue = nil --- array of container objects -Containers = {} --- -SignsToUpdate = {} - --- as a lua array cannot contain nil values, we store references to this object --- in the "Containers" array to indicate that there is no container at an index -EmptyContainerSpace = {} - --- Tick is triggered by cPluginManager.HOOK_TICK -function Tick(TimeDelta) - UpdateQueue:update(MAX_BLOCK_UPDATE_PER_TICK) -end +-- Worlds table +Worlds = {} -- Plugin initialization function Initialize(Plugin) - Plugin:SetName("Docker") - Plugin:SetVersion(1) - - UpdateQueue = NewUpdateQueue() - - -- Hooks - - cPluginManager:AddHook(cPluginManager.HOOK_WORLD_STARTED, WorldStarted); - cPluginManager:AddHook(cPluginManager.HOOK_PLAYER_JOINED, PlayerJoined); - cPluginManager:AddHook(cPluginManager.HOOK_PLAYER_USING_BLOCK, PlayerUsingBlock); - cPluginManager:AddHook(cPluginManager.HOOK_CHUNK_GENERATING, OnChunkGenerating); - cPluginManager:AddHook(cPluginManager.HOOK_PLAYER_FOOD_LEVEL_CHANGE, OnPlayerFoodLevelChange); - cPluginManager:AddHook(cPluginManager.HOOK_TAKE_DAMAGE, OnTakeDamage); - cPluginManager:AddHook(cPluginManager.HOOK_WEATHER_CHANGING, OnWeatherChanging); - cPluginManager:AddHook(cPluginManager.HOOK_SERVER_PING, OnServerPing); - cPluginManager:AddHook(cPluginManager.HOOK_TICK, Tick); - - -- Command Bindings - - cPluginManager.BindCommand("/docker", "*", DockerCommand, " - docker CLI commands") - - Plugin:AddWebTab("Docker",HandleRequest_Docker) + Plugin:SetName("Docker") + Plugin:SetVersion(1) + + -- Register Commands + dofile(cPluginManager:GetPluginsPath() .. "/InfoReg.lua") + RegisterPluginInfoCommands() + + -- Hooks + cPluginManager:AddHook(cPluginManager.HOOK_WORLD_STARTED, OnWorldStarted); + cPluginManager:AddHook(cPluginManager.HOOK_PLAYER_JOINED, OnPlayerJoined); + cPluginManager:AddHook(cPluginManager.HOOK_ENTITY_CHANGING_WORLD, OnEntityChangingWorld); + cPluginManager:AddHook(cPluginManager.HOOK_PLAYER_USING_BLOCK, OnPlayerUsingBlock); + cPluginManager:AddHook(cPluginManager.HOOK_CHUNK_GENERATING, OnChunkGenerating); + cPluginManager:AddHook(cPluginManager.HOOK_PLAYER_FOOD_LEVEL_CHANGE, OnPlayerFoodLevelChange); + cPluginManager:AddHook(cPluginManager.HOOK_TAKE_DAMAGE, OnTakeDamage); + cPluginManager:AddHook(cPluginManager.HOOK_WEATHER_CHANGING, OnWeatherChanging); + cPluginManager:AddHook(cPluginManager.HOOK_SERVER_PING, OnServerPing); + cPluginManager:AddHook(cPluginManager.HOOK_WORLD_TICK, OnWorldTick); + + -- Add WebAdmin Tab + Plugin:AddWebTab("Docker",HandleRequest_Docker) + + -- make all players admin + cRankManager:SetDefaultRank("Admin") + + LOG("Initialised " .. Plugin:GetName() .. " v." .. Plugin:GetVersion()) + + return true +end - -- make all players admin - cRankManager:SetDefaultRank("Admin") +-- OnWorldTick processes the updateQueue of each World +function OnWorldTick(World, TimeDelta) + local name = World:GetName() + if name == nil then + return + end + if Worlds[name] == nil then + return + end + Worlds[name].updateQueue:update(MAX_BLOCK_UPDATE_PER_TICK) +end - - LOG("Initialised " .. Plugin:GetName() .. " v." .. Plugin:GetVersion()) - return true +-- OnWorldStarted creates a new World object for every Dockercraft world +-- and gets the state of the containers +function OnWorldStarted(World) + local name = World:GetName() + Worlds[name] = NewWorld(name) + local y = GROUND_LEVEL + -- just enough to fit one container + -- then it should be dynamic + for x = GROUND_MIN_X, GROUND_MAX_X do + for z=GROUND_MIN_Z,GROUND_MAX_Z do + Worlds[name]:setBlock(x,y,z,E_BLOCK_WOOL,E_META_WOOL_WHITE) + end + end + + local r = os.execute("dockercraft containers?world=" .. name) + LOG("executed: dockercraft containers?world=" .. name .. " -> " .. tostring(r)) end --- updateStats update CPU and memory usage displayed --- on container sign (container identified by id) -function updateStats(id, mem, cpu) - for i=1, table.getn(Containers) - do - if Containers[i] ~= EmptyContainerSpace and Containers[i].id == id - then - Containers[i]:updateMemSign(mem) - Containers[i]:updateCPUSign(cpu) - break - end - end +-- OnEntityChangedWorld +function OnEntityChangingWorld(Entity, World) + if Entity:IsPlayer() then + local name = World:GetName() + local r = os.execute("dockercraft containers?world=" .. name) + LOG("executed: dockercraft containers?world=" .. name .. " -> " .. tostring(r)) + end end --- getStartStopLeverContainer returns the container --- id that corresponds to lever at x,y coordinates -function getStartStopLeverContainer(x, z) - for i=1, table.getn(Containers) - do - if Containers[i] ~= EmptyContainerSpace and x == Containers[i].x + 1 and z == Containers[i].z + 1 - then - return Containers[i].id - end - end - return "" +-- OnPlayerJoined +function OnPlayerJoined(Player) + -- enable flying + Player:SetCanFly(true) end --- getRemoveButtonContainer returns the container --- id and state for the button at x,y coordinates -function getRemoveButtonContainer(x, z) - for i=1, table.getn(Containers) - do - if Containers[i] ~= EmptyContainerSpace and x == Containers[i].x + 2 and z == Containers[i].z + 3 - then - return Containers[i].id, Containers[i].running - end - end - return "", true +-- OnPlayerUsingBlock +function OnPlayerUsingBlock(Player, BlockX, BlockY, BlockZ, BlockFace, CursorX, CursorY, CursorZ, BlockType, BlockMeta) + LOG("Using block: " .. tostring(BlockX) .. "," .. tostring(BlockY) .. "," .. tostring(BlockZ) .. " - " .. tostring(BlockType) .. " - " .. tostring(BlockMeta)) + + -- get world + local w = Player:GetWorld():GetName() + local world = Worlds[w] + + -- lever: 1->OFF 9->ON (in that orientation) + -- lever + if BlockType == 69 then + local containerID = world:getStartStopLeverContainer(BlockX,BlockZ) + LOG("Using lever associated with container ID: " .. containerID) + + if containerID ~= "" then + -- stop + if BlockMeta == 1 then + Player:SendMessage("docker stop " .. string.sub(containerID,1,8)) + local r = os.execute("dockercraft exec?world=" .. w .. "\\&cmd=docker+stop+" .. containerID) + LOG("executed: dockercraft exec?world=" .. w .. "\\&cmd=docker+stop+" .. containerID .. " -> " .. tostring(r)) + -- start + else + Player:SendMessage("docker start " .. string.sub(containerID,1,8)) + local r = os.execute("dockercraft exec?world=" .. w .. "\\&cmd=docker+start+" .. containerID) + LOG("executed: dockercraft exec?world=" .. w .. "\\&cmd=docker+start+" .. containerID .. " -> " .. tostring(r)) + end + else + LOG("WARNING: no docker container ID attached to this lever") + end + end + + -- stone button + if BlockType == 77 then + local containerID, running = world:getRemoveButtonContainer(BlockX,BlockZ) + + if running then + Player:SendMessage("A running container can't be removed.") + else + Player:SendMessage("docker rm " .. string.sub(containerID,1,8)) + r = os.execute("dockercraft exec?world=" .. w .. "\\&cmd=docker+rm+" .. containerID) + LOG("executed: dockercraft exec?world=" .. w .. "\\&cmd=docker+rm+" .. containerID .. " -> " .. tostring(r)) + end + end end --- destroyContainer looks for the first container having the given id, --- removes it from the Minecraft world and from the 'Containers' array -function destroyContainer(id) - LOG("destroyContainer: " .. id) - -- loop over the containers and remove the first having the given id - for i=1, table.getn(Containers) - do - if Containers[i] ~= EmptyContainerSpace and Containers[i].id == id - then - -- remove the container from the world - Containers[i]:destroy() - -- if the container being removed is the last element of the array - -- we reduce the size of the "Container" array, but if it is not, - -- we store a reference to the "EmptyContainerSpace" object at the - -- same index to indicate this is a free space now. - -- We use a reference to this object because it is not possible to - -- have 'nil' values in the middle of a lua array. - if i == table.getn(Containers) - then - table.remove(Containers, i) - -- we have removed the last element of the array. If the array - -- has tailing empty container spaces, we remove them as well. - while Containers[table.getn(Containers)] == EmptyContainerSpace - do - table.remove(Containers, table.getn(Containers)) - end - else - Containers[i] = EmptyContainerSpace - end - -- we removed the container, we can exit the loop - break - end - end +-- OnChunkGenerating overrides the built-in chunk generator +-- to have it generate empty chunks only +function OnChunkGenerating(World, ChunkX, ChunkZ, ChunkDesc) + ChunkDesc:SetUseDefaultBiomes(false) + ChunkDesc:SetUseDefaultComposition(false) + ChunkDesc:SetUseDefaultFinish(false) + ChunkDesc:SetUseDefaultHeight(false) + return true end --- updateContainer accepts 3 different states: running, stopped, created --- sometimes "start" events arrive before "create" ones --- in this case, we just ignore the update -function updateContainer(id,name,imageRepo,imageTag,state) - LOG("Update container with ID: " .. id .. " state: " .. state) - - -- first pass, to see if the container is - -- already displayed (maybe with another state) - for i=1, table.getn(Containers) - do - -- if container found with same ID, we update it - if Containers[i] ~= EmptyContainerSpace and Containers[i].id == id - then - Containers[i]:setInfos(id,name,imageRepo,imageTag,state == CONTAINER_RUNNING) - Containers[i]:display(state == CONTAINER_RUNNING) - LOG("found. updated. now return") - return - end - end - - -- if container isn't already displayed, we see if there's an empty space - -- in the world to display the container - x = CONTAINER_START_X - index = -1 - - for i=1, table.getn(Containers) - do - -- use first empty location - if Containers[i] == EmptyContainerSpace - then - LOG("Found empty location: Containers[" .. tostring(i) .. "]") - index = i - break - end - x = x + CONTAINER_OFFSET_X - end - - container = NewContainer() - container:init(x,CONTAINER_START_Z) - container:setInfos(id,name,imageRepo,imageTag,state == CONTAINER_RUNNING) - container:addGround() - container:display(state == CONTAINER_RUNNING) - - if index == -1 - then - table.insert(Containers, container) - else - Containers[index] = container - end -end +-- DockerCommand handles /docker commands entered by the player +function DockerCommand(Split, Player) --- -function WorldStarted(World) - y = GROUND_LEVEL - -- just enough to fit one container - -- then it should be dynamic - for x= GROUND_MIN_X, GROUND_MAX_X - do - for z=GROUND_MIN_Z,GROUND_MAX_Z - do - setBlock(UpdateQueue,x,y,z,E_BLOCK_WOOL,E_META_WOOL_WHITE) - end - end -end + -- get world name + local world = Player:GetWorld():GetName() --- -function PlayerJoined(Player) - -- enable flying - Player:SetCanFly(true) + if table.getn(Split) > 0 then + LOG("Split[1]: " .. Split[1]) - -- refresh containers - LOG("player joined") - r = os.execute("goproxy containers") - LOG("executed: goproxy containers -> " .. tostring(r)) -end + if Split[1] == "/docker" then + if table.getn(Split) > 1 then + if Split[2] == "pull" or Split[2] == "create" or Split[2] == "run" or Split[2] == "stop" or Split[2] == "rm" or Split[2] == "rmi" or Split[2] == "start" or Split[2] == "kill" then + -- force detach when running a container + if Split[2] == "run" then + table.insert(Split,3,"-d") + end --- -function PlayerUsingBlock(Player, BlockX, BlockY, BlockZ, BlockFace, CursorX, CursorY, CursorZ, BlockType, BlockMeta) - LOG("Using block: " .. tostring(BlockX) .. "," .. tostring(BlockY) .. "," .. tostring(BlockZ) .. " - " .. tostring(BlockType) .. " - " .. tostring(BlockMeta)) - - -- lever: 1->OFF 9->ON (in that orientation) - -- lever - if BlockType == 69 - then - containerID = getStartStopLeverContainer(BlockX,BlockZ) - LOG("Using lever associated with container ID: " .. containerID) - - if containerID ~= "" - then - -- stop - if BlockMeta == 1 - then - Player:SendMessage("docker stop " .. string.sub(containerID,1,8)) - r = os.execute("goproxy exec?cmd=docker+stop+" .. containerID) - -- start - else - Player:SendMessage("docker start " .. string.sub(containerID,1,8)) - os.execute("goproxy exec?cmd=docker+start+" .. containerID) - end - else - LOG("WARNING: no docker container ID attached to this lever") - end - end - - -- stone button - if BlockType == 77 - then - containerID, running = getRemoveButtonContainer(BlockX,BlockZ) - - if running - then - Player:SendMessage("A running container can't be removed.") - else - Player:SendMessage("docker rm " .. string.sub(containerID,1,8)) - os.execute("goproxy exec?cmd=docker+rm+" .. containerID) - end - end -end + local EntireCommand = table.concat(Split, "+") + -- remove '/' at the beginning + local command = string.sub(EntireCommand, 2, -1) + r = os.execute("dockercraft exec?world=" .. world .. "\\&cmd=" .. command) + LOG("executed: " .. command .. " -> " .. tostring(r)) + end + end + end + end -function OnChunkGenerating(World, ChunkX, ChunkZ, ChunkDesc) - -- override the built-in chunk generator - -- to have it generate empty chunks only - ChunkDesc:SetUseDefaultBiomes(false) - ChunkDesc:SetUseDefaultComposition(false) - ChunkDesc:SetUseDefaultFinish(false) - ChunkDesc:SetUseDefaultHeight(false) - return true + return true end +-- OnPlayerFoodLevelChange stops the player from getting hungry +function OnPlayerFoodLevelChange(Player, NewFoodLevel) + return true, Player, NewFoodLevel +end -function DockerCommand(Split, Player) - - if table.getn(Split) > 0 - then - - LOG("Split[1]: " .. Split[1]) - - if Split[1] == "/docker" - then - if table.getn(Split) > 1 - then - if Split[2] == "pull" or Split[2] == "create" or Split[2] == "run" or Split[2] == "stop" or Split[2] == "rm" or Split[2] == "rmi" or Split[2] == "start" or Split[2] == "kill" - then - -- force detach when running a container - if Split[2] == "run" - then - table.insert(Split,3,"-d") - end - - EntireCommand = table.concat(Split, "+") - -- remove '/' at the beginning - command = string.sub(EntireCommand, 2, -1) - - r = os.execute("goproxy exec?cmd=" .. command) - - LOG("executed: " .. command .. " -> " .. tostring(r)) - end - end - end - end - - return true +-- OnTakeDamage stops the player receiving falling or explosion damage +function OnTakeDamage(Receiver, TDI) + if Receiver:GetClass() == 'cPlayer' then + if TDI.DamageType == dtFall or TDI.DamageType == dtExplosion then + return true, Receiver, TDI + end + end + return false, Receiver, TDI end +-- OnServerPing provides a nice favicon and description for Dockercraft +function OnServerPing(ClientHandle, ServerDescription, OnlinePlayers, MaxPlayers, Favicon) + -- Change Server Description + local ServerDescription = "A Docker client for Minecraft" + -- Change favicon + if cFile:IsFile("/srv/logo.png") then + local FaviconData = cFile:ReadWholeFile("/srv/logo.png") + if (FaviconData ~= "") and (FaviconData ~= nil) then + Favicon = Base64Encode(FaviconData) + end + end + return false, ServerDescription, OnlinePlayers, MaxPlayers, Favicon +end +-- OnWeatherChanging makes it sunny all the time! +function OnWeatherChanging(World, Weather) + return true, wSunny +end +-- HandleRequest_Docker handles incoming events from the proxy.go function HandleRequest_Docker(Request) - - content = "[dockerclient]" - if Request.PostParams["action"] ~= nil then + local content = "[dockerclient]" - action = Request.PostParams["action"] + if Request.PostParams["action"] ~= nil then - -- receiving informations about one container - - if action == "containerInfos" - then - LOG("EVENT - containerInfos") + local action = Request.PostParams["action"] - name = Request.PostParams["name"] - imageRepo = Request.PostParams["imageRepo"] - imageTag = Request.PostParams["imageTag"] - id = Request.PostParams["id"] - running = Request.PostParams["running"] + -- receiving informations about one container - -- LOG("containerInfos running: " .. running) + if action == "containerInfos" then + LOG("EVENT - containerInfos") - state = CONTAINER_STOPPED - if running == "true" then - state = CONTAINER_RUNNING - end + local world = Request.PostParams["world"] + local name = Request.PostParams["name"] + local imageRepo = Request.PostParams["imageRepo"] + local imageTag = Request.PostParams["imageTag"] + local id = Request.PostParams["id"] + local running = Request.PostParams["running"] - updateContainer(id,name,imageRepo,imageTag,state) - end + -- LOG("containerInfos running: " .. running) - if action == "startContainer" - then - LOG("EVENT - startContainer") + local state = CONTAINER_STOPPED + if running == "true" then + state = CONTAINER_RUNNING + end - name = Request.PostParams["name"] - imageRepo = Request.PostParams["imageRepo"] - imageTag = Request.PostParams["imageTag"] - id = Request.PostParams["id"] + Worlds[world]:updateContainer(id,name,imageRepo,imageTag,state) + end - updateContainer(id,name,imageRepo,imageTag,CONTAINER_RUNNING) - end + if action == "startContainer" then + LOG("EVENT - startContainer") - if action == "createContainer" - then - LOG("EVENT - createContainer") + local world = Request.PostParams["world"] + local name = Request.PostParams["name"] + local imageRepo = Request.PostParams["imageRepo"] + local imageTag = Request.PostParams["imageTag"] + local id = Request.PostParams["id"] - name = Request.PostParams["name"] - imageRepo = Request.PostParams["imageRepo"] - imageTag = Request.PostParams["imageTag"] - id = Request.PostParams["id"] + Worlds[world]:updateContainer(id,name,imageRepo,imageTag,CONTAINER_RUNNING) + end - updateContainer(id,name,imageRepo,imageTag,CONTAINER_CREATED) - end + if action == "createContainer" then + LOG("EVENT - createContainer") - if action == "stopContainer" - then - LOG("EVENT - stopContainer") + local world = Request.PostParams["world"] + local name = Request.PostParams["name"] + local imageRepo = Request.PostParams["imageRepo"] + local imageTag = Request.PostParams["imageTag"] + local id = Request.PostParams["id"] - name = Request.PostParams["name"] - imageRepo = Request.PostParams["imageRepo"] - imageTag = Request.PostParams["imageTag"] - id = Request.PostParams["id"] + Worlds[world]:updateContainer(id,name,imageRepo,imageTag,CONTAINER_CREATED) + end - updateContainer(id,name,imageRepo,imageTag,CONTAINER_STOPPED) - end + if action == "stopContainer" then + LOG("EVENT - stopContainer") - if action == "destroyContainer" - then - LOG("EVENT - destroyContainer") - id = Request.PostParams["id"] - destroyContainer(id) - end + local world = Request.PostParams["world"] + local name = Request.PostParams["name"] + local imageRepo = Request.PostParams["imageRepo"] + local imageTag = Request.PostParams["imageTag"] + local id = Request.PostParams["id"] - if action == "stats" - then - id = Request.PostParams["id"] - cpu = Request.PostParams["cpu"] - ram = Request.PostParams["ram"] + Worlds[world]:updateContainer(id,name,imageRepo,imageTag,CONTAINER_STOPPED) + end - updateStats(id,ram,cpu) - end + if action == "destroyContainer" then + LOG("EVENT - destroyContainer") + local world = Request.PostParams["world"] + local id = Request.PostParams["id"] + Worlds[world]:destroyContainer(id) + end - content = content .. "{action:\"" .. action .. "\"}" + if action == "stats" then + local world = Request.PostParams["world"] + local id = Request.PostParams["id"] + local cpu = Request.PostParams["cpu"] + local ram = Request.PostParams["ram"] - else - content = content .. "{error:\"action requested\"}" + Worlds[world]:updateStats(id,ram,cpu) + end - end - content = content .. "[/dockerclient]" + content = content .. "{action:\"" .. action .. "\"}" - return content -end + else + content = content .. "{error:\"action requested\"}" -function OnPlayerFoodLevelChange(Player, NewFoodLevel) - -- Don't allow the player to get hungry - return true, Player, NewFoodLevel -end + end -function OnTakeDamage(Receiver, TDI) - -- Don't allow the player to take falling or explosion damage - if Receiver:GetClass() == 'cPlayer' - then - if TDI.DamageType == dtFall or TDI.DamageType == dtExplosion then - return true, Receiver, TDI - end - end - return false, Receiver, TDI -end + content = content .. "[/dockerclient]" -function OnServerPing(ClientHandle, ServerDescription, OnlinePlayers, MaxPlayers, Favicon) - -- Change Server Description - ServerDescription = "A Docker client for Minecraft" - -- Change favicon - if cFile:IsFile("/srv/logo.png") then - local FaviconData = cFile:ReadWholeFile("/srv/logo.png") - if (FaviconData ~= "") and (FaviconData ~= nil) then - Favicon = Base64Encode(FaviconData) - end - end - return false, ServerDescription, OnlinePlayers, MaxPlayers, Favicon -end - --- Make it sunny all the time! -function OnWeatherChanging(World, Weather) - return true, wSunny + return content end diff --git a/world/Plugins/Docker/log.lua b/world/Plugins/Docker/log.lua index 788d387d..035e2129 100644 --- a/world/Plugins/Docker/log.lua +++ b/world/Plugins/Docker/log.lua @@ -1,4 +1,3 @@ - -- Print contents of `tbl`, with indentation. -- `indent` sets the initial level of indentation. function logTable (tbl, indent) @@ -16,4 +15,4 @@ function logTable (tbl, indent) print(formatting .. v) end end -end \ No newline at end of file +end diff --git a/world/Plugins/Docker/update.lua b/world/Plugins/Docker/update.lua index dc6c71ab..13dd0243 100644 --- a/world/Plugins/Docker/update.lua +++ b/world/Plugins/Docker/update.lua @@ -11,112 +11,101 @@ UPDATE_SET = 0 UPDATE_DIG = 1 UPDATE_SIGN = 2 -function NewUpdateQueue() - queue = {first=nil, last=nil, current=nil} - - -- queue.newUpdate creates an update operation and - -- inserts it in the queue or returns an error - -- in case of UPDATE_SIGN, line 1 to 4 should - -- be present in meta parameter. - -- the delay is optional and will make sure that - -- the update is not triggered before given amount - -- of ticks (0 by default) - function queue:newUpdate(updateType, x, y, z, blockID, meta, delay) - if updateType ~= UPDATE_SET and updateType ~= UPDATE_DIG and updateType ~= UPDATE_SIGN - then - return NewError(1,"NewUpdate: wrong update type") - end - - if delay == nil - then - delay = 0 - end +function NewUpdateQueue(world) + q = { + first=nil, + last=nil, + current=nil, + world=world, + newUpdate=Queue.newUpdate, + update=Queue.update + } + return q +end - update = {op=updateType,next=nil,x=x,y=y,z=z,blockID=blockID,meta=meta, delay=delay} +Queue = {first=nil,last=nil,current=nil,world=nil} - -- update.exec executes update operation - -- and returns an error if it fails - function update:exec() - if self.op == UPDATE_SET - then - cRoot:Get():GetDefaultWorld():SetBlock(self.x,self.y,self.z,self.blockID,self.meta) - elseif self.op == UPDATE_DIG - then - cRoot:Get():GetDefaultWorld():DigBlock(self.x,self.y,self.z) - elseif self.op == UPDATE_SIGN - then - cRoot:Get():GetDefaultWorld():SetSignLines(self.x,self.y,self.z,self.meta.line1,self.meta.line2,self.meta.line3,self.meta.line4) - else - return NewError(1,"update:exec: unknown update type: " .. tostring(self.op)) - end - end +-- Queue.newUpdate creates an update operation and +-- inserts it in the queue or returns an error +-- in case of UPDATE_SIGN, line 1 to 4 should +-- be present in meta parameter. +-- the delay is optional and will make sure that +-- the update is not triggered before given amount +-- of ticks (0 by default) +function Queue:newUpdate(updateType, x, y, z, blockID, meta, delay) + if updateType ~= UPDATE_SET and updateType ~= UPDATE_DIG and updateType ~= UPDATE_SIGN + then + return NewError(1,"NewUpdate: wrong update type") + end - if self.first == nil - then - self.first = update - self.last = update - else - self.last.next = update - self.last = update - end - end -- ends queue.newUpdate + if delay == nil + then + delay = 0 + end + + update = {op=updateType,next=nil,world=self.world,x=x,y=y,z=z,blockID=blockID,meta=meta,delay=delay} - -- update triggers updates starting from - -- the first one. It stops when the limit - -- is reached, of if there are no more - -- operations in the queue. It returns - -- the amount of updates executed. - -- When an update has a delay > 0, the delay - -- is decremented, and the number of updates - -- executed is not incremented. - function queue:update(limit) - n = 0 - self.current = self.first + -- update.exec executes update operation + -- and returns an error if it fails + function update:exec() + w = self.world:getWorld() + if w == nil + then + return NewError(1, "NewUpdate: can't get world object") + end - while n < limit and self.current ~= nil - do - if self.current.delay == 0 - then - err = self.current:exec() - if err ~= nil - then - LOG("queue:update error: " .. err.message) - break - end - - if self.current == self.first - then - self.first = self.current.next - end - n = n + 1 - else - self.current.delay = self.current.delay - 1 - end - self.current = self.current.next - end + if self.op == UPDATE_SET + then + w:SetBlock(self.x,self.y,self.z,self.blockID,self.meta) + elseif self.op == UPDATE_DIG + then + w:DigBlock(self.x,self.y,self.z) + elseif self.op == UPDATE_SIGN + then + w:SetSignLines(self.x,self.y,self.z,self.meta.line1,self.meta.line2,self.meta.line3,self.meta.line4) + else + return NewError(1,"update:exec: unknown update type: " .. tostring(self.op)) + end + end -- ends update.exec - return n + if self.first == nil then + self.first = update + self.last = update + else + self.last.next = update + self.last = update end +end -- ends queue.newUpdate - return queue -end +-- Queue:update triggers updates starting from +-- the first one. It stops when the limit +-- is reached, of if there are no more +-- operations in the queue. It returns +-- the amount of updates executed. +-- When an update has a delay > 0, the delay +-- is decremented, and the number of updates +-- executed is not incremented. +function Queue:update(limit) + n = 0 + self.current = self.first --- setBlock adds an update in given queue to --- set a block at x,y,z coordinates -function setBlock(queue,x,y,z,blockID,meta) - queue:newUpdate(UPDATE_SET, x, y, z, blockID, meta) -end + while n < limit and self.current ~= nil + do + if self.current.delay == 0 then + err = self.current:exec() + if err ~= nil then + LOG("queue:update error: " .. err.message) + break + end --- setBlock adds an update in given queue to --- remove a block at x,y,z coordinates -function digBlock(queue,x,y,z) - queue:newUpdate(UPDATE_DIG, x, y, z) + if self.current == self.first then + self.first = self.current.next + end + n = n + 1 + else + self.current.delay = self.current.delay - 1 + end + self.current = self.current.next + end + return n end - --- setBlock adds an update in given queue to --- update a sign at x,y,z coordinates with --- 4 lines of text -function updateSign(queue,x,y,z,line1,line2,line3,line4,delay) - meta = {line1=line1,line2=line2,line3=line3,line4=line4} - queue:newUpdate(UPDATE_SIGN, x, y, z, nil, meta, delay) -end \ No newline at end of file diff --git a/world/Plugins/Docker/world.lua b/world/Plugins/Docker/world.lua new file mode 100644 index 00000000..6be94bbd --- /dev/null +++ b/world/Plugins/Docker/world.lua @@ -0,0 +1,183 @@ +function NewWorld(name) + w = { + -- name of the world + Name = name, + -- array of container objects + containers = {}, + -- + signsToUpdate = {}, + -- as a lua array cannot contain nil values, we store references to this object + -- in the "Containers" array to indicate that there is no container at an index + emptyContainerSpace = {}, + -- queue containing the updates that need to be applied to the minecraft world + -- groundMinX + groundMinX = GROUND_MIN_X, + updateQueue = nil, + getWorld = World.getWorld, + setBlock = World.setBlock, + digBlock = World.digBlock, + updateSign = World.updateSign, + updateStats = World.updateStats, + getStartStopLeverContainer = World.getStartStopLeverContainer, + getRemoveButtonContainer = World.getRemoveButtonContainer, + destroyContainer = World.destroyContainer, + updateContainer = World.updateContainer + } + w.updateQueue = NewUpdateQueue(w) + return w +end + +World = {Name="",containers={},signsToUpdate={},emptyContainerSpace={},groundMinX=GROUND_MIN_X,updateQueue=nil} + +-- getWorld returns the cWorld object for this world +function World:getWorld() + return cRoot:Get():GetWorld(self.Name) +end + +-- setBlock adds an update in given queue to +-- set a block at x,y,z coordinates +function World:setBlock(x,y,z,blockID,meta) + self.updateQueue:newUpdate(UPDATE_SET, x, y, z, blockID, meta) +end + +-- digBlock adds an update in given queue to +-- remove a block at x,y,z coordinates +function World:digBlock(x,y,z) + self.updateQueue:newUpdate(UPDATE_DIG, x, y, z) +end + +-- updateSign adds an update in given queue to +-- update a sign at x,y,z coordinates with +-- 4 lines of text +function World:updateSign(x,y,z,line1,line2,line3,line4,delay) + meta = {line1=line1,line2=line2,line3=line3,line4=line4} + self.updateQueue:newUpdate(UPDATE_SIGN, x, y, z, nil, meta, delay) +end + +-- updateStats update CPU and memory usage displayed +-- on container sign (container identified by id) +function World:updateStats(id, mem, cpu) + for i=1, table.getn(self.containers) + do + if self.containers[i] ~= self.emptyContainerSpace and self.containers[i].id == id + then + self.containers[i]:updateMemSign(mem) + self.containers[i]:updateCPUSign(cpu) + break + end + end +end + +-- getStartStopLeverContainer returns the container +-- id that corresponds to lever at x,y coordinates +function World:getStartStopLeverContainer(x, z) + for i=1, table.getn(self.containers) + do + if self.containers[i] ~= self.emptyContainerSpace and x == self.containers[i].x + 1 and z == self.containers[i].z + 1 + then + return self.containers[i].id + end + end + return "" +end + +-- getRemoveButtonContainer returns the container +-- id and state for the button at x,y coordinates +function World:getRemoveButtonContainer(x, z) + for i=1, table.getn(self.containers) + do + if self.containers[i] ~= self.emptyContainerSpace and x == self.containers[i].x + 2 and z == self.containers[i].z + 3 + then + return self.containers[i].id, self.containers[i].running + end + end + return "", true +end + +-- destroyContainer looks for the first container having the given id, +-- removes it from the Minecraft world and from the 'Containers' array +function World:destroyContainer(id) + LOG("destroyContainer: " .. id) + -- loop over the containers and remove the first having the given id + for i=1, table.getn(self.containers) + do + if self.containers[i] ~= self.emptyContainerSpace and self.containers[i].id == id + then + -- remove the container from the self + self.containers[i]:destroy() + -- if the container being removed is the last element of the array + -- we reduce the size of the "Container" array, but if it is not, + -- we store a reference to the "EmptyContainerSpace" object at the + -- same index to indicate this is a free space now. + -- We use a reference to this object because it is not possible to + -- have 'nil' values in the middle of a lua array. + if i == table.getn(self.containers) + then + table.remove(self.containers, i) + -- we have removed the last element of the array. If the array + -- has tailing empty container spaces, we remove them as well. + while self.containers[table.getn(self.containers)] == self.emptyContainerSpace + do + + table.remove(self.containers, table.getn(self.containers)) + end + else + self.containers[i] = self.emptyContainerSpace + end + -- we removed the container, we can exit the loop + break + end + end +end + +-- updateContainer accepts 3 different states: running, stopped, created +-- sometimes "start" events arrive before "create" ones +-- in this case, we just ignore the update +function World:updateContainer(id,name,imageRepo,imageTag,state) + LOG("Update container with ID: " .. id .. "in world: " .. self.Name .. " state: " .. state) + + -- first pass, to see if the container is + -- already displayed (maybe with another state) + for i=1, table.getn(self.containers) + do + -- if container found with same ID, we update it + if self.containers[i] ~= self.emptyContainerSpace and self.containers[i].id == id + then + self.containers[i]:setInfos(self,id,name,imageRepo,imageTag,state == CONTAINER_RUNNING) + self.containers[i]:display(state == CONTAINER_RUNNING) + LOG("found. updated. now return") + return + end + end + + -- if container isn't already displayed, we see if there's an empty space + -- in the self to display the container + x = CONTAINER_START_X + index = -1 + + for i=1, table.getn(self.containers) + do + -- use first empty location + if self.containers[i] == self.emptyContainerSpace + then + LOG("Found empty location: Containers[" .. tostring(i) .. "]") + index = i + break + end + x = x + CONTAINER_OFFSET_X + end + + container = NewContainer() + container:init(x,CONTAINER_START_Z) + container:setInfos(self,id,name,imageRepo,imageTag,state == CONTAINER_RUNNING) + container:addGround() + container:display(state == CONTAINER_RUNNING) + + if index == -1 + then + table.insert(self.containers, container) + else + self.containers[index] = container + end +end + diff --git a/world/settings.ini b/world/settings.ini.tmpl similarity index 90% rename from world/settings.ini rename to world/settings.ini.tmpl index 6fb99613..22b823f8 100644 --- a/world/settings.ini +++ b/world/settings.ini.tmpl @@ -1,49 +1,52 @@ -; This is the main server configuration -; Most of the settings here can be configured using the webadmin interface, if enabled in webadmin.ini -; See: http://wiki.mc-server.org/doku.php?id=configure:settings.ini for further configuration help - -[Authentication] -Authenticate=0 -AllowBungeeCord=0 -Server=sessionserver.mojang.com -Address=/session/minecraft/hasJoined?username=%USERNAME%&serverId=%SERVERID% - -[MojangAPI] -NameToUUIDServer=api.mojang.com -NameToUUIDAddress=/profiles/minecraft -UUIDToProfileServer=sessionserver.mojang.com -UUIDToProfileAddress=/session/minecraft/profile/%UUID%?unsigned=false - -[Server] -Description=MCServer - in C++! -MaxPlayers=100 -HardcoreEnabled=0 -AllowMultiLogin=0 -Ports=25565 -DefaultViewDistance=10 - -[RCON] -Enabled=0 - -[PlayerData] -LoadOfflinePlayerData=0 -LoadNamedPlayerData=1 - -[Worlds] -DefaultWorld=world - -[Plugins] -; Plugin=Debuggers -; Plugin=HookNotify -; Plugin=ChunkWorx -; Plugin=APIDump -Plugin=Core -Plugin=TransAPI -Plugin=ChatLog -Plugin=Docker - - -[DeadlockDetect] -Enabled=1 -IntervalSec=20 - +; This is the main server configuration +; Most of the settings here can be configured using the webadmin interface, if enabled in webadmin.ini +; See: http://wiki.mc-server.org/doku.php?id=configure:settings.ini for further configuration help + +[Authentication] +Authenticate=0 +AllowBungeeCord=0 +Server=sessionserver.mojang.com +Address=/session/minecraft/hasJoined?username=%USERNAME%&serverId=%SERVERID% + +[MojangAPI] +NameToUUIDServer=api.mojang.com +NameToUUIDAddress=/profiles/minecraft +UUIDToProfileServer=sessionserver.mojang.com +UUIDToProfileAddress=/session/minecraft/profile/%UUID%?unsigned=false + +[Server] +Description=DockerCraft! +MaxPlayers=100 +HardcoreEnabled=0 +AllowMultiLogin=0 +Ports=25565 +DefaultViewDistance=10 + +[RCON] +Enabled=0 + +[PlayerData] +LoadOfflinePlayerData=0 +LoadNamedPlayerData=1 + +[Worlds] +DefaultWorld={{.DefaultWorld}} +{{ range .Worlds }} +World={{.}} +{{end}} + +[Plugins] +; Plugin=Debuggers +; Plugin=HookNotify +; Plugin=ChunkWorx +; Plugin=APIDump +Plugin=Core +Plugin=TransAPI +Plugin=ChatLog +Plugin=Docker + + +[DeadlockDetect] +Enabled=1 +IntervalSec=20 + diff --git a/world/world/level.dat b/world/world_template/level.dat similarity index 100% rename from world/world/level.dat rename to world/world_template/level.dat diff --git a/world/world/region/r.-1.-1.mca b/world/world_template/region/r.-1.-1.mca similarity index 100% rename from world/world/region/r.-1.-1.mca rename to world/world_template/region/r.-1.-1.mca diff --git a/world/world/region/r.-1.-1.mcr b/world/world_template/region/r.-1.-1.mcr similarity index 100% rename from world/world/region/r.-1.-1.mcr rename to world/world_template/region/r.-1.-1.mcr diff --git a/world/world/region/r.-1.-2.mca b/world/world_template/region/r.-1.-2.mca similarity index 100% rename from world/world/region/r.-1.-2.mca rename to world/world_template/region/r.-1.-2.mca diff --git a/world/world/region/r.-1.-2.mcr b/world/world_template/region/r.-1.-2.mcr similarity index 100% rename from world/world/region/r.-1.-2.mcr rename to world/world_template/region/r.-1.-2.mcr diff --git a/world/world/region/r.-1.0.mca b/world/world_template/region/r.-1.0.mca similarity index 100% rename from world/world/region/r.-1.0.mca rename to world/world_template/region/r.-1.0.mca diff --git a/world/world/region/r.-1.0.mcr b/world/world_template/region/r.-1.0.mcr similarity index 100% rename from world/world/region/r.-1.0.mcr rename to world/world_template/region/r.-1.0.mcr diff --git a/world/world/region/r.-1.1.mca b/world/world_template/region/r.-1.1.mca similarity index 100% rename from world/world/region/r.-1.1.mca rename to world/world_template/region/r.-1.1.mca diff --git a/world/world/region/r.-1.1.mcr b/world/world_template/region/r.-1.1.mcr similarity index 100% rename from world/world/region/r.-1.1.mcr rename to world/world_template/region/r.-1.1.mcr diff --git a/world/world/region/r.0.-1.mca b/world/world_template/region/r.0.-1.mca similarity index 100% rename from world/world/region/r.0.-1.mca rename to world/world_template/region/r.0.-1.mca diff --git a/world/world/region/r.0.-1.mcr b/world/world_template/region/r.0.-1.mcr similarity index 100% rename from world/world/region/r.0.-1.mcr rename to world/world_template/region/r.0.-1.mcr diff --git a/world/world/region/r.0.-2.mca b/world/world_template/region/r.0.-2.mca similarity index 100% rename from world/world/region/r.0.-2.mca rename to world/world_template/region/r.0.-2.mca diff --git a/world/world/region/r.0.-2.mcr b/world/world_template/region/r.0.-2.mcr similarity index 100% rename from world/world/region/r.0.-2.mcr rename to world/world_template/region/r.0.-2.mcr diff --git a/world/world/region/r.0.0.mca b/world/world_template/region/r.0.0.mca similarity index 100% rename from world/world/region/r.0.0.mca rename to world/world_template/region/r.0.0.mca diff --git a/world/world/region/r.0.0.mcr b/world/world_template/region/r.0.0.mcr similarity index 100% rename from world/world/region/r.0.0.mcr rename to world/world_template/region/r.0.0.mcr diff --git a/world/world/region/r.0.1.mca b/world/world_template/region/r.0.1.mca similarity index 100% rename from world/world/region/r.0.1.mca rename to world/world_template/region/r.0.1.mca diff --git a/world/world/region/r.0.1.mcr b/world/world_template/region/r.0.1.mcr similarity index 100% rename from world/world/region/r.0.1.mcr rename to world/world_template/region/r.0.1.mcr diff --git a/world/world/region/r.1.-1.mca b/world/world_template/region/r.1.-1.mca similarity index 100% rename from world/world/region/r.1.-1.mca rename to world/world_template/region/r.1.-1.mca diff --git a/world/world/region/r.1.-1.mcr b/world/world_template/region/r.1.-1.mcr similarity index 100% rename from world/world/region/r.1.-1.mcr rename to world/world_template/region/r.1.-1.mcr diff --git a/world/world/region/r.1.-2.mca b/world/world_template/region/r.1.-2.mca similarity index 100% rename from world/world/region/r.1.-2.mca rename to world/world_template/region/r.1.-2.mca diff --git a/world/world/region/r.1.-2.mcr b/world/world_template/region/r.1.-2.mcr similarity index 100% rename from world/world/region/r.1.-2.mcr rename to world/world_template/region/r.1.-2.mcr diff --git a/world/world/region/r.1.0.mca b/world/world_template/region/r.1.0.mca similarity index 100% rename from world/world/region/r.1.0.mca rename to world/world_template/region/r.1.0.mca diff --git a/world/world/region/r.1.0.mcr b/world/world_template/region/r.1.0.mcr similarity index 100% rename from world/world/region/r.1.0.mcr rename to world/world_template/region/r.1.0.mcr diff --git a/world/world/region/r.1.1.mca b/world/world_template/region/r.1.1.mca similarity index 100% rename from world/world/region/r.1.1.mca rename to world/world_template/region/r.1.1.mca diff --git a/world/world/region/r.1.1.mcr b/world/world_template/region/r.1.1.mcr similarity index 100% rename from world/world/region/r.1.1.mcr rename to world/world_template/region/r.1.1.mcr diff --git a/world/world/world.ini b/world/world_template/world.ini similarity index 94% rename from world/world/world.ini rename to world/world_template/world.ini index 2d3a1c0e..3adc7f5a 100644 --- a/world/world/world.ini +++ b/world/world_template/world.ini @@ -1,162 +1,158 @@ -; This is the per-world configuration file, managing settings such as generators, simulators, and spawn points - -[General] -Dimension=Overworld -IsDaylightCycleEnabled=0 -Gamemode=2 -Weather=0 -TimeInTicks=0 - -[Broadcasting] -BroadcastDeathMessages=1 -BroadcastAchievementMessages=1 - -[SpawnPosition] -MaxViewDistance=12 -X=0.000000 -Y=65.0 -Z=0.000000 -PregenerateDistance=20 - -[Storage] -Schema=Default -CompressionFactor=6 - -[Plants] -MaxCactusHeight=3 -MaxSugarcaneHeight=3 -IsCactusBonemealable=0 -IsCarrotsBonemealable=1 -IsCropsBonemealable=1 -IsGrassBonemealable=1 -IsMelonStemBonemealable=1 -IsMelonBonemealable=0 -IsPotatoesBonemealable=1 -IsPumpkinStemBonemealable=1 -IsPumpkinBonemealable=0 -IsSaplingBonemealable=1 -IsSugarcaneBonemealable=0 - -[Physics] -DeepSnow=1 -ShouldLavaSpawnFire=1 -TNTShrapnelLevel=2 -WaterSimulator=Vanilla -LavaSimulator=Vanilla -SandInstantFall=0 -RedstoneSimulator=Incremental - -[Mechanics] -CommandBlocksEnabled=0 -PVPEnabled=1 -UseChatPrefixes=1 - -[Monsters] -VillagersShouldHarvestCrops=1 -AnimalsOn=0 -Types=bat, cavespider, chicken, cow, creeper, enderman, guardian, horse, mooshroom, ocelot, pig, rabbit, sheep, silverfish, skeleton, slime, spider, squid, wolf, zombie - -[LinkedWorlds] -NetherWorldName=world_nether -EndWorldName=world_end - -[Generator] -Generator=Composable -BiomeGen=Grown -ShapeGen=BiomalNoise3D -CompositionGen=Biomal -Finishers=RoughRavines, WormNestCaves, WaterLakes, WaterSprings, LavaLakes, LavaSprings, OreNests, Mineshafts, Trees, Villages, TallGrass, SprinkleFoliage, Ice, Snow, Lilypads, BottomLava, DeadBushes, NaturalPatches, PreSimulator, Animals -BiomeGenCacheSize=64 -BiomeGenMultiCacheLength=4 -SeaLevel=62 -BiomalNoise3DFrequencyX=40.000000 -BiomalNoise3DFrequencyY=40.000000 -BiomalNoise3DFrequencyZ=40.000000 -BiomalNoise3DBaseFrequencyX=40.000000 -BiomalNoise3DBaseFrequencyZ=40.000000 -BiomalNoise3DChoiceFrequencyX=40.000000 -BiomalNoise3DChoiceFrequencyY=80.000000 -BiomalNoise3DChoiceFrequencyZ=40.000000 -BiomalNoise3DAirThreshold=0.000000 -BiomalNoise3DNumChoiceOctaves=4 -BiomalNoise3DNumDensityOctaves=6 -BiomalNoise3DNumBaseOctaves=6 -BiomalNoise3DBaseAmplitude=1.000000 -CompositionGenCacheSize=64 -RoughRavinesGridSize=256 -RoughRavinesMaxOffset=128 -RoughRavinesMaxSize=128 -RoughRavinesMinSize=64 -RoughRavinesMaxCenterWidth=8.000000 -RoughRavinesMinCenterWidth=2.000000 -RoughRavinesMaxRoughness=0.200000 -RoughRavinesMinRoughness=0.050000 -RoughRavinesMaxFloorHeightEdge=8.000000 -RoughRavinesMinFloorHeightEdge=30.000000 -RoughRavinesMaxFloorHeightCenter=20.000000 -RoughRavinesMinFloorHeightCenter=6.000000 -RoughRavinesMaxCeilingHeightEdge=56.000000 -RoughRavinesMinCeilingHeightEdge=38.000000 -RoughRavinesMaxCeilingHeightCenter=58.000000 -RoughRavinesMinCeilingHeightCenter=36.000000 -WormNestCavesSize=64 -WormNestCavesGrid=96 -WormNestMaxOffset=32 -WaterLakesProbability=25 -LavaLakesProbability=10 -MineShaftsGridSize=512 -MineShaftsMaxOffset=256 -MineShaftsMaxSystemSize=160 -MineShaftsChanceCorridor=600 -MineShaftsChanceCrossing=200 -MineShaftsChanceStaircase=200 -VillageGridSize=384 -VillageMaxOffset=128 -VillageMaxDepth=2 -VillageMaxSize=128 -VillageMinDensity=50 -VillageMaxDensity=80 -BottomLavaLevel=10 -PreSimulatorFallingBlocks=1 -PreSimulatorWater=1 -PreSimulatorLava=1 - -[WaterSimulator] -Falloff=1 -TickDelay=5 -NumNeighborsForSource=2 - -[LavaSimulator] -Falloff=2 -TickDelay=30 -NumNeighborsForSource=-1 - -[FireSimulator] -BurnStepTimeFuel=500 -BurnStepTimeNonfuel=100 -Flammability=50 -ReplaceFuelChance=50000 - -[Seed] -Seed=1535741295 - -[WaterSprings] -HeightDistribution=0, 0; 10, 10; 11, 75; 16, 83; 20, 83; 24, 78; 32, 62; 40, 40; 44, 15; 48, 7; 56, 2; 64, 1; 255, 0 -Chance=24 - -[LavaSprings] -HeightDistribution=0, 0; 10, 5; 11, 45; 48, 2; 64, 1; 255, 0 -Chance=9 - -[Animals] -AnimalSpawnChunkPercentage=0 - -[SpawnProtect] -ProtectRadius=10 - -[WorldLimit] -LimitRadius=0 - -[Difficulty] -WorldDifficulty=1 - +; This is the per-world configuration file, managing settings such as generators, simulators, and spawn points + +[General] +Dimension=Overworld +IsDaylightCycleEnabled=0 +Gamemode=2 +Weather=0 +TimeInTicks=0 + +[Broadcasting] +BroadcastDeathMessages=1 +BroadcastAchievementMessages=1 + +[SpawnPosition] +MaxViewDistance=12 +X=0.000000 +Y=65.0 +Z=0.000000 +PregenerateDistance=20 + +[Storage] +Schema=Default +CompressionFactor=6 + +[Plants] +MaxCactusHeight=3 +MaxSugarcaneHeight=3 +IsCactusBonemealable=0 +IsCarrotsBonemealable=1 +IsCropsBonemealable=1 +IsGrassBonemealable=1 +IsMelonStemBonemealable=1 +IsMelonBonemealable=0 +IsPotatoesBonemealable=1 +IsPumpkinStemBonemealable=1 +IsPumpkinBonemealable=0 +IsSaplingBonemealable=1 +IsSugarcaneBonemealable=0 + +[Physics] +DeepSnow=1 +ShouldLavaSpawnFire=1 +TNTShrapnelLevel=2 +WaterSimulator=Vanilla +LavaSimulator=Vanilla +SandInstantFall=0 +RedstoneSimulator=Incremental + +[Mechanics] +CommandBlocksEnabled=0 +PVPEnabled=1 +UseChatPrefixes=1 + +[Monsters] +VillagersShouldHarvestCrops=1 +AnimalsOn=0 +Types=bat, cavespider, chicken, cow, creeper, enderman, guardian, horse, mooshroom, ocelot, pig, rabbit, sheep, silverfish, skeleton, slime, spider, squid, wolf, zombie + +[Generator] +Generator=Composable +BiomeGen=Grown +ShapeGen=BiomalNoise3D +CompositionGen=Biomal +Finishers=RoughRavines, WormNestCaves, WaterLakes, WaterSprings, LavaLakes, LavaSprings, OreNests, Mineshafts, Trees, Villages, TallGrass, SprinkleFoliage, Ice, Snow, Lilypads, BottomLava, DeadBushes, NaturalPatches, PreSimulator, Animals +BiomeGenCacheSize=64 +BiomeGenMultiCacheLength=4 +SeaLevel=62 +BiomalNoise3DFrequencyX=40.000000 +BiomalNoise3DFrequencyY=40.000000 +BiomalNoise3DFrequencyZ=40.000000 +BiomalNoise3DBaseFrequencyX=40.000000 +BiomalNoise3DBaseFrequencyZ=40.000000 +BiomalNoise3DChoiceFrequencyX=40.000000 +BiomalNoise3DChoiceFrequencyY=80.000000 +BiomalNoise3DChoiceFrequencyZ=40.000000 +BiomalNoise3DAirThreshold=0.000000 +BiomalNoise3DNumChoiceOctaves=4 +BiomalNoise3DNumDensityOctaves=6 +BiomalNoise3DNumBaseOctaves=6 +BiomalNoise3DBaseAmplitude=1.000000 +CompositionGenCacheSize=64 +RoughRavinesGridSize=256 +RoughRavinesMaxOffset=128 +RoughRavinesMaxSize=128 +RoughRavinesMinSize=64 +RoughRavinesMaxCenterWidth=8.000000 +RoughRavinesMinCenterWidth=2.000000 +RoughRavinesMaxRoughness=0.200000 +RoughRavinesMinRoughness=0.050000 +RoughRavinesMaxFloorHeightEdge=8.000000 +RoughRavinesMinFloorHeightEdge=30.000000 +RoughRavinesMaxFloorHeightCenter=20.000000 +RoughRavinesMinFloorHeightCenter=6.000000 +RoughRavinesMaxCeilingHeightEdge=56.000000 +RoughRavinesMinCeilingHeightEdge=38.000000 +RoughRavinesMaxCeilingHeightCenter=58.000000 +RoughRavinesMinCeilingHeightCenter=36.000000 +WormNestCavesSize=64 +WormNestCavesGrid=96 +WormNestMaxOffset=32 +WaterLakesProbability=25 +LavaLakesProbability=10 +MineShaftsGridSize=512 +MineShaftsMaxOffset=256 +MineShaftsMaxSystemSize=160 +MineShaftsChanceCorridor=600 +MineShaftsChanceCrossing=200 +MineShaftsChanceStaircase=200 +VillageGridSize=384 +VillageMaxOffset=128 +VillageMaxDepth=2 +VillageMaxSize=128 +VillageMinDensity=50 +VillageMaxDensity=80 +BottomLavaLevel=10 +PreSimulatorFallingBlocks=1 +PreSimulatorWater=1 +PreSimulatorLava=1 + +[WaterSimulator] +Falloff=1 +TickDelay=5 +NumNeighborsForSource=2 + +[LavaSimulator] +Falloff=2 +TickDelay=30 +NumNeighborsForSource=-1 + +[FireSimulator] +BurnStepTimeFuel=500 +BurnStepTimeNonfuel=100 +Flammability=50 +ReplaceFuelChance=50000 + +[Seed] +Seed=1535741295 + +[WaterSprings] +HeightDistribution=0, 0; 10, 10; 11, 75; 16, 83; 20, 83; 24, 78; 32, 62; 40, 40; 44, 15; 48, 7; 56, 2; 64, 1; 255, 0 +Chance=24 + +[LavaSprings] +HeightDistribution=0, 0; 10, 5; 11, 45; 48, 2; 64, 1; 255, 0 +Chance=9 + +[Animals] +AnimalSpawnChunkPercentage=0 + +[SpawnProtect] +ProtectRadius=10 + +[WorldLimit] +LimitRadius=0 + +[Difficulty] +WorldDifficulty=1 +