Jelajahi Sumber

[ADD] odoo engine updated

Gogs 6 tahun lalu
melakukan
d87eff5553
100 mengubah file dengan 13300 tambahan dan 0 penghapusan
  1. 18 0
      .air.conf
  2. 70 0
      Gopkg.lock
  3. 58 0
      Gopkg.toml
  4. 27 0
      cmd/api.go
  5. 13 0
      cmd/cmd.go
  6. 51 0
      data/app.ini
  7. 42 0
      data/templates/openerp-server.tpl
  8. TEMPAT SAMPAH
      data/test.db
  9. 33 0
      main.go
  10. 109 0
      modules/docker/docker.go
  11. 5 0
      modules/init.go
  12. 12 0
      modules/models/issue.go
  13. 1 0
      modules/models/migrations/migrations.go
  14. 41 0
      modules/models/models.go
  15. 30 0
      modules/models/request.go
  16. 11 0
      modules/models/task.go
  17. 26 0
      modules/models/user.go
  18. 126 0
      modules/odoo/odoo.go
  19. 132 0
      modules/settings/settings.go
  20. 650 0
      tmp/server-errors.log
  21. 40 0
      vendor/github.com/flosch/pongo2/.gitignore
  22. 12 0
      vendor/github.com/flosch/pongo2/.travis.yml
  23. 10 0
      vendor/github.com/flosch/pongo2/AUTHORS
  24. 20 0
      vendor/github.com/flosch/pongo2/LICENSE
  25. 248 0
      vendor/github.com/flosch/pongo2/README.md
  26. 122 0
      vendor/github.com/flosch/pongo2/context.go
  27. 31 0
      vendor/github.com/flosch/pongo2/doc.go
  28. 86 0
      vendor/github.com/flosch/pongo2/error.go
  29. 133 0
      vendor/github.com/flosch/pongo2/filters.go
  30. 903 0
      vendor/github.com/flosch/pongo2/filters_builtin.go
  31. 15 0
      vendor/github.com/flosch/pongo2/helpers.go
  32. 421 0
      vendor/github.com/flosch/pongo2/lexer.go
  33. 20 0
      vendor/github.com/flosch/pongo2/nodes.go
  34. 14 0
      vendor/github.com/flosch/pongo2/nodes_html.go
  35. 20 0
      vendor/github.com/flosch/pongo2/nodes_wrapper.go
  36. 267 0
      vendor/github.com/flosch/pongo2/parser.go
  37. 54 0
      vendor/github.com/flosch/pongo2/parser_document.go
  38. 499 0
      vendor/github.com/flosch/pongo2/parser_expression.go
  39. 14 0
      vendor/github.com/flosch/pongo2/pongo2.go
  40. 132 0
      vendor/github.com/flosch/pongo2/tags.go
  41. 56 0
      vendor/github.com/flosch/pongo2/tags_autoescape.go
  42. 94 0
      vendor/github.com/flosch/pongo2/tags_block.go
  43. 31 0
      vendor/github.com/flosch/pongo2/tags_comment.go
  44. 110 0
      vendor/github.com/flosch/pongo2/tags_cycle.go
  45. 56 0
      vendor/github.com/flosch/pongo2/tags_extends.go
  46. 95 0
      vendor/github.com/flosch/pongo2/tags_filter.go
  47. 53 0
      vendor/github.com/flosch/pongo2/tags_firstof.go
  48. 158 0
      vendor/github.com/flosch/pongo2/tags_for.go
  49. 81 0
      vendor/github.com/flosch/pongo2/tags_if.go
  50. 117 0
      vendor/github.com/flosch/pongo2/tags_ifchanged.go
  51. 83 0
      vendor/github.com/flosch/pongo2/tags_ifequal.go
  52. 83 0
      vendor/github.com/flosch/pongo2/tags_ifnotequal.go
  53. 86 0
      vendor/github.com/flosch/pongo2/tags_import.go
  54. 132 0
      vendor/github.com/flosch/pongo2/tags_include.go
  55. 132 0
      vendor/github.com/flosch/pongo2/tags_lorem.go
  56. 149 0
      vendor/github.com/flosch/pongo2/tags_macro.go
  57. 51 0
      vendor/github.com/flosch/pongo2/tags_now.go
  58. 52 0
      vendor/github.com/flosch/pongo2/tags_set.go
  59. 54 0
      vendor/github.com/flosch/pongo2/tags_spaceless.go
  60. 69 0
      vendor/github.com/flosch/pongo2/tags_ssi.go
  61. 49 0
      vendor/github.com/flosch/pongo2/tags_templatetag.go
  62. 84 0
      vendor/github.com/flosch/pongo2/tags_widthratio.go
  63. 92 0
      vendor/github.com/flosch/pongo2/tags_with.go
  64. 164 0
      vendor/github.com/flosch/pongo2/template.go
  65. 296 0
      vendor/github.com/flosch/pongo2/template_sets.go
  66. 439 0
      vendor/github.com/flosch/pongo2/value.go
  67. 656 0
      vendor/github.com/flosch/pongo2/variable.go
  68. 2 0
      vendor/github.com/fsouza/go-dockerclient/.gitignore
  69. 25 0
      vendor/github.com/fsouza/go-dockerclient/.travis.yml
  70. 136 0
      vendor/github.com/fsouza/go-dockerclient/AUTHORS
  71. 6 0
      vendor/github.com/fsouza/go-dockerclient/DOCKER-LICENSE
  72. 22 0
      vendor/github.com/fsouza/go-dockerclient/LICENSE
  73. 57 0
      vendor/github.com/fsouza/go-dockerclient/Makefile
  74. 106 0
      vendor/github.com/fsouza/go-dockerclient/README.markdown
  75. 158 0
      vendor/github.com/fsouza/go-dockerclient/auth.go
  76. 17 0
      vendor/github.com/fsouza/go-dockerclient/cancelable.go
  77. 19 0
      vendor/github.com/fsouza/go-dockerclient/cancelable_go14.go
  78. 43 0
      vendor/github.com/fsouza/go-dockerclient/change.go
  79. 995 0
      vendor/github.com/fsouza/go-dockerclient/client.go
  80. 1325 0
      vendor/github.com/fsouza/go-dockerclient/container.go
  81. 168 0
      vendor/github.com/fsouza/go-dockerclient/env.go
  82. 379 0
      vendor/github.com/fsouza/go-dockerclient/event.go
  83. 202 0
      vendor/github.com/fsouza/go-dockerclient/exec.go
  84. 55 0
      vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/CHANGELOG.md
  85. 21 0
      vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/LICENSE
  86. 365 0
      vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/README.md
  87. 26 0
      vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/doc.go
  88. 264 0
      vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/entry.go
  89. 193 0
      vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/exported.go
  90. 48 0
      vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/formatter.go
  91. 34 0
      vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/hooks.go
  92. 41 0
      vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/json_formatter.go
  93. 212 0
      vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/logger.go
  94. 98 0
      vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/logrus.go
  95. 9 0
      vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/terminal_bsd.go
  96. 12 0
      vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/terminal_linux.go
  97. 21 0
      vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/terminal_notwindows.go
  98. 15 0
      vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/terminal_solaris.go
  99. 27 0
      vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/terminal_windows.go
  100. 161 0
      vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/text_formatter.go

+ 18 - 0
.air.conf

@@ -0,0 +1,18 @@
+root = "."
+# watch_dir = "runner"
+tmp_dir = "tmp"
+
+[build]
+bin = "tmp/automation api"
+cmd = "go build -o ./tmp/automation ./main.go"
+log = "server-errors.log"
+include_ext = ["go", "tpl", "tmpl", "html"]
+exclude_dir = ["assets", "vendor", "frontend/node_modules"]
+delay = 800
+
+[color]
+main = "magenta"
+watcher = "cyan"
+build = "yellow"
+runner = "green"
+app = "white"

+ 70 - 0
Gopkg.lock

@@ -0,0 +1,70 @@
+# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
+
+
+[[projects]]
+  name = "github.com/flosch/pongo2"
+  packages = ["."]
+  revision = "5e81b817a0c48c1c57cdf1a9056cf76bdee02ca9"
+  version = "v3.0"
+
+[[projects]]
+  name = "github.com/fsouza/go-dockerclient"
+  packages = [
+    ".",
+    "external/github.com/Sirupsen/logrus",
+    "external/github.com/docker/docker/opts",
+    "external/github.com/docker/docker/pkg/archive",
+    "external/github.com/docker/docker/pkg/fileutils",
+    "external/github.com/docker/docker/pkg/homedir",
+    "external/github.com/docker/docker/pkg/idtools",
+    "external/github.com/docker/docker/pkg/ioutils",
+    "external/github.com/docker/docker/pkg/longpath",
+    "external/github.com/docker/docker/pkg/pools",
+    "external/github.com/docker/docker/pkg/promise",
+    "external/github.com/docker/docker/pkg/stdcopy",
+    "external/github.com/docker/docker/pkg/system",
+    "external/github.com/docker/go-units",
+    "external/github.com/hashicorp/go-cleanhttp",
+    "external/github.com/opencontainers/runc/libcontainer/user",
+    "external/golang.org/x/net/context",
+    "external/golang.org/x/sys/unix"
+  ]
+  revision = "1d4f4ae73768d3ca16a6fb964694f58dc5eba601"
+  version = "docker-1.9/go-1.4"
+
+[[projects]]
+  name = "github.com/go-ini/ini"
+  packages = ["."]
+  revision = "6529cf7c58879c08d927016dde4477f18a0634cb"
+  version = "v1.36.0"
+
+[[projects]]
+  name = "github.com/jinzhu/gorm"
+  packages = ["."]
+  revision = "6ed508ec6a4ecb3531899a69cbc746ccf65a4166"
+  version = "v1.9.1"
+
+[[projects]]
+  branch = "master"
+  name = "github.com/jinzhu/inflection"
+  packages = ["."]
+  revision = "04140366298a54a039076d798123ffa108fff46c"
+
+[[projects]]
+  name = "github.com/mattn/go-sqlite3"
+  packages = ["."]
+  revision = "6c771bb9887719704b210e87e934f08be014bdb1"
+  version = "v1.6.0"
+
+[[projects]]
+  name = "github.com/urfave/cli"
+  packages = ["."]
+  revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1"
+  version = "v1.20.0"
+
+[solve-meta]
+  analyzer-name = "dep"
+  analyzer-version = 1
+  inputs-digest = "10ad796954001e72772769f21ec442682d88cae0f8d9ddb3366a2fa0d4ed2b82"
+  solver-name = "gps-cdcl"
+  solver-version = 1

+ 58 - 0
Gopkg.toml

@@ -0,0 +1,58 @@
+# Gopkg.toml example
+#
+# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
+# for detailed Gopkg.toml documentation.
+#
+# required = ["github.com/user/thing/cmd/thing"]
+# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
+#
+# [[constraint]]
+#   name = "github.com/user/project"
+#   version = "1.0.0"
+#
+# [[constraint]]
+#   name = "github.com/user/project2"
+#   branch = "dev"
+#   source = "github.com/myfork/project2"
+#
+# [[override]]
+#   name = "github.com/x/y"
+#   version = "2.4.0"
+#
+# [prune]
+#   non-go = false
+#   go-tests = true
+#   unused-packages = true
+
+
+[prune]
+  go-tests = true
+  unused-packages = true
+
+[[constraint]]
+  name = "github.com/urfave/cli"
+  version = "1.20.0"
+
+[[constraint]]
+  name = "github.com/go-ini/ini"
+  version = "1.36.0"
+
+[[constraint]]
+  name = "github.com/jinzhu/gorm"
+  version = "1.9.1"
+
+[[constraint]]
+  name = "github.com/mattn/go-sqlite3"
+  version = "1.6.0"
+
+[[constraint]]
+  name = "github.com/docker/docker"
+  version = "1.13.1"
+
+[[constraint]]
+  name = "github.com/fsouza/go-dockerclient"
+  version = "docker-1.9/go-1.4"
+
+[[constraint]]
+  name = "github.com/flosch/pongo2"
+  version = "3.0.0"

+ 27 - 0
cmd/api.go

@@ -0,0 +1,27 @@
+package cmd
+
+import (
+	"bitbucket.org/robert2206/automation/modules/odoo"
+	"github.com/urfave/cli"
+)
+
+// APICmd is a cli command to manage settings
+var APICmd = cli.Command{
+	Name:   "api",
+	Action: runAPI,
+	Flags: []cli.Flag{
+		cli.StringFlag{
+			Name:  "host",
+			Value: "127.0.0.1",
+		},
+		cli.StringFlag{
+			Name:  "port",
+			Value: "8080",
+		},
+	},
+}
+
+func runAPI(ctx *cli.Context) error {
+	odoo.Create("test 01")
+	return nil
+}

+ 13 - 0
cmd/cmd.go

@@ -0,0 +1,13 @@
+package cmd
+
+import (
+	"github.com/urfave/cli"
+)
+
+func argsSet(ctx *cli.Context, args ...string) {
+	print(args)
+}
+
+func initDBEngine() {
+	print("init database engine")
+}

+ 51 - 0
data/app.ini

@@ -0,0 +1,51 @@
+appName = Eiru Automation
+
+[server]
+protocol = http
+host = 127.0.0.1
+port = 8080
+
+[auth]
+acceptHeader = HTTP_AUTHORIZATION
+prefixHeader = JWT
+secretKey = 37%&/57$%&43Vhgv55
+
+[database]
+dbDialect = sqlite
+dbHost = localhost
+dbPort = 5432
+dbUser = root
+dbPasswd = root
+dbLogger = true
+dbAutomigrate = true
+
+[docker]
+sockDir = /var/run/docker.sock
+hostIP = 127.0.0.1
+networkName = eiru
+networkIP = 127.0.0.1
+hiddenContainers = postgres, automation, mattermost
+
+[odoo]
+imageName = odoo/robert:8.0
+rootPath = /opt/odoo
+configPath = /etc/odoo
+addonsPath = /mnt/extra-addons
+filesPath = /var/lib/odoo
+portsRange = 10000, 30000
+cfgName = openerp-server
+adminPasswd = admin
+dbContainerName = postgres
+dbContainerIP = 172.10.0.10
+dbContainerPort = 5432
+dbUser = odoo
+dbPasswd = odoo
+
+[git]
+reposPath = /opt/gogs/git/gogs-repositories
+
+[mailer]
+emailHost = ns5.serverpy.com
+emailPort = 465
+emailHostUser = robert@eiru.com.py
+emailHostPasswd = @3040/Robert

+ 42 - 0
data/templates/openerp-server.tpl

@@ -0,0 +1,42 @@
+[options]
+addons_path = /mnt/extra-addons,/usr/lib/python2.7/dist-packages/openerp/addons
+data_dir = /var/lib/odoo
+; auto_reload = True
+admin_passwd = {{ admin_password }}
+; csv_internal_sep = ,
+; db_maxconn = 64
+db_host = {{ db_host }}
+db_port = {{ db_port }}
+db_name = {{ db_name }}
+db_user = {{ db_user }}
+db_password = {{ db_password }}
+; db_template = template1
+dbfilter = ^{{ db_name }}$
+; debug_mode = False
+; email_from = False
+limit_memory_hard = 83886080
+limit_memory_soft = 52428800
+limit_request = 1024
+; limit_time_cpu = 240
+; limit_time_real = 840
+list_db = False
+; log_db = False
+; log_handler = [':INFO']
+; log_level = debug_sql
+logfile = /var/lib/odoo/logs/odoo.log
+; longpolling_port = 8072
+; max_cron_threads = 2
+; osv_memory_age_limit = 1.0
+; osv_memory_count_limit = False
+; smtp_password = False
+; smtp_port = 25
+; smtp_server = localhost
+; smtp_ssl = False
+; smtp_user = False
+; workers = 7
+; xmlrpc = True
+; xmlrpc_interface =
+; xmlrpc_port = 8069
+; xmlrpcs = True
+; xmlrpcs_interface =
+; xmlrpcs_port = 8071

TEMPAT SAMPAH
data/test.db


+ 33 - 0
main.go

@@ -0,0 +1,33 @@
+package main
+
+import (
+	"log"
+	"os"
+
+	"bitbucket.org/robert2206/automation/cmd"
+	"bitbucket.org/robert2206/automation/modules/models"
+	"bitbucket.org/robert2206/automation/modules/settings"
+	"github.com/urfave/cli"
+)
+
+func init() {
+	settings.Init()
+	models.Init()
+}
+
+func main() {
+	app := cli.NewApp()
+	app.Name = "Eiru Automation"
+	app.Usage = "A ligthweight eiru automation for docker and odoo"
+	app.Commands = []cli.Command{
+		cmd.APICmd,
+	}
+	app.Flags = append(app.Flags, []cli.Flag{}...)
+	app.Action = cmd.APICmd.Action
+
+	err := app.Run(os.Args)
+
+	if err != nil {
+		log.Fatal(4, "Failed to run app with %s: %v", os.Args, err)
+	}
+}

+ 109 - 0
modules/docker/docker.go

@@ -0,0 +1,109 @@
+package docker
+
+import (
+	"errors"
+	"strings"
+
+	S "bitbucket.org/robert2206/automation/modules/settings"
+	docker "github.com/fsouza/go-dockerclient"
+)
+
+type ContainerDescriptor struct {
+	Name      *string
+	ImageName *string
+}
+
+func Create(dsc *ContainerDescriptor) {
+
+}
+
+func Remove() {
+
+}
+
+// Start container
+func Start(idOrName string) error {
+	cli, err := getClientFor(idOrName)
+
+	if err != nil {
+		return err
+	}
+
+	return cli.StartContainer(idOrName, nil)
+}
+
+// Restart container
+func Restart(idOrName string) error {
+	cli, err := getClientFor(idOrName)
+
+	if err != nil {
+		return err
+	}
+
+	return cli.RestartContainer(idOrName, 3)
+}
+
+// Stop container
+func Stop(idOrName string) error {
+	cli, err := getClientFor(idOrName)
+
+	if err != nil {
+		return err
+	}
+
+	return cli.StopContainer(idOrName, 3)
+}
+
+func GetAllImages() {
+
+}
+
+func GetAllContainers() {
+
+}
+
+func GetAllExternalPorts() {
+
+}
+
+func GetNetwork(idOrName string) {
+
+}
+
+func GetInternalIP(idOrName string) {
+
+}
+
+func getClientFor(idOrName string) (*docker.Client, error) {
+	err := checkIdOrName(idOrName)
+
+	if err != nil {
+		return nil, err
+	}
+
+	cli, err := getDockerClient()
+
+	if err != nil {
+		return nil, err
+	}
+
+	return cli, nil
+}
+
+func checkIdOrName(idOrName string) error {
+	if len(strings.TrimSpace(idOrName)) == 0 {
+		return errors.New("container id or name not provided")
+	}
+
+	return nil
+}
+
+func getDockerClient() (*docker.Client, error) {
+	endpoint := S.Ctx.DockerSockDir
+
+	if strings.HasSuffix(endpoint, ".sock") {
+		endpoint = strings.Join([]string{"unix:", endpoint}, "/")
+	}
+
+	return docker.NewClient(endpoint)
+}

+ 5 - 0
modules/init.go

@@ -0,0 +1,5 @@
+package modules
+
+func init() {
+	print("init modules")
+}

+ 12 - 0
modules/models/issue.go

@@ -0,0 +1,12 @@
+package models
+
+import (
+	"github.com/jinzhu/gorm"
+)
+
+// Issue represent a request issue
+type Issue struct {
+	gorm.Model
+	Name      string `gorm:"type:varchar(50)"`
+	RequestID int
+}

+ 1 - 0
modules/models/migrations/migrations.go

@@ -0,0 +1 @@
+package migrations

+ 41 - 0
modules/models/models.go

@@ -0,0 +1,41 @@
+package models
+
+import (
+	"bitbucket.org/robert2206/automation/modules/settings"
+	"github.com/jinzhu/gorm"
+	// sqlite3 is blank import for lazy load driver and testing database
+	_ "github.com/mattn/go-sqlite3"
+)
+
+var (
+	g      *gorm.DB
+	models []interface{}
+)
+
+func init() {
+	models = append(models,
+		&User{},
+		&Request{},
+		&Task{},
+		&Issue{},
+	)
+}
+
+// Init is a models initializers
+func Init() {
+	db, err := gorm.Open("sqlite3", "data/test.db")
+
+	if err != nil {
+		panic("Failed to connect database")
+	}
+
+	defer db.Close()
+
+	// check if logging
+	db.LogMode(settings.Ctx.DatabaseLogger)
+
+	// check if migrating
+	if settings.Ctx.DatabaseAutomigrate {
+		db.Debug().AutoMigrate(models...)
+	}
+}

+ 30 - 0
modules/models/request.go

@@ -0,0 +1,30 @@
+package models
+
+import (
+	"github.com/jinzhu/gorm"
+)
+
+// RequestStatusType is a request status on timeline
+type RequestStatusType int8
+
+const (
+	// RequestOpenedStatus when request is open
+	RequestOpenedStatus RequestStatusType = iota + 1
+	// RequestRejectedStatus when request is rejected
+	RequestRejectedStatus
+	// RequestProcessingStatus when request is processing
+	RequestProcessingStatus
+	// RequestDoneStatus when request is done
+	RequestDoneStatus
+	// RequestErrorStatus when request is error
+	RequestErrorStatus
+)
+
+// Request represent a automation request
+type Request struct {
+	gorm.Model
+	Name   string `gorm:"varchar(50)"`
+	User   User   `gorm:"association_foreignkey:ID"`
+	UserID int
+	Issues []Issue `gorm:"foreignkey:RequestID`
+}

+ 11 - 0
modules/models/task.go

@@ -0,0 +1,11 @@
+package models
+
+import (
+	"github.com/jinzhu/gorm"
+)
+
+// Task represent a automated task
+type Task struct {
+	gorm.Model
+	LastExecution string
+}

+ 26 - 0
modules/models/user.go

@@ -0,0 +1,26 @@
+package models
+
+import (
+	"strings"
+
+	"github.com/jinzhu/gorm"
+)
+
+// User represent a system user
+type User struct {
+	gorm.Model
+	Name     string    `gorm:"type:varchar(50)"`
+	Username string    `gorm:"type:varchar(35);unique"`
+	Passwd   string    `gorm:"type:varchar(255);unique"`
+	Requests []Request `gorm:"foreign_key:UserID"`
+}
+
+// CreateUser create a user
+func CreateUser(u *User) error {
+	u.Name = strings.ToTitle(u.Name)
+	return nil
+}
+
+func EncodePasswd(u *User) {
+
+}

+ 126 - 0
modules/odoo/odoo.go

@@ -0,0 +1,126 @@
+package odoo
+
+import (
+	"errors"
+	"io/ioutil"
+	"log"
+	"os"
+	"path/filepath"
+	"regexp"
+	"strings"
+
+	S "bitbucket.org/robert2206/automation/modules/settings"
+	"github.com/flosch/pongo2"
+)
+
+var (
+	matchFirstCap  = regexp.MustCompile("(.)([A-Z][a-z]+)")
+	matchAllCap    = regexp.MustCompile("([a-z0-9])([A-Z])")
+	matchAllSpaces = regexp.MustCompile("(\\s*)")
+)
+
+// Create odoo instance
+func Create(odooName string) error {
+	// 1. snakeize name
+	odooName = snakeizeName(odooName)
+	log.Printf("snakeize odoo name: %v", odooName)
+
+	// 2. build odoo path
+	path := filepath.Join(S.Ctx.OdooRootPath, odooName)
+	log.Printf("odoo path builded: %v", path)
+
+	// 3. check if odoo path exists
+	pathExists := existsPath(path)
+
+	if pathExists {
+		log.Printf("odoo path existence: %v", pathExists)
+		return errors.New("odoo path exists")
+	}
+
+	log.Printf("odoo path existence: %v", pathExists)
+
+	// 4. make odoo path
+	err := makePath(odooName)
+
+	if err != nil {
+		return err
+	}
+
+	// 5. make odoo default paths
+	err = makeDefaultPaths(odooName)
+
+	if err != nil {
+		log.Fatalf("odoo default paths cannot be created: %v", err.Error())
+		return err
+	}
+
+	log.Printf("odoo default paths is created")
+
+	// 6. make openerp-server.conf
+	err = makeConfiguration(odooName)
+
+	if err != nil {
+		log.Fatalf("odoo configuration cannot be created: %v", err.Error())
+		return err
+	}
+
+	log.Printf("odoo configuration is created")
+
+	return nil
+}
+
+func snakeizeName(name string) string {
+	snakeCase := matchFirstCap.ReplaceAllString(name, "${1}_${2}")
+	snakeCase = matchAllCap.ReplaceAllString(snakeCase, "${1}_${2}")
+	snakeCase = matchAllSpaces.ReplaceAllString(snakeCase, "")
+
+	return strings.ToLower(snakeCase)
+}
+
+func existsPath(path string) bool {
+	_, err := os.Stat(path)
+	return err == nil
+}
+
+func makePath(odooName string) error {
+	path := filepath.Join(S.Ctx.OdooRootPath, odooName)
+	return os.Mkdir(path, 0777)
+}
+
+func makeDefaultPaths(odooName string) error {
+	paths := [3]string{"custom-addons", "config", "files"}
+
+	for i := 0; i < len(paths); i++ {
+		path := filepath.Join(S.Ctx.OdooRootPath, odooName, paths[i])
+
+		err := os.Mkdir(path, 0777)
+
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func makeConfiguration(odooName string) error {
+	tmpl, err := pongo2.FromFile("data/templates/openerp-server.tpl")
+
+	if err != nil {
+		return err
+	}
+
+	tmpl = pongo2.Must(tmpl, err)
+
+	out, _ := tmpl.Execute(pongo2.Context{
+		"admin_password": "admin",
+		"db_host":        "127.0.0.1",
+		"db_port":        "8069",
+		"db_name":        "odoo",
+		"db_user":        "odoo",
+	})
+
+	confPath := filepath.Join(S.Ctx.OdooRootPath, odooName, "config", "openerp-server.conf")
+
+	return ioutil.WriteFile(confPath, []byte(out), 0777)
+}

+ 132 - 0
modules/settings/settings.go

@@ -0,0 +1,132 @@
+package settings
+
+import (
+	"log"
+	"os"
+
+	"github.com/go-ini/ini"
+)
+
+// Settings represents a app settings
+type Settings struct {
+	IsInit                 bool
+	AppName                string
+	ServerProtocol         string
+	ServerHost             string
+	ServerPort             string
+	AuthAcceptHeader       string
+	AuthPrefixHeader       string
+	AuthSecretKey          string
+	DatabaseDialect        string
+	DatabaseHost           string
+	DatabasePort           string
+	DatabaseUser           string
+	DatabasePasswd         string
+	DatabaseLogger         bool
+	DatabaseAutomigrate    bool
+	DockerSockDir          string
+	DockerHostIP           string
+	DockerNetworkName      string
+	DockerNetworkIP        string
+	DockerHiddenContainers []string
+	OdooImageName          string
+	OdooRootPath           string
+	OdooConfigPath         string
+	OdooAddonsPath         string
+	OdooFilesPath          string
+	OdooPortsRange         []int
+	OdooCfgName            string
+	OdooAdminPasswd        string
+	OdooDbContainerName    string
+	OdooDbContainerIP      string
+	OdooDbContainerPort    string
+	OdooDbUser             string
+	OdooDbPasswd           string
+	GitReposPath           string
+	MailerHost             string
+	MailerPort             string
+	MailerUser             string
+	MailerPasswd           string
+}
+
+// Ctx is a global context settings instance
+var Ctx = Settings{
+	IsInit: false,
+}
+
+// Init initialize all app configuration
+func Init() {
+	if Ctx.IsInit {
+		return
+	}
+
+	cfg, err := ini.Load("data/app.ini")
+
+	if err != nil {
+		log.Fatal("Cannot read file")
+		os.Exit(1)
+	}
+
+	// Read root section
+	rootSection := cfg.Section("")
+	Ctx.AppName = rootSection.Key("appName").MustString("No name")
+
+	// Read server section
+	srvSection := cfg.Section("server")
+	Ctx.ServerProtocol = srvSection.Key("protocol").MustString("http")
+	Ctx.ServerHost = srvSection.Key("host").MustString("127.0.0.1")
+	Ctx.ServerPort = srvSection.Key("port").MustString("8080")
+
+	// Read auth section
+	authSection := cfg.Section("auth")
+	Ctx.AuthAcceptHeader = authSection.Key("acceptHeader").String()
+	Ctx.AuthPrefixHeader = authSection.Key("prefixHeader").String()
+	Ctx.AuthSecretKey = authSection.Key("secretKey").String()
+
+	// Read database section
+	dbSection := cfg.Section("database")
+	Ctx.DatabaseDialect = dbSection.Key("dbDialect").MustString("sqlite")
+	Ctx.DatabaseHost = dbSection.Key("dbHost").String()
+	Ctx.DatabasePort = dbSection.Key("dbPort").String()
+	Ctx.DatabaseUser = dbSection.Key("dbUser").String()
+	Ctx.DatabasePasswd = dbSection.Key("dbPasswd").String()
+	Ctx.DatabaseLogger = dbSection.Key("dbLogger").MustBool(true)
+	Ctx.DatabaseAutomigrate = dbSection.Key("dbAutomigrate").MustBool(true)
+
+	// Read docker section
+	dockerSection := cfg.Section("docker")
+	Ctx.DockerSockDir = dockerSection.Key("sockDir").MustString("/var/run/docker.sock")
+	Ctx.DockerHostIP = dockerSection.Key("hostIP").MustString("127.0.0.1")
+	Ctx.DockerNetworkName = dockerSection.Key("networkName").MustString("bridge")
+	Ctx.DockerNetworkIP = dockerSection.Key("networkIP").MustString("172.10.0.1")
+	Ctx.DockerHiddenContainers = dockerSection.Key("hiddenContainers").Strings(",")
+
+	// Read odoo section
+	odooSection := cfg.Section("odoo")
+	Ctx.OdooImageName = odooSection.Key("imageName").String()
+	Ctx.OdooRootPath = odooSection.Key("rootPath").String()
+	Ctx.OdooConfigPath = odooSection.Key("configPath").String()
+	Ctx.OdooAddonsPath = odooSection.Key("addonsPath").String()
+	Ctx.OdooFilesPath = odooSection.Key("filesPath").String()
+	Ctx.OdooPortsRange = odooSection.Key("portsRange").Ints(",")
+	Ctx.OdooCfgName = odooSection.Key("cfgName").String()
+	Ctx.OdooAdminPasswd = odooSection.Key("adminPasswd").String()
+	Ctx.OdooDbContainerName = odooSection.Key("dbContainerName").String()
+	Ctx.OdooDbContainerIP = odooSection.Key("dbContainerIP").String()
+	Ctx.OdooDbContainerPort = odooSection.Key("dbContainerPort").String()
+	Ctx.OdooDbUser = odooSection.Key("dbUser").String()
+	Ctx.OdooDbPasswd = odooSection.Key("dbPasswd").String()
+
+	// Read git section
+	gitSection := cfg.Section("git")
+	Ctx.GitReposPath = gitSection.Key("reposPath").String()
+
+	// Read mailer section
+	mailerSection := cfg.Section("mailer")
+	Ctx.MailerHost = mailerSection.Key("emailHost").String()
+	Ctx.MailerPort = mailerSection.Key("emailPort").String()
+	Ctx.MailerUser = mailerSection.Key("emailHostUser").String()
+	Ctx.MailerPasswd = mailerSection.Key("emailHostPasswd").String()
+
+	Ctx.IsInit = true
+}

+ 650 - 0
tmp/server-errors.log

@@ -0,0 +1,650 @@
+stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/settings.go:38:42: multiple-value settings.Section("server").GetKey() in single-value context
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/settings.go:39:12: undefined: key
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/settings.go:44:12: undefined: key
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/settings.go:30:2: cannot refer to unexported name path.settings
+cmd/settings.go:30:6: non-name path.settings on left side of :=
+cmd/settings.go:38:2: undefined: settings
+cmd/settings.go:39:15: undefined: settings
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/settings.go:39:34: multiple-value settings.GetSection() in single-value context
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/settings.go:39:29: multiple-value settings.GetSection() in single-value context
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/settings.go:35:11: no new variables on left side of :=
+cmd/settings.go:35:14: undefined: Settings
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/settings.go:35:11: no new variables on left side of :=
+cmd/settings.go:35:22: settings.Settings undefined (type *ini.File has no field or method Settings)
+, cmd err: exit status 2stderr: cmd/settings.go:7:2: cannot find package "bitbucket.org/robert2206/automation/modules/settings" in any of:
+	/home/robert/workspace/go/src/bitbucket.org/robert2206/automation/vendor/bitbucket.org/robert2206/automation/modules/settings (vendor tree)
+	/opt/go/src/bitbucket.org/robert2206/automation/modules/settings (from $GOROOT)
+	/home/robert/workspace/go/src/bitbucket.org/robert2206/automation/modules/settings (from $GOPATH)
+, cmd err: exit status 1stderr: cmd/settings.go:7:2: cannot find package "bitbucket.org/robert2206/automation/modules/settings" in any of:
+	/home/robert/workspace/go/src/bitbucket.org/robert2206/automation/vendor/bitbucket.org/robert2206/automation/modules/settings (vendor tree)
+	/opt/go/src/bitbucket.org/robert2206/automation/modules/settings (from $GOROOT)
+	/home/robert/workspace/go/src/bitbucket.org/robert2206/automation/modules/settings (from $GOPATH)
+, cmd err: exit status 1stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/settings.go:35:2: undefined: settings
+, cmd err: exit status 2stderr: cmd/settings.go:7:2: cannot find package "bitbucket.org/robert2206/automation/modules/settings" in any of:
+	/home/robert/workspace/go/src/bitbucket.org/robert2206/automation/vendor/bitbucket.org/robert2206/automation/modules/settings (vendor tree)
+	/opt/go/src/bitbucket.org/robert2206/automation/modules/settings (from $GOROOT)
+	/home/robert/workspace/go/src/bitbucket.org/robert2206/automation/modules/settings (from $GOPATH)
+, cmd err: exit status 1stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/settings.go:36:18: modules.Settings literal evaluated but not used
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/settings.go:36:18: modules.Settings literal evaluated but not used
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/settings.go:40:2: undefined: S
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/settings.go:41:9: modules.S evaluated but not used
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/settings.go:40:8: undefined: S
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/settings.go:41:7: illegal types for operand: print
+	modules.Settings
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/settings.go:41:7: illegal types for operand: print
+	modules.Settings
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/settings.go:41:7: type modules.Settings is not an expression
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/settings.go:41:8: undefined: modules.SettiSngs
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/settings.go:41:7: illegal types for operand: print
+	modules.Settings
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/settings.go:33:2: cgf declared and not used
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/settings.go:40:8: undefined: m
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/settings.go:40:8: undefined: m
+, cmd err: exit status 2stderr: cmd/settings.go:7:2: no Go files in /home/robert/workspace/go/src/bitbucket.org/robert2206/automation/modules
+, cmd err: exit status 1stderr: cmd/settings.go:7:2: no Go files in /home/robert/workspace/go/src/bitbucket.org/robert2206/automation/modules
+, cmd err: exit status 1stderr: cmd/settings.go:7:2: no Go files in /home/robert/workspace/go/src/bitbucket.org/robert2206/automation/modules
+, cmd err: exit status 1stderr: cmd/settings.go:7:2: no Go files in /home/robert/workspace/go/src/bitbucket.org/robert2206/automation/modules
+, cmd err: exit status 1stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/settings.go:40:8: undefined: modules
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/settings.go:40:8: undefined: settings
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/settings.go:40:17: syntax error: unexpected ), expecting name or (
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/settings.go:41:17: syntax error: unexpected ), expecting name or (
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/settings.go:41:21: settings.Init() used as value
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:10:6: syntax error: unexpected newline, expecting comma or }
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:10:7: syntax error: unexpected newline, expecting comma or }
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:10:2: too few values in struct initializer
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:10:8: syntax error: unexpected ..., expecting expression
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:12:5: syntax error: unexpected newline, expecting comma or }
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:11:2: cannot use nil as type string in field value
+modules/settings/settings.go:12:2: cannot use nil as type string in field value
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/settings.go:44:2: syntax error: unexpected return, expecting name or (
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/api.go:10:10: undefined: runSettings
+, cmd err: exit status 2stderr: # command-line-arguments
+./main.go:16:3: undefined: cmd.SettingsCmd
+./main.go:19:15: undefined: cmd.SettingsCmd
+, cmd err: exit status 2stderr: # command-line-arguments
+./main.go:16:3: undefined: cmd.SettingsCmd
+./main.go:19:15: undefined: cmd.SettingsCmd
+, cmd err: exit status 2stderr: # command-line-arguments
+./main.go:16:3: use of package cmd without selector
+./main.go:19:15: undefined: cmd.SettingsCmd
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:28:2: ini declared and not used
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:29:2: ini declared and not used
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:37:3: non-name S.Host on left side of :=
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:37:12: cannot assign *ini.Key to S.Host (type string) in multiple assignment
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:37:39: multiple-value ini.Section("server").GetKey() in single-value context
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:37:12: cannot assign *ini.Key to S.Host (type string) in multiple assignment
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:38:9: cannot use host (type *ini.Key) as type string in assignment
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:36:1: syntax error: unexpected }, expecting name or (
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:15:9: undefined: Settings
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:16:2: ini declared and not used
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:21:2: ini declared and not used
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:29:6: no new variables on left side of :=
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:29:6: no new variables on left side of :=
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:22:2: cannot use nil as type Server in field value
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:21:7: syntax error: unexpected newline, expecting comma or }
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:22:2: cannot use nil as type Server in field value
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:12:10: undefined: s
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:12:10: undefined: server
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:12:9: undefined: server
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/api.go:27:7: illegal types for operand: print
+	settings.Settings
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:35:2: undefined: srvSection
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:35:2: srvSection declared and not used
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:36:25: multiple-value srvSection.GetKey() in single-value context
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:36:25: multiple-value srvSection.GetKey() in single-value context
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:36:25: multiple-value srvSection.GetKey() in single-value context
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:19:2: cannot use nil as type string in field value
+modules/settings/settings.go:19:2: too few values in struct initializer
+modules/settings/settings.go:33:3: S.Server undefined (type Settings has no field or method Server)
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:21:4: syntax error: unexpected newline, expecting comma or }
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:35:3: S.Server undefined (type Settings has no field or method Server)
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:35:14: undefined: srvSection
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:24:14: undefined: i
+modules/settings/settings.go:36:16: undefined: cf
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:24:14: undefined: i
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:22:2: too few values in struct initializer
+modules/settings/settings.go:40:3: S.S undefined (type Settings has no field or method S)
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:19:15: syntax error: unexpected newline, expecting comma or }
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:37:3: S.S undefined (type Settings has no field or method S)
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:42:2: sshSection declared and not used
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:39:3: S.Protocol undefined (type Settings has no field or method Protocol)
+modules/settings/settings.go:40:3: S.Host undefined (type Settings has no field or method Host)
+modules/settings/settings.go:41:3: S.Port undefined (type Settings has no field or method Port)
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:63:3: S.Odoo undefined (type Settings has no field or method Odoo)
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:63:18: cannot use dockerSection.Key("sockDir") (type *ini.Key) as type string in assignment
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:65:3: S.DockerNetworkName evaluated but not used
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:66:76: syntax error: unexpected newline, expecting comma or )
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:67:27: cannot use dockerSection.Key("hiddenContainers") (type *ini.Key) as type string in assignment
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:67:73: too many arguments in call to dockerSection.Key("hiddenContainers").String
+	have (string)
+	want ()
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:67:73: too many arguments in call to dockerSection.Key("hiddenContainers").String
+	have (string)
+	want ()
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:67:27: cannot use dockerSection.Key("hiddenContainers").Strings(",") (type []string) as type string in assignment
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:21:31: syntax error: unexpected [, expecting semicolon or newline or }
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:76:19: cannot use odooSection.Key("portsRange").Ints(",") (type []int) as type string in assignment
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:76:19: cannot use odooSection.Key("portsRange").Ints(",") (type []int) as type []string in assignment
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:79:24: cannot use odooSection.Key("") (type *ini.Key) as type string in assignment
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:81:26: undefined: odoo
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:95:2: mailerSection declared and not used
+, cmd err: exit status 2stderr: # command-line-arguments
+./main.go:20:13: cannot use settings.Init (type func()) as type cli.BeforeFunc in assignment
+, cmd err: exit status 2stderr: # command-line-arguments
+./main.go:20:13: cannot use settings.Init (type func()) as type cli.BeforeFunc in assignment
+, cmd err: exit status 2stderr: # command-line-arguments
+./main.go:20:13: cannot use settings.Init (type func()) as type cli.BeforeFunc in assignment
+, cmd err: exit status 2stderr: # command-line-arguments
+./main.go:20:13: cannot use settings.Init (type func()) as type cli.BeforeFunc in assignment
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:49:5: undefined: S
+modules/settings/settings.go:62:2: undefined: S
+modules/settings/settings.go:66:2: undefined: S
+modules/settings/settings.go:67:2: undefined: S
+modules/settings/settings.go:68:2: undefined: S
+modules/settings/settings.go:72:2: undefined: S
+modules/settings/settings.go:73:2: undefined: S
+modules/settings/settings.go:74:2: undefined: S
+modules/settings/settings.go:75:2: undefined: S
+modules/settings/settings.go:76:2: undefined: S
+modules/settings/settings.go:76:2: too many errors
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:72:2: undefined: S
+modules/settings/settings.go:73:2: undefined: S
+modules/settings/settings.go:74:2: undefined: S
+modules/settings/settings.go:75:2: undefined: S
+modules/settings/settings.go:76:2: undefined: S
+modules/settings/settings.go:80:2: undefined: S
+modules/settings/settings.go:81:2: undefined: S
+modules/settings/settings.go:82:2: undefined: S
+modules/settings/settings.go:83:2: undefined: S
+modules/settings/settings.go:84:2: undefined: S
+modules/settings/settings.go:84:2: too many errors
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:49:5: undefined: Context
+modules/settings/settings.go:62:2: undefined: Context
+modules/settings/settings.go:66:2: undefined: Context
+modules/settings/settings.go:67:2: undefined: Context
+modules/settings/settings.go:68:2: undefined: Context
+modules/settings/settings.go:72:2: undefined: Context
+modules/settings/settings.go:73:2: undefined: Context
+modules/settings/settings.go:74:2: undefined: Context
+modules/settings/settings.go:75:2: undefined: Context
+modules/settings/settings.go:76:2: undefined: Context
+modules/settings/settings.go:76:2: too many errors
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/api.go:27:8: undefined: settings.S
+cmd/api.go:28:8: undefined: settings.S
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/api.go:27:7: illegal types for operand: print
+	settings.Settings
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:74:2: authSection declared and not used
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:79:2: authSection declared and not used
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:79:2: authSection declared and not used
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:80:28: cfg.Key undefined (type *ini.File has no field or method Key)
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/api.go:25:20: settings.Ctx.Da undefined (type settings.Settings has no field or method Da)
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:86:5: Ctx.DatabaseType undefined (type Settings has no field or method DatabaseType)
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/api.go:25:20: settings.Ctx.DatabaseType undefined (type settings.Settings has no field or method DatabaseType)
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/api.go:25:20: settings.Ctx.DatabaseType undefined (type settings.Settings has no field or method DatabaseType)
+# bitbucket.org/robert2206/automation/modules/models
+modules/models/engine.go:17:1: syntax error: unexpected }, expecting name or (
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/api.go:25:20: settings.Ctx.DatabaseType undefined (type settings.Settings has no field or method DatabaseType)
+# bitbucket.org/robert2206/automation/modules/models
+modules/models/engine.go:17:1: syntax error: unexpected }, expecting name or (
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/api.go:25:20: settings.Ctx.DatabaseType undefined (type settings.Settings has no field or method DatabaseType)
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:19:15: syntax error: unexpected newline, expecting comma or )
+modules/models/models.go:21:2: syntax error: unexpected ), expecting expression
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:20:12: syntax error: unexpected newline, expecting comma or )
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:24:6: Init redeclared in this block
+	previous declaration at modules/models/engine.go:10:6
+, cmd err: exit status 2stderr: main.go:8:2: 
+modules/models/base.go:1:1: expected 'package', found 'EOF'
+, cmd err: exit status 1stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:4:6: Base redeclared in this block
+	previous declaration at modules/models/base.go:4:6
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:21:2: db declared and not used
+modules/models/models.go:21:6: err declared and not used
+, cmd err: exit status 2stderr: modules/models/models.go:5:2: cannot find package "github.com/jinzhu/gorm/dialects/sqlite" in any of:
+	/home/robert/workspace/go/src/bitbucket.org/robert2206/automation/vendor/github.com/jinzhu/gorm/dialects/sqlite (vendor tree)
+	/opt/go/src/github.com/jinzhu/gorm/dialects/sqlite (from $GOROOT)
+	/home/robert/workspace/go/src/github.com/jinzhu/gorm/dialects/sqlite (from $GOPATH)
+, cmd err: exit status 1stderr: modules/models/models.go:5:2: cannot find package "github.com/jinzhu/gorm/dialects/sqlite" in any of:
+	/home/robert/workspace/go/src/bitbucket.org/robert2206/automation/vendor/github.com/jinzhu/gorm/dialects/sqlite (vendor tree)
+	/opt/go/src/github.com/jinzhu/gorm/dialects/sqlite (from $GOROOT)
+	/home/robert/workspace/go/src/github.com/jinzhu/gorm/dialects/sqlite (from $GOPATH)
+, cmd err: exit status 1stderr: modules/models/models.go:5:2: cannot find package "github.com/jinzhu/gorm/dialects/sqlite" in any of:
+	/home/robert/workspace/go/src/bitbucket.org/robert2206/automation/vendor/github.com/jinzhu/gorm/dialects/sqlite (vendor tree)
+	/opt/go/src/github.com/jinzhu/gorm/dialects/sqlite (from $GOROOT)
+	/home/robert/workspace/go/src/github.com/jinzhu/gorm/dialects/sqlite (from $GOPATH)
+, cmd err: exit status 1stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:27:11: not enough arguments in call to os.Mkdir
+	have (string)
+	want (string, os.FileMode)
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:27:12: cannot use os.FileMode.Perm (type func(os.FileMode) os.FileMode) as type os.FileMode in argument to os.Mkdir
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:16:12: &User literal is not a type
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:32:2: migrate declared and not used
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:32:12: syntax error: unexpected AutoMigrate at end of statement
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:92:53: multiple-value dbSection.Key("dbLogger").Bool() in single-value context
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:92:53: too many arguments in call to dbSection.Key("dbLogger").Bool
+	have (string)
+	want ()
+modules/settings/settings.go:92:53: multiple-value dbSection.Key("dbLogger").Bool() in single-value context
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/settings
+modules/settings/settings.go:92:53: too many arguments in call to dbSection.Key("dbLogger").Bool
+	have (bool)
+	want ()
+modules/settings/settings.go:92:53: multiple-value dbSection.Key("dbLogger").Bool() in single-value context
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:33:2: non-bool settings.Ctx.DatabaseUser (type string) used as if condition
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:39:2: non-bool settings.Ctx.DatabaseDialect (type string) used as if condition
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/user.go:13:13: undefined: Requests
+, cmd err: exit status 2stderr: main.go:8:2: 
+modules/models/issue.go:1:1: expected 'package', found 'EOF'
+, cmd err: exit status 1stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/user.go:18:1: missing return at end of function
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:27:2: undefined: gorm.Op
+modules/models/models.go:28:2: undefined: gorm.Ope
+modules/models/models.go:29:2: undefined: gorm.Op
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:27:2: undefined: gorm.Op
+modules/models/models.go:28:2: undefined: gorm.Ope
+modules/models/models.go:29:2: undefined: gorm.Op
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:32:8: undefined: db
+modules/models/models.go:35:2: undefined: db
+modules/models/models.go:39:3: undefined: db
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/user.go:22:2: tx declared and not used
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/user.go:24:18: gorm.DB.Error is a field, not a method
+modules/models/user.go:24:18: cannot use tx.Commit() (type *gorm.DB) as type error in return argument:
+	*gorm.DB does not implement error (missing Error method)
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/user.go:22:2: tx declared and not used
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/api.go:26:19: syntax error: unexpected newline, expecting comma or }
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/api.go:25:2: u declared and not used
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/api.go:29:19: cannot use u (type models.User) as type *models.User in argument to models.CreateUser
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/api.go:29:19: cannot use u (type models.User) as type *models.User in argument to models.CreateUser
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/api.go:25:7: invalid indirect of models.User literal (type models.User)
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/api.go:29:19: cannot use u (type models.User) as type *models.User in argument to models.CreateUser
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/user.go:22:8: cannot take the address of GetORM().Begin()
+modules/models/user.go:24:4: calling method Commit with receiver tx (type **gorm.DB) requires explicit dereference
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:26:5: undefined: err
+modules/models/models.go:28:5: undefined: err
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:28:5: undefined: err
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:26:5: undefined: err
+modules/models/models.go:28:5: undefined: err
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:42:2: undefined: self
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:42:2: too many arguments to return
+	have (*gorm.DB)
+	want ()
+modules/models/models.go:46:13: Init() used as value
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/user.go:22:8: undefined: GetORM
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/user.go:22:8: undefined: GetORM
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/user.go:22:8: undefined: GetORM
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:42:2: tx declared and not used
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:45:17: syntax error: unexpected newline, expecting comma or }
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:49:7: unknown field 'Name' in struct literal of type Request
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:46:18: syntax error: unexpected newline, expecting comma or }
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:46:27: syntax error: unexpected newline, expecting comma or }
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:50:4: syntax error: unexpected newline, expecting comma or }
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:51:5: syntax error: unexpected newline, expecting comma or }
+modules/models/models.go:52:4: syntax error: unexpected newline, expecting comma or }
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:52:4: syntax error: unexpected newline, expecting comma or }
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/models
+modules/models/models.go:49:4: type Request is not an expression
+modules/models/models.go:49:11: index must be non-negative integer constant
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/api.go:26:2: syntax error: unexpected return, expecting name or (
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:10:2: path declared and not used
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:10:14: cannot use name (type string) as type *string in argument to snakeizeName
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:19:17: cannot convert name (type *string) to type []rune
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:19:17: cannot convert &name (type **string) to type []rune
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:19:17: cannot convert name (type *string) to type []rune
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:19:17: cannot convert &name (type **string) to type []rune
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:19:17: cannot convert name (type *string) to type []rune
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:19:17: cannot convert name (type *string) to type []rune
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:12:2: undefined: strings.Tr
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:26:35: cannot use name (type string) as type []byte in argument to matchFirstCap.ReplaceAll
+modules/odoo/odoo.go:26:42: cannot use "${1}_${2}" (type string) as type []byte in argument to matchFirstCap.ReplaceAll
+modules/odoo/odoo.go:27:8: cannot use matchAllCap.ReplaceAllString(snake, "${1}_${2}") (type string) as type []byte in assignment
+modules/odoo/odoo.go:27:38: cannot use snake (type []byte) as type string in argument to matchAllCap.ReplaceAllString
+modules/odoo/odoo.go:29:2: too many arguments to return
+	have (string)
+	want ()
+modules/odoo/odoo.go:29:24: cannot use snake (type []byte) as type string in argument to strings.ToLower
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:26:35: cannot use name (type string) as type []byte in argument to matchFirstCap.ReplaceAll
+modules/odoo/odoo.go:26:42: cannot use "${1}_${2}" (type string) as type []byte in argument to matchFirstCap.ReplaceAll
+modules/odoo/odoo.go:27:8: cannot use matchAllCap.ReplaceAllString(snake, "${1}_${2}") (type string) as type []byte in assignment
+modules/odoo/odoo.go:27:38: cannot use snake (type []byte) as type string in argument to matchAllCap.ReplaceAllString
+modules/odoo/odoo.go:29:24: cannot use snake (type []byte) as type string in argument to strings.ToLower
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:29:25: not enough arguments in call to strings.Replace
+	have (string, string)
+	want (string, string, string, int)
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:29:25: not enough arguments in call to strings.Replace
+	have (string, string, string)
+	want (string, string, string, int)
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:30:43: undefined: snake
+modules/odoo/odoo.go:31:30: undefined: snake
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:15:41: unknown escape sequence
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:15:42: unknown escape sequence
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:23:2: path declared and not used
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:26:2: pathExists declared and not used
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:43:16: syntax error: unexpected := at end of statement
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:44:20: type string is not an expression
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:49:10: not enough arguments in call to os.Mkdir
+	have ()
+	want (string, os.FileMode)
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:49:10: not enough arguments in call to os.Mkdir
+	have ()
+	want (string, os.FileMode)
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:50:10: not enough arguments in call to os.Mkdir
+	have ()
+	want (string, os.FileMode)
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:50:10: not enough arguments in call to os.Mkdir
+	have ()
+	want (string, os.FileMode)
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:50:10: not enough arguments in call to os.Mkdir
+	have ()
+	want (string, os.FileMode)
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:50:10: not enough arguments in call to os.Mkdir
+	have ()
+	want (string, os.FileMode)
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:49:15: syntax error: unexpected newline, expecting type
+modules/odoo/odoo.go:50:2: syntax error: unexpected os at end of statement
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:49:27: syntax error: unexpected comma, expecting ]
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:49:33: syntax error: unexpected comma, expecting :
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:49:18: syntax error: unexpected ], expecting expression
+modules/odoo/odoo.go:50:2: syntax error: non-declaration statement outside function body
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:49:11: invalid type for composite literal: string
+modules/odoo/odoo.go:50:10: not enough arguments in call to os.Mkdir
+modules/odoo/odoo.go:50:16: settings.Ctx.OdooA undefined (type settings.Settings has no field or method OdooA)
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:51:11: syntax error: unexpected in, expecting semicolon or newline
+modules/odoo/odoo.go:53:2: syntax error: unexpected }, expecting expression
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:49:11: invalid type for composite literal: string
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:53:9: undefined: pat
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:32:2: undefined: lo
+modules/odoo/odoo.go:59:17: cannot use err.Error (type func() string) as type string in argument to errors.New
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:31:2: undefined: lo
+modules/odoo/odoo.go:64:2: nil evaluated but not used
+modules/odoo/odoo.go:65:1: missing return at end of function
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:31:2: undefined: lo
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:61:1: missing return at end of function
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/api.go:25:8: docker.Create evaluated but not used
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/docker
+modules/docker/docker.go:35:26: not enough arguments in call to cli.ListContainers
+	have ()
+	want (docker.ListContainersOptions)
+modules/docker/docker.go:35:26: multiple-value cli.ListContainers() in single-value context
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/docker
+modules/docker/docker.go:35:27: undefined: name
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/docker
+modules/docker/docker.go:35:26: not enough arguments in call to cli.StartContainer
+	have (string)
+	want (string, *docker.HostConfig)
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/docker
+modules/docker/docker.go:35:27: not enough arguments in call to cli.StartContainer
+	have (string)
+	want (string, *docker.HostConfig)
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/cmd
+cmd/api.go:25:2: err declared and not used
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/docker
+modules/docker/docker.go:67:27: undefined: idOrName
+modules/docker/docker.go:68:27: undefined: idOrName
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/docker
+modules/docker/docker.go:46:3: too many arguments to return
+	have (error)
+	want ()
+modules/docker/docker.go:52:3: too many arguments to return
+	have (error)
+	want ()
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/docker
+modules/docker/docker.go:32:11: no new variables on left side of :=
+modules/docker/docker.go:46:3: too many arguments to return
+	have (error)
+	want ()
+modules/docker/docker.go:52:3: too many arguments to return
+	have (error)
+	want ()
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/docker
+modules/docker/docker.go:40:3: too many arguments to return
+	have (error)
+	want ()
+modules/docker/docker.go:46:3: too many arguments to return
+	have (error)
+	want ()
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/docker
+modules/docker/docker.go:40:3: too many arguments to return
+	have (error)
+	want ()
+modules/docker/docker.go:43:2: too many arguments to return
+	have (error)
+	want ()
+modules/docker/docker.go:43:29: cannot use nil as type uint in argument to cli.RestartContainer
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/docker
+modules/docker/docker.go:43:29: cannot use nil as type uint in argument to cli.RestartContainer
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/docker
+modules/docker/docker.go:51:3: too many arguments to return
+	have (error)
+	want ()
+modules/docker/docker.go:54:2: too many arguments to return
+	have (error)
+	want ()
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/docker
+modules/docker/docker.go:78:9: undefined: checkIdOrName
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:48:6: no new variables on left side of :=
+modules/odoo/odoo.go:58:2: err evaluated but not used
+modules/odoo/odoo.go:99:1: missing return at end of function
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:99:1: missing return at end of function
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:100:2: syntax error: unexpected return, expecting name or (
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:100:2: syntax error: unexpected return, expecting name or (
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:99:2: tmpl declared and not used
+modules/odoo/odoo.go:99:8: err declared and not used
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:105:20: not enough arguments in call to pongo2.Must
+	have (*pongo2.Template)
+	want (*pongo2.Template, error)
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:107:20: not enough arguments in call to tmpl.ExecuteWriter
+	have ()
+	want (pongo2.Context, io.Writer)
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:109:25: syntax error: unexpected newline, expecting comma or }
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:107:2: undefined: out
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:107:2: out declared and not used
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:107:2: out declared and not used
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:113:12: syntax error: unexpected semicolon, expecting comma or }
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:109:3: missing key in map literal
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:116:18: not enough arguments in call to ioutil.WriteFile
+	have (string, string)
+	want (string, []byte, os.FileMode)
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:116:19: cannot use out (type string) as type []byte in argument to ioutil.WriteFile
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:99:24: undefined: odooName
+, cmd err: exit status 2stderr: # bitbucket.org/robert2206/automation/modules/odoo
+modules/odoo/odoo.go:60:25: not enough arguments in call to makeConfiguration
+	have ()
+	want (string)
+, cmd err: exit status 2

+ 40 - 0
vendor/github.com/flosch/pongo2/.gitignore

@@ -0,0 +1,40 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+.idea
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+
+.project
+EBNF.txt
+test1.tpl
+pongo2_internal_test.go
+tpl-error.out
+/count.out
+/cover.out
+*.swp
+*.iml
+/cpu.out
+/mem.out
+/pongo2.test
+*.error
+/profile
+/coverage.out
+/pongo2_internal_test.ignore

+ 12 - 0
vendor/github.com/flosch/pongo2/.travis.yml

@@ -0,0 +1,12 @@
+language: go
+
+go:
+  - 1.3
+  - tip
+install:
+  - go get code.google.com/p/go.tools/cmd/cover
+  - go get github.com/mattn/goveralls
+  - go get gopkg.in/check.v1
+script:
+  - go test -v -covermode=count -coverprofile=coverage.out -bench . -cpu 1,4
+  - '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN || true'

+ 10 - 0
vendor/github.com/flosch/pongo2/AUTHORS

@@ -0,0 +1,10 @@
+Main author and maintainer of pongo2:
+
+* Florian Schlachter <flori@n-schlachter.de>
+
+Contributors (in no specific order):
+
+* @romanoaugusto88
+* @vitalbh
+
+Feel free to add yourself to the list or to modify your entry if you did a contribution.

+ 20 - 0
vendor/github.com/flosch/pongo2/LICENSE

@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2013-2014 Florian Schlachter
+
+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.

+ 248 - 0
vendor/github.com/flosch/pongo2/README.md

@@ -0,0 +1,248 @@
+# [pongo](https://en.wikipedia.org/wiki/Pongo_%28genus%29)2
+
+[![GoDoc](https://godoc.org/github.com/flosch/pongo2?status.png)](https://godoc.org/github.com/flosch/pongo2)
+[![Build Status](https://travis-ci.org/flosch/pongo2.svg?branch=master)](https://travis-ci.org/flosch/pongo2)
+[![Coverage Status](https://coveralls.io/repos/flosch/pongo2/badge.png?branch=master)](https://coveralls.io/r/flosch/pongo2?branch=master)
+[![gratipay](http://img.shields.io/badge/gratipay-support%20pongo-brightgreen.svg)](https://gratipay.com/flosch/)
+[![Bountysource](https://www.bountysource.com/badge/tracker?tracker_id=3654947)](https://www.bountysource.com/trackers/3654947-pongo2?utm_source=3654947&utm_medium=shield&utm_campaign=TRACKER_BADGE)
+
+pongo2 is the successor of [pongo](https://github.com/flosch/pongo), a Django-syntax like templating-language.
+
+Install/update using `go get` (no dependencies required by pongo2):
+```
+go get -u github.com/flosch/pongo2
+```
+
+Please use the [issue tracker](https://github.com/flosch/pongo2/issues) if you're encountering any problems with pongo2 or if you need help with implementing tags or filters ([create a ticket!](https://github.com/flosch/pongo2/issues/new)). If possible, please use [playground](https://www.florian-schlachter.de/pongo2/) to create a short test case on what's wrong and include the link to the snippet in your issue.
+
+**New**: [Try pongo2 out in the pongo2 playground.](https://www.florian-schlachter.de/pongo2/)
+
+## First impression of a template
+
+```HTML+Django
+<html><head><title>Our admins and users</title></head>
+{# This is a short example to give you a quick overview of pongo2's syntax. #}
+
+{% macro user_details(user, is_admin=false) %}
+	<div class="user_item">
+		<!-- Let's indicate a user's good karma -->
+		<h2 {% if (user.karma >= 40) || (user.karma > calc_avg_karma(userlist)+5) %}
+			class="karma-good"{% endif %}>
+			
+			<!-- This will call user.String() automatically if available: -->
+			{{ user }}
+		</h2>
+
+		<!-- Will print a human-readable time duration like "3 weeks ago" -->
+		<p>This user registered {{ user.register_date|naturaltime }}.</p>
+		
+		<!-- Let's allow the users to write down their biography using markdown;
+		     we will only show the first 15 words as a preview -->
+		<p>The user's biography:</p>
+		<p>{{ user.biography|markdown|truncatewords_html:15 }}
+			<a href="/user/{{ user.id }}/">read more</a></p>
+		
+		{% if is_admin %}<p>This user is an admin!</p>{% endif %}
+	</div>
+{% endmacro %}
+
+<body>
+	<!-- Make use of the macro defined above to avoid repetitive HTML code
+	     since we want to use the same code for admins AND members -->
+	
+	<h1>Our admins</h1>
+	{% for admin in adminlist %}
+		{{ user_details(admin, true) }}
+	{% endfor %}
+	
+	<h1>Our members</h1>
+	{% for user in userlist %}
+		{{ user_details(user) }}
+	{% endfor %}
+</body>
+</html>
+```
+
+## Development status
+
+**Latest stable release**: v3.0 (`go get -u gopkg.in/flosch/pongo2.v3` / [`v3`](https://github.com/flosch/pongo2/tree/v3)-branch) [[read the announcement](https://www.florian-schlachter.de/post/pongo2-v3/)]
+
+**Current development**: v4 (`master`-branch)
+
+*Note*: With the release of pongo v4 the branch v2 will be deprecated.
+
+**Deprecated versions** (not supported anymore): v1
+
+| Topic                                | Status                                                                                 |
+| ------------------------------------ | -------------------------------------------------------------------------------------- |       
+| Django version compatibility:        | [1.7](https://docs.djangoproject.com/en/1.7/ref/templates/builtins/)                  |
+| *Missing* (planned) **filters**:     | none ([hints](https://github.com/flosch/pongo2/blob/master/filters_builtin.go#L3))     | 
+| *Missing* (planned) **tags**:        | none ([hints](https://github.com/flosch/pongo2/blob/master/tags.go#L3))                |
+
+Please also have a look on the [caveats](https://github.com/flosch/pongo2#caveats) and on the [official add-ons](https://github.com/flosch/pongo2#official).
+
+## Features (and new in pongo2)
+
+ * Entirely rewritten from the ground-up.
+ * [Advanced C-like expressions](https://github.com/flosch/pongo2/blob/master/template_tests/expressions.tpl).
+ * [Complex function calls within expressions](https://github.com/flosch/pongo2/blob/master/template_tests/function_calls_wrapper.tpl).
+ * [Easy API to create new filters and tags](http://godoc.org/github.com/flosch/pongo2#RegisterFilter) ([including parsing arguments](http://godoc.org/github.com/flosch/pongo2#Parser))
+ * Additional features:
+    * Macros including importing macros from other files (see [template_tests/macro.tpl](https://github.com/flosch/pongo2/blob/master/template_tests/macro.tpl))
+    * [Template sandboxing](https://godoc.org/github.com/flosch/pongo2#TemplateSet) ([directory patterns](http://golang.org/pkg/path/filepath/#Match), banned tags/filters)
+
+## Recent API changes within pongo2
+
+If you're using the `master`-branch of pongo2, you might be interested in this section. Since pongo2 is still in development (even though there is a first stable release!), there could be (backwards-incompatible) API changes over time. To keep track of these and therefore make it painless for you to adapt your codebase, I'll list them here.
+
+ * Function signature for tag and filter parsing/execution changed (`error` return type changed to `*Error`).
+ * `INodeEvaluator` has been removed and got replaced by `IEvaluator`. You can change your existing tags/filters by simply replacing the interface.
+ * Two new helper functions: [`RenderTemplateFile()`](https://godoc.org/github.com/flosch/pongo2#RenderTemplateFile) and [`RenderTemplateString()`](https://godoc.org/github.com/flosch/pongo2#RenderTemplateString).
+ * `Template.ExecuteRW()` is now [`Template.ExecuteWriter()`](https://godoc.org/github.com/flosch/pongo2#Template.ExecuteWriter)
+ * `Template.Execute*()` functions do now take a `pongo2.Context` directly (no pointer anymore).
+
+## How you can help
+
+ * Write [filters](https://github.com/flosch/pongo2/blob/master/filters_builtin.go#L3) / [tags](https://github.com/flosch/pongo2/blob/master/tags.go#L4) (see [tutorial](https://www.florian-schlachter.de/post/pongo2/)) by forking pongo2 and sending pull requests
+ * Write/improve code tests (use the following command to see what tests are missing: `go test -v -cover -covermode=count -coverprofile=cover.out && go tool cover -html=cover.out`)
+ * Write/improve template tests (see the `template_tests/` directory)
+ * Write middleware, libraries and websites using pongo2. :-)
+
+# Documentation
+
+For a documentation on how the templating language works you can [head over to the Django documentation](https://docs.djangoproject.com/en/dev/topics/templates/). pongo2 aims to be compatible with it.
+
+You can access pongo2's API documentation on [godoc](https://godoc.org/github.com/flosch/pongo2).
+
+## Blog post series
+
+ * [pongo2 v2 released](https://www.florian-schlachter.de/post/pongo2-v2/)
+ * [pongo2 1.0 released](https://www.florian-schlachter.de/post/pongo2-10/) [August 8th 2014]
+ * [pongo2 playground](https://www.florian-schlachter.de/post/pongo2-playground/) [August 1st 2014]
+ * [Release of pongo2 1.0-rc1 + pongo2-addons](https://www.florian-schlachter.de/post/pongo2-10-rc1/) [July 30th 2014]
+ * [Introduction to pongo2 + migration- and "how to write tags/filters"-tutorial.](https://www.florian-schlachter.de/post/pongo2/) [June 29th 2014]
+
+## Caveats 
+
+### Filters
+
+ * **date** / **time**: The `date` and `time` filter are taking the Golang specific time- and date-format (not Django's one) currently. [Take a look on the format here](http://golang.org/pkg/time/#Time.Format).
+ * **stringformat**: `stringformat` does **not** take Python's string format syntax as a parameter, instead it takes Go's. Essentially `{{ 3.14|stringformat:"pi is %.2f" }}` is `fmt.Sprintf("pi is %.2f", 3.14)`.
+ * **escape** / **force_escape**: Unlike Django's behaviour, the `escape`-filter is applied immediately. Therefore there is no need for a `force_escape`-filter yet.
+
+### Tags
+
+ * **for**: All the `forloop` fields (like `forloop.counter`) are written with a capital letter at the beginning. For example, the `counter` can be accessed by `forloop.Counter` and the parentloop by `forloop.Parentloop`.
+ * **now**: takes Go's time format (see **date** and **time**-filter).
+
+### Misc
+
+ * **not in-operator**: You can check whether a map/struct/string contains a key/field/substring by using the in-operator (or the negation of it):
+    `{% if key in map %}Key is in map{% else %}Key not in map{% endif %}` or `{% if !(key in map) %}Key is NOT in map{% else %}Key is in map{% endif %}`.
+
+# Add-ons, libraries and helpers
+
+## Official
+
+ * [ponginae](https://github.com/flosch/ponginae) - A web-framework for Go (using pongo2).
+ * [pongo2-tools](https://github.com/flosch/pongo2-tools) - Official tools and helpers for pongo2
+ * [pongo2-addons](https://github.com/flosch/pongo2-addons) - Official additional filters/tags for pongo2 (for example a **markdown**-filter). They are in their own repository because they're relying on 3rd-party-libraries.
+
+## 3rd-party
+
+ * [beego-pongo2](https://github.com/oal/beego-pongo2) - A tiny little helper for using Pongo2 with [Beego](https://github.com/astaxie/beego).
+ * [beego-pongo2.v2](https://github.com/ipfans/beego-pongo2.v2) - Same as `beego-pongo2`, but for pongo2 v2.
+ * [macaron-pongo2](https://github.com/macaron-contrib/pongo2) - pongo2 support for [Macaron](https://github.com/Unknwon/macaron), a modular web framework.
+ * [ginpongo2](https://github.com/ngerakines/ginpongo2) - middleware for [gin](github.com/gin-gonic/gin) to use pongo2 templates
+ * [pongo2-trans](https://github.com/fromYukki/pongo2trans) - `trans`-tag implementation for internationalization
+
+Please add your project to this list and send me a pull request when you've developed something nice for pongo2.
+
+# API-usage examples
+
+Please see the documentation for a full list of provided API methods.
+
+## A tiny example (template string)
+
+```Go
+// Compile the template first (i. e. creating the AST)
+tpl, err := pongo2.FromString("Hello {{ name|capfirst }}!")
+if err != nil {
+	panic(err)
+}
+// Now you can render the template with the given 
+// pongo2.Context how often you want to.
+out, err := tpl.Execute(pongo2.Context{"name": "florian"})
+if err != nil {
+	panic(err)
+}
+fmt.Println(out) // Output: Hello Florian!
+```
+
+## Example server-usage (template file)
+
+```Go
+package main
+
+import (
+	"github.com/flosch/pongo2"
+	"net/http"
+)
+
+// Pre-compiling the templates at application startup using the
+// little Must()-helper function (Must() will panic if FromFile()
+// or FromString() will return with an error - that's it).
+// It's faster to pre-compile it anywhere at startup and only
+// execute the template later.
+var tplExample = pongo2.Must(pongo2.FromFile("example.html"))
+
+func examplePage(w http.ResponseWriter, r *http.Request) {
+	// Execute the template per HTTP request
+	err := tplExample.ExecuteWriter(pongo2.Context{"query": r.FormValue("query")}, w)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+	}
+}
+
+func main() {
+	http.HandleFunc("/", examplePage)
+	http.ListenAndServe(":8080", nil)
+}
+```
+
+# Benchmark
+
+The benchmarks have been run on the my machine (`Intel(R) Core(TM) i7-2600 CPU @ 3.40GHz`) using the command:
+
+    go test -bench . -cpu 1,2,4,8
+
+All benchmarks are compiling (depends on the benchmark) and executing the `template_tests/complex.tpl` template.
+
+The results are:
+
+    BenchmarkExecuteComplexWithSandboxActive                50000             60450 ns/op
+    BenchmarkExecuteComplexWithSandboxActive-2              50000             56998 ns/op
+    BenchmarkExecuteComplexWithSandboxActive-4              50000             60343 ns/op
+    BenchmarkExecuteComplexWithSandboxActive-8              50000             64229 ns/op
+    BenchmarkCompileAndExecuteComplexWithSandboxActive      10000            164410 ns/op
+    BenchmarkCompileAndExecuteComplexWithSandboxActive-2    10000            156682 ns/op
+    BenchmarkCompileAndExecuteComplexWithSandboxActive-4    10000            164821 ns/op
+    BenchmarkCompileAndExecuteComplexWithSandboxActive-8    10000            171806 ns/op
+    BenchmarkParallelExecuteComplexWithSandboxActive        50000             60428 ns/op
+    BenchmarkParallelExecuteComplexWithSandboxActive-2      50000             31887 ns/op
+    BenchmarkParallelExecuteComplexWithSandboxActive-4     100000             22810 ns/op
+    BenchmarkParallelExecuteComplexWithSandboxActive-8     100000             18820 ns/op
+    BenchmarkExecuteComplexWithoutSandbox                   50000             56942 ns/op
+    BenchmarkExecuteComplexWithoutSandbox-2                 50000             56168 ns/op
+    BenchmarkExecuteComplexWithoutSandbox-4                 50000             57838 ns/op
+    BenchmarkExecuteComplexWithoutSandbox-8                 50000             60539 ns/op
+    BenchmarkCompileAndExecuteComplexWithoutSandbox         10000            162086 ns/op
+    BenchmarkCompileAndExecuteComplexWithoutSandbox-2       10000            159771 ns/op
+    BenchmarkCompileAndExecuteComplexWithoutSandbox-4       10000            163826 ns/op
+    BenchmarkCompileAndExecuteComplexWithoutSandbox-8       10000            169062 ns/op
+    BenchmarkParallelExecuteComplexWithoutSandbox           50000             57152 ns/op
+    BenchmarkParallelExecuteComplexWithoutSandbox-2         50000             30276 ns/op
+    BenchmarkParallelExecuteComplexWithoutSandbox-4        100000             22065 ns/op
+    BenchmarkParallelExecuteComplexWithoutSandbox-8        100000             18034 ns/op
+
+Benchmarked on October 2nd 2014.

+ 122 - 0
vendor/github.com/flosch/pongo2/context.go

@@ -0,0 +1,122 @@
+package pongo2
+
+import (
+	"fmt"
+	"regexp"
+)
+
+var reIdentifiers = regexp.MustCompile("^[a-zA-Z0-9_]+$")
+
+// Use this Context type to provide constants, variables, instances or functions to your template.
+//
+// pongo2 automatically provides meta-information or functions through the "pongo2"-key.
+// Currently, context["pongo2"] contains the following keys:
+//  1. version: returns the version string
+//
+// Template examples for accessing items from your context:
+//     {{ myconstant }}
+//     {{ myfunc("test", 42) }}
+//     {{ user.name }}
+//     {{ pongo2.version }}
+type Context map[string]interface{}
+
+func (c Context) checkForValidIdentifiers() *Error {
+	for k, v := range c {
+		if !reIdentifiers.MatchString(k) {
+			return &Error{
+				Sender:   "checkForValidIdentifiers",
+				ErrorMsg: fmt.Sprintf("Context-key '%s' (value: '%+v') is not a valid identifier.", k, v),
+			}
+		}
+	}
+	return nil
+}
+
+func (c Context) Update(other Context) Context {
+	for k, v := range other {
+		c[k] = v
+	}
+	return c
+}
+
+// If you're writing a custom tag, your tag's Execute()-function will
+// have access to the ExecutionContext. This struct stores anything
+// about the current rendering process's Context including
+// the Context provided by the user (field Public).
+// You can safely use the Private context to provide data to the user's
+// template (like a 'forloop'-information). The Shared-context is used
+// to share data between tags. All ExecutionContexts share this context.
+//
+// Please be careful when accessing the Public data.
+// PLEASE DO NOT MODIFY THE PUBLIC CONTEXT (read-only).
+//
+// To create your own execution context within tags, use the
+// NewChildExecutionContext(parent) function.
+type ExecutionContext struct {
+	template *Template
+
+	Autoescape bool
+	Public     Context
+	Private    Context
+	Shared     Context
+}
+
+var pongo2MetaContext = Context{
+	"version": Version,
+}
+
+func newExecutionContext(tpl *Template, ctx Context) *ExecutionContext {
+	privateCtx := make(Context)
+
+	// Make the pongo2-related funcs/vars available to the context
+	privateCtx["pongo2"] = pongo2MetaContext
+
+	return &ExecutionContext{
+		template: tpl,
+
+		Public:     ctx,
+		Private:    privateCtx,
+		Autoescape: true,
+	}
+}
+
+func NewChildExecutionContext(parent *ExecutionContext) *ExecutionContext {
+	newctx := &ExecutionContext{
+		template: parent.template,
+
+		Public:     parent.Public,
+		Private:    make(Context),
+		Autoescape: parent.Autoescape,
+	}
+	newctx.Shared = parent.Shared
+
+	// Copy all existing private items
+	newctx.Private.Update(parent.Private)
+
+	return newctx
+}
+
+func (ctx *ExecutionContext) Error(msg string, token *Token) *Error {
+	filename := ctx.template.name
+	var line, col int
+	if token != nil {
+		// No tokens available
+		// TODO: Add location (from where?)
+		filename = token.Filename
+		line = token.Line
+		col = token.Col
+	}
+	return &Error{
+		Template: ctx.template,
+		Filename: filename,
+		Line:     line,
+		Column:   col,
+		Token:    token,
+		Sender:   "execution",
+		ErrorMsg: msg,
+	}
+}
+
+func (ctx *ExecutionContext) Logf(format string, args ...interface{}) {
+	ctx.template.set.logf(format, args...)
+}

+ 31 - 0
vendor/github.com/flosch/pongo2/doc.go

@@ -0,0 +1,31 @@
+// A Django-syntax like template-engine
+//
+// Blog posts about pongo2 (including introduction and migration):
+// https://www.florian-schlachter.de/?tag=pongo2
+//
+// Complete documentation on the template language:
+// https://docs.djangoproject.com/en/dev/topics/templates/
+//
+// Try out pongo2 live in the pongo2 playground:
+// https://www.florian-schlachter.de/pongo2/
+//
+// Make sure to read README.md in the repository as well.
+//
+// A tiny example with template strings:
+//
+// (Snippet on playground: https://www.florian-schlachter.de/pongo2/?id=1206546277)
+//
+//     // Compile the template first (i. e. creating the AST)
+//     tpl, err := pongo2.FromString("Hello {{ name|capfirst }}!")
+//     if err != nil {
+//         panic(err)
+//     }
+//     // Now you can render the template with the given
+//     // pongo2.Context how often you want to.
+//     out, err := tpl.Execute(pongo2.Context{"name": "fred"})
+//     if err != nil {
+//         panic(err)
+//     }
+//     fmt.Println(out) // Output: Hello Fred!
+//
+package pongo2

+ 86 - 0
vendor/github.com/flosch/pongo2/error.go

@@ -0,0 +1,86 @@
+package pongo2
+
+import (
+	"bufio"
+	"fmt"
+	"os"
+)
+
+// This Error type is being used to address an error during lexing, parsing or
+// execution. If you want to return an error object (for example in your own
+// tag or filter) fill this object with as much information as you have.
+// Make sure "Sender" is always given (if you're returning an error within
+// a filter, make Sender equals 'filter:yourfilter'; same goes for tags: 'tag:mytag').
+// It's okay if you only fill in ErrorMsg if you don't have any other details at hand.
+type Error struct {
+	Template *Template
+	Filename string
+	Line     int
+	Column   int
+	Token    *Token
+	Sender   string
+	ErrorMsg string
+}
+
+func (e *Error) updateFromTokenIfNeeded(template *Template, t *Token) *Error {
+	if e.Template == nil {
+		e.Template = template
+	}
+
+	if e.Token == nil {
+		e.Token = t
+		if e.Line <= 0 {
+			e.Line = t.Line
+			e.Column = t.Col
+		}
+	}
+
+	return e
+}
+
+// Returns a nice formatted error string.
+func (e *Error) Error() string {
+	s := "[Error"
+	if e.Sender != "" {
+		s += " (where: " + e.Sender + ")"
+	}
+	if e.Filename != "" {
+		s += " in " + e.Filename
+	}
+	if e.Line > 0 {
+		s += fmt.Sprintf(" | Line %d Col %d", e.Line, e.Column)
+		if e.Token != nil {
+			s += fmt.Sprintf(" near '%s'", e.Token.Val)
+		}
+	}
+	s += "] "
+	s += e.ErrorMsg
+	return s
+}
+
+// Returns the affected line from the original template, if available.
+func (e *Error) RawLine() (line string, available bool) {
+	if e.Line <= 0 || e.Filename == "<string>" {
+		return "", false
+	}
+
+	filename := e.Filename
+	if e.Template != nil {
+		filename = e.Template.set.resolveFilename(e.Template, e.Filename)
+	}
+	file, err := os.Open(filename)
+	if err != nil {
+		panic(err)
+	}
+	defer file.Close()
+
+	scanner := bufio.NewScanner(file)
+	l := 0
+	for scanner.Scan() {
+		l++
+		if l == e.Line {
+			return scanner.Text(), true
+		}
+	}
+	return "", false
+}

+ 133 - 0
vendor/github.com/flosch/pongo2/filters.go

@@ -0,0 +1,133 @@
+package pongo2
+
+import (
+	"fmt"
+)
+
+type FilterFunction func(in *Value, param *Value) (out *Value, err *Error)
+
+var filters map[string]FilterFunction
+
+func init() {
+	filters = make(map[string]FilterFunction)
+}
+
+// Registers a new filter. If there's already a filter with the same
+// name, RegisterFilter will panic. You usually want to call this
+// function in the filter's init() function:
+// http://golang.org/doc/effective_go.html#init
+//
+// See http://www.florian-schlachter.de/post/pongo2/ for more about
+// writing filters and tags.
+func RegisterFilter(name string, fn FilterFunction) {
+	_, existing := filters[name]
+	if existing {
+		panic(fmt.Sprintf("Filter with name '%s' is already registered.", name))
+	}
+	filters[name] = fn
+}
+
+// Replaces an already registered filter with a new implementation. Use this
+// function with caution since it allows you to change existing filter behaviour.
+func ReplaceFilter(name string, fn FilterFunction) {
+	_, existing := filters[name]
+	if !existing {
+		panic(fmt.Sprintf("Filter with name '%s' does not exist (therefore cannot be overridden).", name))
+	}
+	filters[name] = fn
+}
+
+// Like ApplyFilter, but panics on an error
+func MustApplyFilter(name string, value *Value, param *Value) *Value {
+	val, err := ApplyFilter(name, value, param)
+	if err != nil {
+		panic(err)
+	}
+	return val
+}
+
+// Applies a filter to a given value using the given parameters. Returns a *pongo2.Value or an error.
+func ApplyFilter(name string, value *Value, param *Value) (*Value, *Error) {
+	fn, existing := filters[name]
+	if !existing {
+		return nil, &Error{
+			Sender:   "applyfilter",
+			ErrorMsg: fmt.Sprintf("Filter with name '%s' not found.", name),
+		}
+	}
+
+	// Make sure param is a *Value
+	if param == nil {
+		param = AsValue(nil)
+	}
+
+	return fn(value, param)
+}
+
+type filterCall struct {
+	token *Token
+
+	name      string
+	parameter IEvaluator
+
+	filterFunc FilterFunction
+}
+
+func (fc *filterCall) Execute(v *Value, ctx *ExecutionContext) (*Value, *Error) {
+	var param *Value
+	var err *Error
+
+	if fc.parameter != nil {
+		param, err = fc.parameter.Evaluate(ctx)
+		if err != nil {
+			return nil, err
+		}
+	} else {
+		param = AsValue(nil)
+	}
+
+	filtered_value, err := fc.filterFunc(v, param)
+	if err != nil {
+		return nil, err.updateFromTokenIfNeeded(ctx.template, fc.token)
+	}
+	return filtered_value, nil
+}
+
+// Filter = IDENT | IDENT ":" FilterArg | IDENT "|" Filter
+func (p *Parser) parseFilter() (*filterCall, *Error) {
+	ident_token := p.MatchType(TokenIdentifier)
+
+	// Check filter ident
+	if ident_token == nil {
+		return nil, p.Error("Filter name must be an identifier.", nil)
+	}
+
+	filter := &filterCall{
+		token: ident_token,
+		name:  ident_token.Val,
+	}
+
+	// Get the appropriate filter function and bind it
+	filterFn, exists := filters[ident_token.Val]
+	if !exists {
+		return nil, p.Error(fmt.Sprintf("Filter '%s' does not exist.", ident_token.Val), ident_token)
+	}
+
+	filter.filterFunc = filterFn
+
+	// Check for filter-argument (2 tokens needed: ':' ARG)
+	if p.Match(TokenSymbol, ":") != nil {
+		if p.Peek(TokenSymbol, "}}") != nil {
+			return nil, p.Error("Filter parameter required after ':'.", nil)
+		}
+
+		// Get filter argument expression
+		v, err := p.parseVariableOrLiteral()
+		if err != nil {
+			return nil, err
+		}
+		filter.parameter = v
+	}
+
+	return filter, nil
+}

+ 903 - 0
vendor/github.com/flosch/pongo2/filters_builtin.go

@@ -0,0 +1,903 @@
+package pongo2
+
+/* Filters that are provided through github.com/flosch/pongo2-addons:
+   ------------------------------------------------------------------
+
+   filesizeformat
+   slugify
+   timesince
+   timeuntil
+
+   Filters that won't be added:
+   ----------------------------
+
+   get_static_prefix (reason: web-framework specific)
+   pprint (reason: python-specific)
+   static (reason: web-framework specific)
+
+   Reconsideration (not implemented yet):
+   --------------------------------------
+
+   force_escape (reason: not yet needed since this is the behaviour of pongo2's escape filter)
+   safeseq (reason: same reason as `force_escape`)
+   unordered_list (python-specific; not sure whether needed or not)
+   dictsort (python-specific; maybe one could add a filter to sort a list of structs by a specific field name)
+   dictsortreversed (see dictsort)
+*/
+
+import (
+	"bytes"
+	"fmt"
+	"math/rand"
+	"net/url"
+	"regexp"
+	"strconv"
+	"strings"
+	"time"
+	"unicode/utf8"
+)
+
+func init() {
+	rand.Seed(time.Now().Unix())
+
+	RegisterFilter("escape", filterEscape)
+	RegisterFilter("safe", filterSafe)
+	RegisterFilter("escapejs", filterEscapejs)
+
+	RegisterFilter("add", filterAdd)
+	RegisterFilter("addslashes", filterAddslashes)
+	RegisterFilter("capfirst", filterCapfirst)
+	RegisterFilter("center", filterCenter)
+	RegisterFilter("cut", filterCut)
+	RegisterFilter("date", filterDate)
+	RegisterFilter("default", filterDefault)
+	RegisterFilter("default_if_none", filterDefaultIfNone)
+	RegisterFilter("divisibleby", filterDivisibleby)
+	RegisterFilter("first", filterFirst)
+	RegisterFilter("floatformat", filterFloatformat)
+	RegisterFilter("get_digit", filterGetdigit)
+	RegisterFilter("iriencode", filterIriencode)
+	RegisterFilter("join", filterJoin)
+	RegisterFilter("last", filterLast)
+	RegisterFilter("length", filterLength)
+	RegisterFilter("length_is", filterLengthis)
+	RegisterFilter("linebreaks", filterLinebreaks)
+	RegisterFilter("linebreaksbr", filterLinebreaksbr)
+	RegisterFilter("linenumbers", filterLinenumbers)
+	RegisterFilter("ljust", filterLjust)
+	RegisterFilter("lower", filterLower)
+	RegisterFilter("make_list", filterMakelist)
+	RegisterFilter("phone2numeric", filterPhone2numeric)
+	RegisterFilter("pluralize", filterPluralize)
+	RegisterFilter("random", filterRandom)
+	RegisterFilter("removetags", filterRemovetags)
+	RegisterFilter("rjust", filterRjust)
+	RegisterFilter("slice", filterSlice)
+	RegisterFilter("stringformat", filterStringformat)
+	RegisterFilter("striptags", filterStriptags)
+	RegisterFilter("time", filterDate) // time uses filterDate (same golang-format)
+	RegisterFilter("title", filterTitle)
+	RegisterFilter("truncatechars", filterTruncatechars)
+	RegisterFilter("truncatechars_html", filterTruncatecharsHtml)
+	RegisterFilter("truncatewords", filterTruncatewords)
+	RegisterFilter("truncatewords_html", filterTruncatewordsHtml)
+	RegisterFilter("upper", filterUpper)
+	RegisterFilter("urlencode", filterUrlencode)
+	RegisterFilter("urlize", filterUrlize)
+	RegisterFilter("urlizetrunc", filterUrlizetrunc)
+	RegisterFilter("wordcount", filterWordcount)
+	RegisterFilter("wordwrap", filterWordwrap)
+	RegisterFilter("yesno", filterYesno)
+
+	RegisterFilter("float", filterFloat)     // pongo-specific
+	RegisterFilter("integer", filterInteger) // pongo-specific
+}
+
+func filterTruncatecharsHelper(s string, newLen int) string {
+	runes := []rune(s)
+	if newLen < len(runes) {
+		if newLen >= 3 {
+			return fmt.Sprintf("%s...", string(runes[:newLen-3]))
+		}
+		// Not enough space for the ellipsis
+		return string(runes[:newLen])
+	}
+	return string(runes)
+}
+
+func filterTruncateHtmlHelper(value string, new_output *bytes.Buffer, cond func() bool, fn func(c rune, s int, idx int) int, finalize func()) {
+	vLen := len(value)
+	tag_stack := make([]string, 0)
+	idx := 0
+
+	for idx < vLen && !cond() {
+		c, s := utf8.DecodeRuneInString(value[idx:])
+		if c == utf8.RuneError {
+			idx += s
+			continue
+		}
+
+		if c == '<' {
+			new_output.WriteRune(c)
+			idx += s // consume "<"
+
+			if idx+1 < vLen {
+				if value[idx] == '/' {
+					// Close tag
+
+					new_output.WriteString("/")
+
+					tag := ""
+					idx += 1 // consume "/"
+
+					for idx < vLen {
+						c2, size2 := utf8.DecodeRuneInString(value[idx:])
+						if c2 == utf8.RuneError {
+							idx += size2
+							continue
+						}
+
+						// End of tag found
+						if c2 == '>' {
+							idx++ // consume ">"
+							break
+						}
+						tag += string(c2)
+						idx += size2
+					}
+
+					if len(tag_stack) > 0 {
+						// Ideally, the close tag is TOP of tag stack
+						// In malformed HTML, it must not be, so iterate through the stack and remove the tag
+						for i := len(tag_stack) - 1; i >= 0; i-- {
+							if tag_stack[i] == tag {
+								// Found the tag
+								tag_stack[i] = tag_stack[len(tag_stack)-1]
+								tag_stack = tag_stack[:len(tag_stack)-1]
+								break
+							}
+						}
+					}
+
+					new_output.WriteString(tag)
+					new_output.WriteString(">")
+				} else {
+					// Open tag
+
+					tag := ""
+
+					params := false
+					for idx < vLen {
+						c2, size2 := utf8.DecodeRuneInString(value[idx:])
+						if c2 == utf8.RuneError {
+							idx += size2
+							continue
+						}
+
+						new_output.WriteRune(c2)
+
+						// End of tag found
+						if c2 == '>' {
+							idx++ // consume ">"
+							break
+						}
+
+						if !params {
+							if c2 == ' ' {
+								params = true
+							} else {
+								tag += string(c2)
+							}
+						}
+
+						idx += size2
+					}
+
+					// Add tag to stack
+					tag_stack = append(tag_stack, tag)
+				}
+			}
+		} else {
+			idx = fn(c, s, idx)
+		}
+	}
+
+	finalize()
+
+	for i := len(tag_stack) - 1; i >= 0; i-- {
+		tag := tag_stack[i]
+		// Close everything from the regular tag stack
+		new_output.WriteString(fmt.Sprintf("</%s>", tag))
+	}
+}
+
+func filterTruncatechars(in *Value, param *Value) (*Value, *Error) {
+	s := in.String()
+	newLen := param.Integer()
+	return AsValue(filterTruncatecharsHelper(s, newLen)), nil
+}
+
+func filterTruncatecharsHtml(in *Value, param *Value) (*Value, *Error) {
+	value := in.String()
+	newLen := max(param.Integer()-3, 0)
+
+	new_output := bytes.NewBuffer(nil)
+
+	textcounter := 0
+
+	filterTruncateHtmlHelper(value, new_output, func() bool {
+		return textcounter >= newLen
+	}, func(c rune, s int, idx int) int {
+		textcounter++
+		new_output.WriteRune(c)
+
+		return idx + s
+	}, func() {
+		if textcounter >= newLen && textcounter < len(value) {
+			new_output.WriteString("...")
+		}
+	})
+
+	return AsSafeValue(new_output.String()), nil
+}
+
+func filterTruncatewords(in *Value, param *Value) (*Value, *Error) {
+	words := strings.Fields(in.String())
+	n := param.Integer()
+	if n <= 0 {
+		return AsValue(""), nil
+	}
+	nlen := min(len(words), n)
+	out := make([]string, 0, nlen)
+	for i := 0; i < nlen; i++ {
+		out = append(out, words[i])
+	}
+
+	if n < len(words) {
+		out = append(out, "...")
+	}
+
+	return AsValue(strings.Join(out, " ")), nil
+}
+
+func filterTruncatewordsHtml(in *Value, param *Value) (*Value, *Error) {
+	value := in.String()
+	newLen := max(param.Integer(), 0)
+
+	new_output := bytes.NewBuffer(nil)
+
+	wordcounter := 0
+
+	filterTruncateHtmlHelper(value, new_output, func() bool {
+		return wordcounter >= newLen
+	}, func(_ rune, _ int, idx int) int {
+		// Get next word
+		word_found := false
+
+		for idx < len(value) {
+			c2, size2 := utf8.DecodeRuneInString(value[idx:])
+			if c2 == utf8.RuneError {
+				idx += size2
+				continue
+			}
+
+			if c2 == '<' {
+				// HTML tag start, don't consume it
+				return idx
+			}
+
+			new_output.WriteRune(c2)
+			idx += size2
+
+			if c2 == ' ' || c2 == '.' || c2 == ',' || c2 == ';' {
+				// Word ends here, stop capturing it now
+				break
+			} else {
+				word_found = true
+			}
+		}
+
+		if word_found {
+			wordcounter++
+		}
+
+		return idx
+	}, func() {
+		if wordcounter >= newLen {
+			new_output.WriteString("...")
+		}
+	})
+
+	return AsSafeValue(new_output.String()), nil
+}
+
+func filterEscape(in *Value, param *Value) (*Value, *Error) {
+	output := strings.Replace(in.String(), "&", "&amp;", -1)
+	output = strings.Replace(output, ">", "&gt;", -1)
+	output = strings.Replace(output, "<", "&lt;", -1)
+	output = strings.Replace(output, "\"", "&quot;", -1)
+	output = strings.Replace(output, "'", "&#39;", -1)
+	return AsValue(output), nil
+}
+
+func filterSafe(in *Value, param *Value) (*Value, *Error) {
+	return in, nil // nothing to do here, just to keep track of the safe application
+}
+
+func filterEscapejs(in *Value, param *Value) (*Value, *Error) {
+	sin := in.String()
+
+	var b bytes.Buffer
+
+	idx := 0
+	for idx < len(sin) {
+		c, size := utf8.DecodeRuneInString(sin[idx:])
+		if c == utf8.RuneError {
+			idx += size
+			continue
+		}
+
+		if c == '\\' {
+			// Escape seq?
+			if idx+1 < len(sin) {
+				switch sin[idx+1] {
+				case 'r':
+					b.WriteString(fmt.Sprintf(`\u%04X`, '\r'))
+					idx += 2
+					continue
+				case 'n':
+					b.WriteString(fmt.Sprintf(`\u%04X`, '\n'))
+					idx += 2
+					continue
+					/*case '\'':
+						b.WriteString(fmt.Sprintf(`\u%04X`, '\''))
+						idx += 2
+						continue
+					case '"':
+						b.WriteString(fmt.Sprintf(`\u%04X`, '"'))
+						idx += 2
+						continue*/
+				}
+			}
+		}
+
+		if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == ' ' || c == '/' {
+			b.WriteRune(c)
+		} else {
+			b.WriteString(fmt.Sprintf(`\u%04X`, c))
+		}
+
+		idx += size
+	}
+
+	return AsValue(b.String()), nil
+}
+
+func filterAdd(in *Value, param *Value) (*Value, *Error) {
+	if in.IsNumber() && param.IsNumber() {
+		if in.IsFloat() || param.IsFloat() {
+			return AsValue(in.Float() + param.Float()), nil
+		} else {
+			return AsValue(in.Integer() + param.Integer()), nil
+		}
+	}
+	// If in/param is not a number, we're relying on the
+	// Value's String() convertion and just add them both together
+	return AsValue(in.String() + param.String()), nil
+}
+
+func filterAddslashes(in *Value, param *Value) (*Value, *Error) {
+	output := strings.Replace(in.String(), "\\", "\\\\", -1)
+	output = strings.Replace(output, "\"", "\\\"", -1)
+	output = strings.Replace(output, "'", "\\'", -1)
+	return AsValue(output), nil
+}
+
+func filterCut(in *Value, param *Value) (*Value, *Error) {
+	return AsValue(strings.Replace(in.String(), param.String(), "", -1)), nil
+}
+
+func filterLength(in *Value, param *Value) (*Value, *Error) {
+	return AsValue(in.Len()), nil
+}
+
+func filterLengthis(in *Value, param *Value) (*Value, *Error) {
+	return AsValue(in.Len() == param.Integer()), nil
+}
+
+func filterDefault(in *Value, param *Value) (*Value, *Error) {
+	if !in.IsTrue() {
+		return param, nil
+	}
+	return in, nil
+}
+
+func filterDefaultIfNone(in *Value, param *Value) (*Value, *Error) {
+	if in.IsNil() {
+		return param, nil
+	}
+	return in, nil
+}
+
+func filterDivisibleby(in *Value, param *Value) (*Value, *Error) {
+	if param.Integer() == 0 {
+		return AsValue(false), nil
+	}
+	return AsValue(in.Integer()%param.Integer() == 0), nil
+}
+
+func filterFirst(in *Value, param *Value) (*Value, *Error) {
+	if in.CanSlice() && in.Len() > 0 {
+		return in.Index(0), nil
+	}
+	return AsValue(""), nil
+}
+
+func filterFloatformat(in *Value, param *Value) (*Value, *Error) {
+	val := in.Float()
+
+	decimals := -1
+	if !param.IsNil() {
+		// Any argument provided?
+		decimals = param.Integer()
+	}
+
+	// if the argument is not a number (e. g. empty), the default
+	// behaviour is trim the result
+	trim := !param.IsNumber()
+
+	if decimals <= 0 {
+		// argument is negative or zero, so we
+		// want the output being trimmed
+		decimals = -decimals
+		trim = true
+	}
+
+	if trim {
+		// Remove zeroes
+		if float64(int(val)) == val {
+			return AsValue(in.Integer()), nil
+		}
+	}
+
+	return AsValue(strconv.FormatFloat(val, 'f', decimals, 64)), nil
+}
+
+func filterGetdigit(in *Value, param *Value) (*Value, *Error) {
+	i := param.Integer()
+	l := len(in.String()) // do NOT use in.Len() here!
+	if i <= 0 || i > l {
+		return in, nil
+	}
+	return AsValue(in.String()[l-i] - 48), nil
+}
+
+const filterIRIChars = "/#%[]=:;$&()+,!?*@'~"
+
+func filterIriencode(in *Value, param *Value) (*Value, *Error) {
+	var b bytes.Buffer
+
+	sin := in.String()
+	for _, r := range sin {
+		if strings.IndexRune(filterIRIChars, r) >= 0 {
+			b.WriteRune(r)
+		} else {
+			b.WriteString(url.QueryEscape(string(r)))
+		}
+	}
+
+	return AsValue(b.String()), nil
+}
+
+func filterJoin(in *Value, param *Value) (*Value, *Error) {
+	if !in.CanSlice() {
+		return in, nil
+	}
+	sep := param.String()
+	sl := make([]string, 0, in.Len())
+	for i := 0; i < in.Len(); i++ {
+		sl = append(sl, in.Index(i).String())
+	}
+	return AsValue(strings.Join(sl, sep)), nil
+}
+
+func filterLast(in *Value, param *Value) (*Value, *Error) {
+	if in.CanSlice() && in.Len() > 0 {
+		return in.Index(in.Len() - 1), nil
+	}
+	return AsValue(""), nil
+}
+
+func filterUpper(in *Value, param *Value) (*Value, *Error) {
+	return AsValue(strings.ToUpper(in.String())), nil
+}
+
+func filterLower(in *Value, param *Value) (*Value, *Error) {
+	return AsValue(strings.ToLower(in.String())), nil
+}
+
+func filterMakelist(in *Value, param *Value) (*Value, *Error) {
+	s := in.String()
+	result := make([]string, 0, len(s))
+	for _, c := range s {
+		result = append(result, string(c))
+	}
+	return AsValue(result), nil
+}
+
+func filterCapfirst(in *Value, param *Value) (*Value, *Error) {
+	if in.Len() <= 0 {
+		return AsValue(""), nil
+	}
+	t := in.String()
+	r, size := utf8.DecodeRuneInString(t)
+	return AsValue(strings.ToUpper(string(r)) + t[size:]), nil
+}
+
+func filterCenter(in *Value, param *Value) (*Value, *Error) {
+	width := param.Integer()
+	slen := in.Len()
+	if width <= slen {
+		return in, nil
+	}
+
+	spaces := width - slen
+	left := spaces/2 + spaces%2
+	right := spaces / 2
+
+	return AsValue(fmt.Sprintf("%s%s%s", strings.Repeat(" ", left),
+		in.String(), strings.Repeat(" ", right))), nil
+}
+
+func filterDate(in *Value, param *Value) (*Value, *Error) {
+	t, is_time := in.Interface().(time.Time)
+	if !is_time {
+		return nil, &Error{
+			Sender:   "filter:date",
+			ErrorMsg: "Filter input argument must be of type 'time.Time'.",
+		}
+	}
+	return AsValue(t.Format(param.String())), nil
+}
+
+func filterFloat(in *Value, param *Value) (*Value, *Error) {
+	return AsValue(in.Float()), nil
+}
+
+func filterInteger(in *Value, param *Value) (*Value, *Error) {
+	return AsValue(in.Integer()), nil
+}
+
+func filterLinebreaks(in *Value, param *Value) (*Value, *Error) {
+	if in.Len() == 0 {
+		return in, nil
+	}
+
+	var b bytes.Buffer
+
+	// Newline = <br />
+	// Double newline = <p>...</p>
+	lines := strings.Split(in.String(), "\n")
+	lenlines := len(lines)
+
+	opened := false
+
+	for idx, line := range lines {
+
+		if !opened {
+			b.WriteString("<p>")
+			opened = true
+		}
+
+		b.WriteString(line)
+
+		if idx < lenlines-1 && strings.TrimSpace(lines[idx]) != "" {
+			// We've not reached the end
+			if strings.TrimSpace(lines[idx+1]) == "" {
+				// Next line is empty
+				if opened {
+					b.WriteString("</p>")
+					opened = false
+				}
+			} else {
+				b.WriteString("<br />")
+			}
+		}
+	}
+
+	if opened {
+		b.WriteString("</p>")
+	}
+
+	return AsValue(b.String()), nil
+}
+
+func filterLinebreaksbr(in *Value, param *Value) (*Value, *Error) {
+	return AsValue(strings.Replace(in.String(), "\n", "<br />", -1)), nil
+}
+
+func filterLinenumbers(in *Value, param *Value) (*Value, *Error) {
+	lines := strings.Split(in.String(), "\n")
+	output := make([]string, 0, len(lines))
+	for idx, line := range lines {
+		output = append(output, fmt.Sprintf("%d. %s", idx+1, line))
+	}
+	return AsValue(strings.Join(output, "\n")), nil
+}
+
+func filterLjust(in *Value, param *Value) (*Value, *Error) {
+	times := param.Integer() - in.Len()
+	if times < 0 {
+		times = 0
+	}
+	return AsValue(fmt.Sprintf("%s%s", in.String(), strings.Repeat(" ", times))), nil
+}
+
+func filterUrlencode(in *Value, param *Value) (*Value, *Error) {
+	return AsValue(url.QueryEscape(in.String())), nil
+}
+
+// TODO: This regexp could do some work
+var filterUrlizeURLRegexp = regexp.MustCompile(`((((http|https)://)|www\.|((^|[ ])[0-9A-Za-z_\-]+(\.com|\.net|\.org|\.info|\.biz|\.de))))(?U:.*)([ ]+|$)`)
+var filterUrlizeEmailRegexp = regexp.MustCompile(`(\w+@\w+\.\w{2,4})`)
+
+func filterUrlizeHelper(input string, autoescape bool, trunc int) string {
+	sout := filterUrlizeURLRegexp.ReplaceAllStringFunc(input, func(raw_url string) string {
+		var prefix string
+		var suffix string
+		if strings.HasPrefix(raw_url, " ") {
+			prefix = " "
+		}
+		if strings.HasSuffix(raw_url, " ") {
+			suffix = " "
+		}
+
+		raw_url = strings.TrimSpace(raw_url)
+
+		t, err := ApplyFilter("iriencode", AsValue(raw_url), nil)
+		if err != nil {
+			panic(err)
+		}
+		url := t.String()
+
+		if !strings.HasPrefix(url, "http") {
+			url = fmt.Sprintf("http://%s", url)
+		}
+
+		title := raw_url
+
+		if trunc > 3 && len(title) > trunc {
+			title = fmt.Sprintf("%s...", title[:trunc-3])
+		}
+
+		if autoescape {
+			t, err := ApplyFilter("escape", AsValue(title), nil)
+			if err != nil {
+				panic(err)
+			}
+			title = t.String()
+		}
+
+		return fmt.Sprintf(`%s<a href="%s" rel="nofollow">%s</a>%s`, prefix, url, title, suffix)
+	})
+
+	sout = filterUrlizeEmailRegexp.ReplaceAllStringFunc(sout, func(mail string) string {
+
+		title := mail
+
+		if trunc > 3 && len(title) > trunc {
+			title = fmt.Sprintf("%s...", title[:trunc-3])
+		}
+
+		return fmt.Sprintf(`<a href="mailto:%s">%s</a>`, mail, title)
+	})
+
+	return sout
+}
+
+func filterUrlize(in *Value, param *Value) (*Value, *Error) {
+	autoescape := true
+	if param.IsBool() {
+		autoescape = param.Bool()
+	}
+
+	return AsValue(filterUrlizeHelper(in.String(), autoescape, -1)), nil
+}
+
+func filterUrlizetrunc(in *Value, param *Value) (*Value, *Error) {
+	return AsValue(filterUrlizeHelper(in.String(), true, param.Integer())), nil
+}
+
+func filterStringformat(in *Value, param *Value) (*Value, *Error) {
+	return AsValue(fmt.Sprintf(param.String(), in.Interface())), nil
+}
+
+var re_striptags = regexp.MustCompile("<[^>]*?>")
+
+func filterStriptags(in *Value, param *Value) (*Value, *Error) {
+	s := in.String()
+
+	// Strip all tags
+	s = re_striptags.ReplaceAllString(s, "")
+
+	return AsValue(strings.TrimSpace(s)), nil
+}
+
+// https://en.wikipedia.org/wiki/Phoneword
+var filterPhone2numericMap = map[string]string{
+	"a": "2", "b": "2", "c": "2", "d": "3", "e": "3", "f": "3", "g": "4", "h": "4", "i": "4", "j": "5", "k": "5",
+	"l": "5", "m": "6", "n": "6", "o": "6", "p": "7", "q": "7", "r": "7", "s": "7", "t": "8", "u": "8", "v": "8",
+	"w": "9", "x": "9", "y": "9", "z": "9",
+}
+
+func filterPhone2numeric(in *Value, param *Value) (*Value, *Error) {
+	sin := in.String()
+	for k, v := range filterPhone2numericMap {
+		sin = strings.Replace(sin, k, v, -1)
+		sin = strings.Replace(sin, strings.ToUpper(k), v, -1)
+	}
+	return AsValue(sin), nil
+}
+
+func filterPluralize(in *Value, param *Value) (*Value, *Error) {
+	if in.IsNumber() {
+		// Works only on numbers
+		if param.Len() > 0 {
+			endings := strings.Split(param.String(), ",")
+			if len(endings) > 2 {
+				return nil, &Error{
+					Sender:   "filter:pluralize",
+					ErrorMsg: "You cannot pass more than 2 arguments to filter 'pluralize'.",
+				}
+			}
+			if len(endings) == 1 {
+				// 1 argument
+				if in.Integer() != 1 {
+					return AsValue(endings[0]), nil
+				}
+			} else {
+				if in.Integer() != 1 {
+					// 2 arguments
+					return AsValue(endings[1]), nil
+				}
+				return AsValue(endings[0]), nil
+			}
+		} else {
+			if in.Integer() != 1 {
+				// return default 's'
+				return AsValue("s"), nil
+			}
+		}
+
+		return AsValue(""), nil
+	} else {
+		return nil, &Error{
+			Sender:   "filter:pluralize",
+			ErrorMsg: "Filter 'pluralize' does only work on numbers.",
+		}
+	}
+}
+
+func filterRandom(in *Value, param *Value) (*Value, *Error) {
+	if !in.CanSlice() || in.Len() <= 0 {
+		return in, nil
+	}
+	i := rand.Intn(in.Len())
+	return in.Index(i), nil
+}
+
+func filterRemovetags(in *Value, param *Value) (*Value, *Error) {
+	s := in.String()
+	tags := strings.Split(param.String(), ",")
+
+	// Strip only specific tags
+	for _, tag := range tags {
+		re := regexp.MustCompile(fmt.Sprintf("</?%s/?>", tag))
+		s = re.ReplaceAllString(s, "")
+	}
+
+	return AsValue(strings.TrimSpace(s)), nil
+}
+
+func filterRjust(in *Value, param *Value) (*Value, *Error) {
+	return AsValue(fmt.Sprintf(fmt.Sprintf("%%%ds", param.Integer()), in.String())), nil
+}
+
+func filterSlice(in *Value, param *Value) (*Value, *Error) {
+	comp := strings.Split(param.String(), ":")
+	if len(comp) != 2 {
+		return nil, &Error{
+			Sender:   "filter:slice",
+			ErrorMsg: "Slice string must have the format 'from:to' [from/to can be omitted, but the ':' is required]",
+		}
+	}
+
+	if !in.CanSlice() {
+		return in, nil
+	}
+
+	from := AsValue(comp[0]).Integer()
+	to := in.Len()
+
+	if from > to {
+		from = to
+	}
+
+	vto := AsValue(comp[1]).Integer()
+	if vto >= from && vto <= in.Len() {
+		to = vto
+	}
+
+	return in.Slice(from, to), nil
+}
+
+func filterTitle(in *Value, param *Value) (*Value, *Error) {
+	if !in.IsString() {
+		return AsValue(""), nil
+	}
+	return AsValue(strings.Title(strings.ToLower(in.String()))), nil
+}
+
+func filterWordcount(in *Value, param *Value) (*Value, *Error) {
+	return AsValue(len(strings.Fields(in.String()))), nil
+}
+
+func filterWordwrap(in *Value, param *Value) (*Value, *Error) {
+	words := strings.Fields(in.String())
+	words_len := len(words)
+	wrap_at := param.Integer()
+	if wrap_at <= 0 {
+		return in, nil
+	}
+
+	linecount := words_len/wrap_at + words_len%wrap_at
+	lines := make([]string, 0, linecount)
+	for i := 0; i < linecount; i++ {
+		lines = append(lines, strings.Join(words[wrap_at*i:min(wrap_at*(i+1), words_len)], " "))
+	}
+	return AsValue(strings.Join(lines, "\n")), nil
+}
+
+func filterYesno(in *Value, param *Value) (*Value, *Error) {
+	choices := map[int]string{
+		0: "yes",
+		1: "no",
+		2: "maybe",
+	}
+	param_string := param.String()
+	custom_choices := strings.Split(param_string, ",")
+	if len(param_string) > 0 {
+		if len(custom_choices) > 3 {
+			return nil, &Error{
+				Sender:   "filter:yesno",
+				ErrorMsg: fmt.Sprintf("You cannot pass more than 3 options to the 'yesno'-filter (got: '%s').", param_string),
+			}
+		}
+		if len(custom_choices) < 2 {
+			return nil, &Error{
+				Sender:   "filter:yesno",
+				ErrorMsg: fmt.Sprintf("You must pass either no or at least 2 arguments to the 'yesno'-filter (got: '%s').", param_string),
+			}
+		}
+
+		// Map to the options now
+		choices[0] = custom_choices[0]
+		choices[1] = custom_choices[1]
+		if len(custom_choices) == 3 {
+			choices[2] = custom_choices[2]
+		}
+	}
+
+	// maybe
+	if in.IsNil() {
+		return AsValue(choices[2]), nil
+	}
+
+	// yes
+	if in.IsTrue() {
+		return AsValue(choices[0]), nil
+	}
+
+	// no
+	return AsValue(choices[1]), nil
+}

+ 15 - 0
vendor/github.com/flosch/pongo2/helpers.go

@@ -0,0 +1,15 @@
+package pongo2
+
+func max(a, b int) int {
+	if a > b {
+		return a
+	}
+	return b
+}
+
+func min(a, b int) int {
+	if a < b {
+		return a
+	}
+	return b
+}

+ 421 - 0
vendor/github.com/flosch/pongo2/lexer.go

@@ -0,0 +1,421 @@
+package pongo2
+
+import (
+	"fmt"
+	"strings"
+	"unicode/utf8"
+)
+
+const (
+	TokenError = iota
+	EOF
+
+	TokenHTML
+
+	TokenKeyword
+	TokenIdentifier
+	TokenString
+	TokenNumber
+	TokenSymbol
+)
+
+var (
+	tokenSpaceChars                = " \n\r\t"
+	tokenIdentifierChars           = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_"
+	tokenIdentifierCharsWithDigits = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789"
+	tokenDigits                    = "0123456789"
+
+	// Available symbols in pongo2 (within filters/tag)
+	TokenSymbols = []string{
+		// 3-Char symbols
+
+		// 2-Char symbols
+		"==", ">=", "<=", "&&", "||", "{{", "}}", "{%", "%}", "!=", "<>",
+
+		// 1-Char symbol
+		"(", ")", "+", "-", "*", "<", ">", "/", "^", ",", ".", "!", "|", ":", "=", "%",
+	}
+
+	// Available keywords in pongo2
+	TokenKeywords = []string{"in", "and", "or", "not", "true", "false", "as", "export"}
+)
+
+type TokenType int
+type Token struct {
+	Filename string
+	Typ      TokenType
+	Val      string
+	Line     int
+	Col      int
+}
+
+type lexerStateFn func() lexerStateFn
+type lexer struct {
+	name      string
+	input     string
+	start     int // start pos of the item
+	pos       int // current pos
+	width     int // width of last rune
+	tokens    []*Token
+	errored   bool
+	startline int
+	startcol  int
+	line      int
+	col       int
+
+	in_verbatim   bool
+	verbatim_name string
+}
+
+func (t *Token) String() string {
+	val := t.Val
+	if len(val) > 1000 {
+		val = fmt.Sprintf("%s...%s", val[:10], val[len(val)-5:len(val)])
+	}
+
+	typ := ""
+	switch t.Typ {
+	case TokenHTML:
+		typ = "HTML"
+	case TokenError:
+		typ = "Error"
+	case TokenIdentifier:
+		typ = "Identifier"
+	case TokenKeyword:
+		typ = "Keyword"
+	case TokenNumber:
+		typ = "Number"
+	case TokenString:
+		typ = "String"
+	case TokenSymbol:
+		typ = "Symbol"
+	default:
+		typ = "Unknown"
+	}
+
+	return fmt.Sprintf("<Token Typ=%s (%d) Val='%s' Line=%d Col=%d>",
+		typ, t.Typ, val, t.Line, t.Col)
+}
+
+func lex(name string, input string) ([]*Token, *Error) {
+	l := &lexer{
+		name:      name,
+		input:     input,
+		tokens:    make([]*Token, 0, 100),
+		line:      1,
+		col:       1,
+		startline: 1,
+		startcol:  1,
+	}
+	l.run()
+	if l.errored {
+		errtoken := l.tokens[len(l.tokens)-1]
+		return nil, &Error{
+			Filename: name,
+			Line:     errtoken.Line,
+			Column:   errtoken.Col,
+			Sender:   "lexer",
+			ErrorMsg: errtoken.Val,
+		}
+	}
+	return l.tokens, nil
+}
+
+func (l *lexer) value() string {
+	return l.input[l.start:l.pos]
+}
+
+func (l *lexer) length() int {
+	return l.pos - l.start
+}
+
+func (l *lexer) emit(t TokenType) {
+	tok := &Token{
+		Filename: l.name,
+		Typ:      t,
+		Val:      l.value(),
+		Line:     l.startline,
+		Col:      l.startcol,
+	}
+
+	if t == TokenString {
+		// Escape sequence \" in strings
+		tok.Val = strings.Replace(tok.Val, `\"`, `"`, -1)
+		tok.Val = strings.Replace(tok.Val, `\\`, `\`, -1)
+	}
+
+	l.tokens = append(l.tokens, tok)
+	l.start = l.pos
+	l.startline = l.line
+	l.startcol = l.col
+}
+
+func (l *lexer) next() rune {
+	if l.pos >= len(l.input) {
+		l.width = 0
+		return EOF
+	}
+	r, w := utf8.DecodeRuneInString(l.input[l.pos:])
+	l.width = w
+	l.pos += l.width
+	l.col += l.width
+	return r
+}
+
+func (l *lexer) backup() {
+	l.pos -= l.width
+	l.col -= l.width
+}
+
+func (l *lexer) peek() rune {
+	r := l.next()
+	l.backup()
+	return r
+}
+
+func (l *lexer) ignore() {
+	l.start = l.pos
+	l.startline = l.line
+	l.startcol = l.col
+}
+
+func (l *lexer) accept(what string) bool {
+	if strings.IndexRune(what, l.next()) >= 0 {
+		return true
+	}
+	l.backup()
+	return false
+}
+
+func (l *lexer) acceptRun(what string) {
+	for strings.IndexRune(what, l.next()) >= 0 {
+	}
+	l.backup()
+}
+
+func (l *lexer) errorf(format string, args ...interface{}) lexerStateFn {
+	t := &Token{
+		Filename: l.name,
+		Typ:      TokenError,
+		Val:      fmt.Sprintf(format, args...),
+		Line:     l.startline,
+		Col:      l.startcol,
+	}
+	l.tokens = append(l.tokens, t)
+	l.errored = true
+	l.startline = l.line
+	l.startcol = l.col
+	return nil
+}
+
+func (l *lexer) eof() bool {
+	return l.start >= len(l.input)-1
+}
+
+func (l *lexer) run() {
+	for {
+		// TODO: Support verbatim tag names
+		// https://docs.djangoproject.com/en/dev/ref/templates/builtins/#verbatim
+		if l.in_verbatim {
+			name := l.verbatim_name
+			if name != "" {
+				name += " "
+			}
+			if strings.HasPrefix(l.input[l.pos:], fmt.Sprintf("{%% endverbatim %s%%}", name)) { // end verbatim
+				if l.pos > l.start {
+					l.emit(TokenHTML)
+				}
+				w := len("{% endverbatim %}")
+				l.pos += w
+				l.col += w
+				l.ignore()
+				l.in_verbatim = false
+			}
+		} else if strings.HasPrefix(l.input[l.pos:], "{% verbatim %}") { // tag
+			if l.pos > l.start {
+				l.emit(TokenHTML)
+			}
+			l.in_verbatim = true
+			w := len("{% verbatim %}")
+			l.pos += w
+			l.col += w
+			l.ignore()
+		}
+
+		if !l.in_verbatim {
+			// Ignore single-line comments {# ... #}
+			if strings.HasPrefix(l.input[l.pos:], "{#") {
+				if l.pos > l.start {
+					l.emit(TokenHTML)
+				}
+
+				l.pos += 2 // pass '{#'
+				l.col += 2
+
+				for {
+					switch l.peek() {
+					case EOF:
+						l.errorf("Single-line comment not closed.")
+						return
+					case '\n':
+						l.errorf("Newline not permitted in a single-line comment.")
+						return
+					}
+
+					if strings.HasPrefix(l.input[l.pos:], "#}") {
+						l.pos += 2 // pass '#}'
+						l.col += 2
+						break
+					}
+
+					l.next()
+				}
+				l.ignore() // ignore whole comment
+
+				// Comment skipped
+				continue // next token
+			}
+
+			if strings.HasPrefix(l.input[l.pos:], "{{") || // variable
+				strings.HasPrefix(l.input[l.pos:], "{%") { // tag
+				if l.pos > l.start {
+					l.emit(TokenHTML)
+				}
+				l.tokenize()
+				if l.errored {
+					return
+				}
+				continue
+			}
+		}
+
+		switch l.peek() {
+		case '\n':
+			l.line++
+			l.col = 0
+		}
+		if l.next() == EOF {
+			break
+		}
+	}
+
+	if l.pos > l.start {
+		l.emit(TokenHTML)
+	}
+
+	if l.in_verbatim {
+		l.errorf("verbatim-tag not closed, got EOF.")
+	}
+}
+
+func (l *lexer) tokenize() {
+	for state := l.stateCode; state != nil; {
+		state = state()
+	}
+}
+
+func (l *lexer) stateCode() lexerStateFn {
+outer_loop:
+	for {
+		switch {
+		case l.accept(tokenSpaceChars):
+			if l.value() == "\n" {
+				return l.errorf("Newline not allowed within tag/variable.")
+			}
+			l.ignore()
+			continue
+		case l.accept(tokenIdentifierChars):
+			return l.stateIdentifier
+		case l.accept(tokenDigits):
+			return l.stateNumber
+		case l.accept(`"`):
+			return l.stateString
+		}
+
+		// Check for symbol
+		for _, sym := range TokenSymbols {
+			if strings.HasPrefix(l.input[l.start:], sym) {
+				l.pos += len(sym)
+				l.col += l.length()
+				l.emit(TokenSymbol)
+
+				if sym == "%}" || sym == "}}" {
+					// Tag/variable end, return after emit
+					return nil
+				}
+
+				continue outer_loop
+			}
+		}
+
+		if l.pos < len(l.input) {
+			return l.errorf("Unknown character: %q (%d)", l.peek(), l.peek())
+		}
+
+		break
+	}
+
+	// Normal shut down
+	return nil
+}
+
+func (l *lexer) stateIdentifier() lexerStateFn {
+	l.acceptRun(tokenIdentifierChars)
+	l.acceptRun(tokenIdentifierCharsWithDigits)
+	for _, kw := range TokenKeywords {
+		if kw == l.value() {
+			l.emit(TokenKeyword)
+			return l.stateCode
+		}
+	}
+	l.emit(TokenIdentifier)
+	return l.stateCode
+}
+
+func (l *lexer) stateNumber() lexerStateFn {
+	l.acceptRun(tokenDigits)
+	/*
+		Maybe context-sensitive number lexing?
+		* comments.0.Text // first comment
+		* usercomments.1.0 // second user, first comment
+		* if (score >= 8.5) // 8.5 as a number
+
+		if l.peek() == '.' {
+			l.accept(".")
+			if !l.accept(tokenDigits) {
+				return l.errorf("Malformed number.")
+			}
+			l.acceptRun(tokenDigits)
+		}
+	*/
+	l.emit(TokenNumber)
+	return l.stateCode
+}
+
+func (l *lexer) stateString() lexerStateFn {
+	l.ignore()
+	l.startcol -= 1 // we're starting the position at the first "
+	for !l.accept(`"`) {
+		switch l.next() {
+		case '\\':
+			// escape sequence
+			switch l.peek() {
+			case '"', '\\':
+				l.next()
+			default:
+				return l.errorf("Unknown escape sequence: \\%c", l.peek())
+			}
+		case EOF:
+			return l.errorf("Unexpected EOF, string not closed.")
+		case '\n':
+			return l.errorf("Newline in string is not allowed.")
+		}
+	}
+	l.backup()
+	l.emit(TokenString)
+
+	l.next()
+	l.ignore()
+
+	return l.stateCode
+}

+ 20 - 0
vendor/github.com/flosch/pongo2/nodes.go

@@ -0,0 +1,20 @@
+package pongo2
+
+import (
+	"bytes"
+)
+
+// The root document
+type nodeDocument struct {
+	Nodes []INode
+}
+
+func (doc *nodeDocument) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	for _, n := range doc.Nodes {
+		err := n.Execute(ctx, buffer)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}

+ 14 - 0
vendor/github.com/flosch/pongo2/nodes_html.go

@@ -0,0 +1,14 @@
+package pongo2
+
+import (
+	"bytes"
+)
+
+type nodeHTML struct {
+	token *Token
+}
+
+func (n *nodeHTML) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	buffer.WriteString(n.token.Val)
+	return nil
+}

+ 20 - 0
vendor/github.com/flosch/pongo2/nodes_wrapper.go

@@ -0,0 +1,20 @@
+package pongo2
+
+import (
+	"bytes"
+)
+
+type NodeWrapper struct {
+	Endtag string
+	nodes  []INode
+}
+
+func (wrapper *NodeWrapper) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	for _, n := range wrapper.nodes {
+		err := n.Execute(ctx, buffer)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}

+ 267 - 0
vendor/github.com/flosch/pongo2/parser.go

@@ -0,0 +1,267 @@
+package pongo2
+
+import (
+	"bytes"
+	"fmt"
+	"strings"
+)
+
+type INode interface {
+	Execute(*ExecutionContext, *bytes.Buffer) *Error
+}
+
+type IEvaluator interface {
+	INode
+	GetPositionToken() *Token
+	Evaluate(*ExecutionContext) (*Value, *Error)
+	FilterApplied(name string) bool
+}
+
+// The parser provides you a comprehensive and easy tool to
+// work with the template document and arguments provided by
+// the user for your custom tag.
+//
+// The parser works on a token list which will be provided by pongo2.
+// A token is a unit you can work with. Tokens are either of type identifier,
+// string, number, keyword, HTML or symbol.
+//
+// (See Token's documentation for more about tokens)
+type Parser struct {
+	name       string
+	idx        int
+	tokens     []*Token
+	last_token *Token
+
+	// if the parser parses a template document, here will be
+	// a reference to it (needed to access the template through Tags)
+	template *Template
+}
+
+// Creates a new parser to parse tokens.
+// Used inside pongo2 to parse documents and to provide an easy-to-use
+// parser for tag authors
+func newParser(name string, tokens []*Token, template *Template) *Parser {
+	p := &Parser{
+		name:     name,
+		tokens:   tokens,
+		template: template,
+	}
+	if len(tokens) > 0 {
+		p.last_token = tokens[len(tokens)-1]
+	}
+	return p
+}
+
+// Consume one token. It will be gone forever.
+func (p *Parser) Consume() {
+	p.ConsumeN(1)
+}
+
+// Consume N tokens. They will be gone forever.
+func (p *Parser) ConsumeN(count int) {
+	p.idx += count
+}
+
+// Returns the current token.
+func (p *Parser) Current() *Token {
+	return p.Get(p.idx)
+}
+
+// Returns the CURRENT token if the given type matches.
+// Consumes this token on success.
+func (p *Parser) MatchType(typ TokenType) *Token {
+	if t := p.PeekType(typ); t != nil {
+		p.Consume()
+		return t
+	}
+	return nil
+}
+
+// Returns the CURRENT token if the given type AND value matches.
+// Consumes this token on success.
+func (p *Parser) Match(typ TokenType, val string) *Token {
+	if t := p.Peek(typ, val); t != nil {
+		p.Consume()
+		return t
+	}
+	return nil
+}
+
+// Returns the CURRENT token if the given type AND *one* of
+// the given values matches.
+// Consumes this token on success.
+func (p *Parser) MatchOne(typ TokenType, vals ...string) *Token {
+	for _, val := range vals {
+		if t := p.Peek(typ, val); t != nil {
+			p.Consume()
+			return t
+		}
+	}
+	return nil
+}
+
+// Returns the CURRENT token if the given type matches.
+// It DOES NOT consume the token.
+func (p *Parser) PeekType(typ TokenType) *Token {
+	return p.PeekTypeN(0, typ)
+}
+
+// Returns the CURRENT token if the given type AND value matches.
+// It DOES NOT consume the token.
+func (p *Parser) Peek(typ TokenType, val string) *Token {
+	return p.PeekN(0, typ, val)
+}
+
+// Returns the CURRENT token if the given type AND *one* of
+// the given values matches.
+// It DOES NOT consume the token.
+func (p *Parser) PeekOne(typ TokenType, vals ...string) *Token {
+	for _, v := range vals {
+		t := p.PeekN(0, typ, v)
+		if t != nil {
+			return t
+		}
+	}
+	return nil
+}
+
+// Returns the tokens[current position + shift] token if the
+// given type AND value matches for that token.
+// DOES NOT consume the token.
+func (p *Parser) PeekN(shift int, typ TokenType, val string) *Token {
+	t := p.Get(p.idx + shift)
+	if t != nil {
+		if t.Typ == typ && t.Val == val {
+			return t
+		}
+	}
+	return nil
+}
+
+// Returns the tokens[current position + shift] token if the given type matches.
+// DOES NOT consume the token for that token.
+func (p *Parser) PeekTypeN(shift int, typ TokenType) *Token {
+	t := p.Get(p.idx + shift)
+	if t != nil {
+		if t.Typ == typ {
+			return t
+		}
+	}
+	return nil
+}
+
+// Returns the UNCONSUMED token count.
+func (p *Parser) Remaining() int {
+	return len(p.tokens) - p.idx
+}
+
+// Returns the total token count.
+func (p *Parser) Count() int {
+	return len(p.tokens)
+}
+
+// Returns tokens[i] or NIL (if i >= len(tokens))
+func (p *Parser) Get(i int) *Token {
+	if i < len(p.tokens) {
+		return p.tokens[i]
+	}
+	return nil
+}
+
+// Returns tokens[current-position + shift] or NIL
+// (if (current-position + i) >= len(tokens))
+func (p *Parser) GetR(shift int) *Token {
+	i := p.idx + shift
+	return p.Get(i)
+}
+
+// Produces a nice error message and returns an error-object.
+// The 'token'-argument is optional. If provided, it will take
+// the token's position information. If not provided, it will
+// automatically use the CURRENT token's position information.
+func (p *Parser) Error(msg string, token *Token) *Error {
+	if token == nil {
+		// Set current token
+		token = p.Current()
+		if token == nil {
+			// Set to last token
+			if len(p.tokens) > 0 {
+				token = p.tokens[len(p.tokens)-1]
+			}
+		}
+	}
+	var line, col int
+	if token != nil {
+		line = token.Line
+		col = token.Col
+	}
+	return &Error{
+		Template: p.template,
+		Filename: p.name,
+		Sender:   "parser",
+		Line:     line,
+		Column:   col,
+		Token:    token,
+		ErrorMsg: msg,
+	}
+}
+
+// Wraps all nodes between starting tag and "{% endtag %}" and provides
+// one simple interface to execute the wrapped nodes.
+// It returns a parser to process provided arguments to the tag.
+func (p *Parser) WrapUntilTag(names ...string) (*NodeWrapper, *Parser, *Error) {
+	wrapper := &NodeWrapper{}
+
+	tagArgs := make([]*Token, 0)
+
+	for p.Remaining() > 0 {
+		// New tag, check whether we have to stop wrapping here
+		if p.Peek(TokenSymbol, "{%") != nil {
+			tag_ident := p.PeekTypeN(1, TokenIdentifier)
+
+			if tag_ident != nil {
+				// We've found a (!) end-tag
+
+				found := false
+				for _, n := range names {
+					if tag_ident.Val == n {
+						found = true
+						break
+					}
+				}
+
+				// We only process the tag if we've found an end tag
+				if found {
+					// Okay, endtag found.
+					p.ConsumeN(2) // '{%' tagname
+
+					for {
+						if p.Match(TokenSymbol, "%}") != nil {
+							// Okay, end the wrapping here
+							wrapper.Endtag = tag_ident.Val
+							return wrapper, newParser(p.template.name, tagArgs, p.template), nil
+						} else {
+							t := p.Current()
+							p.Consume()
+							if t == nil {
+								return nil, nil, p.Error("Unexpected EOF.", p.last_token)
+							}
+							tagArgs = append(tagArgs, t)
+						}
+					}
+				}
+			}
+
+		}
+
+		// Otherwise process next element to be wrapped
+		node, err := p.parseDocElement()
+		if err != nil {
+			return nil, nil, err
+		}
+		wrapper.nodes = append(wrapper.nodes, node)
+	}
+
+	return nil, nil, p.Error(fmt.Sprintf("Unexpected EOF, expected tag %s.", strings.Join(names, " or ")),
+		p.last_token)
+}

+ 54 - 0
vendor/github.com/flosch/pongo2/parser_document.go

@@ -0,0 +1,54 @@
+package pongo2
+
+// Doc = { ( Filter | Tag | HTML ) }
+func (p *Parser) parseDocElement() (INode, *Error) {
+	t := p.Current()
+
+	switch t.Typ {
+	case TokenHTML:
+		p.Consume() // consume HTML element
+		return &nodeHTML{token: t}, nil
+	case TokenSymbol:
+		switch t.Val {
+		case "{{":
+			// parse variable
+			variable, err := p.parseVariableElement()
+			if err != nil {
+				return nil, err
+			}
+			return variable, nil
+		case "{%":
+			// parse tag
+			tag, err := p.parseTagElement()
+			if err != nil {
+				return nil, err
+			}
+			return tag, nil
+		}
+	}
+	return nil, p.Error("Unexpected token (only HTML/tags/filters in templates allowed)", t)
+}
+
+func (tpl *Template) parse() *Error {
+	tpl.parser = newParser(tpl.name, tpl.tokens, tpl)
+	doc, err := tpl.parser.parseDocument()
+	if err != nil {
+		return err
+	}
+	tpl.root = doc
+	return nil
+}
+
+func (p *Parser) parseDocument() (*nodeDocument, *Error) {
+	doc := &nodeDocument{}
+
+	for p.Remaining() > 0 {
+		node, err := p.parseDocElement()
+		if err != nil {
+			return nil, err
+		}
+		doc.Nodes = append(doc.Nodes, node)
+	}
+
+	return doc, nil
+}

+ 499 - 0
vendor/github.com/flosch/pongo2/parser_expression.go

@@ -0,0 +1,499 @@
+package pongo2
+
+import (
+	"bytes"
+	"fmt"
+	"math"
+)
+
+type Expression struct {
+	// TODO: Add location token?
+	expr1    IEvaluator
+	expr2    IEvaluator
+	op_token *Token
+}
+
+type relationalExpression struct {
+	// TODO: Add location token?
+	expr1    IEvaluator
+	expr2    IEvaluator
+	op_token *Token
+}
+
+type simpleExpression struct {
+	negate        bool
+	negative_sign bool
+	term1         IEvaluator
+	term2         IEvaluator
+	op_token      *Token
+}
+
+type term struct {
+	// TODO: Add location token?
+	factor1  IEvaluator
+	factor2  IEvaluator
+	op_token *Token
+}
+
+type power struct {
+	// TODO: Add location token?
+	power1 IEvaluator
+	power2 IEvaluator
+}
+
+func (expr *Expression) FilterApplied(name string) bool {
+	return expr.expr1.FilterApplied(name) && (expr.expr2 == nil ||
+		(expr.expr2 != nil && expr.expr2.FilterApplied(name)))
+}
+
+func (expr *relationalExpression) FilterApplied(name string) bool {
+	return expr.expr1.FilterApplied(name) && (expr.expr2 == nil ||
+		(expr.expr2 != nil && expr.expr2.FilterApplied(name)))
+}
+
+func (expr *simpleExpression) FilterApplied(name string) bool {
+	return expr.term1.FilterApplied(name) && (expr.term2 == nil ||
+		(expr.term2 != nil && expr.term2.FilterApplied(name)))
+}
+
+func (t *term) FilterApplied(name string) bool {
+	return t.factor1.FilterApplied(name) && (t.factor2 == nil ||
+		(t.factor2 != nil && t.factor2.FilterApplied(name)))
+}
+
+func (p *power) FilterApplied(name string) bool {
+	return p.power1.FilterApplied(name) && (p.power2 == nil ||
+		(p.power2 != nil && p.power2.FilterApplied(name)))
+}
+
+func (expr *Expression) GetPositionToken() *Token {
+	return expr.expr1.GetPositionToken()
+}
+
+func (expr *relationalExpression) GetPositionToken() *Token {
+	return expr.expr1.GetPositionToken()
+}
+
+func (expr *simpleExpression) GetPositionToken() *Token {
+	return expr.term1.GetPositionToken()
+}
+
+func (expr *term) GetPositionToken() *Token {
+	return expr.factor1.GetPositionToken()
+}
+
+func (expr *power) GetPositionToken() *Token {
+	return expr.power1.GetPositionToken()
+}
+
+func (expr *Expression) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	value, err := expr.Evaluate(ctx)
+	if err != nil {
+		return err
+	}
+	buffer.WriteString(value.String())
+	return nil
+}
+
+func (expr *relationalExpression) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	value, err := expr.Evaluate(ctx)
+	if err != nil {
+		return err
+	}
+	buffer.WriteString(value.String())
+	return nil
+}
+
+func (expr *simpleExpression) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	value, err := expr.Evaluate(ctx)
+	if err != nil {
+		return err
+	}
+	buffer.WriteString(value.String())
+	return nil
+}
+
+func (expr *term) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	value, err := expr.Evaluate(ctx)
+	if err != nil {
+		return err
+	}
+	buffer.WriteString(value.String())
+	return nil
+}
+
+func (expr *power) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	value, err := expr.Evaluate(ctx)
+	if err != nil {
+		return err
+	}
+	buffer.WriteString(value.String())
+	return nil
+}
+
+func (expr *Expression) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
+	v1, err := expr.expr1.Evaluate(ctx)
+	if err != nil {
+		return nil, err
+	}
+	if expr.expr2 != nil {
+		v2, err := expr.expr2.Evaluate(ctx)
+		if err != nil {
+			return nil, err
+		}
+		switch expr.op_token.Val {
+		case "and", "&&":
+			return AsValue(v1.IsTrue() && v2.IsTrue()), nil
+		case "or", "||":
+			return AsValue(v1.IsTrue() || v2.IsTrue()), nil
+		default:
+			panic(fmt.Sprintf("unimplemented: %s", expr.op_token.Val))
+		}
+	} else {
+		return v1, nil
+	}
+}
+
+func (expr *relationalExpression) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
+	v1, err := expr.expr1.Evaluate(ctx)
+	if err != nil {
+		return nil, err
+	}
+	if expr.expr2 != nil {
+		v2, err := expr.expr2.Evaluate(ctx)
+		if err != nil {
+			return nil, err
+		}
+		switch expr.op_token.Val {
+		case "<=":
+			if v1.IsFloat() || v2.IsFloat() {
+				return AsValue(v1.Float() <= v2.Float()), nil
+			} else {
+				return AsValue(v1.Integer() <= v2.Integer()), nil
+			}
+		case ">=":
+			if v1.IsFloat() || v2.IsFloat() {
+				return AsValue(v1.Float() >= v2.Float()), nil
+			} else {
+				return AsValue(v1.Integer() >= v2.Integer()), nil
+			}
+		case "==":
+			return AsValue(v1.EqualValueTo(v2)), nil
+		case ">":
+			if v1.IsFloat() || v2.IsFloat() {
+				return AsValue(v1.Float() > v2.Float()), nil
+			} else {
+				return AsValue(v1.Integer() > v2.Integer()), nil
+			}
+		case "<":
+			if v1.IsFloat() || v2.IsFloat() {
+				return AsValue(v1.Float() < v2.Float()), nil
+			} else {
+				return AsValue(v1.Integer() < v2.Integer()), nil
+			}
+		case "!=", "<>":
+			return AsValue(!v1.EqualValueTo(v2)), nil
+		case "in":
+			return AsValue(v2.Contains(v1)), nil
+		default:
+			panic(fmt.Sprintf("unimplemented: %s", expr.op_token.Val))
+		}
+	} else {
+		return v1, nil
+	}
+}
+
+func (expr *simpleExpression) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
+	t1, err := expr.term1.Evaluate(ctx)
+	if err != nil {
+		return nil, err
+	}
+	result := t1
+
+	if expr.negate {
+		result = result.Negate()
+	}
+
+	if expr.negative_sign {
+		if result.IsNumber() {
+			switch {
+			case result.IsFloat():
+				result = AsValue(-1 * result.Float())
+			case result.IsInteger():
+				result = AsValue(-1 * result.Integer())
+			default:
+				panic("not possible")
+			}
+		} else {
+			return nil, ctx.Error("Negative sign on a non-number expression", expr.GetPositionToken())
+		}
+	}
+
+	if expr.term2 != nil {
+		t2, err := expr.term2.Evaluate(ctx)
+		if err != nil {
+			return nil, err
+		}
+		switch expr.op_token.Val {
+		case "+":
+			if result.IsFloat() || t2.IsFloat() {
+				// Result will be a float
+				return AsValue(result.Float() + t2.Float()), nil
+			} else {
+				// Result will be an integer
+				return AsValue(result.Integer() + t2.Integer()), nil
+			}
+		case "-":
+			if result.IsFloat() || t2.IsFloat() {
+				// Result will be a float
+				return AsValue(result.Float() - t2.Float()), nil
+			} else {
+				// Result will be an integer
+				return AsValue(result.Integer() - t2.Integer()), nil
+			}
+		default:
+			panic("unimplemented")
+		}
+	}
+
+	return result, nil
+}
+
+func (t *term) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
+	f1, err := t.factor1.Evaluate(ctx)
+	if err != nil {
+		return nil, err
+	}
+	if t.factor2 != nil {
+		f2, err := t.factor2.Evaluate(ctx)
+		if err != nil {
+			return nil, err
+		}
+		switch t.op_token.Val {
+		case "*":
+			if f1.IsFloat() || f2.IsFloat() {
+				// Result will be float
+				return AsValue(f1.Float() * f2.Float()), nil
+			}
+			// Result will be int
+			return AsValue(f1.Integer() * f2.Integer()), nil
+		case "/":
+			if f1.IsFloat() || f2.IsFloat() {
+				// Result will be float
+				return AsValue(f1.Float() / f2.Float()), nil
+			}
+			// Result will be int
+			return AsValue(f1.Integer() / f2.Integer()), nil
+		case "%":
+			// Result will be int
+			return AsValue(f1.Integer() % f2.Integer()), nil
+		default:
+			panic("unimplemented")
+		}
+	} else {
+		return f1, nil
+	}
+}
+
+func (pw *power) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
+	p1, err := pw.power1.Evaluate(ctx)
+	if err != nil {
+		return nil, err
+	}
+	if pw.power2 != nil {
+		p2, err := pw.power2.Evaluate(ctx)
+		if err != nil {
+			return nil, err
+		}
+		return AsValue(math.Pow(p1.Float(), p2.Float())), nil
+	} else {
+		return p1, nil
+	}
+}
+
+func (p *Parser) parseFactor() (IEvaluator, *Error) {
+	if p.Match(TokenSymbol, "(") != nil {
+		expr, err := p.ParseExpression()
+		if err != nil {
+			return nil, err
+		}
+		if p.Match(TokenSymbol, ")") == nil {
+			return nil, p.Error("Closing bracket expected after expression", nil)
+		}
+		return expr, nil
+	}
+
+	return p.parseVariableOrLiteralWithFilter()
+}
+
+func (p *Parser) parsePower() (IEvaluator, *Error) {
+	pw := new(power)
+
+	power1, err := p.parseFactor()
+	if err != nil {
+		return nil, err
+	}
+	pw.power1 = power1
+
+	if p.Match(TokenSymbol, "^") != nil {
+		power2, err := p.parsePower()
+		if err != nil {
+			return nil, err
+		}
+		pw.power2 = power2
+	}
+
+	if pw.power2 == nil {
+		// Shortcut for faster evaluation
+		return pw.power1, nil
+	}
+
+	return pw, nil
+}
+
+func (p *Parser) parseTerm() (IEvaluator, *Error) {
+	return_term := new(term)
+
+	factor1, err := p.parsePower()
+	if err != nil {
+		return nil, err
+	}
+	return_term.factor1 = factor1
+
+	for p.PeekOne(TokenSymbol, "*", "/", "%") != nil {
+		if return_term.op_token != nil {
+			// Create new sub-term
+			return_term = &term{
+				factor1: return_term,
+			}
+		}
+
+		op := p.Current()
+		p.Consume()
+
+		factor2, err := p.parsePower()
+		if err != nil {
+			return nil, err
+		}
+
+		return_term.op_token = op
+		return_term.factor2 = factor2
+	}
+
+	if return_term.op_token == nil {
+		// Shortcut for faster evaluation
+		return return_term.factor1, nil
+	}
+
+	return return_term, nil
+}
+
+func (p *Parser) parseSimpleExpression() (IEvaluator, *Error) {
+	expr := new(simpleExpression)
+
+	if sign := p.MatchOne(TokenSymbol, "+", "-"); sign != nil {
+		if sign.Val == "-" {
+			expr.negative_sign = true
+		}
+	}
+
+	if p.Match(TokenSymbol, "!") != nil || p.Match(TokenKeyword, "not") != nil {
+		expr.negate = true
+	}
+
+	term1, err := p.parseTerm()
+	if err != nil {
+		return nil, err
+	}
+	expr.term1 = term1
+
+	for p.PeekOne(TokenSymbol, "+", "-") != nil {
+		if expr.op_token != nil {
+			// New sub expr
+			expr = &simpleExpression{
+				term1: expr,
+			}
+		}
+
+		op := p.Current()
+		p.Consume()
+
+		term2, err := p.parseTerm()
+		if err != nil {
+			return nil, err
+		}
+
+		expr.term2 = term2
+		expr.op_token = op
+	}
+
+	if expr.negate == false && expr.negative_sign == false && expr.term2 == nil {
+		// Shortcut for faster evaluation
+		return expr.term1, nil
+	}
+
+	return expr, nil
+}
+
+func (p *Parser) parseRelationalExpression() (IEvaluator, *Error) {
+	expr1, err := p.parseSimpleExpression()
+	if err != nil {
+		return nil, err
+	}
+
+	expr := &relationalExpression{
+		expr1: expr1,
+	}
+
+	if t := p.MatchOne(TokenSymbol, "==", "<=", ">=", "!=", "<>", ">", "<"); t != nil {
+		expr2, err := p.parseRelationalExpression()
+		if err != nil {
+			return nil, err
+		}
+		expr.op_token = t
+		expr.expr2 = expr2
+	} else if t := p.MatchOne(TokenKeyword, "in"); t != nil {
+		expr2, err := p.parseSimpleExpression()
+		if err != nil {
+			return nil, err
+		}
+		expr.op_token = t
+		expr.expr2 = expr2
+	}
+
+	if expr.expr2 == nil {
+		// Shortcut for faster evaluation
+		return expr.expr1, nil
+	}
+
+	return expr, nil
+}
+
+func (p *Parser) ParseExpression() (IEvaluator, *Error) {
+	rexpr1, err := p.parseRelationalExpression()
+	if err != nil {
+		return nil, err
+	}
+
+	exp := &Expression{
+		expr1: rexpr1,
+	}
+
+	if p.PeekOne(TokenSymbol, "&&", "||") != nil || p.PeekOne(TokenKeyword, "and", "or") != nil {
+		op := p.Current()
+		p.Consume()
+		expr2, err := p.ParseExpression()
+		if err != nil {
+			return nil, err
+		}
+		exp.expr2 = expr2
+		exp.op_token = op
+	}
+
+	if exp.expr2 == nil {
+		// Shortcut for faster evaluation
+		return exp.expr1, nil
+	}
+
+	return exp, nil
+}

+ 14 - 0
vendor/github.com/flosch/pongo2/pongo2.go

@@ -0,0 +1,14 @@
+package pongo2
+
+// Version string
+const Version = "v3"
+
+// Helper function which panics, if a Template couldn't
+// successfully parsed. This is how you would use it:
+//     var baseTemplate = pongo2.Must(pongo2.FromFile("templates/base.html"))
+func Must(tpl *Template, err error) *Template {
+	if err != nil {
+		panic(err)
+	}
+	return tpl
+}

+ 132 - 0
vendor/github.com/flosch/pongo2/tags.go

@@ -0,0 +1,132 @@
+package pongo2
+
+/* Incomplete:
+   -----------
+
+   verbatim (only the "name" argument is missing for verbatim)
+
+   Reconsideration:
+   ----------------
+
+   debug (reason: not sure what to output yet)
+   regroup / Grouping on other properties (reason: maybe too python-specific; not sure how useful this would be in Go)
+
+   Following built-in tags wont be added:
+   --------------------------------------
+
+   csrf_token (reason: web-framework specific)
+   load (reason: python-specific)
+   url (reason: web-framework specific)
+*/
+
+import (
+	"fmt"
+)
+
+type INodeTag interface {
+	INode
+}
+
+// This is the function signature of the tag's parser you will have
+// to implement in order to create a new tag.
+//
+// 'doc' is providing access to the whole document while 'arguments'
+// is providing access to the user's arguments to the tag:
+//
+//     {% your_tag_name some "arguments" 123 %}
+//
+// start_token will be the *Token with the tag's name in it (here: your_tag_name).
+//
+// Please see the Parser documentation on how to use the parser.
+// See RegisterTag()'s documentation for more information about
+// writing a tag as well.
+type TagParser func(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error)
+
+type tag struct {
+	name   string
+	parser TagParser
+}
+
+var tags map[string]*tag
+
+func init() {
+	tags = make(map[string]*tag)
+}
+
+// Registers a new tag. If there's already a tag with the same
+// name, RegisterTag will panic. You usually want to call this
+// function in the tag's init() function:
+// http://golang.org/doc/effective_go.html#init
+//
+// See http://www.florian-schlachter.de/post/pongo2/ for more about
+// writing filters and tags.
+func RegisterTag(name string, parserFn TagParser) {
+	_, existing := tags[name]
+	if existing {
+		panic(fmt.Sprintf("Tag with name '%s' is already registered.", name))
+	}
+	tags[name] = &tag{
+		name:   name,
+		parser: parserFn,
+	}
+}
+
+// Replaces an already registered tag with a new implementation. Use this
+// function with caution since it allows you to change existing tag behaviour.
+func ReplaceTag(name string, parserFn TagParser) {
+	_, existing := tags[name]
+	if !existing {
+		panic(fmt.Sprintf("Tag with name '%s' does not exist (therefore cannot be overridden).", name))
+	}
+	tags[name] = &tag{
+		name:   name,
+		parser: parserFn,
+	}
+}
+
+// Tag = "{%" IDENT ARGS "%}"
+func (p *Parser) parseTagElement() (INodeTag, *Error) {
+	p.Consume() // consume "{%"
+	token_name := p.MatchType(TokenIdentifier)
+
+	// Check for identifier
+	if token_name == nil {
+		return nil, p.Error("Tag name must be an identifier.", nil)
+	}
+
+	// Check for the existing tag
+	tag, exists := tags[token_name.Val]
+	if !exists {
+		// Does not exists
+		return nil, p.Error(fmt.Sprintf("Tag '%s' not found (or beginning tag not provided)", token_name.Val), token_name)
+	}
+
+	// Check sandbox tag restriction
+	if _, is_banned := p.template.set.bannedTags[token_name.Val]; is_banned {
+		return nil, p.Error(fmt.Sprintf("Usage of tag '%s' is not allowed (sandbox restriction active).", token_name.Val), token_name)
+	}
+
+	args_token := make([]*Token, 0)
+	for p.Peek(TokenSymbol, "%}") == nil && p.Remaining() > 0 {
+		// Add token to args
+		args_token = append(args_token, p.Current())
+		p.Consume() // next token
+	}
+
+	// EOF?
+	if p.Remaining() == 0 {
+		return nil, p.Error("Unexpectedly reached EOF, no tag end found.", p.last_token)
+	}
+
+	p.Match(TokenSymbol, "%}")
+
+	arg_parser := newParser(p.name, args_token, p.template)
+	if len(args_token) == 0 {
+		// This is done to have nice EOF error messages
+		arg_parser.last_token = token_name
+	}
+
+	p.template.level++
+	defer func() { p.template.level-- }()
+	return tag.parser(p, token_name, arg_parser)
+}

+ 56 - 0
vendor/github.com/flosch/pongo2/tags_autoescape.go

@@ -0,0 +1,56 @@
+package pongo2
+
+import (
+	"bytes"
+)
+
+type tagAutoescapeNode struct {
+	wrapper    *NodeWrapper
+	autoescape bool
+}
+
+func (node *tagAutoescapeNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	old := ctx.Autoescape
+	ctx.Autoescape = node.autoescape
+
+	err := node.wrapper.Execute(ctx, buffer)
+	if err != nil {
+		return err
+	}
+
+	ctx.Autoescape = old
+
+	return nil
+}
+
+func tagAutoescapeParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
+	autoescape_node := &tagAutoescapeNode{}
+
+	wrapper, _, err := doc.WrapUntilTag("endautoescape")
+	if err != nil {
+		return nil, err
+	}
+	autoescape_node.wrapper = wrapper
+
+	mode_token := arguments.MatchType(TokenIdentifier)
+	if mode_token == nil {
+		return nil, arguments.Error("A mode is required for autoescape-tag.", nil)
+	}
+	if mode_token.Val == "on" {
+		autoescape_node.autoescape = true
+	} else if mode_token.Val == "off" {
+		autoescape_node.autoescape = false
+	} else {
+		return nil, arguments.Error("Only 'on' or 'off' is valid as an autoescape-mode.", nil)
+	}
+
+	if arguments.Remaining() > 0 {
+		return nil, arguments.Error("Malformed autoescape-tag arguments.", nil)
+	}
+
+	return autoescape_node, nil
+}
+
+func init() {
+	RegisterTag("autoescape", tagAutoescapeParser)
+}

+ 94 - 0
vendor/github.com/flosch/pongo2/tags_block.go

@@ -0,0 +1,94 @@
+package pongo2
+
+import (
+	"bytes"
+	"fmt"
+)
+
+type tagBlockNode struct {
+	name string
+}
+
+func (node *tagBlockNode) getBlockWrapperByName(tpl *Template) *NodeWrapper {
+	var t *NodeWrapper
+	if tpl.child != nil {
+		// First ask the child for the block
+		t = node.getBlockWrapperByName(tpl.child)
+	}
+	if t == nil {
+		// Child has no block, lets look up here at parent
+		t = tpl.blocks[node.name]
+	}
+	return t
+}
+
+func (node *tagBlockNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	tpl := ctx.template
+	if tpl == nil {
+		panic("internal error: tpl == nil")
+	}
+	// Determine the block to execute
+	block_wrapper := node.getBlockWrapperByName(tpl)
+	if block_wrapper == nil {
+		// fmt.Printf("could not find: %s\n", node.name)
+		return ctx.Error("internal error: block_wrapper == nil in tagBlockNode.Execute()", nil)
+	}
+	err := block_wrapper.Execute(ctx, buffer)
+	if err != nil {
+		return err
+	}
+
+	// TODO: Add support for {{ block.super }}
+
+	return nil
+}
+
+func tagBlockParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
+	if arguments.Count() == 0 {
+		return nil, arguments.Error("Tag 'block' requires an identifier.", nil)
+	}
+
+	name_token := arguments.MatchType(TokenIdentifier)
+	if name_token == nil {
+		return nil, arguments.Error("First argument for tag 'block' must be an identifier.", nil)
+	}
+
+	if arguments.Remaining() != 0 {
+		return nil, arguments.Error("Tag 'block' takes exactly 1 argument (an identifier).", nil)
+	}
+
+	wrapper, endtagargs, err := doc.WrapUntilTag("endblock")
+	if err != nil {
+		return nil, err
+	}
+	if endtagargs.Remaining() > 0 {
+		endtagname_token := endtagargs.MatchType(TokenIdentifier)
+		if endtagname_token != nil {
+			if endtagname_token.Val != name_token.Val {
+				return nil, endtagargs.Error(fmt.Sprintf("Name for 'endblock' must equal to 'block'-tag's name ('%s' != '%s').",
+					name_token.Val, endtagname_token.Val), nil)
+			}
+		}
+
+		if endtagname_token == nil || endtagargs.Remaining() > 0 {
+			return nil, endtagargs.Error("Either no or only one argument (identifier) allowed for 'endblock'.", nil)
+		}
+	}
+
+	tpl := doc.template
+	if tpl == nil {
+		panic("internal error: tpl == nil")
+	}
+	_, has_block := tpl.blocks[name_token.Val]
+	if !has_block {
+		tpl.blocks[name_token.Val] = wrapper
+	} else {
+		return nil, arguments.Error(fmt.Sprintf("Block named '%s' already defined", name_token.Val), nil)
+	}
+
+	return &tagBlockNode{name: name_token.Val}, nil
+}
+
+func init() {
+	RegisterTag("block", tagBlockParser)
+}

+ 31 - 0
vendor/github.com/flosch/pongo2/tags_comment.go

@@ -0,0 +1,31 @@
+package pongo2
+
+import (
+	"bytes"
+)
+
+type tagCommentNode struct{}
+
+func (node *tagCommentNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	return nil
+}
+
+func tagCommentParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
+	comment_node := &tagCommentNode{}
+
+	// TODO: Process the endtag's arguments (see django 'comment'-tag documentation)
+	_, _, err := doc.WrapUntilTag("endcomment")
+	if err != nil {
+		return nil, err
+	}
+
+	if arguments.Count() != 0 {
+		return nil, arguments.Error("Tag 'comment' does not take any argument.", nil)
+	}
+
+	return comment_node, nil
+}
+
+func init() {
+	RegisterTag("comment", tagCommentParser)
+}

+ 110 - 0
vendor/github.com/flosch/pongo2/tags_cycle.go

@@ -0,0 +1,110 @@
+package pongo2
+
+import (
+	"bytes"
+)
+
+type tagCycleValue struct {
+	node  *tagCycleNode
+	value *Value
+}
+
+type tagCycleNode struct {
+	position *Token
+	args     []IEvaluator
+	idx      int
+	as_name  string
+	silent   bool
+}
+
+func (cv *tagCycleValue) String() string {
+	return cv.value.String()
+}
+
+func (node *tagCycleNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	item := node.args[node.idx%len(node.args)]
+	node.idx++
+
+	val, err := item.Evaluate(ctx)
+	if err != nil {
+		return err
+	}
+
+	if t, ok := val.Interface().(*tagCycleValue); ok {
+		// {% cycle "test1" "test2"
+		// {% cycle cycleitem %}
+
+		// Update the cycle value with next value
+		item := t.node.args[t.node.idx%len(t.node.args)]
+		t.node.idx++
+
+		val, err := item.Evaluate(ctx)
+		if err != nil {
+			return err
+		}
+
+		t.value = val
+
+		if !t.node.silent {
+			buffer.WriteString(val.String())
+		}
+	} else {
+		// Regular call
+
+		cycle_value := &tagCycleValue{
+			node:  node,
+			value: val,
+		}
+
+		if node.as_name != "" {
+			ctx.Private[node.as_name] = cycle_value
+		}
+		if !node.silent {
+			buffer.WriteString(val.String())
+		}
+	}
+
+	return nil
+}
+
+// HINT: We're not supporting the old comma-seperated list of expresions argument-style
+func tagCycleParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
+	cycle_node := &tagCycleNode{
+		position: start,
+	}
+
+	for arguments.Remaining() > 0 {
+		node, err := arguments.ParseExpression()
+		if err != nil {
+			return nil, err
+		}
+		cycle_node.args = append(cycle_node.args, node)
+
+		if arguments.MatchOne(TokenKeyword, "as") != nil {
+			// as
+
+			name_token := arguments.MatchType(TokenIdentifier)
+			if name_token == nil {
+				return nil, arguments.Error("Name (identifier) expected after 'as'.", nil)
+			}
+			cycle_node.as_name = name_token.Val
+
+			if arguments.MatchOne(TokenIdentifier, "silent") != nil {
+				cycle_node.silent = true
+			}
+
+			// Now we're finished
+			break
+		}
+	}
+
+	if arguments.Remaining() > 0 {
+		return nil, arguments.Error("Malformed cycle-tag.", nil)
+	}
+
+	return cycle_node, nil
+}
+
+func init() {
+	RegisterTag("cycle", tagCycleParser)
+}

+ 56 - 0
vendor/github.com/flosch/pongo2/tags_extends.go

@@ -0,0 +1,56 @@
+package pongo2
+
+import (
+	"bytes"
+)
+
+type tagExtendsNode struct {
+	filename string
+}
+
+func (node *tagExtendsNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	return nil
+}
+
+func tagExtendsParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
+	extends_node := &tagExtendsNode{}
+
+	if doc.template.level > 1 {
+		return nil, arguments.Error("The 'extends' tag can only defined on root level.", start)
+	}
+
+	if doc.template.parent != nil {
+		// Already one parent
+		return nil, arguments.Error("This template has already one parent.", start)
+	}
+
+	if filename_token := arguments.MatchType(TokenString); filename_token != nil {
+		// prepared, static template
+
+		// Get parent's filename
+		parent_filename := doc.template.set.resolveFilename(doc.template, filename_token.Val)
+
+		// Parse the parent
+		parent_template, err := doc.template.set.FromFile(parent_filename)
+		if err != nil {
+			return nil, err.(*Error)
+		}
+
+		// Keep track of things
+		parent_template.child = doc.template
+		doc.template.parent = parent_template
+		extends_node.filename = parent_filename
+	} else {
+		return nil, arguments.Error("Tag 'extends' requires a template filename as string.", nil)
+	}
+
+	if arguments.Remaining() > 0 {
+		return nil, arguments.Error("Tag 'extends' does only take 1 argument.", nil)
+	}
+
+	return extends_node, nil
+}
+
+func init() {
+	RegisterTag("extends", tagExtendsParser)
+}

+ 95 - 0
vendor/github.com/flosch/pongo2/tags_filter.go

@@ -0,0 +1,95 @@
+package pongo2
+
+import (
+	"bytes"
+)
+
+type nodeFilterCall struct {
+	name       string
+	param_expr IEvaluator
+}
+
+type tagFilterNode struct {
+	position    *Token
+	bodyWrapper *NodeWrapper
+	filterChain []*nodeFilterCall
+}
+
+func (node *tagFilterNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	temp := bytes.NewBuffer(make([]byte, 0, 1024)) // 1 KiB size
+
+	err := node.bodyWrapper.Execute(ctx, temp)
+	if err != nil {
+		return err
+	}
+
+	value := AsValue(temp.String())
+
+	for _, call := range node.filterChain {
+		var param *Value
+		if call.param_expr != nil {
+			param, err = call.param_expr.Evaluate(ctx)
+			if err != nil {
+				return err
+			}
+		} else {
+			param = AsValue(nil)
+		}
+		value, err = ApplyFilter(call.name, value, param)
+		if err != nil {
+			return ctx.Error(err.Error(), node.position)
+		}
+	}
+
+	buffer.WriteString(value.String())
+
+	return nil
+}
+
+func tagFilterParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
+	filter_node := &tagFilterNode{
+		position: start,
+	}
+
+	wrapper, _, err := doc.WrapUntilTag("endfilter")
+	if err != nil {
+		return nil, err
+	}
+	filter_node.bodyWrapper = wrapper
+
+	for arguments.Remaining() > 0 {
+		filterCall := &nodeFilterCall{}
+
+		name_token := arguments.MatchType(TokenIdentifier)
+		if name_token == nil {
+			return nil, arguments.Error("Expected a filter name (identifier).", nil)
+		}
+		filterCall.name = name_token.Val
+
+		if arguments.MatchOne(TokenSymbol, ":") != nil {
+			// Filter parameter
+			// NOTICE: we can't use ParseExpression() here, because it would parse the next filter "|..." as well in the argument list
+			expr, err := arguments.parseVariableOrLiteral()
+			if err != nil {
+				return nil, err
+			}
+			filterCall.param_expr = expr
+		}
+
+		filter_node.filterChain = append(filter_node.filterChain, filterCall)
+
+		if arguments.MatchOne(TokenSymbol, "|") == nil {
+			break
+		}
+	}
+
+	if arguments.Remaining() > 0 {
+		return nil, arguments.Error("Malformed filter-tag arguments.", nil)
+	}
+
+	return filter_node, nil
+}
+
+func init() {
+	RegisterTag("filter", tagFilterParser)
+}

+ 53 - 0
vendor/github.com/flosch/pongo2/tags_firstof.go

@@ -0,0 +1,53 @@
+package pongo2
+
+import (
+	"bytes"
+)
+
+type tagFirstofNode struct {
+	position *Token
+	args     []IEvaluator
+}
+
+func (node *tagFirstofNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	for _, arg := range node.args {
+		val, err := arg.Evaluate(ctx)
+		if err != nil {
+			return err
+		}
+
+		if val.IsTrue() {
+			if ctx.Autoescape && !arg.FilterApplied("safe") {
+				val, err = ApplyFilter("escape", val, nil)
+				if err != nil {
+					return err
+				}
+			}
+
+			buffer.WriteString(val.String())
+			return nil
+		}
+	}
+
+	return nil
+}
+
+func tagFirstofParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
+	firstof_node := &tagFirstofNode{
+		position: start,
+	}
+
+	for arguments.Remaining() > 0 {
+		node, err := arguments.ParseExpression()
+		if err != nil {
+			return nil, err
+		}
+		firstof_node.args = append(firstof_node.args, node)
+	}
+
+	return firstof_node, nil
+}
+
+func init() {
+	RegisterTag("firstof", tagFirstofParser)
+}

+ 158 - 0
vendor/github.com/flosch/pongo2/tags_for.go

@@ -0,0 +1,158 @@
+package pongo2
+
+import (
+	"bytes"
+)
+
+type tagForNode struct {
+	key              string
+	value            string // only for maps: for key, value in map
+	object_evaluator IEvaluator
+	reversed         bool
+
+	bodyWrapper  *NodeWrapper
+	emptyWrapper *NodeWrapper
+}
+
+type tagForLoopInformation struct {
+	Counter     int
+	Counter0    int
+	Revcounter  int
+	Revcounter0 int
+	First       bool
+	Last        bool
+	Parentloop  *tagForLoopInformation
+}
+
+func (node *tagForNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) (forError *Error) {
+	// Backup forloop (as parentloop in public context), key-name and value-name
+	forCtx := NewChildExecutionContext(ctx)
+	parentloop := forCtx.Private["forloop"]
+
+	// Create loop struct
+	loopInfo := &tagForLoopInformation{
+		First: true,
+	}
+
+	// Is it a loop in a loop?
+	if parentloop != nil {
+		loopInfo.Parentloop = parentloop.(*tagForLoopInformation)
+	}
+
+	// Register loopInfo in public context
+	forCtx.Private["forloop"] = loopInfo
+
+	obj, err := node.object_evaluator.Evaluate(forCtx)
+	if err != nil {
+		return err
+	}
+
+	obj.IterateOrder(func(idx, count int, key, value *Value) bool {
+		// There's something to iterate over (correct type and at least 1 item)
+
+		// Update loop infos and public context
+		forCtx.Private[node.key] = key
+		if value != nil {
+			forCtx.Private[node.value] = value
+		}
+		loopInfo.Counter = idx + 1
+		loopInfo.Counter0 = idx
+		if idx == 1 {
+			loopInfo.First = false
+		}
+		if idx+1 == count {
+			loopInfo.Last = true
+		}
+		loopInfo.Revcounter = count - idx        // TODO: Not sure about this, have to look it up
+		loopInfo.Revcounter0 = count - (idx + 1) // TODO: Not sure about this, have to look it up
+
+		// Render elements with updated context
+		err := node.bodyWrapper.Execute(forCtx, buffer)
+		if err != nil {
+			forError = err
+			return false
+		}
+		return true
+	}, func() {
+		// Nothing to iterate over (maybe wrong type or no items)
+		if node.emptyWrapper != nil {
+			err := node.emptyWrapper.Execute(forCtx, buffer)
+			if err != nil {
+				forError = err
+			}
+		}
+	}, node.reversed)
+
+	return nil
+}
+
+func tagForParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
+	for_node := &tagForNode{}
+
+	// Arguments parsing
+	var value_token *Token
+	key_token := arguments.MatchType(TokenIdentifier)
+	if key_token == nil {
+		return nil, arguments.Error("Expected an key identifier as first argument for 'for'-tag", nil)
+	}
+
+	if arguments.Match(TokenSymbol, ",") != nil {
+		// Value name is provided
+		value_token = arguments.MatchType(TokenIdentifier)
+		if value_token == nil {
+			return nil, arguments.Error("Value name must be an identifier.", nil)
+		}
+	}
+
+	if arguments.Match(TokenKeyword, "in") == nil {
+		return nil, arguments.Error("Expected keyword 'in'.", nil)
+	}
+
+	object_evaluator, err := arguments.ParseExpression()
+	if err != nil {
+		return nil, err
+	}
+	for_node.object_evaluator = object_evaluator
+	for_node.key = key_token.Val
+	if value_token != nil {
+		for_node.value = value_token.Val
+	}
+
+	if arguments.MatchOne(TokenIdentifier, "reversed") != nil {
+		for_node.reversed = true
+	}
+
+	if arguments.Remaining() > 0 {
+		return nil, arguments.Error("Malformed for-loop arguments.", nil)
+	}
+
+	// Body wrapping
+	wrapper, endargs, err := doc.WrapUntilTag("empty", "endfor")
+	if err != nil {
+		return nil, err
+	}
+	for_node.bodyWrapper = wrapper
+
+	if endargs.Count() > 0 {
+		return nil, endargs.Error("Arguments not allowed here.", nil)
+	}
+
+	if wrapper.Endtag == "empty" {
+		// if there's an else in the if-statement, we need the else-Block as well
+		wrapper, endargs, err = doc.WrapUntilTag("endfor")
+		if err != nil {
+			return nil, err
+		}
+		for_node.emptyWrapper = wrapper
+
+		if endargs.Count() > 0 {
+			return nil, endargs.Error("Arguments not allowed here.", nil)
+		}
+	}
+
+	return for_node, nil
+}
+
+func init() {
+	RegisterTag("for", tagForParser)
+}

+ 81 - 0
vendor/github.com/flosch/pongo2/tags_if.go

@@ -0,0 +1,81 @@
+package pongo2
+
+import (
+	"bytes"
+)
+
+type tagIfNode struct {
+	conditions []IEvaluator
+	wrappers   []*NodeWrapper
+}
+
+func (node *tagIfNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	for i, condition := range node.conditions {
+		result, err := condition.Evaluate(ctx)
+		if err != nil {
+			return err
+		}
+
+		if result.IsTrue() {
+			return node.wrappers[i].Execute(ctx, buffer)
+		} else {
+			// Last condition?
+			if len(node.conditions) == i+1 && len(node.wrappers) > i+1 {
+				return node.wrappers[i+1].Execute(ctx, buffer)
+			}
+		}
+	}
+	return nil
+}
+
+func tagIfParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
+	if_node := &tagIfNode{}
+
+	// Parse first and main IF condition
+	condition, err := arguments.ParseExpression()
+	if err != nil {
+		return nil, err
+	}
+	if_node.conditions = append(if_node.conditions, condition)
+
+	if arguments.Remaining() > 0 {
+		return nil, arguments.Error("If-condition is malformed.", nil)
+	}
+
+	// Check the rest
+	for {
+		wrapper, tag_args, err := doc.WrapUntilTag("elif", "else", "endif")
+		if err != nil {
+			return nil, err
+		}
+		if_node.wrappers = append(if_node.wrappers, wrapper)
+
+		if wrapper.Endtag == "elif" {
+			// elif can take a condition
+			condition, err := tag_args.ParseExpression()
+			if err != nil {
+				return nil, err
+			}
+			if_node.conditions = append(if_node.conditions, condition)
+
+			if tag_args.Remaining() > 0 {
+				return nil, tag_args.Error("Elif-condition is malformed.", nil)
+			}
+		} else {
+			if tag_args.Count() > 0 {
+				// else/endif can't take any conditions
+				return nil, tag_args.Error("Arguments not allowed here.", nil)
+			}
+		}
+
+		if wrapper.Endtag == "endif" {
+			break
+		}
+	}
+
+	return if_node, nil
+}
+
+func init() {
+	RegisterTag("if", tagIfParser)
+}

+ 117 - 0
vendor/github.com/flosch/pongo2/tags_ifchanged.go

@@ -0,0 +1,117 @@
+package pongo2
+
+import (
+	"bytes"
+)
+
+type tagIfchangedNode struct {
+	watched_expr []IEvaluator
+	last_values  []*Value
+	last_content []byte
+	thenWrapper  *NodeWrapper
+	elseWrapper  *NodeWrapper
+}
+
+func (node *tagIfchangedNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+
+	if len(node.watched_expr) == 0 {
+		// Check against own rendered body
+
+		buf := bytes.NewBuffer(make([]byte, 0, 1024)) // 1 KiB
+		err := node.thenWrapper.Execute(ctx, buf)
+		if err != nil {
+			return err
+		}
+
+		buf_bytes := buf.Bytes()
+		if !bytes.Equal(node.last_content, buf_bytes) {
+			// Rendered content changed, output it
+			buffer.Write(buf_bytes)
+			node.last_content = buf_bytes
+		}
+	} else {
+		now_values := make([]*Value, 0, len(node.watched_expr))
+		for _, expr := range node.watched_expr {
+			val, err := expr.Evaluate(ctx)
+			if err != nil {
+				return err
+			}
+			now_values = append(now_values, val)
+		}
+
+		// Compare old to new values now
+		changed := len(node.last_values) == 0
+
+		for idx, old_val := range node.last_values {
+			if !old_val.EqualValueTo(now_values[idx]) {
+				changed = true
+				break // we can stop here because ONE value changed
+			}
+		}
+
+		node.last_values = now_values
+
+		if changed {
+			// Render thenWrapper
+			err := node.thenWrapper.Execute(ctx, buffer)
+			if err != nil {
+				return err
+			}
+		} else {
+			// Render elseWrapper
+			err := node.elseWrapper.Execute(ctx, buffer)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	return nil
+}
+
+func tagIfchangedParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
+	ifchanged_node := &tagIfchangedNode{}
+
+	for arguments.Remaining() > 0 {
+		// Parse condition
+		expr, err := arguments.ParseExpression()
+		if err != nil {
+			return nil, err
+		}
+		ifchanged_node.watched_expr = append(ifchanged_node.watched_expr, expr)
+	}
+
+	if arguments.Remaining() > 0 {
+		return nil, arguments.Error("Ifchanged-arguments are malformed.", nil)
+	}
+
+	// Wrap then/else-blocks
+	wrapper, endargs, err := doc.WrapUntilTag("else", "endifchanged")
+	if err != nil {
+		return nil, err
+	}
+	ifchanged_node.thenWrapper = wrapper
+
+	if endargs.Count() > 0 {
+		return nil, endargs.Error("Arguments not allowed here.", nil)
+	}
+
+	if wrapper.Endtag == "else" {
+		// if there's an else in the if-statement, we need the else-Block as well
+		wrapper, endargs, err = doc.WrapUntilTag("endifchanged")
+		if err != nil {
+			return nil, err
+		}
+		ifchanged_node.elseWrapper = wrapper
+
+		if endargs.Count() > 0 {
+			return nil, endargs.Error("Arguments not allowed here.", nil)
+		}
+	}
+
+	return ifchanged_node, nil
+}
+
+func init() {
+	RegisterTag("ifchanged", tagIfchangedParser)
+}

+ 83 - 0
vendor/github.com/flosch/pongo2/tags_ifequal.go

@@ -0,0 +1,83 @@
+package pongo2
+
+import (
+	"bytes"
+)
+
+type tagIfEqualNode struct {
+	var1, var2  IEvaluator
+	thenWrapper *NodeWrapper
+	elseWrapper *NodeWrapper
+}
+
+func (node *tagIfEqualNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	r1, err := node.var1.Evaluate(ctx)
+	if err != nil {
+		return err
+	}
+	r2, err := node.var2.Evaluate(ctx)
+	if err != nil {
+		return err
+	}
+
+	result := r1.EqualValueTo(r2)
+
+	if result {
+		return node.thenWrapper.Execute(ctx, buffer)
+	} else {
+		if node.elseWrapper != nil {
+			return node.elseWrapper.Execute(ctx, buffer)
+		}
+	}
+	return nil
+}
+
+func tagIfEqualParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
+	ifequal_node := &tagIfEqualNode{}
+
+	// Parse two expressions
+	var1, err := arguments.ParseExpression()
+	if err != nil {
+		return nil, err
+	}
+	var2, err := arguments.ParseExpression()
+	if err != nil {
+		return nil, err
+	}
+	ifequal_node.var1 = var1
+	ifequal_node.var2 = var2
+
+	if arguments.Remaining() > 0 {
+		return nil, arguments.Error("ifequal only takes 2 arguments.", nil)
+	}
+
+	// Wrap then/else-blocks
+	wrapper, endargs, err := doc.WrapUntilTag("else", "endifequal")
+	if err != nil {
+		return nil, err
+	}
+	ifequal_node.thenWrapper = wrapper
+
+	if endargs.Count() > 0 {
+		return nil, endargs.Error("Arguments not allowed here.", nil)
+	}
+
+	if wrapper.Endtag == "else" {
+		// if there's an else in the if-statement, we need the else-Block as well
+		wrapper, endargs, err = doc.WrapUntilTag("endifequal")
+		if err != nil {
+			return nil, err
+		}
+		ifequal_node.elseWrapper = wrapper
+
+		if endargs.Count() > 0 {
+			return nil, endargs.Error("Arguments not allowed here.", nil)
+		}
+	}
+
+	return ifequal_node, nil
+}
+
+func init() {
+	RegisterTag("ifequal", tagIfEqualParser)
+}

+ 83 - 0
vendor/github.com/flosch/pongo2/tags_ifnotequal.go

@@ -0,0 +1,83 @@
+package pongo2
+
+import (
+	"bytes"
+)
+
+type tagIfNotEqualNode struct {
+	var1, var2  IEvaluator
+	thenWrapper *NodeWrapper
+	elseWrapper *NodeWrapper
+}
+
+func (node *tagIfNotEqualNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	r1, err := node.var1.Evaluate(ctx)
+	if err != nil {
+		return err
+	}
+	r2, err := node.var2.Evaluate(ctx)
+	if err != nil {
+		return err
+	}
+
+	result := !r1.EqualValueTo(r2)
+
+	if result {
+		return node.thenWrapper.Execute(ctx, buffer)
+	} else {
+		if node.elseWrapper != nil {
+			return node.elseWrapper.Execute(ctx, buffer)
+		}
+	}
+	return nil
+}
+
+func tagIfNotEqualParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
+	ifnotequal_node := &tagIfNotEqualNode{}
+
+	// Parse two expressions
+	var1, err := arguments.ParseExpression()
+	if err != nil {
+		return nil, err
+	}
+	var2, err := arguments.ParseExpression()
+	if err != nil {
+		return nil, err
+	}
+	ifnotequal_node.var1 = var1
+	ifnotequal_node.var2 = var2
+
+	if arguments.Remaining() > 0 {
+		return nil, arguments.Error("ifequal only takes 2 arguments.", nil)
+	}
+
+	// Wrap then/else-blocks
+	wrapper, endargs, err := doc.WrapUntilTag("else", "endifequal")
+	if err != nil {
+		return nil, err
+	}
+	ifnotequal_node.thenWrapper = wrapper
+
+	if endargs.Count() > 0 {
+		return nil, endargs.Error("Arguments not allowed here.", nil)
+	}
+
+	if wrapper.Endtag == "else" {
+		// if there's an else in the if-statement, we need the else-Block as well
+		wrapper, endargs, err = doc.WrapUntilTag("endifequal")
+		if err != nil {
+			return nil, err
+		}
+		ifnotequal_node.elseWrapper = wrapper
+
+		if endargs.Count() > 0 {
+			return nil, endargs.Error("Arguments not allowed here.", nil)
+		}
+	}
+
+	return ifnotequal_node, nil
+}
+
+func init() {
+	RegisterTag("ifnotequal", tagIfNotEqualParser)
+}

+ 86 - 0
vendor/github.com/flosch/pongo2/tags_import.go

@@ -0,0 +1,86 @@
+package pongo2
+
+import (
+	"bytes"
+	"fmt"
+)
+
+type tagImportNode struct {
+	position *Token
+	filename string
+	template *Template
+	macros   map[string]*tagMacroNode // alias/name -> macro instance
+}
+
+func (node *tagImportNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	for name, macro := range node.macros {
+		func(name string, macro *tagMacroNode) {
+			ctx.Private[name] = func(args ...*Value) *Value {
+				return macro.call(ctx, args...)
+			}
+		}(name, macro)
+	}
+	return nil
+}
+
+func tagImportParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
+	import_node := &tagImportNode{
+		position: start,
+		macros:   make(map[string]*tagMacroNode),
+	}
+
+	filename_token := arguments.MatchType(TokenString)
+	if filename_token == nil {
+		return nil, arguments.Error("Import-tag needs a filename as string.", nil)
+	}
+
+	import_node.filename = doc.template.set.resolveFilename(doc.template, filename_token.Val)
+
+	if arguments.Remaining() == 0 {
+		return nil, arguments.Error("You must at least specify one macro to import.", nil)
+	}
+
+	// Compile the given template
+	tpl, err := doc.template.set.FromFile(import_node.filename)
+	if err != nil {
+		return nil, err.(*Error).updateFromTokenIfNeeded(doc.template, start)
+	}
+
+	for arguments.Remaining() > 0 {
+		macro_name_token := arguments.MatchType(TokenIdentifier)
+		if macro_name_token == nil {
+			return nil, arguments.Error("Expected macro name (identifier).", nil)
+		}
+
+		as_name := macro_name_token.Val
+		if arguments.Match(TokenKeyword, "as") != nil {
+			alias_token := arguments.MatchType(TokenIdentifier)
+			if alias_token == nil {
+				return nil, arguments.Error("Expected macro alias name (identifier).", nil)
+			}
+			as_name = alias_token.Val
+		}
+
+		macro_instance, has := tpl.exported_macros[macro_name_token.Val]
+		if !has {
+			return nil, arguments.Error(fmt.Sprintf("Macro '%s' not found (or not exported) in '%s'.", macro_name_token.Val,
+				import_node.filename), macro_name_token)
+		}
+
+		import_node.macros[as_name] = macro_instance
+
+		if arguments.Remaining() == 0 {
+			break
+		}
+
+		if arguments.Match(TokenSymbol, ",") == nil {
+			return nil, arguments.Error("Expected ','.", nil)
+		}
+	}
+
+	return import_node, nil
+}
+
+func init() {
+	RegisterTag("import", tagImportParser)
+}

+ 132 - 0
vendor/github.com/flosch/pongo2/tags_include.go

@@ -0,0 +1,132 @@
+package pongo2
+
+import (
+	"bytes"
+)
+
+type tagIncludeNode struct {
+	tpl                *Template
+	filename_evaluator IEvaluator
+	lazy               bool
+	only               bool
+	filename           string
+	with_pairs         map[string]IEvaluator
+}
+
+func (node *tagIncludeNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	// Building the context for the template
+	include_ctx := make(Context)
+
+	// Fill the context with all data from the parent
+	if !node.only {
+		include_ctx.Update(ctx.Public)
+		include_ctx.Update(ctx.Private)
+	}
+
+	// Put all custom with-pairs into the context
+	for key, value := range node.with_pairs {
+		val, err := value.Evaluate(ctx)
+		if err != nil {
+			return err
+		}
+		include_ctx[key] = val
+	}
+
+	// Execute the template
+	if node.lazy {
+		// Evaluate the filename
+		filename, err := node.filename_evaluator.Evaluate(ctx)
+		if err != nil {
+			return err
+		}
+
+		if filename.String() == "" {
+			return ctx.Error("Filename for 'include'-tag evaluated to an empty string.", nil)
+		}
+
+		// Get include-filename
+		included_filename := ctx.template.set.resolveFilename(ctx.template, filename.String())
+
+		included_tpl, err2 := ctx.template.set.FromFile(included_filename)
+		if err2 != nil {
+			return err2.(*Error)
+		}
+		err2 = included_tpl.ExecuteWriter(include_ctx, buffer)
+		if err2 != nil {
+			return err2.(*Error)
+		}
+		return nil
+	} else {
+		// Template is already parsed with static filename
+		err := node.tpl.ExecuteWriter(include_ctx, buffer)
+		if err != nil {
+			return err.(*Error)
+		}
+		return nil
+	}
+}
+
+func tagIncludeParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
+	include_node := &tagIncludeNode{
+		with_pairs: make(map[string]IEvaluator),
+	}
+
+	if filename_token := arguments.MatchType(TokenString); filename_token != nil {
+		// prepared, static template
+
+		// Get include-filename
+		included_filename := doc.template.set.resolveFilename(doc.template, filename_token.Val)
+
+		// Parse the parent
+		include_node.filename = included_filename
+		included_tpl, err := doc.template.set.FromFile(included_filename)
+		if err != nil {
+			return nil, err.(*Error).updateFromTokenIfNeeded(doc.template, filename_token)
+		}
+		include_node.tpl = included_tpl
+	} else {
+		// No String, then the user wants to use lazy-evaluation (slower, but possible)
+		filename_evaluator, err := arguments.ParseExpression()
+		if err != nil {
+			return nil, err.updateFromTokenIfNeeded(doc.template, filename_token)
+		}
+		include_node.filename_evaluator = filename_evaluator
+		include_node.lazy = true
+	}
+
+	// After having parsed the filename we're gonna parse the with+only options
+	if arguments.Match(TokenIdentifier, "with") != nil {
+		for arguments.Remaining() > 0 {
+			// We have at least one key=expr pair (because of starting "with")
+			key_token := arguments.MatchType(TokenIdentifier)
+			if key_token == nil {
+				return nil, arguments.Error("Expected an identifier", nil)
+			}
+			if arguments.Match(TokenSymbol, "=") == nil {
+				return nil, arguments.Error("Expected '='.", nil)
+			}
+			value_expr, err := arguments.ParseExpression()
+			if err != nil {
+				return nil, err.updateFromTokenIfNeeded(doc.template, key_token)
+			}
+
+			include_node.with_pairs[key_token.Val] = value_expr
+
+			// Only?
+			if arguments.Match(TokenIdentifier, "only") != nil {
+				include_node.only = true
+				break // stop parsing arguments because it's the last option
+			}
+		}
+	}
+
+	if arguments.Remaining() > 0 {
+		return nil, arguments.Error("Malformed 'include'-tag arguments.", nil)
+	}
+
+	return include_node, nil
+}
+
+func init() {
+	RegisterTag("include", tagIncludeParser)
+}

+ 132 - 0
vendor/github.com/flosch/pongo2/tags_lorem.go

@@ -0,0 +1,132 @@
+package pongo2
+
+import (
+	"bytes"
+	"math/rand"
+	"strings"
+	"time"
+)
+
+var (
+	tagLoremParagraphs = strings.Split(tagLoremText, "\n")
+	tagLoremWords      = strings.Fields(tagLoremText)
+)
+
+type tagLoremNode struct {
+	position *Token
+	count    int    // number of paragraphs
+	method   string // w = words, p = HTML paragraphs, b = plain-text (default is b)
+	random   bool   // does not use the default paragraph "Lorem ipsum dolor sit amet, ..."
+}
+
+func (node *tagLoremNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	switch node.method {
+	case "b":
+		if node.random {
+			for i := 0; i < node.count; i++ {
+				if i > 0 {
+					buffer.WriteString("\n")
+				}
+				par := tagLoremParagraphs[rand.Intn(len(tagLoremParagraphs))]
+				buffer.WriteString(par)
+			}
+		} else {
+			for i := 0; i < node.count; i++ {
+				if i > 0 {
+					buffer.WriteString("\n")
+				}
+				par := tagLoremParagraphs[i%len(tagLoremParagraphs)]
+				buffer.WriteString(par)
+			}
+		}
+	case "w":
+		if node.random {
+			for i := 0; i < node.count; i++ {
+				if i > 0 {
+					buffer.WriteString(" ")
+				}
+				word := tagLoremWords[rand.Intn(len(tagLoremWords))]
+				buffer.WriteString(word)
+			}
+		} else {
+			for i := 0; i < node.count; i++ {
+				if i > 0 {
+					buffer.WriteString(" ")
+				}
+				word := tagLoremWords[i%len(tagLoremWords)]
+				buffer.WriteString(word)
+			}
+		}
+	case "p":
+		if node.random {
+			for i := 0; i < node.count; i++ {
+				if i > 0 {
+					buffer.WriteString("\n")
+				}
+				buffer.WriteString("<p>")
+				par := tagLoremParagraphs[rand.Intn(len(tagLoremParagraphs))]
+				buffer.WriteString(par)
+				buffer.WriteString("</p>")
+			}
+		} else {
+			for i := 0; i < node.count; i++ {
+				if i > 0 {
+					buffer.WriteString("\n")
+				}
+				buffer.WriteString("<p>")
+				par := tagLoremParagraphs[i%len(tagLoremParagraphs)]
+				buffer.WriteString(par)
+				buffer.WriteString("</p>")
+
+			}
+		}
+	default:
+		panic("unsupported method")
+	}
+
+	return nil
+}
+
+func tagLoremParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
+	lorem_node := &tagLoremNode{
+		position: start,
+		count:    1,
+		method:   "b",
+	}
+
+	if count_token := arguments.MatchType(TokenNumber); count_token != nil {
+		lorem_node.count = AsValue(count_token.Val).Integer()
+	}
+
+	if method_token := arguments.MatchType(TokenIdentifier); method_token != nil {
+		if method_token.Val != "w" && method_token.Val != "p" && method_token.Val != "b" {
+			return nil, arguments.Error("lorem-method must be either 'w', 'p' or 'b'.", nil)
+		}
+
+		lorem_node.method = method_token.Val
+	}
+
+	if arguments.MatchOne(TokenIdentifier, "random") != nil {
+		lorem_node.random = true
+	}
+
+	if arguments.Remaining() > 0 {
+		return nil, arguments.Error("Malformed lorem-tag arguments.", nil)
+	}
+
+	return lorem_node, nil
+}
+
+func init() {
+	rand.Seed(time.Now().Unix())
+
+	RegisterTag("lorem", tagLoremParser)
+}
+
+const tagLoremText = `Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
+Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
+Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
+Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis.
+At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.
+Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.`

+ 149 - 0
vendor/github.com/flosch/pongo2/tags_macro.go

@@ -0,0 +1,149 @@
+package pongo2
+
+import (
+	"bytes"
+	"fmt"
+)
+
+type tagMacroNode struct {
+	position   *Token
+	name       string
+	args_order []string
+	args       map[string]IEvaluator
+	exported   bool
+
+	wrapper *NodeWrapper
+}
+
+func (node *tagMacroNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	ctx.Private[node.name] = func(args ...*Value) *Value {
+		return node.call(ctx, args...)
+	}
+
+	return nil
+}
+
+func (node *tagMacroNode) call(ctx *ExecutionContext, args ...*Value) *Value {
+	args_ctx := make(Context)
+
+	for k, v := range node.args {
+		if v == nil {
+			// User did not provided a default value
+			args_ctx[k] = nil
+		} else {
+			// Evaluate the default value
+			value_expr, err := v.Evaluate(ctx)
+			if err != nil {
+				ctx.Logf(err.Error())
+				return AsSafeValue(err.Error())
+			}
+
+			args_ctx[k] = value_expr
+		}
+	}
+
+	if len(args) > len(node.args_order) {
+		// Too many arguments, we're ignoring them and just logging into debug mode.
+		err := ctx.Error(fmt.Sprintf("Macro '%s' called with too many arguments (%d instead of %d).",
+			node.name, len(args), len(node.args_order)), nil).updateFromTokenIfNeeded(ctx.template, node.position)
+
+		ctx.Logf(err.Error()) // TODO: This is a workaround, because the error is not returned yet to the Execution()-methods
+		return AsSafeValue(err.Error())
+	}
+
+	// Make a context for the macro execution
+	macroCtx := NewChildExecutionContext(ctx)
+
+	// Register all arguments in the private context
+	macroCtx.Private.Update(args_ctx)
+
+	for idx, arg_value := range args {
+		macroCtx.Private[node.args_order[idx]] = arg_value.Interface()
+	}
+
+	var b bytes.Buffer
+	err := node.wrapper.Execute(macroCtx, &b)
+	if err != nil {
+		return AsSafeValue(err.updateFromTokenIfNeeded(ctx.template, node.position).Error())
+	}
+
+	return AsSafeValue(b.String())
+}
+
+func tagMacroParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
+	macro_node := &tagMacroNode{
+		position: start,
+		args:     make(map[string]IEvaluator),
+	}
+
+	name_token := arguments.MatchType(TokenIdentifier)
+	if name_token == nil {
+		return nil, arguments.Error("Macro-tag needs at least an identifier as name.", nil)
+	}
+	macro_node.name = name_token.Val
+
+	if arguments.MatchOne(TokenSymbol, "(") == nil {
+		return nil, arguments.Error("Expected '('.", nil)
+	}
+
+	for arguments.Match(TokenSymbol, ")") == nil {
+		arg_name_token := arguments.MatchType(TokenIdentifier)
+		if arg_name_token == nil {
+			return nil, arguments.Error("Expected argument name as identifier.", nil)
+		}
+		macro_node.args_order = append(macro_node.args_order, arg_name_token.Val)
+
+		if arguments.Match(TokenSymbol, "=") != nil {
+			// Default expression follows
+			arg_default_expr, err := arguments.ParseExpression()
+			if err != nil {
+				return nil, err
+			}
+			macro_node.args[arg_name_token.Val] = arg_default_expr
+		} else {
+			// No default expression
+			macro_node.args[arg_name_token.Val] = nil
+		}
+
+		if arguments.Match(TokenSymbol, ")") != nil {
+			break
+		}
+		if arguments.Match(TokenSymbol, ",") == nil {
+			return nil, arguments.Error("Expected ',' or ')'.", nil)
+		}
+	}
+
+	if arguments.Match(TokenKeyword, "export") != nil {
+		macro_node.exported = true
+	}
+
+	if arguments.Remaining() > 0 {
+		return nil, arguments.Error("Malformed macro-tag.", nil)
+	}
+
+	// Body wrapping
+	wrapper, endargs, err := doc.WrapUntilTag("endmacro")
+	if err != nil {
+		return nil, err
+	}
+	macro_node.wrapper = wrapper
+
+	if endargs.Count() > 0 {
+		return nil, endargs.Error("Arguments not allowed here.", nil)
+	}
+
+	if macro_node.exported {
+		// Now register the macro if it wants to be exported
+		_, has := doc.template.exported_macros[macro_node.name]
+		if has {
+			return nil, doc.Error(fmt.Sprintf("Another macro with name '%s' already exported.", macro_node.name), start)
+		}
+		doc.template.exported_macros[macro_node.name] = macro_node
+	}
+
+	return macro_node, nil
+}
+
+func init() {
+	RegisterTag("macro", tagMacroParser)
+}

+ 51 - 0
vendor/github.com/flosch/pongo2/tags_now.go

@@ -0,0 +1,51 @@
+package pongo2
+
+import (
+	"bytes"
+	"time"
+)
+
+type tagNowNode struct {
+	position *Token
+	format   string
+	fake     bool
+}
+
+func (node *tagNowNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	var t time.Time
+	if node.fake {
+		t = time.Date(2014, time.February, 05, 18, 31, 45, 00, time.UTC)
+	} else {
+		t = time.Now()
+	}
+
+	buffer.WriteString(t.Format(node.format))
+
+	return nil
+}
+
+func tagNowParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
+	now_node := &tagNowNode{
+		position: start,
+	}
+
+	format_token := arguments.MatchType(TokenString)
+	if format_token == nil {
+		return nil, arguments.Error("Expected a format string.", nil)
+	}
+	now_node.format = format_token.Val
+
+	if arguments.MatchOne(TokenIdentifier, "fake") != nil {
+		now_node.fake = true
+	}
+
+	if arguments.Remaining() > 0 {
+		return nil, arguments.Error("Malformed now-tag arguments.", nil)
+	}
+
+	return now_node, nil
+}
+
+func init() {
+	RegisterTag("now", tagNowParser)
+}

+ 52 - 0
vendor/github.com/flosch/pongo2/tags_set.go

@@ -0,0 +1,52 @@
+package pongo2
+
+import "bytes"
+
+type tagSetNode struct {
+	name       string
+	expression IEvaluator
+}
+
+func (node *tagSetNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	// Evaluate expression
+	value, err := node.expression.Evaluate(ctx)
+	if err != nil {
+		return err
+	}
+
+	ctx.Private[node.name] = value
+	return nil
+}
+
+func tagSetParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
+	node := &tagSetNode{}
+
+	// Parse variable name
+	typeToken := arguments.MatchType(TokenIdentifier)
+	if typeToken == nil {
+		return nil, arguments.Error("Expected an identifier.", nil)
+	}
+	node.name = typeToken.Val
+
+	if arguments.Match(TokenSymbol, "=") == nil {
+		return nil, arguments.Error("Expected '='.", nil)
+	}
+
+	// Variable expression
+	keyExpression, err := arguments.ParseExpression()
+	if err != nil {
+		return nil, err
+	}
+	node.expression = keyExpression
+
+	// Remaining arguments
+	if arguments.Remaining() > 0 {
+		return nil, arguments.Error("Malformed 'set'-tag arguments.", nil)
+	}
+
+	return node, nil
+}
+
+func init() {
+	RegisterTag("set", tagSetParser)
+}

+ 54 - 0
vendor/github.com/flosch/pongo2/tags_spaceless.go

@@ -0,0 +1,54 @@
+package pongo2
+
+import (
+	"bytes"
+	"regexp"
+)
+
+type tagSpacelessNode struct {
+	wrapper *NodeWrapper
+}
+
+var tagSpacelessRegexp = regexp.MustCompile(`(?U:(<.*>))([\t\n\v\f\r ]+)(?U:(<.*>))`)
+
+func (node *tagSpacelessNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	b := bytes.NewBuffer(make([]byte, 0, 1024)) // 1 KiB
+
+	err := node.wrapper.Execute(ctx, b)
+	if err != nil {
+		return err
+	}
+
+	s := b.String()
+	// Repeat this recursively
+	changed := true
+	for changed {
+		s2 := tagSpacelessRegexp.ReplaceAllString(s, "$1$3")
+		changed = s != s2
+		s = s2
+	}
+
+	buffer.WriteString(s)
+
+	return nil
+}
+
+func tagSpacelessParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
+	spaceless_node := &tagSpacelessNode{}
+
+	wrapper, _, err := doc.WrapUntilTag("endspaceless")
+	if err != nil {
+		return nil, err
+	}
+	spaceless_node.wrapper = wrapper
+
+	if arguments.Remaining() > 0 {
+		return nil, arguments.Error("Malformed spaceless-tag arguments.", nil)
+	}
+
+	return spaceless_node, nil
+}
+
+func init() {
+	RegisterTag("spaceless", tagSpacelessParser)
+}

+ 69 - 0
vendor/github.com/flosch/pongo2/tags_ssi.go

@@ -0,0 +1,69 @@
+package pongo2
+
+import (
+	"bytes"
+	"io/ioutil"
+)
+
+type tagSSINode struct {
+	filename string
+	content  string
+	template *Template
+}
+
+func (node *tagSSINode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	if node.template != nil {
+		// Execute the template within the current context
+		includeCtx := make(Context)
+		includeCtx.Update(ctx.Public)
+		includeCtx.Update(ctx.Private)
+
+		err := node.template.ExecuteWriter(includeCtx, buffer)
+		if err != nil {
+			return err.(*Error)
+		}
+	} else {
+		// Just print out the content
+		buffer.WriteString(node.content)
+	}
+	return nil
+}
+
+func tagSSIParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
+	ssi_node := &tagSSINode{}
+
+	if file_token := arguments.MatchType(TokenString); file_token != nil {
+		ssi_node.filename = file_token.Val
+
+		if arguments.Match(TokenIdentifier, "parsed") != nil {
+			// parsed
+			temporary_tpl, err := doc.template.set.FromFile(doc.template.set.resolveFilename(doc.template, file_token.Val))
+			if err != nil {
+				return nil, err.(*Error).updateFromTokenIfNeeded(doc.template, file_token)
+			}
+			ssi_node.template = temporary_tpl
+		} else {
+			// plaintext
+			buf, err := ioutil.ReadFile(doc.template.set.resolveFilename(doc.template, file_token.Val))
+			if err != nil {
+				return nil, (&Error{
+					Sender:   "tag:ssi",
+					ErrorMsg: err.Error(),
+				}).updateFromTokenIfNeeded(doc.template, file_token)
+			}
+			ssi_node.content = string(buf)
+		}
+	} else {
+		return nil, arguments.Error("First argument must be a string.", nil)
+	}
+
+	if arguments.Remaining() > 0 {
+		return nil, arguments.Error("Malformed SSI-tag argument.", nil)
+	}
+
+	return ssi_node, nil
+}
+
+func init() {
+	RegisterTag("ssi", tagSSIParser)
+}

+ 49 - 0
vendor/github.com/flosch/pongo2/tags_templatetag.go

@@ -0,0 +1,49 @@
+package pongo2
+
+import (
+	"bytes"
+)
+
+type tagTemplateTagNode struct {
+	content string
+}
+
+var templateTagMapping = map[string]string{
+	"openblock":     "{%",
+	"closeblock":    "%}",
+	"openvariable":  "{{",
+	"closevariable": "}}",
+	"openbrace":     "{",
+	"closebrace":    "}",
+	"opencomment":   "{#",
+	"closecomment":  "#}",
+}
+
+func (node *tagTemplateTagNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	buffer.WriteString(node.content)
+	return nil
+}
+
+func tagTemplateTagParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
+	tt_node := &tagTemplateTagNode{}
+
+	if arg_token := arguments.MatchType(TokenIdentifier); arg_token != nil {
+		output, found := templateTagMapping[arg_token.Val]
+		if !found {
+			return nil, arguments.Error("Argument not found", arg_token)
+		}
+		tt_node.content = output
+	} else {
+		return nil, arguments.Error("Identifier expected.", nil)
+	}
+
+	if arguments.Remaining() > 0 {
+		return nil, arguments.Error("Malformed templatetag-tag argument.", nil)
+	}
+
+	return tt_node, nil
+}
+
+func init() {
+	RegisterTag("templatetag", tagTemplateTagParser)
+}

+ 84 - 0
vendor/github.com/flosch/pongo2/tags_widthratio.go

@@ -0,0 +1,84 @@
+package pongo2
+
+import (
+	"bytes"
+	"fmt"
+	"math"
+)
+
+type tagWidthratioNode struct {
+	position     *Token
+	current, max IEvaluator
+	width        IEvaluator
+	ctx_name     string
+}
+
+func (node *tagWidthratioNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	current, err := node.current.Evaluate(ctx)
+	if err != nil {
+		return err
+	}
+
+	max, err := node.max.Evaluate(ctx)
+	if err != nil {
+		return err
+	}
+
+	width, err := node.width.Evaluate(ctx)
+	if err != nil {
+		return err
+	}
+
+	value := int(math.Ceil(current.Float()/max.Float()*width.Float() + 0.5))
+
+	if node.ctx_name == "" {
+		buffer.WriteString(fmt.Sprintf("%d", value))
+	} else {
+		ctx.Private[node.ctx_name] = value
+	}
+
+	return nil
+}
+
+func tagWidthratioParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
+	widthratio_node := &tagWidthratioNode{
+		position: start,
+	}
+
+	current, err := arguments.ParseExpression()
+	if err != nil {
+		return nil, err
+	}
+	widthratio_node.current = current
+
+	max, err := arguments.ParseExpression()
+	if err != nil {
+		return nil, err
+	}
+	widthratio_node.max = max
+
+	width, err := arguments.ParseExpression()
+	if err != nil {
+		return nil, err
+	}
+	widthratio_node.width = width
+
+	if arguments.MatchOne(TokenKeyword, "as") != nil {
+		// Name follows
+		name_token := arguments.MatchType(TokenIdentifier)
+		if name_token == nil {
+			return nil, arguments.Error("Expected name (identifier).", nil)
+		}
+		widthratio_node.ctx_name = name_token.Val
+	}
+
+	if arguments.Remaining() > 0 {
+		return nil, arguments.Error("Malformed widthratio-tag arguments.", nil)
+	}
+
+	return widthratio_node, nil
+}
+
+func init() {
+	RegisterTag("widthratio", tagWidthratioParser)
+}

+ 92 - 0
vendor/github.com/flosch/pongo2/tags_with.go

@@ -0,0 +1,92 @@
+package pongo2
+
+import (
+	"bytes"
+)
+
+type tagWithNode struct {
+	with_pairs map[string]IEvaluator
+	wrapper    *NodeWrapper
+}
+
+func (node *tagWithNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	//new context for block
+	withctx := NewChildExecutionContext(ctx)
+
+	// Put all custom with-pairs into the context
+	for key, value := range node.with_pairs {
+		val, err := value.Evaluate(ctx)
+		if err != nil {
+			return err
+		}
+		withctx.Private[key] = val
+	}
+
+	return node.wrapper.Execute(withctx, buffer)
+}
+
+func tagWithParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
+	with_node := &tagWithNode{
+		with_pairs: make(map[string]IEvaluator),
+	}
+
+	if arguments.Count() == 0 {
+		return nil, arguments.Error("Tag 'with' requires at least one argument.", nil)
+	}
+
+	wrapper, endargs, err := doc.WrapUntilTag("endwith")
+	if err != nil {
+		return nil, err
+	}
+	with_node.wrapper = wrapper
+
+	if endargs.Count() > 0 {
+		return nil, endargs.Error("Arguments not allowed here.", nil)
+	}
+
+	// Scan through all arguments to see which style the user uses (old or new style).
+	// If we find any "as" keyword we will enforce old style; otherwise we will use new style.
+	old_style := false // by default we're using the new_style
+	for i := 0; i < arguments.Count(); i++ {
+		if arguments.PeekN(i, TokenKeyword, "as") != nil {
+			old_style = true
+			break
+		}
+	}
+
+	for arguments.Remaining() > 0 {
+		if old_style {
+			value_expr, err := arguments.ParseExpression()
+			if err != nil {
+				return nil, err
+			}
+			if arguments.Match(TokenKeyword, "as") == nil {
+				return nil, arguments.Error("Expected 'as' keyword.", nil)
+			}
+			key_token := arguments.MatchType(TokenIdentifier)
+			if key_token == nil {
+				return nil, arguments.Error("Expected an identifier", nil)
+			}
+			with_node.with_pairs[key_token.Val] = value_expr
+		} else {
+			key_token := arguments.MatchType(TokenIdentifier)
+			if key_token == nil {
+				return nil, arguments.Error("Expected an identifier", nil)
+			}
+			if arguments.Match(TokenSymbol, "=") == nil {
+				return nil, arguments.Error("Expected '='.", nil)
+			}
+			value_expr, err := arguments.ParseExpression()
+			if err != nil {
+				return nil, err
+			}
+			with_node.with_pairs[key_token.Val] = value_expr
+		}
+	}
+
+	return with_node, nil
+}
+
+func init() {
+	RegisterTag("with", tagWithParser)
+}

+ 164 - 0
vendor/github.com/flosch/pongo2/template.go

@@ -0,0 +1,164 @@
+package pongo2
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+)
+
+type Template struct {
+	set *TemplateSet
+
+	// Input
+	is_tpl_string bool
+	name          string
+	tpl           string
+	size          int
+
+	// Calculation
+	tokens []*Token
+	parser *Parser
+
+	// first come, first serve (it's important to not override existing entries in here)
+	level           int
+	parent          *Template
+	child           *Template
+	blocks          map[string]*NodeWrapper
+	exported_macros map[string]*tagMacroNode
+
+	// Output
+	root *nodeDocument
+}
+
+func newTemplateString(set *TemplateSet, tpl string) (*Template, error) {
+	return newTemplate(set, "<string>", true, tpl)
+}
+
+func newTemplate(set *TemplateSet, name string, is_tpl_string bool, tpl string) (*Template, error) {
+	// Create the template
+	t := &Template{
+		set:             set,
+		is_tpl_string:   is_tpl_string,
+		name:            name,
+		tpl:             tpl,
+		size:            len(tpl),
+		blocks:          make(map[string]*NodeWrapper),
+		exported_macros: make(map[string]*tagMacroNode),
+	}
+
+	// Tokenize it
+	tokens, err := lex(name, tpl)
+	if err != nil {
+		return nil, err
+	}
+	t.tokens = tokens
+
+	// For debugging purposes, show all tokens:
+	/*for i, t := range tokens {
+		fmt.Printf("%3d. %s\n", i, t)
+	}*/
+
+	// Parse it
+	err = t.parse()
+	if err != nil {
+		return nil, err
+	}
+
+	return t, nil
+}
+
+func (tpl *Template) execute(context Context) (*bytes.Buffer, error) {
+	// Create output buffer
+	// We assume that the rendered template will be 30% larger
+	buffer := bytes.NewBuffer(make([]byte, 0, int(float64(tpl.size)*1.3)))
+
+	// Determine the parent to be executed (for template inheritance)
+	parent := tpl
+	for parent.parent != nil {
+		parent = parent.parent
+	}
+
+	// Create context if none is given
+	newContext := make(Context)
+	newContext.Update(tpl.set.Globals)
+
+	if context != nil {
+		newContext.Update(context)
+
+		if len(newContext) > 0 {
+			// Check for context name syntax
+			err := newContext.checkForValidIdentifiers()
+			if err != nil {
+				return nil, err
+			}
+
+			// Check for clashes with macro names
+			for k, _ := range newContext {
+				_, has := tpl.exported_macros[k]
+				if has {
+					return nil, &Error{
+						Filename: tpl.name,
+						Sender:   "execution",
+						ErrorMsg: fmt.Sprintf("Context key name '%s' clashes with macro '%s'.", k, k),
+					}
+				}
+			}
+		}
+	}
+
+	// Create operational context
+	ctx := newExecutionContext(parent, newContext)
+
+	// Run the selected document
+	err := parent.root.Execute(ctx, buffer)
+	if err != nil {
+		return nil, err
+	}
+	return buffer, nil
+}
+
+// Executes the template with the given context and writes to writer (io.Writer)
+// on success. Context can be nil. Nothing is written on error; instead the error
+// is being returned.
+func (tpl *Template) ExecuteWriter(context Context, writer io.Writer) error {
+	buffer, err := tpl.execute(context)
+	if err != nil {
+		return err
+	}
+
+	l := buffer.Len()
+	n, werr := buffer.WriteTo(writer)
+	if int(n) != l {
+		panic(fmt.Sprintf("error on writing template: n(%d) != buffer.Len(%d)", n, l))
+	}
+	if werr != nil {
+		return &Error{
+			Filename: tpl.name,
+			Sender:   "execution",
+			ErrorMsg: werr.Error(),
+		}
+	}
+	return nil
+}
+
+// Executes the template and returns the rendered template as a []byte
+func (tpl *Template) ExecuteBytes(context Context) ([]byte, error) {
+	// Execute template
+	buffer, err := tpl.execute(context)
+	if err != nil {
+		return nil, err
+	}
+	return buffer.Bytes(), nil
+}
+
+// Executes the template and returns the rendered template as a string
+func (tpl *Template) Execute(context Context) (string, error) {
+	// Execute template
+	buffer, err := tpl.execute(context)
+	if err != nil {
+		return "", err
+	}
+
+	return buffer.String(), nil
+
+}

+ 296 - 0
vendor/github.com/flosch/pongo2/template_sets.go

@@ -0,0 +1,296 @@
+package pongo2
+
+import (
+	"fmt"
+	"io/ioutil"
+	"log"
+	"os"
+	"path/filepath"
+	"sync"
+)
+
+// A template set allows you to create your own group of templates with their own global context (which is shared
+// among all members of the set), their own configuration (like a specific base directory) and their own sandbox.
+// It's useful for a separation of different kind of templates (e. g. web templates vs. mail templates).
+type TemplateSet struct {
+	name string
+
+	// Globals will be provided to all templates created within this template set
+	Globals Context
+
+	// If debug is true (default false), ExecutionContext.Logf() will work and output to STDOUT. Furthermore,
+	// FromCache() won't cache the templates. Make sure to synchronize the access to it in case you're changing this
+	// variable during program execution (and template compilation/execution).
+	Debug bool
+
+	// Base directory: If you set the base directory (string is non-empty), all filename lookups in tags/filters are
+	// relative to this directory. If it's empty, all lookups are relative to the current filename which is importing.
+	baseDirectory string
+
+	// Sandbox features
+	// - Limit access to directories (using SandboxDirectories)
+	// - Disallow access to specific tags and/or filters (using BanTag() and BanFilter())
+	//
+	// You can limit file accesses (for all tags/filters which are using pongo2's file resolver technique)
+	// to these sandbox directories. All default pongo2 filters/tags are respecting these restrictions.
+	// For example, if you only have your base directory in the list, a {% ssi "/etc/passwd" %} will not work.
+	// No items in SandboxDirectories means no restrictions at all.
+	//
+	// For efficiency reasons you can ban tags/filters only *before* you have added your first
+	// template to the set (restrictions are statically checked). After you added one, it's not possible anymore
+	// (for your personal security).
+	//
+	// SandboxDirectories can be changed at runtime. Please synchronize the access to it if you need to change it
+	// after you've added your first template to the set. You *must* use this match pattern for your directories:
+	// http://golang.org/pkg/path/filepath/#Match
+	SandboxDirectories   []string
+	firstTemplateCreated bool
+	bannedTags           map[string]bool
+	bannedFilters        map[string]bool
+
+	// Template cache (for FromCache())
+	templateCache      map[string]*Template
+	templateCacheMutex sync.Mutex
+}
+
+// Create your own template sets to separate different kind of templates (e. g. web from mail templates) with
+// different globals or other configurations (like base directories).
+func NewSet(name string) *TemplateSet {
+	return &TemplateSet{
+		name:          name,
+		Globals:       make(Context),
+		bannedTags:    make(map[string]bool),
+		bannedFilters: make(map[string]bool),
+		templateCache: make(map[string]*Template),
+	}
+}
+
+// Use this function to set your template set's base directory. This directory will be used for any relative
+// path in filters, tags and From*-functions to determine your template.
+func (set *TemplateSet) SetBaseDirectory(name string) error {
+	// Make the path absolute
+	if !filepath.IsAbs(name) {
+		abs, err := filepath.Abs(name)
+		if err != nil {
+			return err
+		}
+		name = abs
+	}
+
+	// Check for existence
+	fi, err := os.Stat(name)
+	if err != nil {
+		return err
+	}
+	if !fi.IsDir() {
+		return fmt.Errorf("The given path '%s' is not a directory.")
+	}
+
+	set.baseDirectory = name
+	return nil
+}
+
+func (set *TemplateSet) BaseDirectory() string {
+	return set.baseDirectory
+}
+
+// Ban a specific tag for this template set. See more in the documentation for TemplateSet.
+func (set *TemplateSet) BanTag(name string) {
+	_, has := tags[name]
+	if !has {
+		panic(fmt.Sprintf("Tag '%s' not found.", name))
+	}
+	if set.firstTemplateCreated {
+		panic("You cannot ban any tags after you've added your first template to your template set.")
+	}
+	_, has = set.bannedTags[name]
+	if has {
+		panic(fmt.Sprintf("Tag '%s' is already banned.", name))
+	}
+	set.bannedTags[name] = true
+}
+
+// Ban a specific filter for this template set. See more in the documentation for TemplateSet.
+func (set *TemplateSet) BanFilter(name string) {
+	_, has := filters[name]
+	if !has {
+		panic(fmt.Sprintf("Filter '%s' not found.", name))
+	}
+	if set.firstTemplateCreated {
+		panic("You cannot ban any filters after you've added your first template to your template set.")
+	}
+	_, has = set.bannedFilters[name]
+	if has {
+		panic(fmt.Sprintf("Filter '%s' is already banned.", name))
+	}
+	set.bannedFilters[name] = true
+}
+
+// FromCache() is a convenient method to cache templates. It is thread-safe
+// and will only compile the template associated with a filename once.
+// If TemplateSet.Debug is true (for example during development phase),
+// FromCache() will not cache the template and instead recompile it on any
+// call (to make changes to a template live instantaneously).
+// Like FromFile(), FromCache() takes a relative path to a set base directory.
+// Sandbox restrictions apply (if given).
+func (set *TemplateSet) FromCache(filename string) (*Template, error) {
+	if set.Debug {
+		// Recompile on any request
+		return set.FromFile(filename)
+	} else {
+		// Cache the template
+		cleaned_filename := set.resolveFilename(nil, filename)
+
+		set.templateCacheMutex.Lock()
+		defer set.templateCacheMutex.Unlock()
+
+		tpl, has := set.templateCache[cleaned_filename]
+
+		// Cache miss
+		if !has {
+			tpl, err := set.FromFile(cleaned_filename)
+			if err != nil {
+				return nil, err
+			}
+			set.templateCache[cleaned_filename] = tpl
+			return tpl, nil
+		}
+
+		// Cache hit
+		return tpl, nil
+	}
+}
+
+// Loads  a template from string and returns a Template instance.
+func (set *TemplateSet) FromString(tpl string) (*Template, error) {
+	set.firstTemplateCreated = true
+
+	return newTemplateString(set, tpl)
+}
+
+// Loads a template from a filename and returns a Template instance.
+// If a base directory is set, the filename must be either relative to it
+// or be an absolute path. Sandbox restrictions (SandboxDirectories) apply
+// if given.
+func (set *TemplateSet) FromFile(filename string) (*Template, error) {
+	set.firstTemplateCreated = true
+
+	buf, err := ioutil.ReadFile(set.resolveFilename(nil, filename))
+	if err != nil {
+		return nil, &Error{
+			Filename: filename,
+			Sender:   "fromfile",
+			ErrorMsg: err.Error(),
+		}
+	}
+	return newTemplate(set, filename, false, string(buf))
+}
+
+// Shortcut; renders a template string directly. Panics when providing a
+// malformed template or an error occurs during execution.
+func (set *TemplateSet) RenderTemplateString(s string, ctx Context) string {
+	set.firstTemplateCreated = true
+
+	tpl := Must(set.FromString(s))
+	result, err := tpl.Execute(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return result
+}
+
+// Shortcut; renders a template file directly. Panics when providing a
+// malformed template or an error occurs during execution.
+func (set *TemplateSet) RenderTemplateFile(fn string, ctx Context) string {
+	set.firstTemplateCreated = true
+
+	tpl := Must(set.FromFile(fn))
+	result, err := tpl.Execute(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return result
+}
+
+func (set *TemplateSet) logf(format string, args ...interface{}) {
+	if set.Debug {
+		logger.Printf(fmt.Sprintf("[template set: %s] %s", set.name, format), args...)
+	}
+}
+
+// Resolves a filename relative to the base directory. Absolute paths are allowed.
+// If sandbox restrictions are given (SandboxDirectories), they will be respected and checked.
+// On sandbox restriction violation, resolveFilename() panics.
+func (set *TemplateSet) resolveFilename(tpl *Template, filename string) (resolved_path string) {
+	if len(set.SandboxDirectories) > 0 {
+		defer func() {
+			// Remove any ".." or other crap
+			resolved_path = filepath.Clean(resolved_path)
+
+			// Make the path absolute
+			abs_path, err := filepath.Abs(resolved_path)
+			if err != nil {
+				panic(err)
+			}
+			resolved_path = abs_path
+
+			// Check against the sandbox directories (once one pattern matches, we're done and can allow it)
+			for _, pattern := range set.SandboxDirectories {
+				matched, err := filepath.Match(pattern, resolved_path)
+				if err != nil {
+					panic("Wrong sandbox directory match pattern (see http://golang.org/pkg/path/filepath/#Match).")
+				}
+				if matched {
+					// OK!
+					return
+				}
+			}
+
+			// No pattern matched, we have to log+deny the request
+			set.logf("Access attempt outside of the sandbox directories (blocked): '%s'", resolved_path)
+			resolved_path = ""
+		}()
+	}
+
+	if filepath.IsAbs(filename) {
+		return filename
+	}
+
+	if set.baseDirectory == "" {
+		if tpl != nil {
+			if tpl.is_tpl_string {
+				return filename
+			}
+			base := filepath.Dir(tpl.name)
+			return filepath.Join(base, filename)
+		}
+		return filename
+	} else {
+		return filepath.Join(set.baseDirectory, filename)
+	}
+}
+
+// Logging function (internally used)
+func logf(format string, items ...interface{}) {
+	if debug {
+		logger.Printf(format, items...)
+	}
+}
+
+var (
+	debug  bool // internal debugging
+	logger = log.New(os.Stdout, "[pongo2] ", log.LstdFlags)
+
+	// Creating a default set
+	DefaultSet = NewSet("default")
+
+	// Methods on the default set
+	FromString           = DefaultSet.FromString
+	FromFile             = DefaultSet.FromFile
+	FromCache            = DefaultSet.FromCache
+	RenderTemplateString = DefaultSet.RenderTemplateString
+	RenderTemplateFile   = DefaultSet.RenderTemplateFile
+
+	// Globals for the default set
+	Globals = DefaultSet.Globals
+)

+ 439 - 0
vendor/github.com/flosch/pongo2/value.go

@@ -0,0 +1,439 @@
+package pongo2
+
+import (
+	"fmt"
+	"reflect"
+	"strconv"
+	"strings"
+)
+
+type Value struct {
+	val  reflect.Value
+	safe bool // used to indicate whether a Value needs explicit escaping in the template
+}
+
+// Converts any given value to a pongo2.Value
+// Usually being used within own functions passed to a template
+// through a Context or within filter functions.
+//
+// Example:
+//     AsValue("my string")
+func AsValue(i interface{}) *Value {
+	return &Value{
+		val: reflect.ValueOf(i),
+	}
+}
+
+// Like AsValue, but does not apply the 'escape' filter.
+func AsSafeValue(i interface{}) *Value {
+	return &Value{
+		val:  reflect.ValueOf(i),
+		safe: true,
+	}
+}
+
+func (v *Value) getResolvedValue() reflect.Value {
+	if v.val.IsValid() && v.val.Kind() == reflect.Ptr {
+		return v.val.Elem()
+	}
+	return v.val
+}
+
+// Checks whether the underlying value is a string
+func (v *Value) IsString() bool {
+	return v.getResolvedValue().Kind() == reflect.String
+}
+
+// Checks whether the underlying value is a bool
+func (v *Value) IsBool() bool {
+	return v.getResolvedValue().Kind() == reflect.Bool
+}
+
+// Checks whether the underlying value is a float
+func (v *Value) IsFloat() bool {
+	return v.getResolvedValue().Kind() == reflect.Float32 ||
+		v.getResolvedValue().Kind() == reflect.Float64
+}
+
+// Checks whether the underlying value is an integer
+func (v *Value) IsInteger() bool {
+	return v.getResolvedValue().Kind() == reflect.Int ||
+		v.getResolvedValue().Kind() == reflect.Int8 ||
+		v.getResolvedValue().Kind() == reflect.Int16 ||
+		v.getResolvedValue().Kind() == reflect.Int32 ||
+		v.getResolvedValue().Kind() == reflect.Int64 ||
+		v.getResolvedValue().Kind() == reflect.Uint ||
+		v.getResolvedValue().Kind() == reflect.Uint8 ||
+		v.getResolvedValue().Kind() == reflect.Uint16 ||
+		v.getResolvedValue().Kind() == reflect.Uint32 ||
+		v.getResolvedValue().Kind() == reflect.Uint64
+}
+
+// Checks whether the underlying value is either an integer
+// or a float.
+func (v *Value) IsNumber() bool {
+	return v.IsInteger() || v.IsFloat()
+}
+
+// Checks whether the underlying value is NIL
+func (v *Value) IsNil() bool {
+	//fmt.Printf("%+v\n", v.getResolvedValue().Type().String())
+	return !v.getResolvedValue().IsValid()
+}
+
+// Returns a string for the underlying value. If this value is not
+// of type string, pongo2 tries to convert it. Currently the following
+// types for underlying values are supported:
+//
+//     1. string
+//     2. int/uint (any size)
+//     3. float (any precision)
+//     4. bool
+//     5. time.Time
+//     6. String() will be called on the underlying value if provided
+//
+// NIL values will lead to an empty string. Unsupported types are leading
+// to their respective type name.
+func (v *Value) String() string {
+	if v.IsNil() {
+		return ""
+	}
+
+	switch v.getResolvedValue().Kind() {
+	case reflect.String:
+		return v.getResolvedValue().String()
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return strconv.FormatInt(v.getResolvedValue().Int(), 10)
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+		return strconv.FormatUint(v.getResolvedValue().Uint(), 10)
+	case reflect.Float32, reflect.Float64:
+		return fmt.Sprintf("%f", v.getResolvedValue().Float())
+	case reflect.Bool:
+		if v.Bool() {
+			return "True"
+		} else {
+			return "False"
+		}
+	case reflect.Struct:
+		if t, ok := v.Interface().(fmt.Stringer); ok {
+			return t.String()
+		}
+	}
+
+	logf("Value.String() not implemented for type: %s\n", v.getResolvedValue().Kind().String())
+	return v.getResolvedValue().String()
+}
+
+// Returns the underlying value as an integer (converts the underlying
+// value, if necessary). If it's not possible to convert the underlying value,
+// it will return 0.
+func (v *Value) Integer() int {
+	switch v.getResolvedValue().Kind() {
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return int(v.getResolvedValue().Int())
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+		return int(v.getResolvedValue().Uint())
+	case reflect.Float32, reflect.Float64:
+		return int(v.getResolvedValue().Float())
+	case reflect.String:
+		// Try to convert from string to int (base 10)
+		f, err := strconv.ParseFloat(v.getResolvedValue().String(), 64)
+		if err != nil {
+			return 0
+		}
+		return int(f)
+	default:
+		logf("Value.Integer() not available for type: %s\n", v.getResolvedValue().Kind().String())
+		return 0
+	}
+}
+
+// Returns the underlying value as a float (converts the underlying
+// value, if necessary). If it's not possible to convert the underlying value,
+// it will return 0.0.
+func (v *Value) Float() float64 {
+	switch v.getResolvedValue().Kind() {
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return float64(v.getResolvedValue().Int())
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+		return float64(v.getResolvedValue().Uint())
+	case reflect.Float32, reflect.Float64:
+		return v.getResolvedValue().Float()
+	case reflect.String:
+		// Try to convert from string to float64 (base 10)
+		f, err := strconv.ParseFloat(v.getResolvedValue().String(), 64)
+		if err != nil {
+			return 0.0
+		}
+		return f
+	default:
+		logf("Value.Float() not available for type: %s\n", v.getResolvedValue().Kind().String())
+		return 0.0
+	}
+}
+
+// Returns the underlying value as bool. If the value is not bool, false
+// will always be returned. If you're looking for true/false-evaluation of the
+// underlying value, have a look on the IsTrue()-function.
+func (v *Value) Bool() bool {
+	switch v.getResolvedValue().Kind() {
+	case reflect.Bool:
+		return v.getResolvedValue().Bool()
+	default:
+		logf("Value.Bool() not available for type: %s\n", v.getResolvedValue().Kind().String())
+		return false
+	}
+}
+
+// Tries to evaluate the underlying value the Pythonic-way:
+//
+// Returns TRUE in one the following cases:
+//
+//     * int != 0
+//     * uint != 0
+//     * float != 0.0
+//     * len(array/chan/map/slice/string) > 0
+//     * bool == true
+//     * underlying value is a struct
+//
+// Otherwise returns always FALSE.
+func (v *Value) IsTrue() bool {
+	switch v.getResolvedValue().Kind() {
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return v.getResolvedValue().Int() != 0
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+		return v.getResolvedValue().Uint() != 0
+	case reflect.Float32, reflect.Float64:
+		return v.getResolvedValue().Float() != 0
+	case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String:
+		return v.getResolvedValue().Len() > 0
+	case reflect.Bool:
+		return v.getResolvedValue().Bool()
+	case reflect.Struct:
+		return true // struct instance is always true
+	default:
+		logf("Value.IsTrue() not available for type: %s\n", v.getResolvedValue().Kind().String())
+		return false
+	}
+}
+
+// Tries to negate the underlying value. It's mainly used for
+// the NOT-operator and in conjunction with a call to
+// return_value.IsTrue() afterwards.
+//
+// Example:
+//     AsValue(1).Negate().IsTrue() == false
+func (v *Value) Negate() *Value {
+	switch v.getResolvedValue().Kind() {
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
+		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+		if v.Integer() != 0 {
+			return AsValue(0)
+		} else {
+			return AsValue(1)
+		}
+	case reflect.Float32, reflect.Float64:
+		if v.Float() != 0.0 {
+			return AsValue(float64(0.0))
+		} else {
+			return AsValue(float64(1.1))
+		}
+	case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String:
+		return AsValue(v.getResolvedValue().Len() == 0)
+	case reflect.Bool:
+		return AsValue(!v.getResolvedValue().Bool())
+	default:
+		logf("Value.IsTrue() not available for type: %s\n", v.getResolvedValue().Kind().String())
+		return AsValue(true)
+	}
+}
+
+// Returns the length for an array, chan, map, slice or string.
+// Otherwise it will return 0.
+func (v *Value) Len() int {
+	switch v.getResolvedValue().Kind() {
+	case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
+		return v.getResolvedValue().Len()
+	case reflect.String:
+		runes := []rune(v.getResolvedValue().String())
+		return len(runes)
+	default:
+		logf("Value.Len() not available for type: %s\n", v.getResolvedValue().Kind().String())
+		return 0
+	}
+}
+
+// Slices an array, slice or string. Otherwise it will
+// return an empty []int.
+func (v *Value) Slice(i, j int) *Value {
+	switch v.getResolvedValue().Kind() {
+	case reflect.Array, reflect.Slice:
+		return AsValue(v.getResolvedValue().Slice(i, j).Interface())
+	case reflect.String:
+		runes := []rune(v.getResolvedValue().String())
+		return AsValue(string(runes[i:j]))
+	default:
+		logf("Value.Slice() not available for type: %s\n", v.getResolvedValue().Kind().String())
+		return AsValue([]int{})
+	}
+}
+
+// Get the i-th item of an array, slice or string. Otherwise
+// it will return NIL.
+func (v *Value) Index(i int) *Value {
+	switch v.getResolvedValue().Kind() {
+	case reflect.Array, reflect.Slice:
+		if i >= v.Len() {
+			return AsValue(nil)
+		}
+		return AsValue(v.getResolvedValue().Index(i).Interface())
+	case reflect.String:
+		//return AsValue(v.getResolvedValue().Slice(i, i+1).Interface())
+		s := v.getResolvedValue().String()
+		runes := []rune(s)
+		if i < len(runes) {
+			return AsValue(string(runes[i]))
+		}
+		return AsValue("")
+	default:
+		logf("Value.Slice() not available for type: %s\n", v.getResolvedValue().Kind().String())
+		return AsValue([]int{})
+	}
+}
+
+// Checks whether the underlying value (which must be of type struct, map,
+// string, array or slice) contains of another Value (e. g. used to check
+// whether a struct contains of a specific field or a map contains a specific key).
+//
+// Example:
+//     AsValue("Hello, World!").Contains(AsValue("World")) == true
+func (v *Value) Contains(other *Value) bool {
+	switch v.getResolvedValue().Kind() {
+	case reflect.Struct:
+		field_value := v.getResolvedValue().FieldByName(other.String())
+		return field_value.IsValid()
+	case reflect.Map:
+		var map_value reflect.Value
+		switch other.Interface().(type) {
+		case int:
+			map_value = v.getResolvedValue().MapIndex(other.getResolvedValue())
+		case string:
+			map_value = v.getResolvedValue().MapIndex(other.getResolvedValue())
+		default:
+			logf("Value.Contains() does not support lookup type '%s'\n", other.getResolvedValue().Kind().String())
+			return false
+		}
+
+		return map_value.IsValid()
+	case reflect.String:
+		return strings.Contains(v.getResolvedValue().String(), other.String())
+
+	// TODO: reflect.Array, reflect.Slice
+
+	default:
+		logf("Value.Contains() not available for type: %s\n", v.getResolvedValue().Kind().String())
+		return false
+	}
+}
+
+// Checks whether the underlying value is of type array, slice or string.
+// You normally would use CanSlice() before using the Slice() operation.
+func (v *Value) CanSlice() bool {
+	switch v.getResolvedValue().Kind() {
+	case reflect.Array, reflect.Slice, reflect.String:
+		return true
+	}
+	return false
+}
+
+// Iterates over a map, array, slice or a string. It calls the
+// function's first argument for every value with the following arguments:
+//
+//     idx      current 0-index
+//     count    total amount of items
+//     key      *Value for the key or item
+//     value    *Value (only for maps, the respective value for a specific key)
+//
+// If the underlying value has no items or is not one of the types above,
+// the empty function (function's second argument) will be called.
+func (v *Value) Iterate(fn func(idx, count int, key, value *Value) bool, empty func()) {
+	v.IterateOrder(fn, empty, false)
+}
+
+// Like Value.Iterate, but can iterate through an array/slice/string in reverse. Does
+// not affect the iteration through a map because maps don't have any particular order.
+func (v *Value) IterateOrder(fn func(idx, count int, key, value *Value) bool, empty func(), reverse bool) {
+	switch v.getResolvedValue().Kind() {
+	case reflect.Map:
+		// Reverse not needed for maps, since they are not ordered
+		keys := v.getResolvedValue().MapKeys()
+		keyLen := len(keys)
+		for idx, key := range keys {
+			value := v.getResolvedValue().MapIndex(key)
+			if !fn(idx, keyLen, &Value{val: key}, &Value{val: value}) {
+				return
+			}
+		}
+		if keyLen == 0 {
+			empty()
+		}
+		return // done
+	case reflect.Array, reflect.Slice:
+		itemCount := v.getResolvedValue().Len()
+		if itemCount > 0 {
+			if reverse {
+				for i := itemCount - 1; i >= 0; i-- {
+					if !fn(i, itemCount, &Value{val: v.getResolvedValue().Index(i)}, nil) {
+						return
+					}
+				}
+			} else {
+				for i := 0; i < itemCount; i++ {
+					if !fn(i, itemCount, &Value{val: v.getResolvedValue().Index(i)}, nil) {
+						return
+					}
+				}
+			}
+		} else {
+			empty()
+		}
+		return // done
+	case reflect.String:
+		// TODO: Not utf8-compatible (utf8-decoding neccessary)
+		charCount := v.getResolvedValue().Len()
+		if charCount > 0 {
+			if reverse {
+				for i := charCount - 1; i >= 0; i-- {
+					if !fn(i, charCount, &Value{val: v.getResolvedValue().Slice(i, i+1)}, nil) {
+						return
+					}
+				}
+			} else {
+				for i := 0; i < charCount; i++ {
+					if !fn(i, charCount, &Value{val: v.getResolvedValue().Slice(i, i+1)}, nil) {
+						return
+					}
+				}
+			}
+		} else {
+			empty()
+		}
+		return // done
+	default:
+		logf("Value.Iterate() not available for type: %s\n", v.getResolvedValue().Kind().String())
+	}
+	empty()
+}
+
+// Gives you access to the underlying value.
+func (v *Value) Interface() interface{} {
+	if v.val.IsValid() {
+		return v.val.Interface()
+	}
+	return nil
+}
+
+// Checks whether two values are containing the same value or object.
+func (v *Value) EqualValueTo(other *Value) bool {
+	return v.Interface() == other.Interface()
+}

+ 656 - 0
vendor/github.com/flosch/pongo2/variable.go

@@ -0,0 +1,656 @@
+package pongo2
+
+import (
+	"bytes"
+	"fmt"
+	"reflect"
+	"strconv"
+	"strings"
+)
+
+const (
+	varTypeInt = iota
+	varTypeIdent
+)
+
+type variablePart struct {
+	typ int
+	s   string
+	i   int
+
+	is_function_call bool
+	calling_args     []functionCallArgument // needed for a function call, represents all argument nodes (INode supports nested function calls)
+}
+
+type functionCallArgument interface {
+	Evaluate(*ExecutionContext) (*Value, *Error)
+}
+
+// TODO: Add location tokens
+type stringResolver struct {
+	location_token *Token
+	val            string
+}
+
+type intResolver struct {
+	location_token *Token
+	val            int
+}
+
+type floatResolver struct {
+	location_token *Token
+	val            float64
+}
+
+type boolResolver struct {
+	location_token *Token
+	val            bool
+}
+
+type variableResolver struct {
+	location_token *Token
+
+	parts []*variablePart
+}
+
+type nodeFilteredVariable struct {
+	location_token *Token
+
+	resolver    IEvaluator
+	filterChain []*filterCall
+}
+
+type nodeVariable struct {
+	location_token *Token
+	expr           IEvaluator
+}
+
+func (expr *nodeFilteredVariable) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	value, err := expr.Evaluate(ctx)
+	if err != nil {
+		return err
+	}
+	buffer.WriteString(value.String())
+	return nil
+}
+
+func (expr *variableResolver) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	value, err := expr.Evaluate(ctx)
+	if err != nil {
+		return err
+	}
+	buffer.WriteString(value.String())
+	return nil
+}
+
+func (expr *stringResolver) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	value, err := expr.Evaluate(ctx)
+	if err != nil {
+		return err
+	}
+	buffer.WriteString(value.String())
+	return nil
+}
+
+func (expr *intResolver) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	value, err := expr.Evaluate(ctx)
+	if err != nil {
+		return err
+	}
+	buffer.WriteString(value.String())
+	return nil
+}
+
+func (expr *floatResolver) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	value, err := expr.Evaluate(ctx)
+	if err != nil {
+		return err
+	}
+	buffer.WriteString(value.String())
+	return nil
+}
+
+func (expr *boolResolver) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	value, err := expr.Evaluate(ctx)
+	if err != nil {
+		return err
+	}
+	buffer.WriteString(value.String())
+	return nil
+}
+
+func (v *nodeFilteredVariable) GetPositionToken() *Token {
+	return v.location_token
+}
+
+func (v *variableResolver) GetPositionToken() *Token {
+	return v.location_token
+}
+
+func (v *stringResolver) GetPositionToken() *Token {
+	return v.location_token
+}
+
+func (v *intResolver) GetPositionToken() *Token {
+	return v.location_token
+}
+
+func (v *floatResolver) GetPositionToken() *Token {
+	return v.location_token
+}
+
+func (v *boolResolver) GetPositionToken() *Token {
+	return v.location_token
+}
+
+func (s *stringResolver) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
+	return AsValue(s.val), nil
+}
+
+func (i *intResolver) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
+	return AsValue(i.val), nil
+}
+
+func (f *floatResolver) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
+	return AsValue(f.val), nil
+}
+
+func (b *boolResolver) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
+	return AsValue(b.val), nil
+}
+
+func (s *stringResolver) FilterApplied(name string) bool {
+	return false
+}
+
+func (i *intResolver) FilterApplied(name string) bool {
+	return false
+}
+
+func (f *floatResolver) FilterApplied(name string) bool {
+	return false
+}
+
+func (b *boolResolver) FilterApplied(name string) bool {
+	return false
+}
+
+func (nv *nodeVariable) FilterApplied(name string) bool {
+	return nv.expr.FilterApplied(name)
+}
+
+func (nv *nodeVariable) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
+	value, err := nv.expr.Evaluate(ctx)
+	if err != nil {
+		return err
+	}
+
+	if !nv.expr.FilterApplied("safe") && !value.safe && value.IsString() && ctx.Autoescape {
+		// apply escape filter
+		value, err = filters["escape"](value, nil)
+		if err != nil {
+			return err
+		}
+	}
+
+	buffer.WriteString(value.String())
+	return nil
+}
+
+func (vr *variableResolver) FilterApplied(name string) bool {
+	return false
+}
+
+func (vr *variableResolver) String() string {
+	parts := make([]string, 0, len(vr.parts))
+	for _, p := range vr.parts {
+		switch p.typ {
+		case varTypeInt:
+			parts = append(parts, strconv.Itoa(p.i))
+		case varTypeIdent:
+			parts = append(parts, p.s)
+		default:
+			panic("unimplemented")
+		}
+	}
+	return strings.Join(parts, ".")
+}
+
+func (vr *variableResolver) resolve(ctx *ExecutionContext) (*Value, error) {
+	var current reflect.Value
+	var is_safe bool
+
+	for idx, part := range vr.parts {
+		if idx == 0 {
+			// We're looking up the first part of the variable.
+			// First we're having a look in our private
+			// context (e. g. information provided by tags, like the forloop)
+			val, in_private := ctx.Private[vr.parts[0].s]
+			if !in_private {
+				// Nothing found? Then have a final lookup in the public context
+				val = ctx.Public[vr.parts[0].s]
+			}
+			current = reflect.ValueOf(val) // Get the initial value
+		} else {
+			// Next parts, resolve it from current
+
+			// Before resolving the pointer, let's see if we have a method to call
+			// Problem with resolving the pointer is we're changing the receiver
+			is_func := false
+			if part.typ == varTypeIdent {
+				func_value := current.MethodByName(part.s)
+				if func_value.IsValid() {
+					current = func_value
+					is_func = true
+				}
+			}
+
+			if !is_func {
+				// If current a pointer, resolve it
+				if current.Kind() == reflect.Ptr {
+					current = current.Elem()
+					if !current.IsValid() {
+						// Value is not valid (anymore)
+						return AsValue(nil), nil
+					}
+				}
+
+				// Look up which part must be called now
+				switch part.typ {
+				case varTypeInt:
+					// Calling an index is only possible for:
+					// * slices/arrays/strings
+					switch current.Kind() {
+					case reflect.String, reflect.Array, reflect.Slice:
+						current = current.Index(part.i)
+					default:
+						return nil, fmt.Errorf("Can't access an index on type %s (variable %s)",
+							current.Kind().String(), vr.String())
+					}
+				case varTypeIdent:
+					// debugging:
+					// fmt.Printf("now = %s (kind: %s)\n", part.s, current.Kind().String())
+
+					// Calling a field or key
+					switch current.Kind() {
+					case reflect.Struct:
+						current = current.FieldByName(part.s)
+					case reflect.Map:
+						current = current.MapIndex(reflect.ValueOf(part.s))
+					default:
+						return nil, fmt.Errorf("Can't access a field by name on type %s (variable %s)",
+							current.Kind().String(), vr.String())
+					}
+				default:
+					panic("unimplemented")
+				}
+			}
+		}
+
+		if !current.IsValid() {
+			// Value is not valid (anymore)
+			return AsValue(nil), nil
+		}
+
+		// If current is a reflect.ValueOf(pongo2.Value), then unpack it
+		// Happens in function calls (as a return value) or by injecting
+		// into the execution context (e.g. in a for-loop)
+		if current.Type() == reflect.TypeOf(&Value{}) {
+			tmp_value := current.Interface().(*Value)
+			current = tmp_value.val
+			is_safe = tmp_value.safe
+		}
+
+		// Check whether this is an interface and resolve it where required
+		if current.Kind() == reflect.Interface {
+			current = reflect.ValueOf(current.Interface())
+		}
+
+		// Check if the part is a function call
+		if part.is_function_call || current.Kind() == reflect.Func {
+			// Check for callable
+			if current.Kind() != reflect.Func {
+				return nil, fmt.Errorf("'%s' is not a function (it is %s).", vr.String(), current.Kind().String())
+			}
+
+			// Check for correct function syntax and types
+			// func(*Value, ...) *Value
+			t := current.Type()
+
+			// Input arguments
+			if len(part.calling_args) != t.NumIn() && !(len(part.calling_args) >= t.NumIn()-1 && t.IsVariadic()) {
+				return nil,
+					fmt.Errorf("Function input argument count (%d) of '%s' must be equal to the calling argument count (%d).",
+						t.NumIn(), vr.String(), len(part.calling_args))
+			}
+
+			// Output arguments
+			if t.NumOut() != 1 {
+				return nil, fmt.Errorf("'%s' must have exactly 1 output argument.", vr.String())
+			}
+
+			// Evaluate all parameters
+			parameters := make([]reflect.Value, 0)
+
+			num_args := t.NumIn()
+			is_variadic := t.IsVariadic()
+			var fn_arg reflect.Type
+
+			for idx, arg := range part.calling_args {
+				pv, err := arg.Evaluate(ctx)
+				if err != nil {
+					return nil, err
+				}
+
+				if is_variadic {
+					if idx >= t.NumIn()-1 {
+						fn_arg = t.In(num_args - 1).Elem()
+					} else {
+						fn_arg = t.In(idx)
+					}
+				} else {
+					fn_arg = t.In(idx)
+				}
+
+				if fn_arg != reflect.TypeOf(new(Value)) {
+					// Function's argument is not a *pongo2.Value, then we have to check whether input argument is of the same type as the function's argument
+					if !is_variadic {
+						if fn_arg != reflect.TypeOf(pv.Interface()) && fn_arg.Kind() != reflect.Interface {
+							return nil, fmt.Errorf("Function input argument %d of '%s' must be of type %s or *pongo2.Value (not %T).",
+								idx, vr.String(), fn_arg.String(), pv.Interface())
+						} else {
+							// Function's argument has another type, using the interface-value
+							parameters = append(parameters, reflect.ValueOf(pv.Interface()))
+						}
+					} else {
+						if fn_arg != reflect.TypeOf(pv.Interface()) && fn_arg.Kind() != reflect.Interface {
+							return nil, fmt.Errorf("Function variadic input argument of '%s' must be of type %s or *pongo2.Value (not %T).",
+								vr.String(), fn_arg.String(), pv.Interface())
+						} else {
+							// Function's argument has another type, using the interface-value
+							parameters = append(parameters, reflect.ValueOf(pv.Interface()))
+						}
+					}
+				} else {
+					// Function's argument is a *pongo2.Value
+					parameters = append(parameters, reflect.ValueOf(pv))
+				}
+			}
+
+			// Call it and get first return parameter back
+			rv := current.Call(parameters)[0]
+
+			if rv.Type() != reflect.TypeOf(new(Value)) {
+				current = reflect.ValueOf(rv.Interface())
+			} else {
+				// Return the function call value
+				current = rv.Interface().(*Value).val
+				is_safe = rv.Interface().(*Value).safe
+			}
+		}
+	}
+
+	if !current.IsValid() {
+		// Value is not valid (e. g. NIL value)
+		return AsValue(nil), nil
+	}
+
+	return &Value{val: current, safe: is_safe}, nil
+}
+
+func (vr *variableResolver) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
+	value, err := vr.resolve(ctx)
+	if err != nil {
+		return AsValue(nil), ctx.Error(err.Error(), vr.location_token)
+	}
+	return value, nil
+}
+
+func (v *nodeFilteredVariable) FilterApplied(name string) bool {
+	for _, filter := range v.filterChain {
+		if filter.name == name {
+			return true
+		}
+	}
+	return false
+}
+
+func (v *nodeFilteredVariable) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
+	value, err := v.resolver.Evaluate(ctx)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, filter := range v.filterChain {
+		value, err = filter.Execute(value, ctx)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return value, nil
+}
+
+// IDENT | IDENT.(IDENT|NUMBER)...
+func (p *Parser) parseVariableOrLiteral() (IEvaluator, *Error) {
+	t := p.Current()
+
+	if t == nil {
+		return nil, p.Error("Unexpected EOF, expected a number, string, keyword or identifier.", p.last_token)
+	}
+
+	// Is first part a number or a string, there's nothing to resolve (because there's only to return the value then)
+	switch t.Typ {
+	case TokenNumber:
+		p.Consume()
+
+		// One exception to the rule that we don't have float64 literals is at the beginning
+		// of an expression (or a variable name). Since we know we started with an integer
+		// which can't obviously be a variable name, we can check whether the first number
+		// is followed by dot (and then a number again). If so we're converting it to a float64.
+
+		if p.Match(TokenSymbol, ".") != nil {
+			// float64
+			t2 := p.MatchType(TokenNumber)
+			if t2 == nil {
+				return nil, p.Error("Expected a number after the '.'.", nil)
+			}
+			f, err := strconv.ParseFloat(fmt.Sprintf("%s.%s", t.Val, t2.Val), 64)
+			if err != nil {
+				return nil, p.Error(err.Error(), t)
+			}
+			fr := &floatResolver{
+				location_token: t,
+				val:            f,
+			}
+			return fr, nil
+		} else {
+			i, err := strconv.Atoi(t.Val)
+			if err != nil {
+				return nil, p.Error(err.Error(), t)
+			}
+			nr := &intResolver{
+				location_token: t,
+				val:            i,
+			}
+			return nr, nil
+		}
+	case TokenString:
+		p.Consume()
+		sr := &stringResolver{
+			location_token: t,
+			val:            t.Val,
+		}
+		return sr, nil
+	case TokenKeyword:
+		p.Consume()
+		switch t.Val {
+		case "true":
+			br := &boolResolver{
+				location_token: t,
+				val:            true,
+			}
+			return br, nil
+		case "false":
+			br := &boolResolver{
+				location_token: t,
+				val:            false,
+			}
+			return br, nil
+		default:
+			return nil, p.Error("This keyword is not allowed here.", nil)
+		}
+	}
+
+	resolver := &variableResolver{
+		location_token: t,
+	}
+
+	// First part of a variable MUST be an identifier
+	if t.Typ != TokenIdentifier {
+		return nil, p.Error("Expected either a number, string, keyword or identifier.", t)
+	}
+
+	resolver.parts = append(resolver.parts, &variablePart{
+		typ: varTypeIdent,
+		s:   t.Val,
+	})
+
+	p.Consume() // we consumed the first identifier of the variable name
+
+variableLoop:
+	for p.Remaining() > 0 {
+		t = p.Current()
+
+		if p.Match(TokenSymbol, ".") != nil {
+			// Next variable part (can be either NUMBER or IDENT)
+			t2 := p.Current()
+			if t2 != nil {
+				switch t2.Typ {
+				case TokenIdentifier:
+					resolver.parts = append(resolver.parts, &variablePart{
+						typ: varTypeIdent,
+						s:   t2.Val,
+					})
+					p.Consume() // consume: IDENT
+					continue variableLoop
+				case TokenNumber:
+					i, err := strconv.Atoi(t2.Val)
+					if err != nil {
+						return nil, p.Error(err.Error(), t2)
+					}
+					resolver.parts = append(resolver.parts, &variablePart{
+						typ: varTypeInt,
+						i:   i,
+					})
+					p.Consume() // consume: NUMBER
+					continue variableLoop
+				default:
+					return nil, p.Error("This token is not allowed within a variable name.", t2)
+				}
+			} else {
+				// EOF
+				return nil, p.Error("Unexpected EOF, expected either IDENTIFIER or NUMBER after DOT.",
+					p.last_token)
+			}
+		} else if p.Match(TokenSymbol, "(") != nil {
+			// Function call
+			// FunctionName '(' Comma-separated list of expressions ')'
+			part := resolver.parts[len(resolver.parts)-1]
+			part.is_function_call = true
+		argumentLoop:
+			for {
+				if p.Remaining() == 0 {
+					return nil, p.Error("Unexpected EOF, expected function call argument list.", p.last_token)
+				}
+
+				if p.Peek(TokenSymbol, ")") == nil {
+					// No closing bracket, so we're parsing an expression
+					expr_arg, err := p.ParseExpression()
+					if err != nil {
+						return nil, err
+					}
+					part.calling_args = append(part.calling_args, expr_arg)
+
+					if p.Match(TokenSymbol, ")") != nil {
+						// If there's a closing bracket after an expression, we will stop parsing the arguments
+						break argumentLoop
+					} else {
+						// If there's NO closing bracket, there MUST be an comma
+						if p.Match(TokenSymbol, ",") == nil {
+							return nil, p.Error("Missing comma or closing bracket after argument.", nil)
+						}
+					}
+				} else {
+					// We got a closing bracket, so stop parsing arguments
+					p.Consume()
+					break argumentLoop
+				}
+
+			}
+			// We're done parsing the function call, next variable part
+			continue variableLoop
+		}
+
+		// No dot or function call? Then we're done with the variable parsing
+		break
+	}
+
+	return resolver, nil
+}
+
+func (p *Parser) parseVariableOrLiteralWithFilter() (*nodeFilteredVariable, *Error) {
+	v := &nodeFilteredVariable{
+		location_token: p.Current(),
+	}
+
+	// Parse the variable name
+	resolver, err := p.parseVariableOrLiteral()
+	if err != nil {
+		return nil, err
+	}
+	v.resolver = resolver
+
+	// Parse all the filters
+filterLoop:
+	for p.Match(TokenSymbol, "|") != nil {
+		// Parse one single filter
+		filter, err := p.parseFilter()
+		if err != nil {
+			return nil, err
+		}
+
+		// Check sandbox filter restriction
+		if _, is_banned := p.template.set.bannedFilters[filter.name]; is_banned {
+			return nil, p.Error(fmt.Sprintf("Usage of filter '%s' is not allowed (sandbox restriction active).", filter.name), nil)
+		}
+
+		v.filterChain = append(v.filterChain, filter)
+
+		continue filterLoop
+
+		return nil, p.Error("This token is not allowed within a variable.", nil)
+	}
+
+	return v, nil
+}
+
+func (p *Parser) parseVariableElement() (INode, *Error) {
+	node := &nodeVariable{
+		location_token: p.Current(),
+	}
+
+	p.Consume() // consume '{{'
+
+	expr, err := p.ParseExpression()
+	if err != nil {
+		return nil, err
+	}
+	node.expr = expr
+
+	if p.Match(TokenSymbol, "}}") == nil {
+		return nil, p.Error("'}}' expected", nil)
+	}
+
+	return node, nil
+}

+ 2 - 0
vendor/github.com/fsouza/go-dockerclient/.gitignore

@@ -0,0 +1,2 @@
+# temporary symlink for testing
+testing/data/symlink

+ 25 - 0
vendor/github.com/fsouza/go-dockerclient/.travis.yml

@@ -0,0 +1,25 @@
+language: go
+sudo: required
+go:
+  - 1.4.3
+  - 1.5.4
+  - 1.6.2
+  - tip
+os:
+  - linux
+  - osx
+env:
+  - GOARCH=amd64 DOCKER_VERSION=1.9.1
+  - GOARCH=386   DOCKER_VERSION=1.9.1
+  - GOARCH=amd64 DOCKER_VERSION=1.10.3
+  - GOARCH=386   DOCKER_VERSION=1.10.3
+  - GOARCH=amd64 DOCKER_VERSION=1.11.1
+  - GOARCH=386   DOCKER_VERSION=1.11.1
+install:
+  - travis_retry travis-scripts/install.bash
+script:
+  - travis-scripts/run-tests.bash
+services:
+  - docker
+matrix:
+  fast_finish: true

+ 136 - 0
vendor/github.com/fsouza/go-dockerclient/AUTHORS

@@ -0,0 +1,136 @@
+# This is the official list of go-dockerclient authors for copyright purposes.
+
+Abhishek Chanda <abhishek.becs@gmail.com>
+Adam Bell-Hanssen <adamb@aller.no>
+Adrien Kohlbecker <adrien.kohlbecker@gmail.com>
+Aldrin Leal <aldrin@leal.eng.br>
+Andreas Jaekle <andreas@jaekle.net>
+Andrews Medina <andrewsmedina@gmail.com>
+Andrey Sibiryov <kobolog@uber.com>
+Andy Goldstein <andy.goldstein@redhat.com>
+Antonio Murdaca <runcom@redhat.com>
+Artem Sidorenko <artem@2realities.com>
+Ben Marini <ben@remind101.com>
+Ben McCann <benmccann.com>
+Ben Parees <bparees@redhat.com>
+Benno van den Berg <bennovandenberg@gmail.com>
+Bradley Cicenas <bradley.cicenas@gmail.com>
+Brendan Fosberry <brendan@codeship.com>
+Brian Lalor <blalor@bravo5.org>
+Brian P. Hamachek <brian@brianhama.com>
+Brian Palmer <brianp@instructure.com>
+Bryan Boreham <bjboreham@gmail.com>
+Burke Libbey <burke@libbey.me>
+Carlos Diaz-Padron <cpadron@mozilla.com>
+Cesar Wong <cewong@redhat.com>
+Cezar Sa Espinola <cezar.sa@corp.globo.com>
+Cheah Chu Yeow <chuyeow@gmail.com>
+cheneydeng <cheneydeng@qq.com>
+Chris Bednarski <banzaimonkey@gmail.com>
+CMGS <ilskdw@gmail.com>
+Colin Hebert <hebert.colin@gmail.com>
+Craig Jellick <craig@rancher.com>
+Dan Williams <dcbw@redhat.com>
+Daniel, Dao Quang Minh <dqminh89@gmail.com>
+Daniel Garcia <daniel@danielgarcia.info>
+Daniel Hiltgen <daniel.hiltgen@docker.com>
+Darren Shepherd <darren@rancher.com>
+Dave Choi <dave.choi@daumkakao.com>
+David Huie <dahuie@gmail.com>
+Dawn Chen <dawnchen@google.com>
+Dinesh Subhraveti <dinesh@gemini-systems.net>
+Drew Wells <drew.wells00@gmail.com>
+Ed <edrocksit@gmail.com>
+Elias G. Schneevoigt <eliasgs@gmail.com>
+Erez Horev <erez.horev@elastifile.com>
+Eric Anderson <anderson@copperegg.com>
+Ewout Prangsma <ewout@prangsma.net>
+Fabio Rehm <fgrehm@gmail.com>
+Fatih Arslan <ftharsln@gmail.com>
+Flavia Missi <flaviamissi@gmail.com>
+Francisco Souza <f@souza.cc>
+Frank Groeneveld <frank@frankgroeneveld.nl>
+George Moura <gwmoura@gmail.com>
+Grégoire Delattre <gregoire.delattre@gmail.com>
+Guillermo Álvarez Fernández <guillermo@cientifico.net>
+Harry Zhang <harryzhang@zju.edu.cn>
+He Simei <hesimei@zju.edu.cn>
+Ivan Mikushin <i.mikushin@gmail.com>
+James Bardin <jbardin@litl.com>
+James Nugent <james@jen20.com>
+Jari Kolehmainen <jari.kolehmainen@digia.com>
+Jason Wilder <jwilder@litl.com>
+Jawher Moussa <jawher.moussa@gmail.com>
+Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
+Jeff Mitchell <jeffrey.mitchell@gmail.com>
+Jeffrey Hulten <jhulten@gmail.com>
+Jen Andre <jandre@gmail.com>
+Jérôme Laurens <jeromelaurens@gmail.com>
+Johan Euphrosine <proppy@google.com>
+John Hughes <hughesj@visa.com>
+Kamil Domanski <kamil@domanski.co>
+Karan Misra <kidoman@gmail.com>
+Ken Herner <chosenken@gmail.com>
+Kim, Hirokuni <hirokuni.kim@kvh.co.jp>
+Kyle Allan <kallan357@gmail.com>
+Liron Levin <levinlir@gmail.com>
+Lior Yankovich <lior@twistlock.com>
+Liu Peng <vslene@gmail.com>
+Lorenz Leutgeb <lorenz.leutgeb@gmail.com>
+Lucas Clemente <lucas@clemente.io>
+Lucas Weiblen <lucasweiblen@gmail.com>
+Lyon Hill <lyondhill@gmail.com>
+Mantas Matelis <mmatelis@coursera.org>
+Martin Sweeney <martin@sweeney.io>
+Máximo Cuadros Ortiz <mcuadros@gmail.com>
+Michael Schmatz <michaelschmatz@gmail.com>
+Michal Fojtik <mfojtik@redhat.com>
+Mike Dillon <mike.dillon@synctree.com>
+Mrunal Patel <mrunalp@gmail.com>
+Nate Jones <nate@endot.org>
+Nguyen Sy Thanh Son <sonnst@sigma-solutions.eu>
+Nicholas Van Wiggeren <nvanwiggeren@digitalocean.com>
+Nick Ethier <ncethier@gmail.com>
+Omeid Matten <public@omeid.me>
+Orivej Desh <orivej@gmx.fr>
+Paul Bellamy <paul.a.bellamy@gmail.com>
+Paul Morie <pmorie@gmail.com>
+Paul Weil <pweil@redhat.com>
+Peter Edge <peter.edge@gmail.com>
+Peter Jihoon Kim <raingrove@gmail.com>
+Phil Lu <lu@stackengine.com>
+Philippe Lafoucrière <philippe.lafoucriere@tech-angels.com>
+Rafe Colton <rafael.colton@gmail.com>
+Raphaël Pinson <raphael.pinson@camptocamp.com>
+Rob Miller <rob@kalistra.com>
+Robbert Klarenbeek <robbertkl@renbeek.nl>
+Robert Williamson <williamson.robert@gmail.com>
+Roman Khlystik <roman.khlystik@gmail.com>
+Salvador Gironès <salvadorgirones@gmail.com>
+Sam Rijs <srijs@airpost.net>
+Sami Wagiaalla <swagiaal@redhat.com>
+Samuel Archambault <sarchambault@lapresse.ca>
+Samuel Karp <skarp@amazon.com>
+Seth Jennings <sjenning@redhat.com>
+Silas Sewell <silas@sewell.org>
+Simon Eskildsen <sirup@sirupsen.com>
+Simon Menke <simon.menke@gmail.com>
+Skolos <skolos@gopherlab.com>
+Soulou <leo@unbekandt.eu>
+Sridhar Ratnakumar <sridharr@activestate.com>
+Summer Mousa <smousa@zenoss.com>
+Sunjin Lee <styner32@gmail.com>
+Tarsis Azevedo <tarsis@corp.globo.com>
+Tim Schindler <tim@catalyst-zero.com>
+Timothy St. Clair <tstclair@redhat.com>
+Tobi Knaup <tobi@mesosphere.io>
+Tom Wilkie <tom.wilkie@gmail.com>
+Tonic <tonicbupt@gmail.com>
+ttyh061 <ttyh061@gmail.com>
+Victor Marmol <vmarmol@google.com>
+Vincenzo Prignano <vincenzo.prignano@gmail.com>
+Vlad Alexandru Ionescu <vlad.alexandru.ionescu@gmail.com>
+Wiliam Souza <wiliamsouza83@gmail.com>
+Ye Yin <eyniy@qq.com>
+Yu, Zou <zouyu7@huawei.com>
+Yuriy Bogdanov <chinsay@gmail.com>

+ 6 - 0
vendor/github.com/fsouza/go-dockerclient/DOCKER-LICENSE

@@ -0,0 +1,6 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+You can find the Docker license at the following link:
+https://raw.githubusercontent.com/docker/docker/master/LICENSE

+ 22 - 0
vendor/github.com/fsouza/go-dockerclient/LICENSE

@@ -0,0 +1,22 @@
+Copyright (c) 2016, go-dockerclient 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.
+
+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.

+ 57 - 0
vendor/github.com/fsouza/go-dockerclient/Makefile

@@ -0,0 +1,57 @@
+.PHONY: \
+	all \
+	vendor \
+	lint \
+	vet \
+	fmt \
+	fmtcheck \
+	pretest \
+	test \
+	integration \
+	cov \
+	clean
+
+PKGS = . ./testing
+
+all: test
+
+vendor:
+	@ go get -v github.com/mjibson/party
+	party -d external -c -u
+
+lint:
+	@ go get -v github.com/golang/lint/golint
+	@for file in $$(git ls-files '*.go' | grep -v 'external/'); do \
+		export output="$$(golint $${file} | grep -v 'type name will be used as docker.DockerInfo')"; \
+		[ -n "$${output}" ] && echo "$${output}" && export status=1; \
+	done; \
+	exit $${status:-0}
+
+vet:
+	go vet $(PKGS)
+
+fmt:
+	gofmt -s -w $(PKGS)
+
+fmtcheck:
+	@ export output=$$(gofmt -s -d $(PKGS)); \
+		[ -n "$${output}" ] && echo "$${output}" && export status=1; \
+		exit $${status:-0}
+
+pretest: lint vet fmtcheck
+
+gotest:
+	go test $(GO_TEST_FLAGS) $(PKGS)
+
+test: pretest gotest
+
+integration:
+	go test -tags docker_integration -run TestIntegration -v
+
+cov:
+	@ go get -v github.com/axw/gocov/gocov
+	@ go get golang.org/x/tools/cmd/cover
+	gocov test | gocov report
+
+clean:
+	go clean $(PKGS)

+ 106 - 0
vendor/github.com/fsouza/go-dockerclient/README.markdown

@@ -0,0 +1,106 @@
+# go-dockerclient
+
+[![Travis](https://img.shields.io/travis/fsouza/go-dockerclient/master.svg?style=flat-square)](https://travis-ci.org/fsouza/go-dockerclient)
+[![GoDoc](https://img.shields.io/badge/api-Godoc-blue.svg?style=flat-square)](https://godoc.org/github.com/fsouza/go-dockerclient)
+
+This package presents a client for the Docker remote API. It also provides
+support for the extensions in the [Swarm API](https://docs.docker.com/swarm/swarm-api/).
+It currently supports the Docker API up to version 1.23.
+
+This package also provides support for docker's network API, which is a simple
+passthrough to the libnetwork remote API.  Note that docker's network API is
+only available in docker 1.8 and above, and only enabled in docker if
+DOCKER_EXPERIMENTAL is defined during the docker build process.
+
+For more details, check the [remote API documentation](http://docs.docker.com/engine/reference/api/docker_remote_api/).
+
+## Vendoring
+
+If you are having issues with Go 1.5 and have `GO15VENDOREXPERIMENT` set with an application that has go-dockerclient vendored,
+please update your vendoring of go-dockerclient :) We recently moved the `vendor` directory to `external` so that go-dockerclient
+is compatible with this configuration. See [338](https://github.com/fsouza/go-dockerclient/issues/338) and [339](https://github.com/fsouza/go-dockerclient/pull/339)
+for details.
+
+## Example
+
+```go
+package main
+
+import (
+	"fmt"
+
+	"github.com/fsouza/go-dockerclient"
+)
+
+func main() {
+	endpoint := "unix:///var/run/docker.sock"
+	client, _ := docker.NewClient(endpoint)
+	imgs, _ := client.ListImages(docker.ListImagesOptions{All: false})
+	for _, img := range imgs {
+		fmt.Println("ID: ", img.ID)
+		fmt.Println("RepoTags: ", img.RepoTags)
+		fmt.Println("Created: ", img.Created)
+		fmt.Println("Size: ", img.Size)
+		fmt.Println("VirtualSize: ", img.VirtualSize)
+		fmt.Println("ParentId: ", img.ParentID)
+	}
+}
+```
+
+## Using with TLS
+
+In order to instantiate the client for a TLS-enabled daemon, you should use NewTLSClient, passing the endpoint and path for key and certificates as parameters.
+
+```go
+package main
+
+import (
+	"fmt"
+
+	"github.com/fsouza/go-dockerclient"
+)
+
+func main() {
+	endpoint := "tcp://[ip]:[port]"
+	path := os.Getenv("DOCKER_CERT_PATH")
+	ca := fmt.Sprintf("%s/ca.pem", path)
+	cert := fmt.Sprintf("%s/cert.pem", path)
+	key := fmt.Sprintf("%s/key.pem", path)
+	client, _ := docker.NewTLSClient(endpoint, cert, key, ca)
+	// use client
+}
+```
+
+If using [docker-machine](https://docs.docker.com/machine/), or another application that exports environment variables
+`DOCKER_HOST, DOCKER_TLS_VERIFY, DOCKER_CERT_PATH`, you can use NewClientFromEnv.
+
+
+```go
+package main
+
+import (
+	"fmt"
+
+	"github.com/fsouza/go-dockerclient"
+)
+
+func main() {
+	client, _ := docker.NewClientFromEnv()
+	// use client
+}
+```
+
+See the documentation for more details.
+
+## Developing
+
+All development commands can be seen in the [Makefile](Makefile).
+
+Commited code must pass:
+
+* [golint](https://github.com/golang/lint)
+* [go vet](https://godoc.org/golang.org/x/tools/cmd/vet)
+* [gofmt](https://golang.org/cmd/gofmt)
+* [go test](https://golang.org/cmd/go/#hdr-Test_packages)
+
+Running `make test` will check all of these. If your editor does not automatically call gofmt, `make fmt` will format all go files in this repository.

+ 158 - 0
vendor/github.com/fsouza/go-dockerclient/auth.go

@@ -0,0 +1,158 @@
+// Copyright 2015 go-dockerclient authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package docker
+
+import (
+	"bytes"
+	"encoding/base64"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path"
+	"strings"
+)
+
+// ErrCannotParseDockercfg is the error returned by NewAuthConfigurations when the dockercfg cannot be parsed.
+var ErrCannotParseDockercfg = errors.New("Failed to read authentication from dockercfg")
+
+// AuthConfiguration represents authentication options to use in the PushImage
+// method. It represents the authentication in the Docker index server.
+type AuthConfiguration struct {
+	Username      string `json:"username,omitempty"`
+	Password      string `json:"password,omitempty"`
+	Email         string `json:"email,omitempty"`
+	ServerAddress string `json:"serveraddress,omitempty"`
+}
+
+// AuthConfigurations represents authentication options to use for the
+// PushImage method accommodating the new X-Registry-Config header
+type AuthConfigurations struct {
+	Configs map[string]AuthConfiguration `json:"configs"`
+}
+
+// AuthConfigurations119 is used to serialize a set of AuthConfigurations
+// for Docker API >= 1.19.
+type AuthConfigurations119 map[string]AuthConfiguration
+
+// dockerConfig represents a registry authentation configuration from the
+// .dockercfg file.
+type dockerConfig struct {
+	Auth  string `json:"auth"`
+	Email string `json:"email"`
+}
+
+// NewAuthConfigurationsFromDockerCfg returns AuthConfigurations from the
+// ~/.dockercfg file.
+func NewAuthConfigurationsFromDockerCfg() (*AuthConfigurations, error) {
+	var r io.Reader
+	var err error
+	p := path.Join(os.Getenv("HOME"), ".docker", "config.json")
+	r, err = os.Open(p)
+	if err != nil {
+		p := path.Join(os.Getenv("HOME"), ".dockercfg")
+		r, err = os.Open(p)
+		if err != nil {
+			return nil, err
+		}
+	}
+	return NewAuthConfigurations(r)
+}
+
+// NewAuthConfigurations returns AuthConfigurations from a JSON encoded string in the
+// same format as the .dockercfg file.
+func NewAuthConfigurations(r io.Reader) (*AuthConfigurations, error) {
+	var auth *AuthConfigurations
+	confs, err := parseDockerConfig(r)
+	if err != nil {
+		return nil, err
+	}
+	auth, err = authConfigs(confs)
+	if err != nil {
+		return nil, err
+	}
+	return auth, nil
+}
+
+func parseDockerConfig(r io.Reader) (map[string]dockerConfig, error) {
+	buf := new(bytes.Buffer)
+	buf.ReadFrom(r)
+	byteData := buf.Bytes()
+
+	confsWrapper := struct {
+		Auths map[string]dockerConfig `json:"auths"`
+	}{}
+	if err := json.Unmarshal(byteData, &confsWrapper); err == nil {
+		if len(confsWrapper.Auths) > 0 {
+			return confsWrapper.Auths, nil
+		}
+	}
+
+	var confs map[string]dockerConfig
+	if err := json.Unmarshal(byteData, &confs); err != nil {
+		return nil, err
+	}
+	return confs, nil
+}
+
+// authConfigs converts a dockerConfigs map to a AuthConfigurations object.
+func authConfigs(confs map[string]dockerConfig) (*AuthConfigurations, error) {
+	c := &AuthConfigurations{
+		Configs: make(map[string]AuthConfiguration),
+	}
+	for reg, conf := range confs {
+		data, err := base64.StdEncoding.DecodeString(conf.Auth)
+		if err != nil {
+			return nil, err
+		}
+		userpass := strings.SplitN(string(data), ":", 2)
+		if len(userpass) != 2 {
+			return nil, ErrCannotParseDockercfg
+		}
+		c.Configs[reg] = AuthConfiguration{
+			Email:         conf.Email,
+			Username:      userpass[0],
+			Password:      userpass[1],
+			ServerAddress: reg,
+		}
+	}
+	return c, nil
+}
+
+// AuthStatus returns the authentication status for Docker API versions >= 1.23.
+type AuthStatus struct {
+	Status        string `json:"Status,omitempty" yaml:"Status,omitempty"`
+	IdentityToken string `json:"IdentityToken,omitempty" yaml:"IdentityToken,omitempty"`
+}
+
+// AuthCheck validates the given credentials. It returns nil if successful.
+//
+// For Docker API versions >= 1.23, the AuthStatus struct will be populated, otherwise it will be empty.`
+//
+// See https://goo.gl/6nsZkH for more details.
+func (c *Client) AuthCheck(conf *AuthConfiguration) (AuthStatus, error) {
+	var authStatus AuthStatus
+	if conf == nil {
+		return authStatus, fmt.Errorf("conf is nil")
+	}
+	resp, err := c.do("POST", "/auth", doOptions{data: conf})
+	if err != nil {
+		return authStatus, err
+	}
+	defer resp.Body.Close()
+	data, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return authStatus, err
+	}
+	if len(data) == 0 {
+		return authStatus, nil
+	}
+	if err := json.Unmarshal(data, &authStatus); err != nil {
+		return authStatus, err
+	}
+	return authStatus, nil
+}

+ 17 - 0
vendor/github.com/fsouza/go-dockerclient/cancelable.go

@@ -0,0 +1,17 @@
+// Copyright 2016 go-dockerclient authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.5
+
+package docker
+
+import "net/http"
+
+func cancelable(client *http.Client, req *http.Request) func() {
+	ch := make(chan struct{})
+	req.Cancel = ch
+	return func() {
+		close(ch)
+	}
+}

+ 19 - 0
vendor/github.com/fsouza/go-dockerclient/cancelable_go14.go

@@ -0,0 +1,19 @@
+// Copyright 2016 go-dockerclient authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !go1.5
+
+package docker
+
+import "net/http"
+
+func cancelable(client *http.Client, req *http.Request) func() {
+	return func() {
+		if rc, ok := client.Transport.(interface {
+			CancelRequest(*http.Request)
+		}); ok {
+			rc.CancelRequest(req)
+		}
+	}
+}

+ 43 - 0
vendor/github.com/fsouza/go-dockerclient/change.go

@@ -0,0 +1,43 @@
+// Copyright 2014 go-dockerclient authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package docker
+
+import "fmt"
+
+// ChangeType is a type for constants indicating the type of change
+// in a container
+type ChangeType int
+
+const (
+	// ChangeModify is the ChangeType for container modifications
+	ChangeModify ChangeType = iota
+
+	// ChangeAdd is the ChangeType for additions to a container
+	ChangeAdd
+
+	// ChangeDelete is the ChangeType for deletions from a container
+	ChangeDelete
+)
+
+// Change represents a change in a container.
+//
+// See https://goo.gl/9GsTIF for more details.
+type Change struct {
+	Path string
+	Kind ChangeType
+}
+
+func (change *Change) String() string {
+	var kind string
+	switch change.Kind {
+	case ChangeModify:
+		kind = "C"
+	case ChangeAdd:
+		kind = "A"
+	case ChangeDelete:
+		kind = "D"
+	}
+	return fmt.Sprintf("%s %s", kind, change.Path)
+}

+ 995 - 0
vendor/github.com/fsouza/go-dockerclient/client.go

@@ -0,0 +1,995 @@
+// Copyright 2015 go-dockerclient authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package docker provides a client for the Docker remote API.
+//
+// See https://goo.gl/G3plxW for more details on the remote API.
+package docker
+
+import (
+	"bufio"
+	"bytes"
+	"crypto/tls"
+	"crypto/x509"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net"
+	"net/http"
+	"net/http/httputil"
+	"net/url"
+	"os"
+	"path/filepath"
+	"reflect"
+	"runtime"
+	"strconv"
+	"strings"
+	"sync/atomic"
+	"time"
+
+	"github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts"
+	"github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/homedir"
+	"github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/stdcopy"
+	"github.com/fsouza/go-dockerclient/external/github.com/hashicorp/go-cleanhttp"
+)
+
+const userAgent = "go-dockerclient"
+
+var (
+	// ErrInvalidEndpoint is returned when the endpoint is not a valid HTTP URL.
+	ErrInvalidEndpoint = errors.New("invalid endpoint")
+
+	// ErrConnectionRefused is returned when the client cannot connect to the given endpoint.
+	ErrConnectionRefused = errors.New("cannot connect to Docker endpoint")
+
+	// ErrInactivityTimeout is returned when a streamable call has been inactive for some time.
+	ErrInactivityTimeout = errors.New("inactivity time exceeded timeout")
+
+	apiVersion112, _ = NewAPIVersion("1.12")
+
+	apiVersion119, _ = NewAPIVersion("1.19")
+)
+
+// APIVersion is an internal representation of a version of the Remote API.
+type APIVersion []int
+
+// NewAPIVersion returns an instance of APIVersion for the given string.
+//
+// The given string must be in the form <major>.<minor>.<patch>, where <major>,
+// <minor> and <patch> are integer numbers.
+func NewAPIVersion(input string) (APIVersion, error) {
+	if !strings.Contains(input, ".") {
+		return nil, fmt.Errorf("Unable to parse version %q", input)
+	}
+	raw := strings.Split(input, "-")
+	arr := strings.Split(raw[0], ".")
+	ret := make(APIVersion, len(arr))
+	var err error
+	for i, val := range arr {
+		ret[i], err = strconv.Atoi(val)
+		if err != nil {
+			return nil, fmt.Errorf("Unable to parse version %q: %q is not an integer", input, val)
+		}
+	}
+	return ret, nil
+}
+
+func (version APIVersion) String() string {
+	var str string
+	for i, val := range version {
+		str += strconv.Itoa(val)
+		if i < len(version)-1 {
+			str += "."
+		}
+	}
+	return str
+}
+
+// LessThan is a function for comparing APIVersion structs
+func (version APIVersion) LessThan(other APIVersion) bool {
+	return version.compare(other) < 0
+}
+
+// LessThanOrEqualTo is a function for comparing APIVersion structs
+func (version APIVersion) LessThanOrEqualTo(other APIVersion) bool {
+	return version.compare(other) <= 0
+}
+
+// GreaterThan is a function for comparing APIVersion structs
+func (version APIVersion) GreaterThan(other APIVersion) bool {
+	return version.compare(other) > 0
+}
+
+// GreaterThanOrEqualTo is a function for comparing APIVersion structs
+func (version APIVersion) GreaterThanOrEqualTo(other APIVersion) bool {
+	return version.compare(other) >= 0
+}
+
+func (version APIVersion) compare(other APIVersion) int {
+	for i, v := range version {
+		if i <= len(other)-1 {
+			otherVersion := other[i]
+
+			if v < otherVersion {
+				return -1
+			} else if v > otherVersion {
+				return 1
+			}
+		}
+	}
+	if len(version) > len(other) {
+		return 1
+	}
+	if len(version) < len(other) {
+		return -1
+	}
+	return 0
+}
+
+// Client is the basic type of this package. It provides methods for
+// interaction with the API.
+type Client struct {
+	SkipServerVersionCheck bool
+	HTTPClient             *http.Client
+	TLSConfig              *tls.Config
+	Dialer                 *net.Dialer
+
+	endpoint            string
+	endpointURL         *url.URL
+	eventMonitor        *eventMonitoringState
+	requestedAPIVersion APIVersion
+	serverAPIVersion    APIVersion
+	expectedAPIVersion  APIVersion
+	unixHTTPClient      *http.Client
+}
+
+// NewClient returns a Client instance ready for communication with the given
+// server endpoint. It will use the latest remote API version available in the
+// server.
+func NewClient(endpoint string) (*Client, error) {
+	client, err := NewVersionedClient(endpoint, "")
+	if err != nil {
+		return nil, err
+	}
+	client.SkipServerVersionCheck = true
+	return client, nil
+}
+
+// NewTLSClient returns a Client instance ready for TLS communications with the givens
+// server endpoint, key and certificates . It will use the latest remote API version
+// available in the server.
+func NewTLSClient(endpoint string, cert, key, ca string) (*Client, error) {
+	client, err := NewVersionedTLSClient(endpoint, cert, key, ca, "")
+	if err != nil {
+		return nil, err
+	}
+	client.SkipServerVersionCheck = true
+	return client, nil
+}
+
+// NewTLSClientFromBytes returns a Client instance ready for TLS communications with the givens
+// server endpoint, key and certificates (passed inline to the function as opposed to being
+// read from a local file). It will use the latest remote API version available in the server.
+func NewTLSClientFromBytes(endpoint string, certPEMBlock, keyPEMBlock, caPEMCert []byte) (*Client, error) {
+	client, err := NewVersionedTLSClientFromBytes(endpoint, certPEMBlock, keyPEMBlock, caPEMCert, "")
+	if err != nil {
+		return nil, err
+	}
+	client.SkipServerVersionCheck = true
+	return client, nil
+}
+
+// NewVersionedClient returns a Client instance ready for communication with
+// the given server endpoint, using a specific remote API version.
+func NewVersionedClient(endpoint string, apiVersionString string) (*Client, error) {
+	u, err := parseEndpoint(endpoint, false)
+	if err != nil {
+		return nil, err
+	}
+	var requestedAPIVersion APIVersion
+	if strings.Contains(apiVersionString, ".") {
+		requestedAPIVersion, err = NewAPIVersion(apiVersionString)
+		if err != nil {
+			return nil, err
+		}
+	}
+	return &Client{
+		HTTPClient:          cleanhttp.DefaultClient(),
+		Dialer:              &net.Dialer{},
+		endpoint:            endpoint,
+		endpointURL:         u,
+		eventMonitor:        new(eventMonitoringState),
+		requestedAPIVersion: requestedAPIVersion,
+	}, nil
+}
+
+// NewVersionnedTLSClient has been DEPRECATED, please use NewVersionedTLSClient.
+func NewVersionnedTLSClient(endpoint string, cert, key, ca, apiVersionString string) (*Client, error) {
+	return NewVersionedTLSClient(endpoint, cert, key, ca, apiVersionString)
+}
+
+// NewVersionedTLSClient returns a Client instance ready for TLS communications with the givens
+// server endpoint, key and certificates, using a specific remote API version.
+func NewVersionedTLSClient(endpoint string, cert, key, ca, apiVersionString string) (*Client, error) {
+	certPEMBlock, err := ioutil.ReadFile(cert)
+	if err != nil {
+		return nil, err
+	}
+	keyPEMBlock, err := ioutil.ReadFile(key)
+	if err != nil {
+		return nil, err
+	}
+	caPEMCert, err := ioutil.ReadFile(ca)
+	if err != nil {
+		return nil, err
+	}
+	return NewVersionedTLSClientFromBytes(endpoint, certPEMBlock, keyPEMBlock, caPEMCert, apiVersionString)
+}
+
+// NewClientFromEnv returns a Client instance ready for communication created from
+// Docker's default logic for the environment variables DOCKER_HOST, DOCKER_TLS_VERIFY, and DOCKER_CERT_PATH.
+//
+// See https://github.com/docker/docker/blob/1f963af697e8df3a78217f6fdbf67b8123a7db94/docker/docker.go#L68.
+// See https://github.com/docker/compose/blob/81707ef1ad94403789166d2fe042c8a718a4c748/compose/cli/docker_client.py#L7.
+func NewClientFromEnv() (*Client, error) {
+	client, err := NewVersionedClientFromEnv("")
+	if err != nil {
+		return nil, err
+	}
+	client.SkipServerVersionCheck = true
+	return client, nil
+}
+
+// NewVersionedClientFromEnv returns a Client instance ready for TLS communications created from
+// Docker's default logic for the environment variables DOCKER_HOST, DOCKER_TLS_VERIFY, and DOCKER_CERT_PATH,
+// and using a specific remote API version.
+//
+// See https://github.com/docker/docker/blob/1f963af697e8df3a78217f6fdbf67b8123a7db94/docker/docker.go#L68.
+// See https://github.com/docker/compose/blob/81707ef1ad94403789166d2fe042c8a718a4c748/compose/cli/docker_client.py#L7.
+func NewVersionedClientFromEnv(apiVersionString string) (*Client, error) {
+	dockerEnv, err := getDockerEnv()
+	if err != nil {
+		return nil, err
+	}
+	dockerHost := dockerEnv.dockerHost
+	if dockerEnv.dockerTLSVerify {
+		parts := strings.SplitN(dockerEnv.dockerHost, "://", 2)
+		if len(parts) != 2 {
+			return nil, fmt.Errorf("could not split %s into two parts by ://", dockerHost)
+		}
+		cert := filepath.Join(dockerEnv.dockerCertPath, "cert.pem")
+		key := filepath.Join(dockerEnv.dockerCertPath, "key.pem")
+		ca := filepath.Join(dockerEnv.dockerCertPath, "ca.pem")
+		return NewVersionedTLSClient(dockerEnv.dockerHost, cert, key, ca, apiVersionString)
+	}
+	return NewVersionedClient(dockerEnv.dockerHost, apiVersionString)
+}
+
+// NewVersionedTLSClientFromBytes returns a Client instance ready for TLS communications with the givens
+// server endpoint, key and certificates (passed inline to the function as opposed to being
+// read from a local file), using a specific remote API version.
+func NewVersionedTLSClientFromBytes(endpoint string, certPEMBlock, keyPEMBlock, caPEMCert []byte, apiVersionString string) (*Client, error) {
+	u, err := parseEndpoint(endpoint, true)
+	if err != nil {
+		return nil, err
+	}
+	var requestedAPIVersion APIVersion
+	if strings.Contains(apiVersionString, ".") {
+		requestedAPIVersion, err = NewAPIVersion(apiVersionString)
+		if err != nil {
+			return nil, err
+		}
+	}
+	if certPEMBlock == nil || keyPEMBlock == nil {
+		return nil, errors.New("Both cert and key are required")
+	}
+	tlsCert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock)
+	if err != nil {
+		return nil, err
+	}
+	tlsConfig := &tls.Config{Certificates: []tls.Certificate{tlsCert}}
+	if caPEMCert == nil {
+		tlsConfig.InsecureSkipVerify = true
+	} else {
+		caPool := x509.NewCertPool()
+		if !caPool.AppendCertsFromPEM(caPEMCert) {
+			return nil, errors.New("Could not add RootCA pem")
+		}
+		tlsConfig.RootCAs = caPool
+	}
+	tr := cleanhttp.DefaultTransport()
+	tr.TLSClientConfig = tlsConfig
+	if err != nil {
+		return nil, err
+	}
+	return &Client{
+		HTTPClient:          &http.Client{Transport: tr},
+		TLSConfig:           tlsConfig,
+		Dialer:              &net.Dialer{},
+		endpoint:            endpoint,
+		endpointURL:         u,
+		eventMonitor:        new(eventMonitoringState),
+		requestedAPIVersion: requestedAPIVersion,
+	}, nil
+}
+
+func (c *Client) checkAPIVersion() error {
+	serverAPIVersionString, err := c.getServerAPIVersionString()
+	if err != nil {
+		return err
+	}
+	c.serverAPIVersion, err = NewAPIVersion(serverAPIVersionString)
+	if err != nil {
+		return err
+	}
+	if c.requestedAPIVersion == nil {
+		c.expectedAPIVersion = c.serverAPIVersion
+	} else {
+		c.expectedAPIVersion = c.requestedAPIVersion
+	}
+	return nil
+}
+
+// Endpoint returns the current endpoint. It's useful for getting the endpoint
+// when using functions that get this data from the environment (like
+// NewClientFromEnv.
+func (c *Client) Endpoint() string {
+	return c.endpoint
+}
+
+// Ping pings the docker server
+//
+// See https://goo.gl/kQCfJj for more details.
+func (c *Client) Ping() error {
+	path := "/_ping"
+	resp, err := c.do("GET", path, doOptions{})
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode != http.StatusOK {
+		return newError(resp)
+	}
+	resp.Body.Close()
+	return nil
+}
+
+func (c *Client) getServerAPIVersionString() (version string, err error) {
+	resp, err := c.do("GET", "/version", doOptions{})
+	if err != nil {
+		return "", err
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode != http.StatusOK {
+		return "", fmt.Errorf("Received unexpected status %d while trying to retrieve the server version", resp.StatusCode)
+	}
+	var versionResponse map[string]interface{}
+	if err := json.NewDecoder(resp.Body).Decode(&versionResponse); err != nil {
+		return "", err
+	}
+	if version, ok := (versionResponse["ApiVersion"]).(string); ok {
+		return version, nil
+	}
+	return "", nil
+}
+
+type doOptions struct {
+	data      interface{}
+	forceJSON bool
+	headers   map[string]string
+}
+
+func (c *Client) do(method, path string, doOptions doOptions) (*http.Response, error) {
+	var params io.Reader
+	if doOptions.data != nil || doOptions.forceJSON {
+		buf, err := json.Marshal(doOptions.data)
+		if err != nil {
+			return nil, err
+		}
+		params = bytes.NewBuffer(buf)
+	}
+	if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil {
+		err := c.checkAPIVersion()
+		if err != nil {
+			return nil, err
+		}
+	}
+	httpClient := c.HTTPClient
+	protocol := c.endpointURL.Scheme
+	var u string
+	if protocol == "unix" {
+		httpClient = c.unixClient()
+		u = c.getFakeUnixURL(path)
+	} else {
+		u = c.getURL(path)
+	}
+	req, err := http.NewRequest(method, u, params)
+	if err != nil {
+		return nil, err
+	}
+	req.Header.Set("User-Agent", userAgent)
+	if doOptions.data != nil {
+		req.Header.Set("Content-Type", "application/json")
+	} else if method == "POST" {
+		req.Header.Set("Content-Type", "plain/text")
+	}
+
+	for k, v := range doOptions.headers {
+		req.Header.Set(k, v)
+	}
+	resp, err := httpClient.Do(req)
+	if err != nil {
+		if strings.Contains(err.Error(), "connection refused") {
+			return nil, ErrConnectionRefused
+		}
+		return nil, err
+	}
+	if resp.StatusCode < 200 || resp.StatusCode >= 400 {
+		return nil, newError(resp)
+	}
+	return resp, nil
+}
+
+type streamOptions struct {
+	setRawTerminal bool
+	rawJSONStream  bool
+	useJSONDecoder bool
+	headers        map[string]string
+	in             io.Reader
+	stdout         io.Writer
+	stderr         io.Writer
+	// timeout is the initial connection timeout
+	timeout time.Duration
+	// Timeout with no data is received, it's reset every time new data
+	// arrives
+	inactivityTimeout time.Duration
+}
+
+func (c *Client) stream(method, path string, streamOptions streamOptions) error {
+	if (method == "POST" || method == "PUT") && streamOptions.in == nil {
+		streamOptions.in = bytes.NewReader(nil)
+	}
+	if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil {
+		err := c.checkAPIVersion()
+		if err != nil {
+			return err
+		}
+	}
+	req, err := http.NewRequest(method, c.getURL(path), streamOptions.in)
+	if err != nil {
+		return err
+	}
+	req.Header.Set("User-Agent", userAgent)
+	if method == "POST" {
+		req.Header.Set("Content-Type", "plain/text")
+	}
+	for key, val := range streamOptions.headers {
+		req.Header.Set(key, val)
+	}
+	var resp *http.Response
+	protocol := c.endpointURL.Scheme
+	address := c.endpointURL.Path
+	if streamOptions.stdout == nil {
+		streamOptions.stdout = ioutil.Discard
+	}
+	if streamOptions.stderr == nil {
+		streamOptions.stderr = ioutil.Discard
+	}
+	cancelRequest := cancelable(c.HTTPClient, req)
+	if protocol == "unix" {
+		dial, err := c.Dialer.Dial(protocol, address)
+		if err != nil {
+			return err
+		}
+		cancelRequest = func() { dial.Close() }
+		defer dial.Close()
+		breader := bufio.NewReader(dial)
+		err = req.Write(dial)
+		if err != nil {
+			return err
+		}
+
+		// ReadResponse may hang if server does not replay
+		if streamOptions.timeout > 0 {
+			dial.SetDeadline(time.Now().Add(streamOptions.timeout))
+		}
+
+		if resp, err = http.ReadResponse(breader, req); err != nil {
+			// Cancel timeout for future I/O operations
+			if streamOptions.timeout > 0 {
+				dial.SetDeadline(time.Time{})
+			}
+			if strings.Contains(err.Error(), "connection refused") {
+				return ErrConnectionRefused
+			}
+			return err
+		}
+	} else {
+		if resp, err = c.HTTPClient.Do(req); err != nil {
+			if strings.Contains(err.Error(), "connection refused") {
+				return ErrConnectionRefused
+			}
+			return err
+		}
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode < 200 || resp.StatusCode >= 400 {
+		return newError(resp)
+	}
+	var canceled uint32
+	if streamOptions.inactivityTimeout > 0 {
+		ch := handleInactivityTimeout(&streamOptions, cancelRequest, &canceled)
+		defer close(ch)
+	}
+	err = handleStreamResponse(resp, &streamOptions)
+	if err != nil {
+		if atomic.LoadUint32(&canceled) != 0 {
+			return ErrInactivityTimeout
+		}
+		return err
+	}
+	return nil
+}
+
+func handleStreamResponse(resp *http.Response, streamOptions *streamOptions) error {
+	var err error
+	if !streamOptions.useJSONDecoder && resp.Header.Get("Content-Type") != "application/json" {
+		if streamOptions.setRawTerminal {
+			_, err = io.Copy(streamOptions.stdout, resp.Body)
+		} else {
+			_, err = stdcopy.StdCopy(streamOptions.stdout, streamOptions.stderr, resp.Body)
+		}
+		return err
+	}
+	// if we want to get raw json stream, just copy it back to output
+	// without decoding it
+	if streamOptions.rawJSONStream {
+		_, err = io.Copy(streamOptions.stdout, resp.Body)
+		return err
+	}
+	dec := json.NewDecoder(resp.Body)
+	for {
+		var m jsonMessage
+		if err := dec.Decode(&m); err == io.EOF {
+			break
+		} else if err != nil {
+			return err
+		}
+		if m.Stream != "" {
+			fmt.Fprint(streamOptions.stdout, m.Stream)
+		} else if m.Progress != "" {
+			fmt.Fprintf(streamOptions.stdout, "%s %s\r", m.Status, m.Progress)
+		} else if m.Error != "" {
+			return errors.New(m.Error)
+		}
+		if m.Status != "" {
+			fmt.Fprintln(streamOptions.stdout, m.Status)
+		}
+	}
+	return nil
+}
+
+type proxyWriter struct {
+	io.Writer
+	calls uint64
+}
+
+func (p *proxyWriter) callCount() uint64 {
+	return atomic.LoadUint64(&p.calls)
+}
+
+func (p *proxyWriter) Write(data []byte) (int, error) {
+	atomic.AddUint64(&p.calls, 1)
+	return p.Writer.Write(data)
+}
+
+func handleInactivityTimeout(options *streamOptions, cancelRequest func(), canceled *uint32) chan<- struct{} {
+	done := make(chan struct{})
+	proxyStdout := &proxyWriter{Writer: options.stdout}
+	proxyStderr := &proxyWriter{Writer: options.stderr}
+	options.stdout = proxyStdout
+	options.stderr = proxyStderr
+	go func() {
+		var lastCallCount uint64
+		for {
+			select {
+			case <-time.After(options.inactivityTimeout):
+			case <-done:
+				return
+			}
+			curCallCount := proxyStdout.callCount() + proxyStderr.callCount()
+			if curCallCount == lastCallCount {
+				atomic.AddUint32(canceled, 1)
+				cancelRequest()
+				return
+			}
+			lastCallCount = curCallCount
+		}
+	}()
+	return done
+}
+
+type hijackOptions struct {
+	success        chan struct{}
+	setRawTerminal bool
+	in             io.Reader
+	stdout         io.Writer
+	stderr         io.Writer
+	data           interface{}
+}
+
+// CloseWaiter is an interface with methods for closing the underlying resource
+// and then waiting for it to finish processing.
+type CloseWaiter interface {
+	io.Closer
+	Wait() error
+}
+
+type waiterFunc func() error
+
+func (w waiterFunc) Wait() error { return w() }
+
+type closerFunc func() error
+
+func (c closerFunc) Close() error { return c() }
+
+func (c *Client) hijack(method, path string, hijackOptions hijackOptions) (CloseWaiter, error) {
+	if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil {
+		err := c.checkAPIVersion()
+		if err != nil {
+			return nil, err
+		}
+	}
+	var params io.Reader
+	if hijackOptions.data != nil {
+		buf, err := json.Marshal(hijackOptions.data)
+		if err != nil {
+			return nil, err
+		}
+		params = bytes.NewBuffer(buf)
+	}
+	req, err := http.NewRequest(method, c.getURL(path), params)
+	if err != nil {
+		return nil, err
+	}
+	req.Header.Set("Content-Type", "application/json")
+	req.Header.Set("Connection", "Upgrade")
+	req.Header.Set("Upgrade", "tcp")
+	protocol := c.endpointURL.Scheme
+	address := c.endpointURL.Path
+	if protocol != "unix" {
+		protocol = "tcp"
+		address = c.endpointURL.Host
+	}
+	var dial net.Conn
+	if c.TLSConfig != nil && protocol != "unix" {
+		dial, err = tlsDialWithDialer(c.Dialer, protocol, address, c.TLSConfig)
+		if err != nil {
+			return nil, err
+		}
+	} else {
+		dial, err = c.Dialer.Dial(protocol, address)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	errs := make(chan error)
+	quit := make(chan struct{})
+	go func() {
+		clientconn := httputil.NewClientConn(dial, nil)
+		defer clientconn.Close()
+		clientconn.Do(req)
+		if hijackOptions.success != nil {
+			hijackOptions.success <- struct{}{}
+			<-hijackOptions.success
+		}
+		rwc, br := clientconn.Hijack()
+		defer rwc.Close()
+
+		errChanOut := make(chan error, 1)
+		errChanIn := make(chan error, 1)
+		if hijackOptions.stdout == nil && hijackOptions.stderr == nil {
+			close(errChanOut)
+		} else {
+			// Only copy if hijackOptions.stdout and/or hijackOptions.stderr is actually set.
+			// Otherwise, if the only stream you care about is stdin, your attach session
+			// will "hang" until the container terminates, even though you're not reading
+			// stdout/stderr
+			if hijackOptions.stdout == nil {
+				hijackOptions.stdout = ioutil.Discard
+			}
+			if hijackOptions.stderr == nil {
+				hijackOptions.stderr = ioutil.Discard
+			}
+
+			go func() {
+				defer func() {
+					if hijackOptions.in != nil {
+						if closer, ok := hijackOptions.in.(io.Closer); ok {
+							closer.Close()
+						}
+						errChanIn <- nil
+					}
+				}()
+
+				var err error
+				if hijackOptions.setRawTerminal {
+					_, err = io.Copy(hijackOptions.stdout, br)
+				} else {
+					_, err = stdcopy.StdCopy(hijackOptions.stdout, hijackOptions.stderr, br)
+				}
+				errChanOut <- err
+			}()
+		}
+
+		go func() {
+			var err error
+			if hijackOptions.in != nil {
+				_, err = io.Copy(rwc, hijackOptions.in)
+			}
+			errChanIn <- err
+			rwc.(interface {
+				CloseWrite() error
+			}).CloseWrite()
+		}()
+
+		var errIn error
+		select {
+		case errIn = <-errChanIn:
+		case <-quit:
+			return
+		}
+
+		var errOut error
+		select {
+		case errOut = <-errChanOut:
+		case <-quit:
+			return
+		}
+
+		if errIn != nil {
+			errs <- errIn
+		} else {
+			errs <- errOut
+		}
+	}()
+
+	return struct {
+		closerFunc
+		waiterFunc
+	}{
+		closerFunc(func() error { close(quit); return nil }),
+		waiterFunc(func() error { return <-errs }),
+	}, nil
+}
+
+func (c *Client) getURL(path string) string {
+	urlStr := strings.TrimRight(c.endpointURL.String(), "/")
+	if c.endpointURL.Scheme == "unix" {
+		urlStr = ""
+	}
+	if c.requestedAPIVersion != nil {
+		return fmt.Sprintf("%s/v%s%s", urlStr, c.requestedAPIVersion, path)
+	}
+	return fmt.Sprintf("%s%s", urlStr, path)
+}
+
+// getFakeUnixURL returns the URL needed to make an HTTP request over a UNIX
+// domain socket to the given path.
+func (c *Client) getFakeUnixURL(path string) string {
+	u := *c.endpointURL // Copy.
+
+	// Override URL so that net/http will not complain.
+	u.Scheme = "http"
+	u.Host = "unix.sock" // Doesn't matter what this is - it's not used.
+	u.Path = ""
+	urlStr := strings.TrimRight(u.String(), "/")
+	if c.requestedAPIVersion != nil {
+		return fmt.Sprintf("%s/v%s%s", urlStr, c.requestedAPIVersion, path)
+	}
+	return fmt.Sprintf("%s%s", urlStr, path)
+}
+
+func (c *Client) unixClient() *http.Client {
+	if c.unixHTTPClient != nil {
+		return c.unixHTTPClient
+	}
+	socketPath := c.endpointURL.Path
+	tr := &http.Transport{
+		Dial: func(network, addr string) (net.Conn, error) {
+			return c.Dialer.Dial("unix", socketPath)
+		},
+	}
+	cleanhttp.SetTransportFinalizer(tr)
+	c.unixHTTPClient = &http.Client{Transport: tr}
+	return c.unixHTTPClient
+}
+
+type jsonMessage struct {
+	Status   string `json:"status,omitempty"`
+	Progress string `json:"progress,omitempty"`
+	Error    string `json:"error,omitempty"`
+	Stream   string `json:"stream,omitempty"`
+}
+
+func queryString(opts interface{}) string {
+	if opts == nil {
+		return ""
+	}
+	value := reflect.ValueOf(opts)
+	if value.Kind() == reflect.Ptr {
+		value = value.Elem()
+	}
+	if value.Kind() != reflect.Struct {
+		return ""
+	}
+	items := url.Values(map[string][]string{})
+	for i := 0; i < value.NumField(); i++ {
+		field := value.Type().Field(i)
+		if field.PkgPath != "" {
+			continue
+		}
+		key := field.Tag.Get("qs")
+		if key == "" {
+			key = strings.ToLower(field.Name)
+		} else if key == "-" {
+			continue
+		}
+		addQueryStringValue(items, key, value.Field(i))
+	}
+	return items.Encode()
+}
+
+func addQueryStringValue(items url.Values, key string, v reflect.Value) {
+	switch v.Kind() {
+	case reflect.Bool:
+		if v.Bool() {
+			items.Add(key, "1")
+		}
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		if v.Int() > 0 {
+			items.Add(key, strconv.FormatInt(v.Int(), 10))
+		}
+	case reflect.Float32, reflect.Float64:
+		if v.Float() > 0 {
+			items.Add(key, strconv.FormatFloat(v.Float(), 'f', -1, 64))
+		}
+	case reflect.String:
+		if v.String() != "" {
+			items.Add(key, v.String())
+		}
+	case reflect.Ptr:
+		if !v.IsNil() {
+			if b, err := json.Marshal(v.Interface()); err == nil {
+				items.Add(key, string(b))
+			}
+		}
+	case reflect.Map:
+		if len(v.MapKeys()) > 0 {
+			if b, err := json.Marshal(v.Interface()); err == nil {
+				items.Add(key, string(b))
+			}
+		}
+	case reflect.Array, reflect.Slice:
+		vLen := v.Len()
+		if vLen > 0 {
+			for i := 0; i < vLen; i++ {
+				addQueryStringValue(items, key, v.Index(i))
+			}
+		}
+	}
+}
+
+// Error represents failures in the API. It represents a failure from the API.
+type Error struct {
+	Status  int
+	Message string
+}
+
+func newError(resp *http.Response) *Error {
+	defer resp.Body.Close()
+	data, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return &Error{Status: resp.StatusCode, Message: fmt.Sprintf("cannot read body, err: %v", err)}
+	}
+	return &Error{Status: resp.StatusCode, Message: string(data)}
+}
+
+func (e *Error) Error() string {
+	return fmt.Sprintf("API error (%d): %s", e.Status, e.Message)
+}
+
+func parseEndpoint(endpoint string, tls bool) (*url.URL, error) {
+	if endpoint != "" && !strings.Contains(endpoint, "://") {
+		endpoint = "tcp://" + endpoint
+	}
+	u, err := url.Parse(endpoint)
+	if err != nil {
+		return nil, ErrInvalidEndpoint
+	}
+	if tls {
+		u.Scheme = "https"
+	}
+	switch u.Scheme {
+	case "unix":
+		return u, nil
+	case "http", "https", "tcp":
+		_, port, err := net.SplitHostPort(u.Host)
+		if err != nil {
+			if e, ok := err.(*net.AddrError); ok {
+				if e.Err == "missing port in address" {
+					return u, nil
+				}
+			}
+			return nil, ErrInvalidEndpoint
+		}
+		number, err := strconv.ParseInt(port, 10, 64)
+		if err == nil && number > 0 && number < 65536 {
+			if u.Scheme == "tcp" {
+				if tls {
+					u.Scheme = "https"
+				} else {
+					u.Scheme = "http"
+				}
+			}
+			return u, nil
+		}
+		return nil, ErrInvalidEndpoint
+	default:
+		return nil, ErrInvalidEndpoint
+	}
+}
+
+type dockerEnv struct {
+	dockerHost      string
+	dockerTLSVerify bool
+	dockerCertPath  string
+}
+
+func getDockerEnv() (*dockerEnv, error) {
+	dockerHost := os.Getenv("DOCKER_HOST")
+	var err error
+	if dockerHost == "" {
+		dockerHost, err = DefaultDockerHost()
+		if err != nil {
+			return nil, err
+		}
+	}
+	dockerTLSVerify := os.Getenv("DOCKER_TLS_VERIFY") != ""
+	var dockerCertPath string
+	if dockerTLSVerify {
+		dockerCertPath = os.Getenv("DOCKER_CERT_PATH")
+		if dockerCertPath == "" {
+			home := homedir.Get()
+			if home == "" {
+				return nil, errors.New("environment variable HOME must be set if DOCKER_CERT_PATH is not set")
+			}
+			dockerCertPath = filepath.Join(home, ".docker")
+			dockerCertPath, err = filepath.Abs(dockerCertPath)
+			if err != nil {
+				return nil, err
+			}
+		}
+	}
+	return &dockerEnv{
+		dockerHost:      dockerHost,
+		dockerTLSVerify: dockerTLSVerify,
+		dockerCertPath:  dockerCertPath,
+	}, nil
+}
+
+// DefaultDockerHost returns the default docker socket for the current OS
+func DefaultDockerHost() (string, error) {
+	var defaultHost string
+	if runtime.GOOS == "windows" {
+		// If we do not have a host, default to TCP socket on Windows
+		defaultHost = fmt.Sprintf("tcp://%s:%d", opts.DefaultHTTPHost, opts.DefaultHTTPPort)
+	} else {
+		// If we do not have a host, default to unix socket
+		defaultHost = fmt.Sprintf("unix://%s", opts.DefaultUnixSocket)
+	}
+	return opts.ValidateHost(defaultHost)
+}

+ 1325 - 0
vendor/github.com/fsouza/go-dockerclient/container.go

@@ -0,0 +1,1325 @@
+// Copyright 2015 go-dockerclient authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package docker
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/fsouza/go-dockerclient/external/github.com/docker/go-units"
+)
+
+// ErrContainerAlreadyExists is the error returned by CreateContainer when the
+// container already exists.
+var ErrContainerAlreadyExists = errors.New("container already exists")
+
+// ListContainersOptions specify parameters to the ListContainers function.
+//
+// See https://goo.gl/47a6tO for more details.
+type ListContainersOptions struct {
+	All     bool
+	Size    bool
+	Limit   int
+	Since   string
+	Before  string
+	Filters map[string][]string
+}
+
+// APIPort is a type that represents a port mapping returned by the Docker API
+type APIPort struct {
+	PrivatePort int64  `json:"PrivatePort,omitempty" yaml:"PrivatePort,omitempty"`
+	PublicPort  int64  `json:"PublicPort,omitempty" yaml:"PublicPort,omitempty"`
+	Type        string `json:"Type,omitempty" yaml:"Type,omitempty"`
+	IP          string `json:"IP,omitempty" yaml:"IP,omitempty"`
+}
+
+// APIMount represents a mount point for a container.
+type APIMount struct {
+	Name        string `json:"Name,omitempty" yaml:"Name,omitempty"`
+	Source      string `json:"Source,omitempty" yaml:"Source,omitempty"`
+	Destination string `json:"Destination,omitempty" yaml:"Destination,omitempty"`
+	Driver      string `json:"Driver,omitempty" yaml:"Driver,omitempty"`
+	Mode        string `json:"Mode,omitempty" yaml:"Mode,omitempty"`
+	RW          bool   `json:"RW,omitempty" yaml:"RW,omitempty"`
+	Propogation string `json:"Propogation,omitempty" yaml:"Propogation,omitempty"`
+}
+
+// APIContainers represents each container in the list returned by
+// ListContainers.
+type APIContainers struct {
+	ID         string            `json:"Id" yaml:"Id"`
+	Image      string            `json:"Image,omitempty" yaml:"Image,omitempty"`
+	Command    string            `json:"Command,omitempty" yaml:"Command,omitempty"`
+	Created    int64             `json:"Created,omitempty" yaml:"Created,omitempty"`
+	State      string            `json:"State,omitempty" yaml:"State,omitempty"`
+	Status     string            `json:"Status,omitempty" yaml:"Status,omitempty"`
+	Ports      []APIPort         `json:"Ports,omitempty" yaml:"Ports,omitempty"`
+	SizeRw     int64             `json:"SizeRw,omitempty" yaml:"SizeRw,omitempty"`
+	SizeRootFs int64             `json:"SizeRootFs,omitempty" yaml:"SizeRootFs,omitempty"`
+	Names      []string          `json:"Names,omitempty" yaml:"Names,omitempty"`
+	Labels     map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty"`
+	Networks   NetworkList       `json:"NetworkSettings,omitempty" yaml:"NetworkSettings,omitempty"`
+	Mounts     []APIMount        `json:"Mounts,omitempty" yaml:"Mounts,omitempty"`
+}
+
+// NetworkList encapsulates a map of networks, as returned by the Docker API in
+// ListContainers.
+type NetworkList struct {
+	Networks map[string]ContainerNetwork `json:"Networks" yaml:"Networks,omitempty"`
+}
+
+// ListContainers returns a slice of containers matching the given criteria.
+//
+// See https://goo.gl/47a6tO for more details.
+func (c *Client) ListContainers(opts ListContainersOptions) ([]APIContainers, error) {
+	path := "/containers/json?" + queryString(opts)
+	resp, err := c.do("GET", path, doOptions{})
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+	var containers []APIContainers
+	if err := json.NewDecoder(resp.Body).Decode(&containers); err != nil {
+		return nil, err
+	}
+	return containers, nil
+}
+
+// Port represents the port number and the protocol, in the form
+// <number>/<protocol>. For example: 80/tcp.
+type Port string
+
+// Port returns the number of the port.
+func (p Port) Port() string {
+	return strings.Split(string(p), "/")[0]
+}
+
+// Proto returns the name of the protocol.
+func (p Port) Proto() string {
+	parts := strings.Split(string(p), "/")
+	if len(parts) == 1 {
+		return "tcp"
+	}
+	return parts[1]
+}
+
+// State represents the state of a container.
+type State struct {
+	Status            string    `json:"Status,omitempty" yaml:"Status,omitempty"`
+	Running           bool      `json:"Running,omitempty" yaml:"Running,omitempty"`
+	Paused            bool      `json:"Paused,omitempty" yaml:"Paused,omitempty"`
+	Restarting        bool      `json:"Restarting,omitempty" yaml:"Restarting,omitempty"`
+	OOMKilled         bool      `json:"OOMKilled,omitempty" yaml:"OOMKilled,omitempty"`
+	RemovalInProgress bool      `json:"RemovalInProgress,omitempty" yaml:"RemovalInProgress,omitempty"`
+	Dead              bool      `json:"Dead,omitempty" yaml:"Dead,omitempty"`
+	Pid               int       `json:"Pid,omitempty" yaml:"Pid,omitempty"`
+	ExitCode          int       `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty"`
+	Error             string    `json:"Error,omitempty" yaml:"Error,omitempty"`
+	StartedAt         time.Time `json:"StartedAt,omitempty" yaml:"StartedAt,omitempty"`
+	FinishedAt        time.Time `json:"FinishedAt,omitempty" yaml:"FinishedAt,omitempty"`
+}
+
+// String returns a human-readable description of the state
+func (s *State) String() string {
+	if s.Running {
+		if s.Paused {
+			return fmt.Sprintf("Up %s (Paused)", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)))
+		}
+		if s.Restarting {
+			return fmt.Sprintf("Restarting (%d) %s ago", s.ExitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt)))
+		}
+
+		return fmt.Sprintf("Up %s", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)))
+	}
+
+	if s.RemovalInProgress {
+		return "Removal In Progress"
+	}
+
+	if s.Dead {
+		return "Dead"
+	}
+
+	if s.StartedAt.IsZero() {
+		return "Created"
+	}
+
+	if s.FinishedAt.IsZero() {
+		return ""
+	}
+
+	return fmt.Sprintf("Exited (%d) %s ago", s.ExitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt)))
+}
+
+// StateString returns a single string to describe state
+func (s *State) StateString() string {
+	if s.Running {
+		if s.Paused {
+			return "paused"
+		}
+		if s.Restarting {
+			return "restarting"
+		}
+		return "running"
+	}
+
+	if s.Dead {
+		return "dead"
+	}
+
+	if s.StartedAt.IsZero() {
+		return "created"
+	}
+
+	return "exited"
+}
+
+// PortBinding represents the host/container port mapping as returned in the
+// `docker inspect` json
+type PortBinding struct {
+	HostIP   string `json:"HostIP,omitempty" yaml:"HostIP,omitempty"`
+	HostPort string `json:"HostPort,omitempty" yaml:"HostPort,omitempty"`
+}
+
+// PortMapping represents a deprecated field in the `docker inspect` output,
+// and its value as found in NetworkSettings should always be nil
+type PortMapping map[string]string
+
+// ContainerNetwork represents the networking settings of a container per network.
+type ContainerNetwork struct {
+	MacAddress          string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"`
+	GlobalIPv6PrefixLen int    `json:"GlobalIPv6PrefixLen,omitempty" yaml:"GlobalIPv6PrefixLen,omitempty"`
+	GlobalIPv6Address   string `json:"GlobalIPv6Address,omitempty" yaml:"GlobalIPv6Address,omitempty"`
+	IPv6Gateway         string `json:"IPv6Gateway,omitempty" yaml:"IPv6Gateway,omitempty"`
+	IPPrefixLen         int    `json:"IPPrefixLen,omitempty" yaml:"IPPrefixLen,omitempty"`
+	IPAddress           string `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty"`
+	Gateway             string `json:"Gateway,omitempty" yaml:"Gateway,omitempty"`
+	EndpointID          string `json:"EndpointID,omitempty" yaml:"EndpointID,omitempty"`
+	NetworkID           string `json:"NetworkID,omitempty" yaml:"NetworkID,omitempty"`
+}
+
+// NetworkSettings contains network-related information about a container
+type NetworkSettings struct {
+	Networks               map[string]ContainerNetwork `json:"Networks,omitempty" yaml:"Networks,omitempty"`
+	IPAddress              string                      `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty"`
+	IPPrefixLen            int                         `json:"IPPrefixLen,omitempty" yaml:"IPPrefixLen,omitempty"`
+	MacAddress             string                      `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"`
+	Gateway                string                      `json:"Gateway,omitempty" yaml:"Gateway,omitempty"`
+	Bridge                 string                      `json:"Bridge,omitempty" yaml:"Bridge,omitempty"`
+	PortMapping            map[string]PortMapping      `json:"PortMapping,omitempty" yaml:"PortMapping,omitempty"`
+	Ports                  map[Port][]PortBinding      `json:"Ports,omitempty" yaml:"Ports,omitempty"`
+	NetworkID              string                      `json:"NetworkID,omitempty" yaml:"NetworkID,omitempty"`
+	EndpointID             string                      `json:"EndpointID,omitempty" yaml:"EndpointID,omitempty"`
+	SandboxKey             string                      `json:"SandboxKey,omitempty" yaml:"SandboxKey,omitempty"`
+	GlobalIPv6Address      string                      `json:"GlobalIPv6Address,omitempty" yaml:"GlobalIPv6Address,omitempty"`
+	GlobalIPv6PrefixLen    int                         `json:"GlobalIPv6PrefixLen,omitempty" yaml:"GlobalIPv6PrefixLen,omitempty"`
+	IPv6Gateway            string                      `json:"IPv6Gateway,omitempty" yaml:"IPv6Gateway,omitempty"`
+	LinkLocalIPv6Address   string                      `json:"LinkLocalIPv6Address,omitempty" yaml:"LinkLocalIPv6Address,omitempty"`
+	LinkLocalIPv6PrefixLen int                         `json:"LinkLocalIPv6PrefixLen,omitempty" yaml:"LinkLocalIPv6PrefixLen,omitempty"`
+	SecondaryIPAddresses   []string                    `json:"SecondaryIPAddresses,omitempty" yaml:"SecondaryIPAddresses,omitempty"`
+	SecondaryIPv6Addresses []string                    `json:"SecondaryIPv6Addresses,omitempty" yaml:"SecondaryIPv6Addresses,omitempty"`
+}
+
+// PortMappingAPI translates the port mappings as contained in NetworkSettings
+// into the format in which they would appear when returned by the API
+func (settings *NetworkSettings) PortMappingAPI() []APIPort {
+	var mapping []APIPort
+	for port, bindings := range settings.Ports {
+		p, _ := parsePort(port.Port())
+		if len(bindings) == 0 {
+			mapping = append(mapping, APIPort{
+				PrivatePort: int64(p),
+				Type:        port.Proto(),
+			})
+			continue
+		}
+		for _, binding := range bindings {
+			p, _ := parsePort(port.Port())
+			h, _ := parsePort(binding.HostPort)
+			mapping = append(mapping, APIPort{
+				PrivatePort: int64(p),
+				PublicPort:  int64(h),
+				Type:        port.Proto(),
+				IP:          binding.HostIP,
+			})
+		}
+	}
+	return mapping
+}
+
+func parsePort(rawPort string) (int, error) {
+	port, err := strconv.ParseUint(rawPort, 10, 16)
+	if err != nil {
+		return 0, err
+	}
+	return int(port), nil
+}
+
+// Config is the list of configuration options used when creating a container.
+// Config does not contain the options that are specific to starting a container on a
+// given host.  Those are contained in HostConfig
+type Config struct {
+	Hostname          string              `json:"Hostname,omitempty" yaml:"Hostname,omitempty"`
+	Domainname        string              `json:"Domainname,omitempty" yaml:"Domainname,omitempty"`
+	User              string              `json:"User,omitempty" yaml:"User,omitempty"`
+	Memory            int64               `json:"Memory,omitempty" yaml:"Memory,omitempty"`
+	MemorySwap        int64               `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty"`
+	MemoryReservation int64               `json:"MemoryReservation,omitempty" yaml:"MemoryReservation,omitempty"`
+	KernelMemory      int64               `json:"KernelMemory,omitempty" yaml:"KernelMemory,omitempty"`
+	PidsLimit         int64               `json:"PidsLimit,omitempty" yaml:"PidsLimit,omitempty"`
+	CPUShares         int64               `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty"`
+	CPUSet            string              `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty"`
+	AttachStdin       bool                `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty"`
+	AttachStdout      bool                `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty"`
+	AttachStderr      bool                `json:"AttachStderr,omitempty" yaml:"AttachStderr,omitempty"`
+	PortSpecs         []string            `json:"PortSpecs,omitempty" yaml:"PortSpecs,omitempty"`
+	ExposedPorts      map[Port]struct{}   `json:"ExposedPorts,omitempty" yaml:"ExposedPorts,omitempty"`
+	StopSignal        string              `json:"StopSignal,omitempty" yaml:"StopSignal,omitempty"`
+	Tty               bool                `json:"Tty,omitempty" yaml:"Tty,omitempty"`
+	OpenStdin         bool                `json:"OpenStdin,omitempty" yaml:"OpenStdin,omitempty"`
+	StdinOnce         bool                `json:"StdinOnce,omitempty" yaml:"StdinOnce,omitempty"`
+	Env               []string            `json:"Env,omitempty" yaml:"Env,omitempty"`
+	Cmd               []string            `json:"Cmd" yaml:"Cmd"`
+	DNS               []string            `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.9 and below only
+	Image             string              `json:"Image,omitempty" yaml:"Image,omitempty"`
+	Volumes           map[string]struct{} `json:"Volumes,omitempty" yaml:"Volumes,omitempty"`
+	VolumeDriver      string              `json:"VolumeDriver,omitempty" yaml:"VolumeDriver,omitempty"`
+	VolumesFrom       string              `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"`
+	WorkingDir        string              `json:"WorkingDir,omitempty" yaml:"WorkingDir,omitempty"`
+	MacAddress        string              `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"`
+	Entrypoint        []string            `json:"Entrypoint" yaml:"Entrypoint"`
+	NetworkDisabled   bool                `json:"NetworkDisabled,omitempty" yaml:"NetworkDisabled,omitempty"`
+	SecurityOpts      []string            `json:"SecurityOpts,omitempty" yaml:"SecurityOpts,omitempty"`
+	OnBuild           []string            `json:"OnBuild,omitempty" yaml:"OnBuild,omitempty"`
+	Mounts            []Mount             `json:"Mounts,omitempty" yaml:"Mounts,omitempty"`
+	Labels            map[string]string   `json:"Labels,omitempty" yaml:"Labels,omitempty"`
+}
+
+// Mount represents a mount point in the container.
+//
+// It has been added in the version 1.20 of the Docker API, available since
+// Docker 1.8.
+type Mount struct {
+	Name        string
+	Source      string
+	Destination string
+	Driver      string
+	Mode        string
+	RW          bool
+}
+
+// LogConfig defines the log driver type and the configuration for it.
+type LogConfig struct {
+	Type   string            `json:"Type,omitempty" yaml:"Type,omitempty"`
+	Config map[string]string `json:"Config,omitempty" yaml:"Config,omitempty"`
+}
+
+// ULimit defines system-wide resource limitations
+// This can help a lot in system administration, e.g. when a user starts too many processes and therefore makes the system unresponsive for other users.
+type ULimit struct {
+	Name string `json:"Name,omitempty" yaml:"Name,omitempty"`
+	Soft int64  `json:"Soft,omitempty" yaml:"Soft,omitempty"`
+	Hard int64  `json:"Hard,omitempty" yaml:"Hard,omitempty"`
+}
+
+// SwarmNode containers information about which Swarm node the container is on
+type SwarmNode struct {
+	ID     string            `json:"ID,omitempty" yaml:"ID,omitempty"`
+	IP     string            `json:"IP,omitempty" yaml:"IP,omitempty"`
+	Addr   string            `json:"Addr,omitempty" yaml:"Addr,omitempty"`
+	Name   string            `json:"Name,omitempty" yaml:"Name,omitempty"`
+	CPUs   int64             `json:"CPUs,omitempty" yaml:"CPUs,omitempty"`
+	Memory int64             `json:"Memory,omitempty" yaml:"Memory,omitempty"`
+	Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty"`
+}
+
+// GraphDriver contains information about the GraphDriver used by the container
+type GraphDriver struct {
+	Name string            `json:"Name,omitempty" yaml:"Name,omitempty"`
+	Data map[string]string `json:"Data,omitempty" yaml:"Data,omitempty"`
+}
+
+// Container is the type encompasing everything about a container - its config,
+// hostconfig, etc.
+type Container struct {
+	ID string `json:"Id" yaml:"Id"`
+
+	Created time.Time `json:"Created,omitempty" yaml:"Created,omitempty"`
+
+	Path string   `json:"Path,omitempty" yaml:"Path,omitempty"`
+	Args []string `json:"Args,omitempty" yaml:"Args,omitempty"`
+
+	Config *Config `json:"Config,omitempty" yaml:"Config,omitempty"`
+	State  State   `json:"State,omitempty" yaml:"State,omitempty"`
+	Image  string  `json:"Image,omitempty" yaml:"Image,omitempty"`
+
+	Node *SwarmNode `json:"Node,omitempty" yaml:"Node,omitempty"`
+
+	NetworkSettings *NetworkSettings `json:"NetworkSettings,omitempty" yaml:"NetworkSettings,omitempty"`
+
+	SysInitPath    string  `json:"SysInitPath,omitempty" yaml:"SysInitPath,omitempty"`
+	ResolvConfPath string  `json:"ResolvConfPath,omitempty" yaml:"ResolvConfPath,omitempty"`
+	HostnamePath   string  `json:"HostnamePath,omitempty" yaml:"HostnamePath,omitempty"`
+	HostsPath      string  `json:"HostsPath,omitempty" yaml:"HostsPath,omitempty"`
+	LogPath        string  `json:"LogPath,omitempty" yaml:"LogPath,omitempty"`
+	Name           string  `json:"Name,omitempty" yaml:"Name,omitempty"`
+	Driver         string  `json:"Driver,omitempty" yaml:"Driver,omitempty"`
+	Mounts         []Mount `json:"Mounts,omitempty" yaml:"Mounts,omitempty"`
+
+	Volumes     map[string]string `json:"Volumes,omitempty" yaml:"Volumes,omitempty"`
+	VolumesRW   map[string]bool   `json:"VolumesRW,omitempty" yaml:"VolumesRW,omitempty"`
+	HostConfig  *HostConfig       `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty"`
+	ExecIDs     []string          `json:"ExecIDs,omitempty" yaml:"ExecIDs,omitempty"`
+	GraphDriver *GraphDriver      `json:"GraphDriver,omitempty" yaml:"GraphDriver,omitempty"`
+
+	RestartCount int `json:"RestartCount,omitempty" yaml:"RestartCount,omitempty"`
+
+	AppArmorProfile string `json:"AppArmorProfile,omitempty" yaml:"AppArmorProfile,omitempty"`
+}
+
+// UpdateContainerOptions specify parameters to the UpdateContainer function.
+//
+// See https://goo.gl/Y6fXUy for more details.
+type UpdateContainerOptions struct {
+	BlkioWeight       int           `json:"BlkioWeight"`
+	CPUShares         int           `json:"CpuShares"`
+	CPUPeriod         int           `json:"CpuPeriod"`
+	CPUQuota          int           `json:"CpuQuota"`
+	CpusetCpus        string        `json:"CpusetCpus"`
+	CpusetMems        string        `json:"CpusetMems"`
+	Memory            int           `json:"Memory"`
+	MemorySwap        int           `json:"MemorySwap"`
+	MemoryReservation int           `json:"MemoryReservation"`
+	KernelMemory      int           `json:"KernelMemory"`
+	RestartPolicy     RestartPolicy `json:"RestartPolicy,omitempty"`
+}
+
+// UpdateContainer updates the container at ID with the options
+//
+// See https://goo.gl/Y6fXUy for more details.
+func (c *Client) UpdateContainer(id string, opts UpdateContainerOptions) error {
+	resp, err := c.do("POST", fmt.Sprintf("/containers/"+id+"/update"), doOptions{data: opts, forceJSON: true})
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+	return nil
+}
+
+// RenameContainerOptions specify parameters to the RenameContainer function.
+//
+// See https://goo.gl/laSOIy for more details.
+type RenameContainerOptions struct {
+	// ID of container to rename
+	ID string `qs:"-"`
+
+	// New name
+	Name string `json:"name,omitempty" yaml:"name,omitempty"`
+}
+
+// RenameContainer updates and existing containers name
+//
+// See https://goo.gl/laSOIy for more details.
+func (c *Client) RenameContainer(opts RenameContainerOptions) error {
+	resp, err := c.do("POST", fmt.Sprintf("/containers/"+opts.ID+"/rename?%s", queryString(opts)), doOptions{})
+	if err != nil {
+		return err
+	}
+	resp.Body.Close()
+	return nil
+}
+
+// InspectContainer returns information about a container by its ID.
+//
+// See https://goo.gl/RdIq0b for more details.
+func (c *Client) InspectContainer(id string) (*Container, error) {
+	path := "/containers/" + id + "/json"
+	resp, err := c.do("GET", path, doOptions{})
+	if err != nil {
+		if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+			return nil, &NoSuchContainer{ID: id}
+		}
+		return nil, err
+	}
+	defer resp.Body.Close()
+	var container Container
+	if err := json.NewDecoder(resp.Body).Decode(&container); err != nil {
+		return nil, err
+	}
+	return &container, nil
+}
+
+// ContainerChanges returns changes in the filesystem of the given container.
+//
+// See https://goo.gl/9GsTIF for more details.
+func (c *Client) ContainerChanges(id string) ([]Change, error) {
+	path := "/containers/" + id + "/changes"
+	resp, err := c.do("GET", path, doOptions{})
+	if err != nil {
+		if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+			return nil, &NoSuchContainer{ID: id}
+		}
+		return nil, err
+	}
+	defer resp.Body.Close()
+	var changes []Change
+	if err := json.NewDecoder(resp.Body).Decode(&changes); err != nil {
+		return nil, err
+	}
+	return changes, nil
+}
+
+// CreateContainerOptions specify parameters to the CreateContainer function.
+//
+// See https://goo.gl/WxQzrr for more details.
+type CreateContainerOptions struct {
+	Name       string
+	Config     *Config     `qs:"-"`
+	HostConfig *HostConfig `qs:"-"`
+}
+
+// CreateContainer creates a new container, returning the container instance,
+// or an error in case of failure.
+//
+// See https://goo.gl/WxQzrr for more details.
+func (c *Client) CreateContainer(opts CreateContainerOptions) (*Container, error) {
+	path := "/containers/create?" + queryString(opts)
+	resp, err := c.do(
+		"POST",
+		path,
+		doOptions{
+			data: struct {
+				*Config
+				HostConfig *HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty"`
+			}{
+				opts.Config,
+				opts.HostConfig,
+			},
+		},
+	)
+
+	if e, ok := err.(*Error); ok {
+		if e.Status == http.StatusNotFound {
+			return nil, ErrNoSuchImage
+		}
+		if e.Status == http.StatusConflict {
+			return nil, ErrContainerAlreadyExists
+		}
+	}
+
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+	var container Container
+	if err := json.NewDecoder(resp.Body).Decode(&container); err != nil {
+		return nil, err
+	}
+
+	container.Name = opts.Name
+
+	return &container, nil
+}
+
+// KeyValuePair is a type for generic key/value pairs as used in the Lxc
+// configuration
+type KeyValuePair struct {
+	Key   string `json:"Key,omitempty" yaml:"Key,omitempty"`
+	Value string `json:"Value,omitempty" yaml:"Value,omitempty"`
+}
+
+// RestartPolicy represents the policy for automatically restarting a container.
+//
+// Possible values are:
+//
+//   - always: the docker daemon will always restart the container
+//   - on-failure: the docker daemon will restart the container on failures, at
+//                 most MaximumRetryCount times
+//   - no: the docker daemon will not restart the container automatically
+type RestartPolicy struct {
+	Name              string `json:"Name,omitempty" yaml:"Name,omitempty"`
+	MaximumRetryCount int    `json:"MaximumRetryCount,omitempty" yaml:"MaximumRetryCount,omitempty"`
+}
+
+// AlwaysRestart returns a restart policy that tells the Docker daemon to
+// always restart the container.
+func AlwaysRestart() RestartPolicy {
+	return RestartPolicy{Name: "always"}
+}
+
+// RestartOnFailure returns a restart policy that tells the Docker daemon to
+// restart the container on failures, trying at most maxRetry times.
+func RestartOnFailure(maxRetry int) RestartPolicy {
+	return RestartPolicy{Name: "on-failure", MaximumRetryCount: maxRetry}
+}
+
+// NeverRestart returns a restart policy that tells the Docker daemon to never
+// restart the container on failures.
+func NeverRestart() RestartPolicy {
+	return RestartPolicy{Name: "no"}
+}
+
+// Device represents a device mapping between the Docker host and the
+// container.
+type Device struct {
+	PathOnHost        string `json:"PathOnHost,omitempty" yaml:"PathOnHost,omitempty"`
+	PathInContainer   string `json:"PathInContainer,omitempty" yaml:"PathInContainer,omitempty"`
+	CgroupPermissions string `json:"CgroupPermissions,omitempty" yaml:"CgroupPermissions,omitempty"`
+}
+
+// BlockWeight represents a relative device weight for an individual device inside
+// of a container
+//
+// See https://goo.gl/FSdP0H for more details.
+type BlockWeight struct {
+	Path   string `json:"Path,omitempty"`
+	Weight string `json:"Weight,omitempty"`
+}
+
+// BlockLimit represents a read/write limit in IOPS or Bandwidth for a device
+// inside of a container
+//
+// See https://goo.gl/FSdP0H for more details.
+type BlockLimit struct {
+	Path string `json:"Path,omitempty"`
+	Rate string `json:"Rate,omitempty"`
+}
+
+// HostConfig contains the container options related to starting a container on
+// a given host
+type HostConfig struct {
+	Binds                []string               `json:"Binds,omitempty" yaml:"Binds,omitempty"`
+	CapAdd               []string               `json:"CapAdd,omitempty" yaml:"CapAdd,omitempty"`
+	CapDrop              []string               `json:"CapDrop,omitempty" yaml:"CapDrop,omitempty"`
+	GroupAdd             []string               `json:"GroupAdd,omitempty" yaml:"GroupAdd,omitempty"`
+	ContainerIDFile      string                 `json:"ContainerIDFile,omitempty" yaml:"ContainerIDFile,omitempty"`
+	LxcConf              []KeyValuePair         `json:"LxcConf,omitempty" yaml:"LxcConf,omitempty"`
+	Privileged           bool                   `json:"Privileged,omitempty" yaml:"Privileged,omitempty"`
+	PortBindings         map[Port][]PortBinding `json:"PortBindings,omitempty" yaml:"PortBindings,omitempty"`
+	Links                []string               `json:"Links,omitempty" yaml:"Links,omitempty"`
+	PublishAllPorts      bool                   `json:"PublishAllPorts,omitempty" yaml:"PublishAllPorts,omitempty"`
+	DNS                  []string               `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.10 and above only
+	DNSOptions           []string               `json:"DnsOptions,omitempty" yaml:"DnsOptions,omitempty"`
+	DNSSearch            []string               `json:"DnsSearch,omitempty" yaml:"DnsSearch,omitempty"`
+	ExtraHosts           []string               `json:"ExtraHosts,omitempty" yaml:"ExtraHosts,omitempty"`
+	VolumesFrom          []string               `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"`
+	UsernsMode           string                 `json:"UsernsMode,omitempty" yaml:"UsernsMode,omitempty"`
+	NetworkMode          string                 `json:"NetworkMode,omitempty" yaml:"NetworkMode,omitempty"`
+	IpcMode              string                 `json:"IpcMode,omitempty" yaml:"IpcMode,omitempty"`
+	PidMode              string                 `json:"PidMode,omitempty" yaml:"PidMode,omitempty"`
+	UTSMode              string                 `json:"UTSMode,omitempty" yaml:"UTSMode,omitempty"`
+	RestartPolicy        RestartPolicy          `json:"RestartPolicy,omitempty" yaml:"RestartPolicy,omitempty"`
+	Devices              []Device               `json:"Devices,omitempty" yaml:"Devices,omitempty"`
+	LogConfig            LogConfig              `json:"LogConfig,omitempty" yaml:"LogConfig,omitempty"`
+	ReadonlyRootfs       bool                   `json:"ReadonlyRootfs,omitempty" yaml:"ReadonlyRootfs,omitempty"`
+	SecurityOpt          []string               `json:"SecurityOpt,omitempty" yaml:"SecurityOpt,omitempty"`
+	CgroupParent         string                 `json:"CgroupParent,omitempty" yaml:"CgroupParent,omitempty"`
+	Memory               int64                  `json:"Memory,omitempty" yaml:"Memory,omitempty"`
+	MemorySwap           int64                  `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty"`
+	MemorySwappiness     int64                  `json:"MemorySwappiness,omitempty" yaml:"MemorySwappiness,omitempty"`
+	OOMKillDisable       bool                   `json:"OomKillDisable,omitempty" yaml:"OomKillDisable"`
+	CPUShares            int64                  `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty"`
+	CPUSet               string                 `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty"`
+	CPUSetCPUs           string                 `json:"CpusetCpus,omitempty" yaml:"CpusetCpus,omitempty"`
+	CPUSetMEMs           string                 `json:"CpusetMems,omitempty" yaml:"CpusetMems,omitempty"`
+	CPUQuota             int64                  `json:"CpuQuota,omitempty" yaml:"CpuQuota,omitempty"`
+	CPUPeriod            int64                  `json:"CpuPeriod,omitempty" yaml:"CpuPeriod,omitempty"`
+	BlkioWeight          int64                  `json:"BlkioWeight,omitempty" yaml:"BlkioWeight"`
+	BlkioWeightDevice    []BlockWeight          `json:"BlkioWeightDevice,omitempty" yaml:"BlkioWeightDevice"`
+	BlkioDeviceReadBps   []BlockLimit           `json:"BlkioDeviceReadBps,omitempty" yaml:"BlkioDeviceReadBps"`
+	BlkioDeviceReadIOps  []BlockLimit           `json:"BlkioDeviceReadIOps,omitempty" yaml:"BlkioDeviceReadIOps"`
+	BlkioDeviceWriteBps  []BlockLimit           `json:"BlkioDeviceWriteBps,omitempty" yaml:"BlkioDeviceWriteBps"`
+	BlkioDeviceWriteIOps []BlockLimit           `json:"BlkioDeviceWriteIOps,omitempty" yaml:"BlkioDeviceWriteIOps"`
+	Ulimits              []ULimit               `json:"Ulimits,omitempty" yaml:"Ulimits,omitempty"`
+	VolumeDriver         string                 `json:"VolumeDriver,omitempty" yaml:"VolumeDriver,omitempty"`
+	OomScoreAdj          int                    `json:"OomScoreAdj,omitempty" yaml:"OomScoreAdj,omitempty"`
+	ShmSize              int64                  `json:"ShmSize,omitempty" yaml:"ShmSize,omitempty"`
+}
+
+// StartContainer starts a container, returning an error in case of failure.
+//
+// See https://goo.gl/MrBAJv for more details.
+func (c *Client) StartContainer(id string, hostConfig *HostConfig) error {
+	path := "/containers/" + id + "/start"
+	resp, err := c.do("POST", path, doOptions{data: hostConfig, forceJSON: true})
+	if err != nil {
+		if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+			return &NoSuchContainer{ID: id, Err: err}
+		}
+		return err
+	}
+	if resp.StatusCode == http.StatusNotModified {
+		return &ContainerAlreadyRunning{ID: id}
+	}
+	resp.Body.Close()
+	return nil
+}
+
+// StopContainer stops a container, killing it after the given timeout (in
+// seconds).
+//
+// See https://goo.gl/USqsFt for more details.
+func (c *Client) StopContainer(id string, timeout uint) error {
+	path := fmt.Sprintf("/containers/%s/stop?t=%d", id, timeout)
+	resp, err := c.do("POST", path, doOptions{})
+	if err != nil {
+		if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+			return &NoSuchContainer{ID: id}
+		}
+		return err
+	}
+	if resp.StatusCode == http.StatusNotModified {
+		return &ContainerNotRunning{ID: id}
+	}
+	resp.Body.Close()
+	return nil
+}
+
+// RestartContainer stops a container, killing it after the given timeout (in
+// seconds), during the stop process.
+//
+// See https://goo.gl/QzsDnz for more details.
+func (c *Client) RestartContainer(id string, timeout uint) error {
+	path := fmt.Sprintf("/containers/%s/restart?t=%d", id, timeout)
+	resp, err := c.do("POST", path, doOptions{})
+	if err != nil {
+		if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+			return &NoSuchContainer{ID: id}
+		}
+		return err
+	}
+	resp.Body.Close()
+	return nil
+}
+
+// PauseContainer pauses the given container.
+//
+// See https://goo.gl/OF7W9X for more details.
+func (c *Client) PauseContainer(id string) error {
+	path := fmt.Sprintf("/containers/%s/pause", id)
+	resp, err := c.do("POST", path, doOptions{})
+	if err != nil {
+		if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+			return &NoSuchContainer{ID: id}
+		}
+		return err
+	}
+	resp.Body.Close()
+	return nil
+}
+
+// UnpauseContainer unpauses the given container.
+//
+// See https://goo.gl/7dwyPA for more details.
+func (c *Client) UnpauseContainer(id string) error {
+	path := fmt.Sprintf("/containers/%s/unpause", id)
+	resp, err := c.do("POST", path, doOptions{})
+	if err != nil {
+		if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+			return &NoSuchContainer{ID: id}
+		}
+		return err
+	}
+	resp.Body.Close()
+	return nil
+}
+
+// TopResult represents the list of processes running in a container, as
+// returned by /containers/<id>/top.
+//
+// See https://goo.gl/Rb46aY for more details.
+type TopResult struct {
+	Titles    []string
+	Processes [][]string
+}
+
+// TopContainer returns processes running inside a container
+//
+// See https://goo.gl/Rb46aY for more details.
+func (c *Client) TopContainer(id string, psArgs string) (TopResult, error) {
+	var args string
+	var result TopResult
+	if psArgs != "" {
+		args = fmt.Sprintf("?ps_args=%s", psArgs)
+	}
+	path := fmt.Sprintf("/containers/%s/top%s", id, args)
+	resp, err := c.do("GET", path, doOptions{})
+	if err != nil {
+		if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+			return result, &NoSuchContainer{ID: id}
+		}
+		return result, err
+	}
+	defer resp.Body.Close()
+	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+		return result, err
+	}
+	return result, nil
+}
+
+// Stats represents container statistics, returned by /containers/<id>/stats.
+//
+// See https://goo.gl/GNmLHb for more details.
+type Stats struct {
+	Read      time.Time `json:"read,omitempty" yaml:"read,omitempty"`
+	PidsStats struct {
+		Current uint64 `json:"current,omitempty" yaml:"current,omitempty"`
+	} `json:"pids_stats,omitempty" yaml:"pids_stats,omitempty"`
+	Network     NetworkStats            `json:"network,omitempty" yaml:"network,omitempty"`
+	Networks    map[string]NetworkStats `json:"networks,omitempty" yaml:"networks,omitempty"`
+	MemoryStats struct {
+		Stats struct {
+			TotalPgmafault          uint64 `json:"total_pgmafault,omitempty" yaml:"total_pgmafault,omitempty"`
+			Cache                   uint64 `json:"cache,omitempty" yaml:"cache,omitempty"`
+			MappedFile              uint64 `json:"mapped_file,omitempty" yaml:"mapped_file,omitempty"`
+			TotalInactiveFile       uint64 `json:"total_inactive_file,omitempty" yaml:"total_inactive_file,omitempty"`
+			Pgpgout                 uint64 `json:"pgpgout,omitempty" yaml:"pgpgout,omitempty"`
+			Rss                     uint64 `json:"rss,omitempty" yaml:"rss,omitempty"`
+			TotalMappedFile         uint64 `json:"total_mapped_file,omitempty" yaml:"total_mapped_file,omitempty"`
+			Writeback               uint64 `json:"writeback,omitempty" yaml:"writeback,omitempty"`
+			Unevictable             uint64 `json:"unevictable,omitempty" yaml:"unevictable,omitempty"`
+			Pgpgin                  uint64 `json:"pgpgin,omitempty" yaml:"pgpgin,omitempty"`
+			TotalUnevictable        uint64 `json:"total_unevictable,omitempty" yaml:"total_unevictable,omitempty"`
+			Pgmajfault              uint64 `json:"pgmajfault,omitempty" yaml:"pgmajfault,omitempty"`
+			TotalRss                uint64 `json:"total_rss,omitempty" yaml:"total_rss,omitempty"`
+			TotalRssHuge            uint64 `json:"total_rss_huge,omitempty" yaml:"total_rss_huge,omitempty"`
+			TotalWriteback          uint64 `json:"total_writeback,omitempty" yaml:"total_writeback,omitempty"`
+			TotalInactiveAnon       uint64 `json:"total_inactive_anon,omitempty" yaml:"total_inactive_anon,omitempty"`
+			RssHuge                 uint64 `json:"rss_huge,omitempty" yaml:"rss_huge,omitempty"`
+			HierarchicalMemoryLimit uint64 `json:"hierarchical_memory_limit,omitempty" yaml:"hierarchical_memory_limit,omitempty"`
+			TotalPgfault            uint64 `json:"total_pgfault,omitempty" yaml:"total_pgfault,omitempty"`
+			TotalActiveFile         uint64 `json:"total_active_file,omitempty" yaml:"total_active_file,omitempty"`
+			ActiveAnon              uint64 `json:"active_anon,omitempty" yaml:"active_anon,omitempty"`
+			TotalActiveAnon         uint64 `json:"total_active_anon,omitempty" yaml:"total_active_anon,omitempty"`
+			TotalPgpgout            uint64 `json:"total_pgpgout,omitempty" yaml:"total_pgpgout,omitempty"`
+			TotalCache              uint64 `json:"total_cache,omitempty" yaml:"total_cache,omitempty"`
+			InactiveAnon            uint64 `json:"inactive_anon,omitempty" yaml:"inactive_anon,omitempty"`
+			ActiveFile              uint64 `json:"active_file,omitempty" yaml:"active_file,omitempty"`
+			Pgfault                 uint64 `json:"pgfault,omitempty" yaml:"pgfault,omitempty"`
+			InactiveFile            uint64 `json:"inactive_file,omitempty" yaml:"inactive_file,omitempty"`
+			TotalPgpgin             uint64 `json:"total_pgpgin,omitempty" yaml:"total_pgpgin,omitempty"`
+			HierarchicalMemswLimit  uint64 `json:"hierarchical_memsw_limit,omitempty" yaml:"hierarchical_memsw_limit,omitempty"`
+			Swap                    uint64 `json:"swap,omitempty" yaml:"swap,omitempty"`
+		} `json:"stats,omitempty" yaml:"stats,omitempty"`
+		MaxUsage uint64 `json:"max_usage,omitempty" yaml:"max_usage,omitempty"`
+		Usage    uint64 `json:"usage,omitempty" yaml:"usage,omitempty"`
+		Failcnt  uint64 `json:"failcnt,omitempty" yaml:"failcnt,omitempty"`
+		Limit    uint64 `json:"limit,omitempty" yaml:"limit,omitempty"`
+	} `json:"memory_stats,omitempty" yaml:"memory_stats,omitempty"`
+	BlkioStats struct {
+		IOServiceBytesRecursive []BlkioStatsEntry `json:"io_service_bytes_recursive,omitempty" yaml:"io_service_bytes_recursive,omitempty"`
+		IOServicedRecursive     []BlkioStatsEntry `json:"io_serviced_recursive,omitempty" yaml:"io_serviced_recursive,omitempty"`
+		IOQueueRecursive        []BlkioStatsEntry `json:"io_queue_recursive,omitempty" yaml:"io_queue_recursive,omitempty"`
+		IOServiceTimeRecursive  []BlkioStatsEntry `json:"io_service_time_recursive,omitempty" yaml:"io_service_time_recursive,omitempty"`
+		IOWaitTimeRecursive     []BlkioStatsEntry `json:"io_wait_time_recursive,omitempty" yaml:"io_wait_time_recursive,omitempty"`
+		IOMergedRecursive       []BlkioStatsEntry `json:"io_merged_recursive,omitempty" yaml:"io_merged_recursive,omitempty"`
+		IOTimeRecursive         []BlkioStatsEntry `json:"io_time_recursive,omitempty" yaml:"io_time_recursive,omitempty"`
+		SectorsRecursive        []BlkioStatsEntry `json:"sectors_recursive,omitempty" yaml:"sectors_recursive,omitempty"`
+	} `json:"blkio_stats,omitempty" yaml:"blkio_stats,omitempty"`
+	CPUStats    CPUStats `json:"cpu_stats,omitempty" yaml:"cpu_stats,omitempty"`
+	PreCPUStats CPUStats `json:"precpu_stats,omitempty"`
+}
+
+// NetworkStats is a stats entry for network stats
+type NetworkStats struct {
+	RxDropped uint64 `json:"rx_dropped,omitempty" yaml:"rx_dropped,omitempty"`
+	RxBytes   uint64 `json:"rx_bytes,omitempty" yaml:"rx_bytes,omitempty"`
+	RxErrors  uint64 `json:"rx_errors,omitempty" yaml:"rx_errors,omitempty"`
+	TxPackets uint64 `json:"tx_packets,omitempty" yaml:"tx_packets,omitempty"`
+	TxDropped uint64 `json:"tx_dropped,omitempty" yaml:"tx_dropped,omitempty"`
+	RxPackets uint64 `json:"rx_packets,omitempty" yaml:"rx_packets,omitempty"`
+	TxErrors  uint64 `json:"tx_errors,omitempty" yaml:"tx_errors,omitempty"`
+	TxBytes   uint64 `json:"tx_bytes,omitempty" yaml:"tx_bytes,omitempty"`
+}
+
+// CPUStats is a stats entry for cpu stats
+type CPUStats struct {
+	CPUUsage struct {
+		PercpuUsage       []uint64 `json:"percpu_usage,omitempty" yaml:"percpu_usage,omitempty"`
+		UsageInUsermode   uint64   `json:"usage_in_usermode,omitempty" yaml:"usage_in_usermode,omitempty"`
+		TotalUsage        uint64   `json:"total_usage,omitempty" yaml:"total_usage,omitempty"`
+		UsageInKernelmode uint64   `json:"usage_in_kernelmode,omitempty" yaml:"usage_in_kernelmode,omitempty"`
+	} `json:"cpu_usage,omitempty" yaml:"cpu_usage,omitempty"`
+	SystemCPUUsage uint64 `json:"system_cpu_usage,omitempty" yaml:"system_cpu_usage,omitempty"`
+	ThrottlingData struct {
+		Periods          uint64 `json:"periods,omitempty"`
+		ThrottledPeriods uint64 `json:"throttled_periods,omitempty"`
+		ThrottledTime    uint64 `json:"throttled_time,omitempty"`
+	} `json:"throttling_data,omitempty" yaml:"throttling_data,omitempty"`
+}
+
+// BlkioStatsEntry is a stats entry for blkio_stats
+type BlkioStatsEntry struct {
+	Major uint64 `json:"major,omitempty" yaml:"major,omitempty"`
+	Minor uint64 `json:"minor,omitempty" yaml:"minor,omitempty"`
+	Op    string `json:"op,omitempty" yaml:"op,omitempty"`
+	Value uint64 `json:"value,omitempty" yaml:"value,omitempty"`
+}
+
+// StatsOptions specify parameters to the Stats function.
+//
+// See https://goo.gl/GNmLHb for more details.
+type StatsOptions struct {
+	ID     string
+	Stats  chan<- *Stats
+	Stream bool
+	// A flag that enables stopping the stats operation
+	Done <-chan bool
+	// Initial connection timeout
+	Timeout time.Duration
+	// Timeout with no data is received, it's reset every time new data
+	// arrives
+	InactivityTimeout time.Duration `qs:"-"`
+}
+
+// Stats sends container statistics for the given container to the given channel.
+//
+// This function is blocking, similar to a streaming call for logs, and should be run
+// on a separate goroutine from the caller. Note that this function will block until
+// the given container is removed, not just exited. When finished, this function
+// will close the given channel. Alternatively, function can be stopped by
+// signaling on the Done channel.
+//
+// See https://goo.gl/GNmLHb for more details.
+func (c *Client) Stats(opts StatsOptions) (retErr error) {
+	errC := make(chan error, 1)
+	readCloser, writeCloser := io.Pipe()
+
+	defer func() {
+		close(opts.Stats)
+
+		select {
+		case err := <-errC:
+			if err != nil && retErr == nil {
+				retErr = err
+			}
+		default:
+			// No errors
+		}
+
+		if err := readCloser.Close(); err != nil && retErr == nil {
+			retErr = err
+		}
+	}()
+
+	go func() {
+		err := c.stream("GET", fmt.Sprintf("/containers/%s/stats?stream=%v", opts.ID, opts.Stream), streamOptions{
+			rawJSONStream:     true,
+			useJSONDecoder:    true,
+			stdout:            writeCloser,
+			timeout:           opts.Timeout,
+			inactivityTimeout: opts.InactivityTimeout,
+		})
+		if err != nil {
+			dockerError, ok := err.(*Error)
+			if ok {
+				if dockerError.Status == http.StatusNotFound {
+					err = &NoSuchContainer{ID: opts.ID}
+				}
+			}
+		}
+		if closeErr := writeCloser.Close(); closeErr != nil && err == nil {
+			err = closeErr
+		}
+		errC <- err
+		close(errC)
+	}()
+
+	quit := make(chan struct{})
+	defer close(quit)
+	go func() {
+		// block here waiting for the signal to stop function
+		select {
+		case <-opts.Done:
+			readCloser.Close()
+		case <-quit:
+			return
+		}
+	}()
+
+	decoder := json.NewDecoder(readCloser)
+	stats := new(Stats)
+	for err := decoder.Decode(stats); err != io.EOF; err = decoder.Decode(stats) {
+		if err != nil {
+			return err
+		}
+		opts.Stats <- stats
+		stats = new(Stats)
+	}
+	return nil
+}
+
+// KillContainerOptions represents the set of options that can be used in a
+// call to KillContainer.
+//
+// See https://goo.gl/hkS9i8 for more details.
+type KillContainerOptions struct {
+	// The ID of the container.
+	ID string `qs:"-"`
+
+	// The signal to send to the container. When omitted, Docker server
+	// will assume SIGKILL.
+	Signal Signal
+}
+
+// KillContainer sends a signal to a container, returning an error in case of
+// failure.
+//
+// See https://goo.gl/hkS9i8 for more details.
+func (c *Client) KillContainer(opts KillContainerOptions) error {
+	path := "/containers/" + opts.ID + "/kill" + "?" + queryString(opts)
+	resp, err := c.do("POST", path, doOptions{})
+	if err != nil {
+		if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+			return &NoSuchContainer{ID: opts.ID}
+		}
+		return err
+	}
+	resp.Body.Close()
+	return nil
+}
+
+// RemoveContainerOptions encapsulates options to remove a container.
+//
+// See https://goo.gl/RQyX62 for more details.
+type RemoveContainerOptions struct {
+	// The ID of the container.
+	ID string `qs:"-"`
+
+	// A flag that indicates whether Docker should remove the volumes
+	// associated to the container.
+	RemoveVolumes bool `qs:"v"`
+
+	// A flag that indicates whether Docker should remove the container
+	// even if it is currently running.
+	Force bool
+}
+
+// RemoveContainer removes a container, returning an error in case of failure.
+//
+// See https://goo.gl/RQyX62 for more details.
+func (c *Client) RemoveContainer(opts RemoveContainerOptions) error {
+	path := "/containers/" + opts.ID + "?" + queryString(opts)
+	resp, err := c.do("DELETE", path, doOptions{})
+	if err != nil {
+		if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+			return &NoSuchContainer{ID: opts.ID}
+		}
+		return err
+	}
+	resp.Body.Close()
+	return nil
+}
+
+// UploadToContainerOptions is the set of options that can be used when
+// uploading an archive into a container.
+//
+// See https://goo.gl/Ss97HW for more details.
+type UploadToContainerOptions struct {
+	InputStream          io.Reader `json:"-" qs:"-"`
+	Path                 string    `qs:"path"`
+	NoOverwriteDirNonDir bool      `qs:"noOverwriteDirNonDir"`
+}
+
+// UploadToContainer uploads a tar archive to be extracted to a path in the
+// filesystem of the container.
+//
+// See https://goo.gl/Ss97HW for more details.
+func (c *Client) UploadToContainer(id string, opts UploadToContainerOptions) error {
+	url := fmt.Sprintf("/containers/%s/archive?", id) + queryString(opts)
+
+	return c.stream("PUT", url, streamOptions{
+		in: opts.InputStream,
+	})
+}
+
+// DownloadFromContainerOptions is the set of options that can be used when
+// downloading resources from a container.
+//
+// See https://goo.gl/KnZJDX for more details.
+type DownloadFromContainerOptions struct {
+	OutputStream      io.Writer     `json:"-" qs:"-"`
+	Path              string        `qs:"path"`
+	InactivityTimeout time.Duration `qs:"-"`
+}
+
+// DownloadFromContainer downloads a tar archive of files or folders in a container.
+//
+// See https://goo.gl/KnZJDX for more details.
+func (c *Client) DownloadFromContainer(id string, opts DownloadFromContainerOptions) error {
+	url := fmt.Sprintf("/containers/%s/archive?", id) + queryString(opts)
+
+	return c.stream("GET", url, streamOptions{
+		setRawTerminal:    true,
+		stdout:            opts.OutputStream,
+		inactivityTimeout: opts.InactivityTimeout,
+	})
+}
+
+// CopyFromContainerOptions has been DEPRECATED, please use DownloadFromContainerOptions along with DownloadFromContainer.
+//
+// See https://goo.gl/R2jevW for more details.
+type CopyFromContainerOptions struct {
+	OutputStream io.Writer `json:"-"`
+	Container    string    `json:"-"`
+	Resource     string
+}
+
+// CopyFromContainer has been DEPRECATED, please use DownloadFromContainerOptions along with DownloadFromContainer.
+//
+// See https://goo.gl/R2jevW for more details.
+func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error {
+	if opts.Container == "" {
+		return &NoSuchContainer{ID: opts.Container}
+	}
+	url := fmt.Sprintf("/containers/%s/copy", opts.Container)
+	resp, err := c.do("POST", url, doOptions{data: opts})
+	if err != nil {
+		if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+			return &NoSuchContainer{ID: opts.Container}
+		}
+		return err
+	}
+	defer resp.Body.Close()
+	_, err = io.Copy(opts.OutputStream, resp.Body)
+	return err
+}
+
+// WaitContainer blocks until the given container stops, return the exit code
+// of the container status.
+//
+// See https://goo.gl/Gc1rge for more details.
+func (c *Client) WaitContainer(id string) (int, error) {
+	resp, err := c.do("POST", "/containers/"+id+"/wait", doOptions{})
+	if err != nil {
+		if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+			return 0, &NoSuchContainer{ID: id}
+		}
+		return 0, err
+	}
+	defer resp.Body.Close()
+	var r struct{ StatusCode int }
+	if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
+		return 0, err
+	}
+	return r.StatusCode, nil
+}
+
+// CommitContainerOptions aggregates parameters to the CommitContainer method.
+//
+// See https://goo.gl/mqfoCw for more details.
+type CommitContainerOptions struct {
+	Container  string
+	Repository string `qs:"repo"`
+	Tag        string
+	Message    string `qs:"comment"`
+	Author     string
+	Run        *Config `qs:"-"`
+}
+
+// CommitContainer creates a new image from a container's changes.
+//
+// See https://goo.gl/mqfoCw for more details.
+func (c *Client) CommitContainer(opts CommitContainerOptions) (*Image, error) {
+	path := "/commit?" + queryString(opts)
+	resp, err := c.do("POST", path, doOptions{data: opts.Run})
+	if err != nil {
+		if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+			return nil, &NoSuchContainer{ID: opts.Container}
+		}
+		return nil, err
+	}
+	defer resp.Body.Close()
+	var image Image
+	if err := json.NewDecoder(resp.Body).Decode(&image); err != nil {
+		return nil, err
+	}
+	return &image, nil
+}
+
+// AttachToContainerOptions is the set of options that can be used when
+// attaching to a container.
+//
+// See https://goo.gl/NKpkFk for more details.
+type AttachToContainerOptions struct {
+	Container    string    `qs:"-"`
+	InputStream  io.Reader `qs:"-"`
+	OutputStream io.Writer `qs:"-"`
+	ErrorStream  io.Writer `qs:"-"`
+
+	// Get container logs, sending it to OutputStream.
+	Logs bool
+
+	// Stream the response?
+	Stream bool
+
+	// Attach to stdin, and use InputStream.
+	Stdin bool
+
+	// Attach to stdout, and use OutputStream.
+	Stdout bool
+
+	// Attach to stderr, and use ErrorStream.
+	Stderr bool
+
+	// If set, after a successful connect, a sentinel will be sent and then the
+	// client will block on receive before continuing.
+	//
+	// It must be an unbuffered channel. Using a buffered channel can lead
+	// to unexpected behavior.
+	Success chan struct{}
+
+	// Use raw terminal? Usually true when the container contains a TTY.
+	RawTerminal bool `qs:"-"`
+}
+
+// AttachToContainer attaches to a container, using the given options.
+//
+// See https://goo.gl/NKpkFk for more details.
+func (c *Client) AttachToContainer(opts AttachToContainerOptions) error {
+	cw, err := c.AttachToContainerNonBlocking(opts)
+	if err != nil {
+		return err
+	}
+	return cw.Wait()
+}
+
+// AttachToContainerNonBlocking attaches to a container, using the given options.
+// This function does not block.
+//
+// See https://goo.gl/NKpkFk for more details.
+func (c *Client) AttachToContainerNonBlocking(opts AttachToContainerOptions) (CloseWaiter, error) {
+	if opts.Container == "" {
+		return nil, &NoSuchContainer{ID: opts.Container}
+	}
+	path := "/containers/" + opts.Container + "/attach?" + queryString(opts)
+	return c.hijack("POST", path, hijackOptions{
+		success:        opts.Success,
+		setRawTerminal: opts.RawTerminal,
+		in:             opts.InputStream,
+		stdout:         opts.OutputStream,
+		stderr:         opts.ErrorStream,
+	})
+}
+
+// LogsOptions represents the set of options used when getting logs from a
+// container.
+//
+// See https://goo.gl/yl8PGm for more details.
+type LogsOptions struct {
+	Container         string        `qs:"-"`
+	OutputStream      io.Writer     `qs:"-"`
+	ErrorStream       io.Writer     `qs:"-"`
+	InactivityTimeout time.Duration `qs:"-"`
+	Follow            bool
+	Stdout            bool
+	Stderr            bool
+	Since             int64
+	Timestamps        bool
+	Tail              string
+
+	// Use raw terminal? Usually true when the container contains a TTY.
+	RawTerminal bool `qs:"-"`
+}
+
+// Logs gets stdout and stderr logs from the specified container.
+//
+// See https://goo.gl/yl8PGm for more details.
+func (c *Client) Logs(opts LogsOptions) error {
+	if opts.Container == "" {
+		return &NoSuchContainer{ID: opts.Container}
+	}
+	if opts.Tail == "" {
+		opts.Tail = "all"
+	}
+	path := "/containers/" + opts.Container + "/logs?" + queryString(opts)
+	return c.stream("GET", path, streamOptions{
+		setRawTerminal:    opts.RawTerminal,
+		stdout:            opts.OutputStream,
+		stderr:            opts.ErrorStream,
+		inactivityTimeout: opts.InactivityTimeout,
+	})
+}
+
+// ResizeContainerTTY resizes the terminal to the given height and width.
+//
+// See https://goo.gl/xERhCc for more details.
+func (c *Client) ResizeContainerTTY(id string, height, width int) error {
+	params := make(url.Values)
+	params.Set("h", strconv.Itoa(height))
+	params.Set("w", strconv.Itoa(width))
+	resp, err := c.do("POST", "/containers/"+id+"/resize?"+params.Encode(), doOptions{})
+	if err != nil {
+		return err
+	}
+	resp.Body.Close()
+	return nil
+}
+
+// ExportContainerOptions is the set of parameters to the ExportContainer
+// method.
+//
+// See https://goo.gl/dOkTyk for more details.
+type ExportContainerOptions struct {
+	ID                string
+	OutputStream      io.Writer
+	InactivityTimeout time.Duration `qs:"-"`
+}
+
+// ExportContainer export the contents of container id as tar archive
+// and prints the exported contents to stdout.
+//
+// See https://goo.gl/dOkTyk for more details.
+func (c *Client) ExportContainer(opts ExportContainerOptions) error {
+	if opts.ID == "" {
+		return &NoSuchContainer{ID: opts.ID}
+	}
+	url := fmt.Sprintf("/containers/%s/export", opts.ID)
+	return c.stream("GET", url, streamOptions{
+		setRawTerminal:    true,
+		stdout:            opts.OutputStream,
+		inactivityTimeout: opts.InactivityTimeout,
+	})
+}
+
+// NoSuchContainer is the error returned when a given container does not exist.
+type NoSuchContainer struct {
+	ID  string
+	Err error
+}
+
+func (err *NoSuchContainer) Error() string {
+	if err.Err != nil {
+		return err.Err.Error()
+	}
+	return "No such container: " + err.ID
+}
+
+// ContainerAlreadyRunning is the error returned when a given container is
+// already running.
+type ContainerAlreadyRunning struct {
+	ID string
+}
+
+func (err *ContainerAlreadyRunning) Error() string {
+	return "Container already running: " + err.ID
+}
+
+// ContainerNotRunning is the error returned when a given container is not
+// running.
+type ContainerNotRunning struct {
+	ID string
+}
+
+func (err *ContainerNotRunning) Error() string {
+	return "Container not running: " + err.ID
+}

+ 168 - 0
vendor/github.com/fsouza/go-dockerclient/env.go

@@ -0,0 +1,168 @@
+// Copyright 2014 Docker authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the DOCKER-LICENSE file.
+
+package docker
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"strconv"
+	"strings"
+)
+
+// Env represents a list of key-pair represented in the form KEY=VALUE.
+type Env []string
+
+// Get returns the string value of the given key.
+func (env *Env) Get(key string) (value string) {
+	return env.Map()[key]
+}
+
+// Exists checks whether the given key is defined in the internal Env
+// representation.
+func (env *Env) Exists(key string) bool {
+	_, exists := env.Map()[key]
+	return exists
+}
+
+// GetBool returns a boolean representation of the given key. The key is false
+// whenever its value if 0, no, false, none or an empty string. Any other value
+// will be interpreted as true.
+func (env *Env) GetBool(key string) (value bool) {
+	s := strings.ToLower(strings.Trim(env.Get(key), " \t"))
+	if s == "" || s == "0" || s == "no" || s == "false" || s == "none" {
+		return false
+	}
+	return true
+}
+
+// SetBool defines a boolean value to the given key.
+func (env *Env) SetBool(key string, value bool) {
+	if value {
+		env.Set(key, "1")
+	} else {
+		env.Set(key, "0")
+	}
+}
+
+// GetInt returns the value of the provided key, converted to int.
+//
+// It the value cannot be represented as an integer, it returns -1.
+func (env *Env) GetInt(key string) int {
+	return int(env.GetInt64(key))
+}
+
+// SetInt defines an integer value to the given key.
+func (env *Env) SetInt(key string, value int) {
+	env.Set(key, strconv.Itoa(value))
+}
+
+// GetInt64 returns the value of the provided key, converted to int64.
+//
+// It the value cannot be represented as an integer, it returns -1.
+func (env *Env) GetInt64(key string) int64 {
+	s := strings.Trim(env.Get(key), " \t")
+	val, err := strconv.ParseInt(s, 10, 64)
+	if err != nil {
+		return -1
+	}
+	return val
+}
+
+// SetInt64 defines an integer (64-bit wide) value to the given key.
+func (env *Env) SetInt64(key string, value int64) {
+	env.Set(key, strconv.FormatInt(value, 10))
+}
+
+// GetJSON unmarshals the value of the provided key in the provided iface.
+//
+// iface is a value that can be provided to the json.Unmarshal function.
+func (env *Env) GetJSON(key string, iface interface{}) error {
+	sval := env.Get(key)
+	if sval == "" {
+		return nil
+	}
+	return json.Unmarshal([]byte(sval), iface)
+}
+
+// SetJSON marshals the given value to JSON format and stores it using the
+// provided key.
+func (env *Env) SetJSON(key string, value interface{}) error {
+	sval, err := json.Marshal(value)
+	if err != nil {
+		return err
+	}
+	env.Set(key, string(sval))
+	return nil
+}
+
+// GetList returns a list of strings matching the provided key. It handles the
+// list as a JSON representation of a list of strings.
+//
+// If the given key matches to a single string, it will return a list
+// containing only the value that matches the key.
+func (env *Env) GetList(key string) []string {
+	sval := env.Get(key)
+	if sval == "" {
+		return nil
+	}
+	var l []string
+	if err := json.Unmarshal([]byte(sval), &l); err != nil {
+		l = append(l, sval)
+	}
+	return l
+}
+
+// SetList stores the given list in the provided key, after serializing it to
+// JSON format.
+func (env *Env) SetList(key string, value []string) error {
+	return env.SetJSON(key, value)
+}
+
+// Set defines the value of a key to the given string.
+func (env *Env) Set(key, value string) {
+	*env = append(*env, key+"="+value)
+}
+
+// Decode decodes `src` as a json dictionary, and adds each decoded key-value
+// pair to the environment.
+//
+// If `src` cannot be decoded as a json dictionary, an error is returned.
+func (env *Env) Decode(src io.Reader) error {
+	m := make(map[string]interface{})
+	if err := json.NewDecoder(src).Decode(&m); err != nil {
+		return err
+	}
+	for k, v := range m {
+		env.SetAuto(k, v)
+	}
+	return nil
+}
+
+// SetAuto will try to define the Set* method to call based on the given value.
+func (env *Env) SetAuto(key string, value interface{}) {
+	if fval, ok := value.(float64); ok {
+		env.SetInt64(key, int64(fval))
+	} else if sval, ok := value.(string); ok {
+		env.Set(key, sval)
+	} else if val, err := json.Marshal(value); err == nil {
+		env.Set(key, string(val))
+	} else {
+		env.Set(key, fmt.Sprintf("%v", value))
+	}
+}
+
+// Map returns the map representation of the env.
+func (env *Env) Map() map[string]string {
+	if len(*env) == 0 {
+		return nil
+	}
+	m := make(map[string]string)
+	for _, kv := range *env {
+		parts := strings.SplitN(kv, "=", 2)
+		m[parts[0]] = parts[1]
+	}
+	return m
+}

+ 379 - 0
vendor/github.com/fsouza/go-dockerclient/event.go

@@ -0,0 +1,379 @@
+// Copyright 2015 go-dockerclient authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package docker
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"math"
+	"net"
+	"net/http"
+	"net/http/httputil"
+	"sync"
+	"sync/atomic"
+	"time"
+)
+
+// APIEvents represents events coming from the Docker API
+// The fields in the Docker API changed in API version 1.22, and
+// events for more than images and containers are now fired off.
+// To maintain forward and backward compatibility, go-dockerclient
+// replicates the event in both the new and old format as faithfully as possible.
+//
+// For events that only exist in 1.22 in later, `Status` is filled in as
+// `"Type:Action"` instead of just `Action` to allow for older clients to
+// differentiate and not break if they rely on the pre-1.22 Status types.
+//
+// The transformEvent method can be consulted for more information about how
+// events are translated from new/old API formats
+type APIEvents struct {
+	// New API Fields in 1.22
+	Action string   `json:"action,omitempty"`
+	Type   string   `json:"type,omitempty"`
+	Actor  APIActor `json:"actor,omitempty"`
+
+	// Old API fields for < 1.22
+	Status string `json:"status,omitempty"`
+	ID     string `json:"id,omitempty"`
+	From   string `json:"from,omitempty"`
+
+	// Fields in both
+	Time     int64 `json:"time,omitempty"`
+	TimeNano int64 `json:"timeNano,omitempty"`
+}
+
+// APIActor represents an actor that accomplishes something for an event
+type APIActor struct {
+	ID         string            `json:"id,omitempty"`
+	Attributes map[string]string `json:"attributes,omitempty"`
+}
+
+type eventMonitoringState struct {
+	sync.RWMutex
+	sync.WaitGroup
+	enabled   bool
+	lastSeen  int64
+	C         chan *APIEvents
+	errC      chan error
+	listeners []chan<- *APIEvents
+}
+
+const (
+	maxMonitorConnRetries = 5
+	retryInitialWaitTime  = 10.
+)
+
+var (
+	// ErrNoListeners is the error returned when no listeners are available
+	// to receive an event.
+	ErrNoListeners = errors.New("no listeners present to receive event")
+
+	// ErrListenerAlreadyExists is the error returned when the listerner already
+	// exists.
+	ErrListenerAlreadyExists = errors.New("listener already exists for docker events")
+
+	// EOFEvent is sent when the event listener receives an EOF error.
+	EOFEvent = &APIEvents{
+		Type:   "EOF",
+		Status: "EOF",
+	}
+)
+
+// AddEventListener adds a new listener to container events in the Docker API.
+//
+// The parameter is a channel through which events will be sent.
+func (c *Client) AddEventListener(listener chan<- *APIEvents) error {
+	var err error
+	if !c.eventMonitor.isEnabled() {
+		err = c.eventMonitor.enableEventMonitoring(c)
+		if err != nil {
+			return err
+		}
+	}
+	err = c.eventMonitor.addListener(listener)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// RemoveEventListener removes a listener from the monitor.
+func (c *Client) RemoveEventListener(listener chan *APIEvents) error {
+	err := c.eventMonitor.removeListener(listener)
+	if err != nil {
+		return err
+	}
+	if len(c.eventMonitor.listeners) == 0 {
+		c.eventMonitor.disableEventMonitoring()
+	}
+	return nil
+}
+
+func (eventState *eventMonitoringState) addListener(listener chan<- *APIEvents) error {
+	eventState.Lock()
+	defer eventState.Unlock()
+	if listenerExists(listener, &eventState.listeners) {
+		return ErrListenerAlreadyExists
+	}
+	eventState.Add(1)
+	eventState.listeners = append(eventState.listeners, listener)
+	return nil
+}
+
+func (eventState *eventMonitoringState) removeListener(listener chan<- *APIEvents) error {
+	eventState.Lock()
+	defer eventState.Unlock()
+	if listenerExists(listener, &eventState.listeners) {
+		var newListeners []chan<- *APIEvents
+		for _, l := range eventState.listeners {
+			if l != listener {
+				newListeners = append(newListeners, l)
+			}
+		}
+		eventState.listeners = newListeners
+		eventState.Add(-1)
+	}
+	return nil
+}
+
+func (eventState *eventMonitoringState) closeListeners() {
+	for _, l := range eventState.listeners {
+		close(l)
+		eventState.Add(-1)
+	}
+	eventState.listeners = nil
+}
+
+func listenerExists(a chan<- *APIEvents, list *[]chan<- *APIEvents) bool {
+	for _, b := range *list {
+		if b == a {
+			return true
+		}
+	}
+	return false
+}
+
+func (eventState *eventMonitoringState) enableEventMonitoring(c *Client) error {
+	eventState.Lock()
+	defer eventState.Unlock()
+	if !eventState.enabled {
+		eventState.enabled = true
+		atomic.StoreInt64(&eventState.lastSeen, 0)
+		eventState.C = make(chan *APIEvents, 100)
+		eventState.errC = make(chan error, 1)
+		go eventState.monitorEvents(c)
+	}
+	return nil
+}
+
+func (eventState *eventMonitoringState) disableEventMonitoring() error {
+	eventState.Lock()
+	defer eventState.Unlock()
+
+	eventState.closeListeners()
+
+	eventState.Wait()
+
+	if eventState.enabled {
+		eventState.enabled = false
+		close(eventState.C)
+		close(eventState.errC)
+	}
+	return nil
+}
+
+func (eventState *eventMonitoringState) monitorEvents(c *Client) {
+	var err error
+	for eventState.noListeners() {
+		time.Sleep(10 * time.Millisecond)
+	}
+	if err = eventState.connectWithRetry(c); err != nil {
+		// terminate if connect failed
+		eventState.disableEventMonitoring()
+		return
+	}
+	for eventState.isEnabled() {
+		timeout := time.After(100 * time.Millisecond)
+		select {
+		case ev, ok := <-eventState.C:
+			if !ok {
+				return
+			}
+			if ev == EOFEvent {
+				eventState.disableEventMonitoring()
+				return
+			}
+			eventState.updateLastSeen(ev)
+			go eventState.sendEvent(ev)
+		case err = <-eventState.errC:
+			if err == ErrNoListeners {
+				eventState.disableEventMonitoring()
+				return
+			} else if err != nil {
+				defer func() { go eventState.monitorEvents(c) }()
+				return
+			}
+		case <-timeout:
+			continue
+		}
+	}
+}
+
+func (eventState *eventMonitoringState) connectWithRetry(c *Client) error {
+	var retries int
+	eventState.RLock()
+	eventChan := eventState.C
+	errChan := eventState.errC
+	eventState.RUnlock()
+	err := c.eventHijack(atomic.LoadInt64(&eventState.lastSeen), eventChan, errChan)
+	for ; err != nil && retries < maxMonitorConnRetries; retries++ {
+		waitTime := int64(retryInitialWaitTime * math.Pow(2, float64(retries)))
+		time.Sleep(time.Duration(waitTime) * time.Millisecond)
+		eventState.RLock()
+		eventChan = eventState.C
+		errChan = eventState.errC
+		eventState.RUnlock()
+		err = c.eventHijack(atomic.LoadInt64(&eventState.lastSeen), eventChan, errChan)
+	}
+	return err
+}
+
+func (eventState *eventMonitoringState) noListeners() bool {
+	eventState.RLock()
+	defer eventState.RUnlock()
+	return len(eventState.listeners) == 0
+}
+
+func (eventState *eventMonitoringState) isEnabled() bool {
+	eventState.RLock()
+	defer eventState.RUnlock()
+	return eventState.enabled
+}
+
+func (eventState *eventMonitoringState) sendEvent(event *APIEvents) {
+	eventState.RLock()
+	defer eventState.RUnlock()
+	eventState.Add(1)
+	defer eventState.Done()
+	if eventState.enabled {
+		if len(eventState.listeners) == 0 {
+			eventState.errC <- ErrNoListeners
+			return
+		}
+
+		for _, listener := range eventState.listeners {
+			listener <- event
+		}
+	}
+}
+
+func (eventState *eventMonitoringState) updateLastSeen(e *APIEvents) {
+	eventState.Lock()
+	defer eventState.Unlock()
+	if atomic.LoadInt64(&eventState.lastSeen) < e.Time {
+		atomic.StoreInt64(&eventState.lastSeen, e.Time)
+	}
+}
+
+func (c *Client) eventHijack(startTime int64, eventChan chan *APIEvents, errChan chan error) error {
+	uri := "/events"
+	if startTime != 0 {
+		uri += fmt.Sprintf("?since=%d", startTime)
+	}
+	protocol := c.endpointURL.Scheme
+	address := c.endpointURL.Path
+	if protocol != "unix" {
+		protocol = "tcp"
+		address = c.endpointURL.Host
+	}
+	var dial net.Conn
+	var err error
+	if c.TLSConfig == nil {
+		dial, err = c.Dialer.Dial(protocol, address)
+	} else {
+		dial, err = tlsDialWithDialer(c.Dialer, protocol, address, c.TLSConfig)
+	}
+	if err != nil {
+		return err
+	}
+	conn := httputil.NewClientConn(dial, nil)
+	req, err := http.NewRequest("GET", uri, nil)
+	if err != nil {
+		return err
+	}
+	res, err := conn.Do(req)
+	if err != nil {
+		return err
+	}
+	go func(res *http.Response, conn *httputil.ClientConn) {
+		defer conn.Close()
+		defer res.Body.Close()
+		decoder := json.NewDecoder(res.Body)
+		for {
+			var event APIEvents
+			if err = decoder.Decode(&event); err != nil {
+				if err == io.EOF || err == io.ErrUnexpectedEOF {
+					c.eventMonitor.RLock()
+					if c.eventMonitor.enabled && c.eventMonitor.C == eventChan {
+						// Signal that we're exiting.
+						eventChan <- EOFEvent
+					}
+					c.eventMonitor.RUnlock()
+					break
+				}
+				errChan <- err
+			}
+			if event.Time == 0 {
+				continue
+			}
+			if !c.eventMonitor.isEnabled() || c.eventMonitor.C != eventChan {
+				return
+			}
+			transformEvent(&event)
+			eventChan <- &event
+		}
+	}(res, conn)
+	return nil
+}
+
+// transformEvent takes an event and determines what version it is from
+// then populates both versions of the event
+func transformEvent(event *APIEvents) {
+	// if event version is <= 1.21 there will be no Action and no Type
+	if event.Action == "" && event.Type == "" {
+		event.Action = event.Status
+		event.Actor.ID = event.ID
+		event.Actor.Attributes = map[string]string{}
+		switch event.Status {
+		case "delete", "import", "pull", "push", "tag", "untag":
+			event.Type = "image"
+		default:
+			event.Type = "container"
+			if event.From != "" {
+				event.Actor.Attributes["image"] = event.From
+			}
+		}
+	} else {
+		if event.Status == "" {
+			if event.Type == "image" || event.Type == "container" {
+				event.Status = event.Action
+			} else {
+				// Because just the Status has been overloaded with different Types
+				// if an event is not for an image or a container, we prepend the type
+				// to avoid problems for people relying on actions being only for
+				// images and containers
+				event.Status = event.Type + ":" + event.Action
+			}
+		}
+		if event.ID == "" {
+			event.ID = event.Actor.ID
+		}
+		if event.From == "" {
+			event.From = event.Actor.Attributes["image"]
+		}
+	}
+}

+ 202 - 0
vendor/github.com/fsouza/go-dockerclient/exec.go

@@ -0,0 +1,202 @@
+// Copyright 2015 go-dockerclient authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package docker
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"strconv"
+)
+
+// Exec is the type representing a `docker exec` instance and containing the
+// instance ID
+type Exec struct {
+	ID string `json:"Id,omitempty" yaml:"Id,omitempty"`
+}
+
+// CreateExecOptions specify parameters to the CreateExecContainer function.
+//
+// See https://goo.gl/1KSIb7 for more details
+type CreateExecOptions struct {
+	AttachStdin  bool     `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty"`
+	AttachStdout bool     `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty"`
+	AttachStderr bool     `json:"AttachStderr,omitempty" yaml:"AttachStderr,omitempty"`
+	Tty          bool     `json:"Tty,omitempty" yaml:"Tty,omitempty"`
+	Cmd          []string `json:"Cmd,omitempty" yaml:"Cmd,omitempty"`
+	Container    string   `json:"Container,omitempty" yaml:"Container,omitempty"`
+	User         string   `json:"User,omitempty" yaml:"User,omitempty"`
+}
+
+// CreateExec sets up an exec instance in a running container `id`, returning the exec
+// instance, or an error in case of failure.
+//
+// See https://goo.gl/1KSIb7 for more details
+func (c *Client) CreateExec(opts CreateExecOptions) (*Exec, error) {
+	path := fmt.Sprintf("/containers/%s/exec", opts.Container)
+	resp, err := c.do("POST", path, doOptions{data: opts})
+	if err != nil {
+		if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+			return nil, &NoSuchContainer{ID: opts.Container}
+		}
+		return nil, err
+	}
+	defer resp.Body.Close()
+	var exec Exec
+	if err := json.NewDecoder(resp.Body).Decode(&exec); err != nil {
+		return nil, err
+	}
+
+	return &exec, nil
+}
+
+// StartExecOptions specify parameters to the StartExecContainer function.
+//
+// See https://goo.gl/iQCnto for more details
+type StartExecOptions struct {
+	Detach bool `json:"Detach,omitempty" yaml:"Detach,omitempty"`
+
+	Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty"`
+
+	InputStream  io.Reader `qs:"-"`
+	OutputStream io.Writer `qs:"-"`
+	ErrorStream  io.Writer `qs:"-"`
+
+	// Use raw terminal? Usually true when the container contains a TTY.
+	RawTerminal bool `qs:"-"`
+
+	// If set, after a successful connect, a sentinel will be sent and then the
+	// client will block on receive before continuing.
+	//
+	// It must be an unbuffered channel. Using a buffered channel can lead
+	// to unexpected behavior.
+	Success chan struct{} `json:"-"`
+}
+
+// StartExec starts a previously set up exec instance id. If opts.Detach is
+// true, it returns after starting the exec command. Otherwise, it sets up an
+// interactive session with the exec command.
+//
+// See https://goo.gl/iQCnto for more details
+func (c *Client) StartExec(id string, opts StartExecOptions) error {
+	cw, err := c.StartExecNonBlocking(id, opts)
+	if err != nil {
+		return err
+	}
+	if cw != nil {
+		return cw.Wait()
+	}
+	return nil
+}
+
+// StartExecNonBlocking starts a previously set up exec instance id. If opts.Detach is
+// true, it returns after starting the exec command. Otherwise, it sets up an
+// interactive session with the exec command.
+//
+// See https://goo.gl/iQCnto for more details
+func (c *Client) StartExecNonBlocking(id string, opts StartExecOptions) (CloseWaiter, error) {
+	if id == "" {
+		return nil, &NoSuchExec{ID: id}
+	}
+
+	path := fmt.Sprintf("/exec/%s/start", id)
+
+	if opts.Detach {
+		resp, err := c.do("POST", path, doOptions{data: opts})
+		if err != nil {
+			if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+				return nil, &NoSuchExec{ID: id}
+			}
+			return nil, err
+		}
+		defer resp.Body.Close()
+		return nil, nil
+	}
+
+	return c.hijack("POST", path, hijackOptions{
+		success:        opts.Success,
+		setRawTerminal: opts.RawTerminal,
+		in:             opts.InputStream,
+		stdout:         opts.OutputStream,
+		stderr:         opts.ErrorStream,
+		data:           opts,
+	})
+}
+
+// ResizeExecTTY resizes the tty session used by the exec command id. This API
+// is valid only if Tty was specified as part of creating and starting the exec
+// command.
+//
+// See https://goo.gl/e1JpsA for more details
+func (c *Client) ResizeExecTTY(id string, height, width int) error {
+	params := make(url.Values)
+	params.Set("h", strconv.Itoa(height))
+	params.Set("w", strconv.Itoa(width))
+
+	path := fmt.Sprintf("/exec/%s/resize?%s", id, params.Encode())
+	resp, err := c.do("POST", path, doOptions{})
+	if err != nil {
+		return err
+	}
+	resp.Body.Close()
+	return nil
+}
+
+// ExecProcessConfig is a type describing the command associated to a Exec
+// instance. It's used in the ExecInspect type.
+type ExecProcessConfig struct {
+	Privileged bool     `json:"privileged,omitempty" yaml:"privileged,omitempty"`
+	User       string   `json:"user,omitempty" yaml:"user,omitempty"`
+	Tty        bool     `json:"tty,omitempty" yaml:"tty,omitempty"`
+	EntryPoint string   `json:"entrypoint,omitempty" yaml:"entrypoint,omitempty"`
+	Arguments  []string `json:"arguments,omitempty" yaml:"arguments,omitempty"`
+}
+
+// ExecInspect is a type with details about a exec instance, including the
+// exit code if the command has finished running. It's returned by a api
+// call to /exec/(id)/json
+//
+// See https://goo.gl/gPtX9R for more details
+type ExecInspect struct {
+	ID            string            `json:"ID,omitempty" yaml:"ID,omitempty"`
+	Running       bool              `json:"Running,omitempty" yaml:"Running,omitempty"`
+	ExitCode      int               `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty"`
+	OpenStdin     bool              `json:"OpenStdin,omitempty" yaml:"OpenStdin,omitempty"`
+	OpenStderr    bool              `json:"OpenStderr,omitempty" yaml:"OpenStderr,omitempty"`
+	OpenStdout    bool              `json:"OpenStdout,omitempty" yaml:"OpenStdout,omitempty"`
+	ProcessConfig ExecProcessConfig `json:"ProcessConfig,omitempty" yaml:"ProcessConfig,omitempty"`
+	Container     Container         `json:"Container,omitempty" yaml:"Container,omitempty"`
+}
+
+// InspectExec returns low-level information about the exec command id.
+//
+// See https://goo.gl/gPtX9R for more details
+func (c *Client) InspectExec(id string) (*ExecInspect, error) {
+	path := fmt.Sprintf("/exec/%s/json", id)
+	resp, err := c.do("GET", path, doOptions{})
+	if err != nil {
+		if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+			return nil, &NoSuchExec{ID: id}
+		}
+		return nil, err
+	}
+	defer resp.Body.Close()
+	var exec ExecInspect
+	if err := json.NewDecoder(resp.Body).Decode(&exec); err != nil {
+		return nil, err
+	}
+	return &exec, nil
+}
+
+// NoSuchExec is the error returned when a given exec instance does not exist.
+type NoSuchExec struct {
+	ID string
+}
+
+func (err *NoSuchExec) Error() string {
+	return "No such exec instance: " + err.ID
+}

+ 55 - 0
vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/CHANGELOG.md

@@ -0,0 +1,55 @@
+# 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
+* logrus/hooks/papertrail: move out of main repository
+* logrus/hooks/bugsnag: 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)

+ 21 - 0
vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/LICENSE

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Simon Eskildsen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 365 - 0
vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/README.md

@@ -0,0 +1,365 @@
+# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/>&nbsp;[![Build Status](https://travis-ci.org/Sirupsen/logrus.svg?branch=master)](https://travis-ci.org/Sirupsen/logrus)&nbsp;[![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/polds/logrus-papertrail-hook) | Send errors to the [Papertrail](https://papertrailapp.com) 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/Shopify/logrus-bugsnag/blob/master/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 |
+| [InfluxDB](https://github.com/Abramovic/logrus_influxdb) | Hook for logging to influxdb |
+| [Octokit](https://github.com/dorajistyle/logrus-octokit-hook) | Hook for logging to github via octokit |
+| [DeferPanic](https://github.com/deferpanic/dp-logrus) | Hook for logging to DeferPanic |
+
+#### 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:
+
+* [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout.
+* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
+
+You can define your formatter by implementing the `Formatter` interface,
+requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
+`Fields` type (`map[string]interface{}`) with all your fields as well as the
+default ones (see Entries section above):
+
+```go
+type MyJSONFormatter struct {
+}
+
+log.SetFormatter(new(MyJSONFormatter))
+
+func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) {
+  // Note this doesn't include Time, Level and Message which are available on
+  // the Entry. Consult `godoc` on information about those fields or read the
+  // source of the official loggers.
+  serialized, err := json.Marshal(entry.Data)
+    if err != nil {
+      return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
+    }
+  return append(serialized, '\n'), nil
+}
+```
+
+#### Logger as an `io.Writer`
+
+Logrus can be transformed into an `io.Writer`. That writer is the end of an `io.Pipe` and it is your responsibility to close it.
+
+```go
+w := logger.Writer()
+defer w.Close()
+
+srv := http.Server{
+    // create a stdlib log.Logger that writes to
+    // logrus.Logger.
+    ErrorLog: log.New(w, "", 0),
+}
+```
+
+Each line written to that writer will be printed the usual way, using formatters
+and hooks. The level for those entries is `info`.
+
+#### Rotation
+
+Log rotation is not provided with Logrus. Log rotation should be done by an
+external program (like `logrotate(8)`) that can compress and delete old log
+entries. It should not be a feature of the application-level logger.
+
+#### Tools
+
+| Tool | Description |
+| ---- | ----------- |
+|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will generated with different config at different environment.|
+
+[godoc]: https://godoc.org/github.com/Sirupsen/logrus

+ 26 - 0
vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/doc.go

@@ -0,0 +1,26 @@
+/*
+Package logrus is a structured logger for Go, completely API compatible with the standard library logger.
+
+
+The simplest way to use Logrus is simply the package-level exported logger:
+
+  package main
+
+  import (
+    log "github.com/Sirupsen/logrus"
+  )
+
+  func main() {
+    log.WithFields(log.Fields{
+      "animal": "walrus",
+      "number": 1,
+      "size":   10,
+    }).Info("A walrus appears")
+  }
+
+Output:
+  time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10
+
+For a full guide visit https://github.com/Sirupsen/logrus
+*/
+package logrus

+ 264 - 0
vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/entry.go

@@ -0,0 +1,264 @@
+package logrus
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"os"
+	"time"
+)
+
+// Defines the key when adding errors using WithError.
+var ErrorKey = "error"
+
+// An entry is the final or intermediate Logrus logging entry. It contains all
+// the fields passed with WithField{,s}. It's finally logged when Debug, Info,
+// Warn, Error, Fatal or Panic is called on it. These objects can be reused and
+// passed around as much as you wish to avoid field duplication.
+type Entry struct {
+	Logger *Logger
+
+	// Contains all the fields set by the user.
+	Data Fields
+
+	// Time at which the log entry was created
+	Time time.Time
+
+	// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
+	Level Level
+
+	// Message passed to Debug, Info, Warn, Error, Fatal or Panic
+	Message string
+}
+
+func NewEntry(logger *Logger) *Entry {
+	return &Entry{
+		Logger: logger,
+		// Default is three fields, give a little extra room
+		Data: make(Fields, 5),
+	}
+}
+
+// Returns a reader for the entry, which is a proxy to the formatter.
+func (entry *Entry) Reader() (*bytes.Buffer, error) {
+	serialized, err := entry.Logger.Formatter.Format(entry)
+	return bytes.NewBuffer(serialized), err
+}
+
+// Returns the string representation from the reader and ultimately the
+// formatter.
+func (entry *Entry) String() (string, error) {
+	reader, err := entry.Reader()
+	if err != nil {
+		return "", err
+	}
+
+	return reader.String(), err
+}
+
+// Add an error as single field (using the key defined in ErrorKey) to the Entry.
+func (entry *Entry) WithError(err error) *Entry {
+	return entry.WithField(ErrorKey, err)
+}
+
+// Add a single field to the Entry.
+func (entry *Entry) WithField(key string, value interface{}) *Entry {
+	return entry.WithFields(Fields{key: value})
+}
+
+// Add a map of fields to the Entry.
+func (entry *Entry) WithFields(fields Fields) *Entry {
+	data := Fields{}
+	for k, v := range entry.Data {
+		data[k] = v
+	}
+	for k, v := range fields {
+		data[k] = v
+	}
+	return &Entry{Logger: entry.Logger, Data: data}
+}
+
+// This function is not declared with a pointer value because otherwise
+// race conditions will occur when using multiple goroutines
+func (entry Entry) log(level Level, msg string) {
+	entry.Time = time.Now()
+	entry.Level = level
+	entry.Message = msg
+
+	if err := entry.Logger.Hooks.Fire(level, &entry); err != nil {
+		entry.Logger.mu.Lock()
+		fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
+		entry.Logger.mu.Unlock()
+	}
+
+	reader, err := entry.Reader()
+	if err != nil {
+		entry.Logger.mu.Lock()
+		fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
+		entry.Logger.mu.Unlock()
+	}
+
+	entry.Logger.mu.Lock()
+	defer entry.Logger.mu.Unlock()
+
+	_, err = io.Copy(entry.Logger.Out, reader)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
+	}
+
+	// To avoid Entry#log() returning a value that only would make sense for
+	// panic() to use in Entry#Panic(), we avoid the allocation by checking
+	// directly here.
+	if level <= PanicLevel {
+		panic(&entry)
+	}
+}
+
+func (entry *Entry) Debug(args ...interface{}) {
+	if entry.Logger.Level >= DebugLevel {
+		entry.log(DebugLevel, fmt.Sprint(args...))
+	}
+}
+
+func (entry *Entry) Print(args ...interface{}) {
+	entry.Info(args...)
+}
+
+func (entry *Entry) Info(args ...interface{}) {
+	if entry.Logger.Level >= InfoLevel {
+		entry.log(InfoLevel, fmt.Sprint(args...))
+	}
+}
+
+func (entry *Entry) Warn(args ...interface{}) {
+	if entry.Logger.Level >= WarnLevel {
+		entry.log(WarnLevel, fmt.Sprint(args...))
+	}
+}
+
+func (entry *Entry) Warning(args ...interface{}) {
+	entry.Warn(args...)
+}
+
+func (entry *Entry) Error(args ...interface{}) {
+	if entry.Logger.Level >= ErrorLevel {
+		entry.log(ErrorLevel, fmt.Sprint(args...))
+	}
+}
+
+func (entry *Entry) Fatal(args ...interface{}) {
+	if entry.Logger.Level >= FatalLevel {
+		entry.log(FatalLevel, fmt.Sprint(args...))
+	}
+	os.Exit(1)
+}
+
+func (entry *Entry) Panic(args ...interface{}) {
+	if entry.Logger.Level >= PanicLevel {
+		entry.log(PanicLevel, fmt.Sprint(args...))
+	}
+	panic(fmt.Sprint(args...))
+}
+
+// Entry Printf family functions
+
+func (entry *Entry) Debugf(format string, args ...interface{}) {
+	if entry.Logger.Level >= DebugLevel {
+		entry.Debug(fmt.Sprintf(format, args...))
+	}
+}
+
+func (entry *Entry) Infof(format string, args ...interface{}) {
+	if entry.Logger.Level >= InfoLevel {
+		entry.Info(fmt.Sprintf(format, args...))
+	}
+}
+
+func (entry *Entry) Printf(format string, args ...interface{}) {
+	entry.Infof(format, args...)
+}
+
+func (entry *Entry) Warnf(format string, args ...interface{}) {
+	if entry.Logger.Level >= WarnLevel {
+		entry.Warn(fmt.Sprintf(format, args...))
+	}
+}
+
+func (entry *Entry) Warningf(format string, args ...interface{}) {
+	entry.Warnf(format, args...)
+}
+
+func (entry *Entry) Errorf(format string, args ...interface{}) {
+	if entry.Logger.Level >= ErrorLevel {
+		entry.Error(fmt.Sprintf(format, args...))
+	}
+}
+
+func (entry *Entry) Fatalf(format string, args ...interface{}) {
+	if entry.Logger.Level >= FatalLevel {
+		entry.Fatal(fmt.Sprintf(format, args...))
+	}
+	os.Exit(1)
+}
+
+func (entry *Entry) Panicf(format string, args ...interface{}) {
+	if entry.Logger.Level >= PanicLevel {
+		entry.Panic(fmt.Sprintf(format, args...))
+	}
+}
+
+// Entry Println family functions
+
+func (entry *Entry) Debugln(args ...interface{}) {
+	if entry.Logger.Level >= DebugLevel {
+		entry.Debug(entry.sprintlnn(args...))
+	}
+}
+
+func (entry *Entry) Infoln(args ...interface{}) {
+	if entry.Logger.Level >= InfoLevel {
+		entry.Info(entry.sprintlnn(args...))
+	}
+}
+
+func (entry *Entry) Println(args ...interface{}) {
+	entry.Infoln(args...)
+}
+
+func (entry *Entry) Warnln(args ...interface{}) {
+	if entry.Logger.Level >= WarnLevel {
+		entry.Warn(entry.sprintlnn(args...))
+	}
+}
+
+func (entry *Entry) Warningln(args ...interface{}) {
+	entry.Warnln(args...)
+}
+
+func (entry *Entry) Errorln(args ...interface{}) {
+	if entry.Logger.Level >= ErrorLevel {
+		entry.Error(entry.sprintlnn(args...))
+	}
+}
+
+func (entry *Entry) Fatalln(args ...interface{}) {
+	if entry.Logger.Level >= FatalLevel {
+		entry.Fatal(entry.sprintlnn(args...))
+	}
+	os.Exit(1)
+}
+
+func (entry *Entry) Panicln(args ...interface{}) {
+	if entry.Logger.Level >= PanicLevel {
+		entry.Panic(entry.sprintlnn(args...))
+	}
+}
+
+// Sprintlnn => Sprint no newline. This is to get the behavior of how
+// fmt.Sprintln where spaces are always added between operands, regardless of
+// their type. Instead of vendoring the Sprintln implementation to spare a
+// string allocation, we do the simplest thing.
+func (entry *Entry) sprintlnn(args ...interface{}) string {
+	msg := fmt.Sprintln(args...)
+	return msg[:len(msg)-1]
+}

+ 193 - 0
vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/exported.go

@@ -0,0 +1,193 @@
+package logrus
+
+import (
+	"io"
+)
+
+var (
+	// std is the name of the standard logger in stdlib `log`
+	std = New()
+)
+
+func StandardLogger() *Logger {
+	return std
+}
+
+// SetOutput sets the standard logger output.
+func SetOutput(out io.Writer) {
+	std.mu.Lock()
+	defer std.mu.Unlock()
+	std.Out = out
+}
+
+// SetFormatter sets the standard logger formatter.
+func SetFormatter(formatter Formatter) {
+	std.mu.Lock()
+	defer std.mu.Unlock()
+	std.Formatter = formatter
+}
+
+// SetLevel sets the standard logger level.
+func SetLevel(level Level) {
+	std.mu.Lock()
+	defer std.mu.Unlock()
+	std.Level = level
+}
+
+// GetLevel returns the standard logger level.
+func GetLevel() Level {
+	std.mu.Lock()
+	defer std.mu.Unlock()
+	return std.Level
+}
+
+// AddHook adds a hook to the standard logger hooks.
+func AddHook(hook Hook) {
+	std.mu.Lock()
+	defer std.mu.Unlock()
+	std.Hooks.Add(hook)
+}
+
+// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key.
+func WithError(err error) *Entry {
+	return std.WithField(ErrorKey, err)
+}
+
+// WithField creates an entry from the standard logger and adds a field to
+// it. If you want multiple fields, use `WithFields`.
+//
+// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
+// or Panic on the Entry it returns.
+func WithField(key string, value interface{}) *Entry {
+	return std.WithField(key, value)
+}
+
+// WithFields creates an entry from the standard logger and adds multiple
+// fields to it. This is simply a helper for `WithField`, invoking it
+// once for each field.
+//
+// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
+// or Panic on the Entry it returns.
+func WithFields(fields Fields) *Entry {
+	return std.WithFields(fields)
+}
+
+// Debug logs a message at level Debug on the standard logger.
+func Debug(args ...interface{}) {
+	std.Debug(args...)
+}
+
+// Print logs a message at level Info on the standard logger.
+func Print(args ...interface{}) {
+	std.Print(args...)
+}
+
+// Info logs a message at level Info on the standard logger.
+func Info(args ...interface{}) {
+	std.Info(args...)
+}
+
+// Warn logs a message at level Warn on the standard logger.
+func Warn(args ...interface{}) {
+	std.Warn(args...)
+}
+
+// Warning logs a message at level Warn on the standard logger.
+func Warning(args ...interface{}) {
+	std.Warning(args...)
+}
+
+// Error logs a message at level Error on the standard logger.
+func Error(args ...interface{}) {
+	std.Error(args...)
+}
+
+// Panic logs a message at level Panic on the standard logger.
+func Panic(args ...interface{}) {
+	std.Panic(args...)
+}
+
+// Fatal logs a message at level Fatal on the standard logger.
+func Fatal(args ...interface{}) {
+	std.Fatal(args...)
+}
+
+// Debugf logs a message at level Debug on the standard logger.
+func Debugf(format string, args ...interface{}) {
+	std.Debugf(format, args...)
+}
+
+// Printf logs a message at level Info on the standard logger.
+func Printf(format string, args ...interface{}) {
+	std.Printf(format, args...)
+}
+
+// Infof logs a message at level Info on the standard logger.
+func Infof(format string, args ...interface{}) {
+	std.Infof(format, args...)
+}
+
+// Warnf logs a message at level Warn on the standard logger.
+func Warnf(format string, args ...interface{}) {
+	std.Warnf(format, args...)
+}
+
+// Warningf logs a message at level Warn on the standard logger.
+func Warningf(format string, args ...interface{}) {
+	std.Warningf(format, args...)
+}
+
+// Errorf logs a message at level Error on the standard logger.
+func Errorf(format string, args ...interface{}) {
+	std.Errorf(format, args...)
+}
+
+// Panicf logs a message at level Panic on the standard logger.
+func Panicf(format string, args ...interface{}) {
+	std.Panicf(format, args...)
+}
+
+// Fatalf logs a message at level Fatal on the standard logger.
+func Fatalf(format string, args ...interface{}) {
+	std.Fatalf(format, args...)
+}
+
+// Debugln logs a message at level Debug on the standard logger.
+func Debugln(args ...interface{}) {
+	std.Debugln(args...)
+}
+
+// Println logs a message at level Info on the standard logger.
+func Println(args ...interface{}) {
+	std.Println(args...)
+}
+
+// Infoln logs a message at level Info on the standard logger.
+func Infoln(args ...interface{}) {
+	std.Infoln(args...)
+}
+
+// Warnln logs a message at level Warn on the standard logger.
+func Warnln(args ...interface{}) {
+	std.Warnln(args...)
+}
+
+// Warningln logs a message at level Warn on the standard logger.
+func Warningln(args ...interface{}) {
+	std.Warningln(args...)
+}
+
+// Errorln logs a message at level Error on the standard logger.
+func Errorln(args ...interface{}) {
+	std.Errorln(args...)
+}
+
+// Panicln logs a message at level Panic on the standard logger.
+func Panicln(args ...interface{}) {
+	std.Panicln(args...)
+}
+
+// Fatalln logs a message at level Fatal on the standard logger.
+func Fatalln(args ...interface{}) {
+	std.Fatalln(args...)
+}

+ 48 - 0
vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/formatter.go

@@ -0,0 +1,48 @@
+package logrus
+
+import "time"
+
+const DefaultTimestampFormat = time.RFC3339
+
+// The Formatter interface is used to implement a custom Formatter. It takes an
+// `Entry`. It exposes all the fields, including the default ones:
+//
+// * `entry.Data["msg"]`. The message passed from Info, Warn, Error ..
+// * `entry.Data["time"]`. The timestamp.
+// * `entry.Data["level"]. The level the entry was logged at.
+//
+// Any additional fields added with `WithField` or `WithFields` are also in
+// `entry.Data`. Format is expected to return an array of bytes which are then
+// logged to `logger.Out`.
+type Formatter interface {
+	Format(*Entry) ([]byte, error)
+}
+
+// This is to not silently overwrite `time`, `msg` and `level` fields when
+// dumping it. If this code wasn't there doing:
+//
+//  logrus.WithField("level", 1).Info("hello")
+//
+// Would just silently drop the user provided level. Instead with this code
+// it'll logged as:
+//
+//  {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
+//
+// It's not exported because it's still using Data in an opinionated way. It's to
+// avoid code duplication between the two default formatters.
+func prefixFieldClashes(data Fields) {
+	_, ok := data["time"]
+	if ok {
+		data["fields.time"] = data["time"]
+	}
+
+	_, ok = data["msg"]
+	if ok {
+		data["fields.msg"] = data["msg"]
+	}
+
+	_, ok = data["level"]
+	if ok {
+		data["fields.level"] = data["level"]
+	}
+}

+ 34 - 0
vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/hooks.go

@@ -0,0 +1,34 @@
+package logrus
+
+// A hook to be fired when logging on the logging levels returned from
+// `Levels()` on your implementation of the interface. Note that this is not
+// fired in a goroutine or a channel with workers, you should handle such
+// functionality yourself if your call is non-blocking and you don't wish for
+// the logging calls for levels returned from `Levels()` to block.
+type Hook interface {
+	Levels() []Level
+	Fire(*Entry) error
+}
+
+// Internal type for storing the hooks on a logger instance.
+type LevelHooks map[Level][]Hook
+
+// Add a hook to an instance of logger. This is called with
+// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface.
+func (hooks LevelHooks) Add(hook Hook) {
+	for _, level := range hook.Levels() {
+		hooks[level] = append(hooks[level], hook)
+	}
+}
+
+// Fire all the hooks for the passed level. Used by `entry.log` to fire
+// appropriate hooks for a log entry.
+func (hooks LevelHooks) Fire(level Level, entry *Entry) error {
+	for _, hook := range hooks[level] {
+		if err := hook.Fire(entry); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}

+ 41 - 0
vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/json_formatter.go

@@ -0,0 +1,41 @@
+package logrus
+
+import (
+	"encoding/json"
+	"fmt"
+)
+
+type JSONFormatter struct {
+	// TimestampFormat sets the format used for marshaling timestamps.
+	TimestampFormat string
+}
+
+func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
+	data := make(Fields, len(entry.Data)+3)
+	for k, v := range entry.Data {
+		switch v := v.(type) {
+		case error:
+			// Otherwise errors are ignored by `encoding/json`
+			// https://github.com/Sirupsen/logrus/issues/137
+			data[k] = v.Error()
+		default:
+			data[k] = v
+		}
+	}
+	prefixFieldClashes(data)
+
+	timestampFormat := f.TimestampFormat
+	if timestampFormat == "" {
+		timestampFormat = DefaultTimestampFormat
+	}
+
+	data["time"] = entry.Time.Format(timestampFormat)
+	data["msg"] = entry.Message
+	data["level"] = entry.Level.String()
+
+	serialized, err := json.Marshal(data)
+	if err != nil {
+		return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
+	}
+	return append(serialized, '\n'), nil
+}

+ 212 - 0
vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/logger.go

@@ -0,0 +1,212 @@
+package logrus
+
+import (
+	"io"
+	"os"
+	"sync"
+)
+
+type Logger struct {
+	// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
+	// file, or leave it default which is `os.Stderr`. You can also set this to
+	// something more adventorous, such as logging to Kafka.
+	Out io.Writer
+	// Hooks for the logger instance. These allow firing events based on logging
+	// levels and log entries. For example, to send errors to an error tracking
+	// service, log to StatsD or dump the core on fatal errors.
+	Hooks LevelHooks
+	// All log entries pass through the formatter before logged to Out. The
+	// included formatters are `TextFormatter` and `JSONFormatter` for which
+	// TextFormatter is the default. In development (when a TTY is attached) it
+	// logs with colors, but to a file it wouldn't. You can easily implement your
+	// own that implements the `Formatter` interface, see the `README` or included
+	// formatters for examples.
+	Formatter Formatter
+	// The logging level the logger should log at. This is typically (and defaults
+	// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
+	// logged. `logrus.Debug` is useful in
+	Level Level
+	// Used to sync writing to the log.
+	mu sync.Mutex
+}
+
+// Creates a new logger. Configuration should be set by changing `Formatter`,
+// `Out` and `Hooks` directly on the default logger instance. You can also just
+// instantiate your own:
+//
+//    var log = &Logger{
+//      Out: os.Stderr,
+//      Formatter: new(JSONFormatter),
+//      Hooks: make(LevelHooks),
+//      Level: logrus.DebugLevel,
+//    }
+//
+// It's recommended to make this a global instance called `log`.
+func New() *Logger {
+	return &Logger{
+		Out:       os.Stderr,
+		Formatter: new(TextFormatter),
+		Hooks:     make(LevelHooks),
+		Level:     InfoLevel,
+	}
+}
+
+// Adds a field to the log entry, note that you it doesn't log until you call
+// Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry.
+// If you want multiple fields, use `WithFields`.
+func (logger *Logger) WithField(key string, value interface{}) *Entry {
+	return NewEntry(logger).WithField(key, value)
+}
+
+// Adds a struct of fields to the log entry. All it does is call `WithField` for
+// each `Field`.
+func (logger *Logger) WithFields(fields Fields) *Entry {
+	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...)
+	}
+}
+
+func (logger *Logger) Infof(format string, args ...interface{}) {
+	if logger.Level >= InfoLevel {
+		NewEntry(logger).Infof(format, args...)
+	}
+}
+
+func (logger *Logger) Printf(format string, args ...interface{}) {
+	NewEntry(logger).Printf(format, args...)
+}
+
+func (logger *Logger) Warnf(format string, args ...interface{}) {
+	if logger.Level >= WarnLevel {
+		NewEntry(logger).Warnf(format, args...)
+	}
+}
+
+func (logger *Logger) Warningf(format string, args ...interface{}) {
+	if logger.Level >= WarnLevel {
+		NewEntry(logger).Warnf(format, args...)
+	}
+}
+
+func (logger *Logger) Errorf(format string, args ...interface{}) {
+	if logger.Level >= ErrorLevel {
+		NewEntry(logger).Errorf(format, args...)
+	}
+}
+
+func (logger *Logger) Fatalf(format string, args ...interface{}) {
+	if logger.Level >= FatalLevel {
+		NewEntry(logger).Fatalf(format, args...)
+	}
+	os.Exit(1)
+}
+
+func (logger *Logger) Panicf(format string, args ...interface{}) {
+	if logger.Level >= PanicLevel {
+		NewEntry(logger).Panicf(format, args...)
+	}
+}
+
+func (logger *Logger) Debug(args ...interface{}) {
+	if logger.Level >= DebugLevel {
+		NewEntry(logger).Debug(args...)
+	}
+}
+
+func (logger *Logger) Info(args ...interface{}) {
+	if logger.Level >= InfoLevel {
+		NewEntry(logger).Info(args...)
+	}
+}
+
+func (logger *Logger) Print(args ...interface{}) {
+	NewEntry(logger).Info(args...)
+}
+
+func (logger *Logger) Warn(args ...interface{}) {
+	if logger.Level >= WarnLevel {
+		NewEntry(logger).Warn(args...)
+	}
+}
+
+func (logger *Logger) Warning(args ...interface{}) {
+	if logger.Level >= WarnLevel {
+		NewEntry(logger).Warn(args...)
+	}
+}
+
+func (logger *Logger) Error(args ...interface{}) {
+	if logger.Level >= ErrorLevel {
+		NewEntry(logger).Error(args...)
+	}
+}
+
+func (logger *Logger) Fatal(args ...interface{}) {
+	if logger.Level >= FatalLevel {
+		NewEntry(logger).Fatal(args...)
+	}
+	os.Exit(1)
+}
+
+func (logger *Logger) Panic(args ...interface{}) {
+	if logger.Level >= PanicLevel {
+		NewEntry(logger).Panic(args...)
+	}
+}
+
+func (logger *Logger) Debugln(args ...interface{}) {
+	if logger.Level >= DebugLevel {
+		NewEntry(logger).Debugln(args...)
+	}
+}
+
+func (logger *Logger) Infoln(args ...interface{}) {
+	if logger.Level >= InfoLevel {
+		NewEntry(logger).Infoln(args...)
+	}
+}
+
+func (logger *Logger) Println(args ...interface{}) {
+	NewEntry(logger).Println(args...)
+}
+
+func (logger *Logger) Warnln(args ...interface{}) {
+	if logger.Level >= WarnLevel {
+		NewEntry(logger).Warnln(args...)
+	}
+}
+
+func (logger *Logger) Warningln(args ...interface{}) {
+	if logger.Level >= WarnLevel {
+		NewEntry(logger).Warnln(args...)
+	}
+}
+
+func (logger *Logger) Errorln(args ...interface{}) {
+	if logger.Level >= ErrorLevel {
+		NewEntry(logger).Errorln(args...)
+	}
+}
+
+func (logger *Logger) Fatalln(args ...interface{}) {
+	if logger.Level >= FatalLevel {
+		NewEntry(logger).Fatalln(args...)
+	}
+	os.Exit(1)
+}
+
+func (logger *Logger) Panicln(args ...interface{}) {
+	if logger.Level >= PanicLevel {
+		NewEntry(logger).Panicln(args...)
+	}
+}

+ 98 - 0
vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/logrus.go

@@ -0,0 +1,98 @@
+package logrus
+
+import (
+	"fmt"
+	"log"
+)
+
+// Fields type, used to pass to `WithFields`.
+type Fields map[string]interface{}
+
+// Level type
+type Level uint8
+
+// Convert the Level to a string. E.g. PanicLevel becomes "panic".
+func (level Level) String() string {
+	switch level {
+	case DebugLevel:
+		return "debug"
+	case InfoLevel:
+		return "info"
+	case WarnLevel:
+		return "warning"
+	case ErrorLevel:
+		return "error"
+	case FatalLevel:
+		return "fatal"
+	case PanicLevel:
+		return "panic"
+	}
+
+	return "unknown"
+}
+
+// ParseLevel takes a string level and returns the Logrus log level constant.
+func ParseLevel(lvl string) (Level, error) {
+	switch lvl {
+	case "panic":
+		return PanicLevel, nil
+	case "fatal":
+		return FatalLevel, nil
+	case "error":
+		return ErrorLevel, nil
+	case "warn", "warning":
+		return WarnLevel, nil
+	case "info":
+		return InfoLevel, nil
+	case "debug":
+		return DebugLevel, nil
+	}
+
+	var l Level
+	return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
+}
+
+// These are the different logging levels. You can set the logging level to log
+// on your instance of logger, obtained with `logrus.New()`.
+const (
+	// PanicLevel level, highest level of severity. Logs and then calls panic with the
+	// message passed to Debug, Info, ...
+	PanicLevel Level = iota
+	// FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the
+	// logging level is set to Panic.
+	FatalLevel
+	// ErrorLevel level. Logs. Used for errors that should definitely be noted.
+	// Commonly used for hooks to send errors to an error tracking service.
+	ErrorLevel
+	// WarnLevel level. Non-critical entries that deserve eyes.
+	WarnLevel
+	// InfoLevel level. General operational entries about what's going on inside the
+	// application.
+	InfoLevel
+	// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
+	DebugLevel
+)
+
+// Won't compile if StdLogger can't be realized by a log.Logger
+var (
+	_ StdLogger = &log.Logger{}
+	_ StdLogger = &Entry{}
+	_ StdLogger = &Logger{}
+)
+
+// StdLogger is what your logrus-enabled library should take, that way
+// it'll accept a stdlib logger and a logrus logger. There's no standard
+// interface, this is the closest we get, unfortunately.
+type StdLogger interface {
+	Print(...interface{})
+	Printf(string, ...interface{})
+	Println(...interface{})
+
+	Fatal(...interface{})
+	Fatalf(string, ...interface{})
+	Fatalln(...interface{})
+
+	Panic(...interface{})
+	Panicf(string, ...interface{})
+	Panicln(...interface{})
+}

+ 9 - 0
vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/terminal_bsd.go

@@ -0,0 +1,9 @@
+// +build darwin freebsd openbsd netbsd dragonfly
+
+package logrus
+
+import "syscall"
+
+const ioctlReadTermios = syscall.TIOCGETA
+
+type Termios syscall.Termios

+ 12 - 0
vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/terminal_linux.go

@@ -0,0 +1,12 @@
+// Based on ssh/terminal:
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package logrus
+
+import "syscall"
+
+const ioctlReadTermios = syscall.TCGETS
+
+type Termios syscall.Termios

+ 21 - 0
vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/terminal_notwindows.go

@@ -0,0 +1,21 @@
+// Based on ssh/terminal:
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build linux darwin freebsd openbsd netbsd dragonfly
+
+package logrus
+
+import (
+	"syscall"
+	"unsafe"
+)
+
+// IsTerminal returns true if stderr's file descriptor is a terminal.
+func IsTerminal() bool {
+	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
+}

+ 15 - 0
vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/terminal_solaris.go

@@ -0,0 +1,15 @@
+// +build solaris
+
+package logrus
+
+import (
+	"os"
+
+	"github.com/fsouza/go-dockerclient/external/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
+}

+ 27 - 0
vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/terminal_windows.go

@@ -0,0 +1,27 @@
+// Based on ssh/terminal:
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build windows
+
+package logrus
+
+import (
+	"syscall"
+	"unsafe"
+)
+
+var kernel32 = syscall.NewLazyDLL("kernel32.dll")
+
+var (
+	procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
+)
+
+// IsTerminal returns true if stderr's file descriptor is a terminal.
+func IsTerminal() bool {
+	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
+}

+ 161 - 0
vendor/github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus/text_formatter.go

@@ -0,0 +1,161 @@
+package logrus
+
+import (
+	"bytes"
+	"fmt"
+	"runtime"
+	"sort"
+	"strings"
+	"time"
+)
+
+const (
+	nocolor = 0
+	red     = 31
+	green   = 32
+	yellow  = 33
+	blue    = 34
+	gray    = 37
+)
+
+var (
+	baseTimestamp time.Time
+	isTerminal    bool
+)
+
+func init() {
+	baseTimestamp = time.Now()
+	isTerminal = IsTerminal()
+}
+
+func miniTS() int {
+	return int(time.Since(baseTimestamp) / time.Second)
+}
+
+type TextFormatter struct {
+	// Set to true to bypass checking for a TTY before outputting colors.
+	ForceColors bool
+
+	// Force disabling colors.
+	DisableColors bool
+
+	// Disable timestamp logging. useful when output is redirected to logging
+	// system that already adds timestamps.
+	DisableTimestamp bool
+
+	// Enable logging the full timestamp when a TTY is attached instead of just
+	// the time passed since beginning of execution.
+	FullTimestamp bool
+
+	// TimestampFormat to use for display when a full timestamp is printed
+	TimestampFormat string
+
+	// The fields are sorted by default for a consistent output. For applications
+	// that log extremely frequently and don't use the JSON formatter this may not
+	// be desired.
+	DisableSorting bool
+}
+
+func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
+	var keys []string = make([]string, 0, len(entry.Data))
+	for k := range entry.Data {
+		keys = append(keys, k)
+	}
+
+	if !f.DisableSorting {
+		sort.Strings(keys)
+	}
+
+	b := &bytes.Buffer{}
+
+	prefixFieldClashes(entry.Data)
+
+	isColorTerminal := isTerminal && (runtime.GOOS != "windows")
+	isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors
+
+	timestampFormat := f.TimestampFormat
+	if timestampFormat == "" {
+		timestampFormat = DefaultTimestampFormat
+	}
+	if isColored {
+		f.printColored(b, entry, keys, timestampFormat)
+	} else {
+		if !f.DisableTimestamp {
+			f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat))
+		}
+		f.appendKeyValue(b, "level", entry.Level.String())
+		if entry.Message != "" {
+			f.appendKeyValue(b, "msg", entry.Message)
+		}
+		for _, key := range keys {
+			f.appendKeyValue(b, key, entry.Data[key])
+		}
+	}
+
+	b.WriteByte('\n')
+	return b.Bytes(), nil
+}
+
+func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, timestampFormat string) {
+	var levelColor int
+	switch entry.Level {
+	case DebugLevel:
+		levelColor = gray
+	case WarnLevel:
+		levelColor = yellow
+	case ErrorLevel, FatalLevel, PanicLevel:
+		levelColor = red
+	default:
+		levelColor = blue
+	}
+
+	levelText := strings.ToUpper(entry.Level.String())[0:4]
+
+	if !f.FullTimestamp {
+		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message)
+	} else {
+		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message)
+	}
+	for _, k := range keys {
+		v := entry.Data[k]
+		fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%+v", levelColor, k, v)
+	}
+}
+
+func needsQuoting(text string) bool {
+	for _, ch := range text {
+		if !((ch >= 'a' && ch <= 'z') ||
+			(ch >= 'A' && ch <= 'Z') ||
+			(ch >= '0' && ch <= '9') ||
+			ch == '-' || ch == '.') {
+			return false
+		}
+	}
+	return true
+}
+
+func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
+
+	b.WriteString(key)
+	b.WriteByte('=')
+
+	switch value := value.(type) {
+	case string:
+		if needsQuoting(value) {
+			b.WriteString(value)
+		} else {
+			fmt.Fprintf(b, "%q", value)
+		}
+	case error:
+		errmsg := value.Error()
+		if needsQuoting(errmsg) {
+			b.WriteString(errmsg)
+		} else {
+			fmt.Fprintf(b, "%q", value)
+		}
+	default:
+		fmt.Fprint(b, value)
+	}
+
+	b.WriteByte(' ')
+}

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini