You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by lb...@apache.org on 2018/11/15 15:30:44 UTC

[camel-k] 02/03: Add eventing subscription

This is an automated email from the ASF dual-hosted git repository.

lburgazzoli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel-k.git

commit e01f80be3fc32f25686669038e949b4af6bcca1f
Author: nferraro <ni...@gmail.com>
AuthorDate: Tue Nov 13 16:41:03 2018 +0100

    Add eventing subscription
---
 Gopkg.lock                                         |   79 +-
 deploy/operator-role-knative.yaml                  |   13 +
 deploy/resources.go                                |   13 +
 pkg/trait/knative.go                               |  134 ++-
 pkg/util/knative/types.go                          |   70 ++
 pkg/util/knative/uri.go                            |   43 +
 pkg/util/knative/uri_test.go                       |   35 +
 .../camel/component/knative/KnativeComponent.java  |   19 +-
 .../component/knative/KnativeConfiguration.java    |    9 -
 .../camel/component/knative/KnativeEndpoint.java   |    2 -
 .../component/knative/KnativeEnvironment.java      |    8 +
 vendor/github.com/gobuffalo/envy/LICENSE.txt       |    8 +
 vendor/github.com/gobuffalo/envy/envy.go           |  232 ++++
 vendor/github.com/gobuffalo/envy/version.go        |    3 +
 .../github.com/google/go-cmp/cmp/cmpopts/equate.go |   89 ++
 .../github.com/google/go-cmp/cmp/cmpopts/ignore.go |  145 +++
 .../github.com/google/go-cmp/cmp/cmpopts/sort.go   |  146 +++
 .../google/go-cmp/cmp/cmpopts/sort_go17.go         |   46 +
 .../google/go-cmp/cmp/cmpopts/sort_go18.go         |   31 +
 .../google/go-cmp/cmp/cmpopts/struct_filter.go     |  182 +++
 vendor/github.com/joho/godotenv/LICENCE            |   23 +
 vendor/github.com/joho/godotenv/godotenv.go        |  346 ++++++
 vendor/github.com/knative/eventing/AUTHORS         |    8 +
 vendor/github.com/knative/eventing/LICENSE         |  201 ++++
 .../knative/eventing/cmd/controller/kodata/HEAD    |    1 +
 .../knative/eventing/cmd/controller/kodata/LICENSE |    1 +
 .../eventing/cmd/controller/kodata/VENDOR-LICENSE  |    1 +
 .../knative/eventing/cmd/fanoutsidecar/kodata/HEAD |    1 +
 .../eventing/cmd/fanoutsidecar/kodata/LICENSE      |    1 +
 .../cmd/fanoutsidecar/kodata/VENDOR-LICENSE        |    1 +
 .../knative/eventing/cmd/sendevent/kodata/HEAD     |    1 +
 .../knative/eventing/cmd/sendevent/kodata/LICENSE  |    1 +
 .../eventing/cmd/sendevent/kodata/VENDOR-LICENSE   |    1 +
 .../knative/eventing/cmd/webhook/kodata/HEAD       |    1 +
 .../knative/eventing/cmd/webhook/kodata/LICENSE    |    1 +
 .../eventing/cmd/webhook/kodata/VENDOR-LICENSE     |    1 +
 .../knative/eventing/pkg/apis/duck/v1alpha1/doc.go |   21 +
 .../pkg/apis/duck/v1alpha1/subscribable_types.go   |  111 ++
 .../apis/duck/v1alpha1/zz_generated.deepcopy.go    |  151 +++
 .../knative/eventing/pkg/apis/eventing/register.go |   21 +
 .../pkg/apis/eventing/v1alpha1/channel_defaults.go |   26 +
 .../pkg/apis/eventing/v1alpha1/channel_types.go    |  163 +++
 .../apis/eventing/v1alpha1/channel_validation.go   |   66 ++
 .../cluster_channel_provisioner_defaults.go        |   27 +
 .../v1alpha1/cluster_channel_provisioner_types.go  |  122 ++
 .../cluster_channel_provisioner_validation.go      |   33 +
 .../eventing/pkg/apis/eventing/v1alpha1/doc.go     |   17 +
 .../pkg/apis/eventing/v1alpha1/register.go         |   57 +
 .../subscribable_channelable_validation.go         |  101 ++
 .../eventing/v1alpha1/subscription_defaults.go     |   25 +
 .../apis/eventing/v1alpha1/subscription_types.go   |  231 ++++
 .../eventing/v1alpha1/subscription_validation.go   |  129 +++
 .../eventing/v1alpha1/zz_generated.deepcopy.go     |  451 ++++++++
 .../eventing/inmemory/controller/kodata/LICENSE    |    1 +
 .../inmemory/controller/kodata/VENDOR-LICENSE      |    1 +
 .../eventing/test/e2e/k8sevents/kodata/LICENSE     |    1 +
 .../test/e2e/k8sevents/kodata/VENDOR-LICENSE       |    1 +
 vendor/github.com/knative/pkg/logging/config.go    |  150 +++
 vendor/github.com/knative/pkg/logging/logger.go    |   57 +
 .../knative/pkg/logging/logkey/constants.go        |   58 +
 .../knative/pkg/logging/zz_generated.deepcopy.go   |   48 +
 vendor/github.com/knative/pkg/webhook/certs.go     |  166 +++
 vendor/github.com/knative/pkg/webhook/webhook.go   |  668 +++++++++++
 vendor/github.com/markbates/inflect/LICENCE        |    7 +
 vendor/github.com/markbates/inflect/helpers.go     |   19 +
 vendor/github.com/markbates/inflect/inflect.go     |  892 +++++++++++++++
 vendor/github.com/markbates/inflect/name.go        |  163 +++
 vendor/github.com/markbates/inflect/version.go     |    3 +
 vendor/go.uber.org/atomic/LICENSE.txt              |   19 +
 vendor/go.uber.org/atomic/atomic.go                |  351 ++++++
 vendor/go.uber.org/atomic/string.go                |   49 +
 vendor/go.uber.org/multierr/LICENSE.txt            |   19 +
 vendor/go.uber.org/multierr/error.go               |  401 +++++++
 vendor/go.uber.org/zap/LICENSE.txt                 |   19 +
 vendor/go.uber.org/zap/array.go                    |  320 ++++++
 vendor/go.uber.org/zap/buffer/buffer.go            |  115 ++
 vendor/go.uber.org/zap/buffer/pool.go              |   49 +
 vendor/go.uber.org/zap/config.go                   |  243 ++++
 vendor/go.uber.org/zap/doc.go                      |  113 ++
 vendor/go.uber.org/zap/encoder.go                  |   75 ++
 vendor/go.uber.org/zap/error.go                    |   80 ++
 vendor/go.uber.org/zap/field.go                    |  310 +++++
 vendor/go.uber.org/zap/flag.go                     |   39 +
 vendor/go.uber.org/zap/global.go                   |  169 +++
 vendor/go.uber.org/zap/http_handler.go             |   81 ++
 .../zap/internal/bufferpool/bufferpool.go          |   31 +
 vendor/go.uber.org/zap/internal/color/color.go     |   44 +
 vendor/go.uber.org/zap/internal/exit/exit.go       |   64 ++
 vendor/go.uber.org/zap/level.go                    |  132 +++
 vendor/go.uber.org/zap/logger.go                   |  305 +++++
 vendor/go.uber.org/zap/options.go                  |  109 ++
 vendor/go.uber.org/zap/sink.go                     |  161 +++
 vendor/go.uber.org/zap/stacktrace.go               |  126 ++
 vendor/go.uber.org/zap/sugar.go                    |  304 +++++
 vendor/go.uber.org/zap/time.go                     |   27 +
 vendor/go.uber.org/zap/writer.go                   |   99 ++
 vendor/go.uber.org/zap/zapcore/console_encoder.go  |  147 +++
 vendor/go.uber.org/zap/zapcore/core.go             |  113 ++
 vendor/go.uber.org/zap/zapcore/doc.go              |   24 +
 vendor/go.uber.org/zap/zapcore/encoder.go          |  348 ++++++
 vendor/go.uber.org/zap/zapcore/entry.go            |  257 +++++
 vendor/go.uber.org/zap/zapcore/error.go            |  120 ++
 vendor/go.uber.org/zap/zapcore/field.go            |  201 ++++
 vendor/go.uber.org/zap/zapcore/hook.go             |   68 ++
 vendor/go.uber.org/zap/zapcore/json_encoder.go     |  502 ++++++++
 vendor/go.uber.org/zap/zapcore/level.go            |  175 +++
 vendor/go.uber.org/zap/zapcore/level_strings.go    |   46 +
 vendor/go.uber.org/zap/zapcore/marshaler.go        |   53 +
 vendor/go.uber.org/zap/zapcore/memory_encoder.go   |  179 +++
 vendor/go.uber.org/zap/zapcore/sampler.go          |  134 +++
 vendor/go.uber.org/zap/zapcore/tee.go              |   81 ++
 vendor/go.uber.org/zap/zapcore/write_syncer.go     |  123 ++
 vendor/k8s.io/api/admission/v1beta1/doc.go         |   21 +
 .../k8s.io/api/admission/v1beta1/generated.pb.go   | 1208 ++++++++++++++++++++
 vendor/k8s.io/api/admission/v1beta1/register.go    |   51 +
 vendor/k8s.io/api/admission/v1beta1/types.go       |  116 ++
 .../v1beta1/types_swagger_doc_generated.go         |   71 ++
 .../api/admission/v1beta1/zz_generated.deepcopy.go |  129 +++
 118 files changed, 13876 insertions(+), 29 deletions(-)

diff --git a/Gopkg.lock b/Gopkg.lock
index 37c62b0..6e9a08d 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -109,6 +109,14 @@
   version = "v0.17.2"
 
 [[projects]]
+  digest = "1:ea634ab47d5c1f363bac9f8e0ff0ff0a29bcf291c5e470bd7489f05f099f9c56"
+  name = "github.com/gobuffalo/envy"
+  packages = ["."]
+  pruneopts = "NUT"
+  revision = "910ef88c9d32c6e779231577dfcf6ed8959bea2f"
+  version = "v1.6.8"
+
+[[projects]]
   digest = "1:8679b8a64f3613e9749c5640c3535c83399b8e69f67ce54d91dc73f6d77373af"
   name = "github.com/gogo/protobuf"
   packages = [
@@ -150,10 +158,11 @@
   revision = "4030bb1f1f0c35b30ca7009e9ebd06849dd45306"
 
 [[projects]]
-  digest = "1:2e3c336fc7fde5c984d2841455a658a6d626450b1754a854b3b32e7a8f49a07a"
+  digest = "1:d2754cafcab0d22c13541618a8029a70a8959eb3525ff201fe971637e2274cd0"
   name = "github.com/google/go-cmp"
   packages = [
     "cmp",
+    "cmp/cmpopts",
     "cmp/internal/diff",
     "cmp/internal/function",
     "cmp/internal/value",
@@ -221,6 +230,14 @@
   version = "v1.0"
 
 [[projects]]
+  digest = "1:da62aa6632d04e080b8a8b85a59ed9ed1550842a0099a55f3ae3a20d02a3745a"
+  name = "github.com/joho/godotenv"
+  packages = ["."]
+  pruneopts = "NUT"
+  revision = "23d116af351c84513e1946b527c88823e476be13"
+  version = "v1.3.0"
+
+[[projects]]
   digest = "1:8e36686e8b139f8fe240c1d5cf3a145bc675c22ff8e707857cdd3ae17b00d728"
   name = "github.com/json-iterator/go"
   packages = ["."]
@@ -241,13 +258,28 @@
 
 [[projects]]
   branch = "master"
-  digest = "1:96faa335e445f205f2e3ce5a1077b4b9351047d7f0d08447ed4fc97c6470ef74"
+  digest = "1:66120bf2cc6bd35213b3bb29513b3ce5e28b12ef647de2dbd3623ebaea9fe741"
+  name = "github.com/knative/eventing"
+  packages = [
+    "pkg/apis/duck/v1alpha1",
+    "pkg/apis/eventing",
+    "pkg/apis/eventing/v1alpha1",
+  ]
+  pruneopts = "NUT"
+  revision = "8fcf54f7147d51b0825fc1d14befd88e1355fec5"
+
+[[projects]]
+  branch = "master"
+  digest = "1:d9cf052ed0cbd1fd616fc1c0a88877afb48ea58d7ca9aa13c0d308534c44d3ab"
   name = "github.com/knative/pkg"
   packages = [
     "apis",
     "apis/duck",
     "apis/duck/v1alpha1",
     "kmeta",
+    "logging",
+    "logging/logkey",
+    "webhook",
   ]
   pruneopts = "NUT"
   revision = "af2c4bc84ed90694967a799bde0f6a29cb713d4c"
@@ -287,6 +319,14 @@
   revision = "60711f1a8329503b04e1c88535f419d0bb440bff"
 
 [[projects]]
+  digest = "1:56dbf15e091bf7926cb33a57cb6bdfc658fc6d3498d2f76f10a97ce7856f1fde"
+  name = "github.com/markbates/inflect"
+  packages = ["."]
+  pruneopts = "NUT"
+  revision = "24b83195037b3bc61fcda2d28b7b0518bce293b6"
+  version = "v1.0.4"
+
+[[projects]]
   branch = "master"
   digest = "1:0e9bfc47ab9941ecc3344e580baca5deb4091177e84dd9773b48b38ec26b93d5"
   name = "github.com/mattbaird/jsonpatch"
@@ -493,6 +533,37 @@
   version = "v1.2.2"
 
 [[projects]]
+  digest = "1:22f696cee54865fb8e9ff91df7b633f6b8f22037a8015253c6b6a71ca82219c7"
+  name = "go.uber.org/atomic"
+  packages = ["."]
+  pruneopts = "NUT"
+  revision = "1ea20fb1cbb1cc08cbd0d913a96dead89aa18289"
+  version = "v1.3.2"
+
+[[projects]]
+  digest = "1:58ca93bdf81bac106ded02226b5395a0595d5346cdc4caa8d9c1f3a5f8f9976e"
+  name = "go.uber.org/multierr"
+  packages = ["."]
+  pruneopts = "NUT"
+  revision = "3c4937480c32f4c13a875a1829af76c98ca3d40a"
+  version = "v1.1.0"
+
+[[projects]]
+  digest = "1:85674ac609b704fd4e9f463553b6ffc3a3527a993ae0ba550eb56beaabdfe094"
+  name = "go.uber.org/zap"
+  packages = [
+    ".",
+    "buffer",
+    "internal/bufferpool",
+    "internal/color",
+    "internal/exit",
+    "zapcore",
+  ]
+  pruneopts = "NUT"
+  revision = "ff33455a0e382e8a81d14dd7c922020b6b5e7982"
+  version = "v1.9.1"
+
+[[projects]]
   branch = "master"
   digest = "1:3f3a05ae0b95893d90b9b3b5afdb79a9b3d96e4e36e099d841ae602e4aca0da8"
   name = "golang.org/x/crypto"
@@ -621,9 +692,10 @@
   version = "v2.2.1"
 
 [[projects]]
-  digest = "1:ef716a2116d8a040e16fbcd7fca71d3354915a94720de6af22c7a09970234296"
+  digest = "1:ba83efacee6fc544aaebcc643ec8ad8d345bfa7f6cd5afb1492fb19cd994cc4a"
   name = "k8s.io/api"
   packages = [
+    "admission/v1beta1",
     "admissionregistration/v1alpha1",
     "admissionregistration/v1beta1",
     "apps/v1",
@@ -847,6 +919,7 @@
   analyzer-version = 1
   input-imports = [
     "github.com/fatih/structs",
+    "github.com/knative/eventing/pkg/apis/eventing/v1alpha1",
     "github.com/knative/serving/pkg/apis/serving/v1alpha1",
     "github.com/mitchellh/mapstructure",
     "github.com/openshift/api/apps/v1",
diff --git a/deploy/operator-role-knative.yaml b/deploy/operator-role-knative.yaml
index e362e7a..9785245 100644
--- a/deploy/operator-role-knative.yaml
+++ b/deploy/operator-role-knative.yaml
@@ -18,3 +18,16 @@ rules:
   - patch
   - update
   - watch
+- apiGroups:
+  - eventing.knative.dev
+  resources:
+  - "*"
+  verbs:
+  - create
+  - delete
+  - deletecollection
+  - get
+  - list
+  - patch
+  - update
+  - watch
diff --git a/deploy/resources.go b/deploy/resources.go
index 09a831e..58bfc75 100644
--- a/deploy/resources.go
+++ b/deploy/resources.go
@@ -2345,6 +2345,19 @@ rules:
   - patch
   - update
   - watch
+- apiGroups:
+  - eventing.knative.dev
+  resources:
+  - "*"
+  verbs:
+  - create
+  - delete
+  - deletecollection
+  - get
+  - list
+  - patch
+  - update
+  - watch
 
 `
 	Resources["operator-role-kubernetes.yaml"] =
diff --git a/pkg/trait/knative.go b/pkg/trait/knative.go
index f769bd5..ac5a7d8 100644
--- a/pkg/trait/knative.go
+++ b/pkg/trait/knative.go
@@ -18,14 +18,21 @@ limitations under the License.
 package trait
 
 import (
+	"encoding/json"
+	"github.com/apache/camel-k/pkg/metadata"
+	knativeutil "github.com/apache/camel-k/pkg/util/knative"
 	"github.com/apache/camel-k/pkg/util/kubernetes"
-	knative "github.com/knative/serving/pkg/apis/serving/v1alpha1"
+	eventing "github.com/knative/eventing/pkg/apis/eventing/v1alpha1"
+	serving "github.com/knative/serving/pkg/apis/serving/v1alpha1"
+	"github.com/sirupsen/logrus"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"strings"
 )
 
 type knativeTrait struct {
-	BaseTrait `property:",squash"`
+	BaseTrait     `property:",squash"`
+	Subscriptions string `property:"subscriptions"`
 }
 
 func newKnativeTrait() *knativeTrait {
@@ -34,12 +41,23 @@ func newKnativeTrait() *knativeTrait {
 	}
 }
 
+func (t *knativeTrait) autoconfigure(environment *environment, resources *kubernetes.Collection) error {
+	if t.Subscriptions == "" {
+		channels := t.getSourceChannels(environment)
+		t.Subscriptions = strings.Join(channels, ",")
+	}
+	return nil
+}
+
 func (t *knativeTrait) beforeDeploy(environment *environment, resources *kubernetes.Collection) error {
 	resources.Add(t.getServiceFor(environment))
+	for _, sub := range t.getSubscriptionsFor(environment) {
+		resources.Add(sub)
+	}
 	return nil
 }
 
-func (*knativeTrait) getServiceFor(e *environment) *knative.Service {
+func (t *knativeTrait) getServiceFor(e *environment) *serving.Service {
 	// combine properties of integration with context, integration
 	// properties have the priority
 	properties := CombineConfigurationAsMap("property", e.Context, e.Integration)
@@ -64,14 +82,17 @@ func (*knativeTrait) getServiceFor(e *environment) *knative.Service {
 	// optimizations
 	environment["AB_JOLOKIA_OFF"] = "true"
 
+	// Knative integration
+	environment["CAMEL_KNATIVE_CONFIGURATION"] = t.getConfigurationSerialized(e)
+
 	labels := map[string]string{
 		"camel.apache.org/integration": e.Integration.Name,
 	}
 
-	svc := knative.Service{
+	svc := serving.Service{
 		TypeMeta: metav1.TypeMeta{
 			Kind:       "Service",
-			APIVersion: knative.SchemeGroupVersion.String(),
+			APIVersion: serving.SchemeGroupVersion.String(),
 		},
 		ObjectMeta: metav1.ObjectMeta{
 			Name:        e.Integration.Name,
@@ -79,11 +100,11 @@ func (*knativeTrait) getServiceFor(e *environment) *knative.Service {
 			Labels:      labels,
 			Annotations: e.Integration.Annotations,
 		},
-		Spec: knative.ServiceSpec{
-			RunLatest: &knative.RunLatestType{
-				Configuration: knative.ConfigurationSpec{
-					RevisionTemplate: knative.RevisionTemplateSpec{
-						Spec: knative.RevisionSpec{
+		Spec: serving.ServiceSpec{
+			RunLatest: &serving.RunLatestType{
+				Configuration: serving.ConfigurationSpec{
+					RevisionTemplate: serving.RevisionTemplateSpec{
+						Spec: serving.RevisionSpec{
 							Container: corev1.Container{
 								Image: e.Integration.Status.Image,
 								Env:   EnvironmentAsEnvVarSlice(environment),
@@ -97,3 +118,96 @@ func (*knativeTrait) getServiceFor(e *environment) *knative.Service {
 
 	return &svc
 }
+
+func (t *knativeTrait) getSubscriptionsFor(e *environment) []*eventing.Subscription {
+	channels := t.getConfiguredSourceChannels()
+	subs := make([]*eventing.Subscription, 0)
+	for _, ch := range channels {
+		subs = append(subs, t.getSubscriptionFor(e, ch))
+	}
+	return subs
+}
+
+func (*knativeTrait) getSubscriptionFor(e *environment, channel string) *eventing.Subscription {
+	return &eventing.Subscription{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: eventing.SchemeGroupVersion.String(),
+			Kind:       "Subscription",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: e.Integration.Namespace,
+			Name:      channel + "-" + e.Integration.Name,
+		},
+		Spec: eventing.SubscriptionSpec{
+			Channel: corev1.ObjectReference{
+				APIVersion: eventing.SchemeGroupVersion.String(),
+				Kind:       "Channel",
+				Name:       channel,
+			},
+			Subscriber: &eventing.SubscriberSpec{
+				Ref: &corev1.ObjectReference{
+					APIVersion: serving.SchemeGroupVersion.String(),
+					Kind:       "Service",
+					Name:       e.Integration.Name,
+				},
+			},
+		},
+	}
+}
+
+func (t *knativeTrait) getConfigurationSerialized(e *environment) string {
+	env := t.getConfiguration(e)
+	res, err := json.Marshal(env)
+	if err != nil {
+		logrus.Warning("Unable to serialize Knative configuration", err)
+		return ""
+	}
+	return string(res)
+}
+
+func (t *knativeTrait) getConfiguration(e *environment) knativeutil.CamelKnativeEnvironment {
+	sourceChannels := t.getConfiguredSourceChannels()
+	env := knativeutil.NewCamelKnativeEnvironment()
+	for _, ch := range sourceChannels {
+		svc := knativeutil.CamelKnativeServiceDefinition{
+			Name:        ch,
+			Host:        "0.0.0.0",
+			Port:        8080,
+			Protocol:    knativeutil.CamelKnativeProtocolHTTP,
+			ServiceType: knativeutil.CamelKnativeServiceTypeChannel,
+			Metadata: map[string]string{
+				knativeutil.CamelKnativeMetaServicePath: "/",
+			},
+		}
+		env.Services = append(env.Services, svc)
+	}
+	// Adding default endpoint
+	defSvc := knativeutil.CamelKnativeServiceDefinition{
+		Name:        "default",
+		Host:        "0.0.0.0",
+		Port:        8080,
+		Protocol:    knativeutil.CamelKnativeProtocolHTTP,
+		ServiceType: knativeutil.CamelKnativeServiceTypeEndpoint,
+		Metadata: map[string]string{
+			knativeutil.CamelKnativeMetaServicePath: "/",
+		},
+	}
+	env.Services = append(env.Services, defSvc)
+	return env
+}
+
+func (t *knativeTrait) getConfiguredSourceChannels() []string {
+	channels := make([]string, 0)
+	for _, ch := range strings.Split(t.Subscriptions, ",") {
+		cht := strings.Trim(ch, " \t\"")
+		if cht != "" {
+			channels = append(channels, cht)
+		}
+	}
+	return channels
+}
+
+func (*knativeTrait) getSourceChannels(e *environment) []string {
+	meta := metadata.Extract(e.Integration.Spec.Source)
+	return knativeutil.ExtractChannelNames(meta.FromURIs)
+}
diff --git a/pkg/util/knative/types.go b/pkg/util/knative/types.go
new file mode 100644
index 0000000..b36b4fc
--- /dev/null
+++ b/pkg/util/knative/types.go
@@ -0,0 +1,70 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package knative
+
+// CamelKnativeEnvironment is the top level configuration object expected by the Camel Knative component
+type CamelKnativeEnvironment struct {
+	Services []CamelKnativeServiceDefinition `json:"services"`
+}
+
+// NewCamelKnativeEnvironment creates a new env
+func NewCamelKnativeEnvironment() CamelKnativeEnvironment {
+	return CamelKnativeEnvironment{
+		Services: make([]CamelKnativeServiceDefinition, 0),
+	}
+}
+
+// CamelKnativeServiceDefinition defines the parameters to connect to Knative service. It's also used for exposed services
+type CamelKnativeServiceDefinition struct {
+	ServiceType CamelKnativeServiceType `json:"type"`
+	Protocol    CamelKnativeProtocol    `json:"protocol"`
+	Name        string                  `json:"name"`
+	Host        string                  `json:"host"`
+	Port        int                     `json:"port"`
+	Metadata    map[string]string       `json:"metadata"`
+}
+
+// CamelKnativeServiceType --
+type CamelKnativeServiceType string
+
+const (
+	// CamelKnativeServiceTypeEndpoint is a callable endpoint
+	CamelKnativeServiceTypeEndpoint CamelKnativeServiceType = "endpoint"
+	// CamelKnativeServiceTypeChannel is a callable endpoint that will be also associated to a subscription
+	CamelKnativeServiceTypeChannel CamelKnativeServiceType = "channel"
+)
+
+// CamelKnativeProtocol is the communication protocol to use for the service
+type CamelKnativeProtocol string
+
+// Knative protocols
+const (
+	CamelKnativeProtocolHTTP  CamelKnativeProtocol = "http"
+	CamelKnativeProtocolHTTPS CamelKnativeProtocol = "https"
+)
+
+// Meta Options
+const (
+	CamelKnativeMetaServicePath     = "service.path"
+	CamelKnativeMetaServiceID       = "service.id"
+	CamelKnativeMetaServiceName     = "service.name"
+	CamelKnativeMetaServiceHost     = "service.host"
+	CamelKnativeMetaServicePort     = "service.port"
+	CamelKnativeMetaServiceZone     = "service.zone"
+	CamelKnativeMetaServiceProtocol = "service.protocol"
+)
diff --git a/pkg/util/knative/uri.go b/pkg/util/knative/uri.go
new file mode 100644
index 0000000..f263454
--- /dev/null
+++ b/pkg/util/knative/uri.go
@@ -0,0 +1,43 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package knative
+
+import "regexp"
+
+var channelRegexp = regexp.MustCompile("^knative:[/]*channel/([a-z0-9.-]+)(?:[/?].*|$)")
+
+// ExtractChannelNames extracts all Knative named channels from the given URIs
+func ExtractChannelNames(uris []string) []string {
+	channels := make([]string, 0)
+	for _, uri := range uris {
+		channel := ExtractChannelName(uri)
+		if channel != "" {
+			channels = append(channels, channel)
+		}
+	}
+	return channels
+}
+
+// ExtractChannelName returns a channel name from the Knative URI if present
+func ExtractChannelName(uri string) string {
+	match := channelRegexp.FindStringSubmatch(uri)
+	if len(match) == 2 {
+		return match[1]
+	}
+	return ""
+}
\ No newline at end of file
diff --git a/pkg/util/knative/uri_test.go b/pkg/util/knative/uri_test.go
new file mode 100644
index 0000000..8bc35b5
--- /dev/null
+++ b/pkg/util/knative/uri_test.go
@@ -0,0 +1,35 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package knative
+
+import (
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestChannelUri(t *testing.T) {
+	assert.Equal(t, "pippo", ExtractChannelName("knative:channel/pippo"))
+	assert.Equal(t, "pippo1", ExtractChannelName("knative://channel/pippo1"))
+	assert.Equal(t, "pippo-2", ExtractChannelName("knative://channel/pippo-2?pluto=12"))
+	assert.Equal(t, "pip-p-o", ExtractChannelName("knative:/channel/pip-p-o?pluto=12"))
+	assert.Equal(t, "pip.po", ExtractChannelName("knative:channel/pip.po?pluto=12"))
+	assert.Equal(t, "pip.po-1", ExtractChannelName("knative:channel/pip.po-1/hello"))
+	assert.Empty(t, ExtractChannelName("http://wikipedia.org"))
+	assert.Empty(t, ExtractChannelName("a:knative:channel/chan"))
+	assert.Empty(t, ExtractChannelName("knative:channel/pippa$"))
+}
diff --git a/runtime/camel-knative/src/main/java/org/apache/camel/component/knative/KnativeComponent.java b/runtime/camel-knative/src/main/java/org/apache/camel/component/knative/KnativeComponent.java
index 0c88d0c..496a71a 100644
--- a/runtime/camel-knative/src/main/java/org/apache/camel/component/knative/KnativeComponent.java
+++ b/runtime/camel-knative/src/main/java/org/apache/camel/component/knative/KnativeComponent.java
@@ -26,6 +26,8 @@ import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.StringHelper;
 
 public class KnativeComponent extends DefaultComponent {
+    public static final String CONFIGURATION_ENV_VARIABLE = "CAMEL_KNATIVE_CONFIGURATION";
+
     private final KnativeConfiguration configuration;
     private String environmentPath;
 
@@ -99,11 +101,18 @@ public class KnativeComponent extends DefaultComponent {
         KnativeConfiguration conf = configuration.copy();
 
         if (conf.getEnvironment() == null) {
-            ObjectHelper.notNull(environmentPath, "Environment Path");
-
-            conf.setEnvironment(
-                KnativeEnvironment.mandatoryLoadFromResource(getCamelContext(), this.environmentPath)
-            );
+            String envConfig = System.getenv(CONFIGURATION_ENV_VARIABLE);
+            if (environmentPath != null) {
+                conf.setEnvironment(
+                        KnativeEnvironment.mandatoryLoadFromResource(getCamelContext(), this.environmentPath)
+                );
+            } else if (envConfig != null) {
+                conf.setEnvironment(
+                        KnativeEnvironment.mandatoryLoadFromSerializedString(getCamelContext(), envConfig)
+                );
+            } else {
+                throw new IllegalStateException("Cannot load Knative configuration from file or env variable");
+            }
         }
 
         return conf;
diff --git a/runtime/camel-knative/src/main/java/org/apache/camel/component/knative/KnativeConfiguration.java b/runtime/camel-knative/src/main/java/org/apache/camel/component/knative/KnativeConfiguration.java
index 7b29444..f3b0472 100644
--- a/runtime/camel-knative/src/main/java/org/apache/camel/component/knative/KnativeConfiguration.java
+++ b/runtime/camel-knative/src/main/java/org/apache/camel/component/knative/KnativeConfiguration.java
@@ -31,15 +31,6 @@ public class KnativeConfiguration implements Cloneable {
     private KnativeEnvironment environment;
 
     public KnativeConfiguration() {
-        this.environment = new KnativeEnvironment(Arrays.asList(
-                new KnativeEnvironment.KnativeServiceDefinition(
-                        Knative.Type.endpoint,
-                        Knative.Protocol.http,
-                        "default",
-                        "0.0.0.0",
-                        8080,
-                        mapOf(ServiceDefinition.SERVICE_META_PATH, "/"))
-        ));
     }
 
     // ************************
diff --git a/runtime/camel-knative/src/main/java/org/apache/camel/component/knative/KnativeEndpoint.java b/runtime/camel-knative/src/main/java/org/apache/camel/component/knative/KnativeEndpoint.java
index 986ebeb..1b94484 100644
--- a/runtime/camel-knative/src/main/java/org/apache/camel/component/knative/KnativeEndpoint.java
+++ b/runtime/camel-knative/src/main/java/org/apache/camel/component/knative/KnativeEndpoint.java
@@ -79,8 +79,6 @@ public class KnativeEndpoint extends DefaultEndpoint implements DelegateEndpoint
 
         switch (service.getProtocol()) {
         case http:
-            this.endpoint = http(component.getCamelContext(), service);
-            break;
         case https:
             this.endpoint = http(component.getCamelContext(), service);
             break;
diff --git a/runtime/camel-knative/src/main/java/org/apache/camel/component/knative/KnativeEnvironment.java b/runtime/camel-knative/src/main/java/org/apache/camel/component/knative/KnativeEnvironment.java
index 254ef8d..86758e2 100644
--- a/runtime/camel-knative/src/main/java/org/apache/camel/component/knative/KnativeEnvironment.java
+++ b/runtime/camel-knative/src/main/java/org/apache/camel/component/knative/KnativeEnvironment.java
@@ -17,6 +17,8 @@
 package org.apache.camel.component.knative;
 
 import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringReader;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -97,6 +99,12 @@ public class KnativeEnvironment {
     //
     // ************************
 
+    public static KnativeEnvironment mandatoryLoadFromSerializedString(CamelContext context, String configuration) throws Exception {
+        try (Reader reader = new StringReader(configuration)) {
+            return Knative.MAPPER.readValue(reader, KnativeEnvironment.class);
+        }
+    }
+
     public static KnativeEnvironment mandatoryLoadFromResource(CamelContext context, String path) throws Exception {
         try (InputStream is = ResourceHelper.resolveMandatoryResourceAsInputStream(context, path)) {
 
diff --git a/vendor/github.com/gobuffalo/envy/LICENSE.txt b/vendor/github.com/gobuffalo/envy/LICENSE.txt
new file mode 100644
index 0000000..123ddc0
--- /dev/null
+++ b/vendor/github.com/gobuffalo/envy/LICENSE.txt
@@ -0,0 +1,8 @@
+The MIT License (MIT)
+Copyright (c) 2018 Mark Bates
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/github.com/gobuffalo/envy/envy.go b/vendor/github.com/gobuffalo/envy/envy.go
new file mode 100644
index 0000000..9d43448
--- /dev/null
+++ b/vendor/github.com/gobuffalo/envy/envy.go
@@ -0,0 +1,232 @@
+/*
+package envy makes working with ENV variables in Go trivial.
+
+* Get ENV variables with default values.
+* Set ENV variables safely without affecting the underlying system.
+* Temporarily change ENV vars; useful for testing.
+* Map all of the key/values in the ENV.
+* Loads .env files (by using [godotenv](https://github.com/joho/godotenv/))
+* More!
+*/
+package envy
+
+import (
+	"flag"
+	"fmt"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"runtime"
+	"strings"
+	"sync"
+
+	"github.com/joho/godotenv"
+)
+
+var gil = &sync.RWMutex{}
+var env = map[string]string{}
+
+// GO111MODULE is ENV for turning mods on/off
+const GO111MODULE = "GO111MODULE"
+
+func init() {
+	Load()
+	loadEnv()
+}
+
+// Load the ENV variables to the env map
+func loadEnv() {
+	gil.Lock()
+	defer gil.Unlock()
+
+	if os.Getenv("GO_ENV") == "" {
+		// if the flag "test.v" is *defined*, we're running as a unit test. Note that we don't care
+		// about v.Value (verbose test mode); we just want to know if the test environment has defined
+		// it. It's also possible that the flags are not yet fully parsed (i.e. flag.Parsed() == false),
+		// so we could not depend on v.Value anyway.
+		//
+		if v := flag.Lookup("test.v"); v != nil {
+			env["GO_ENV"] = "test"
+		}
+	}
+
+	// set the GOPATH if using >= 1.8 and the GOPATH isn't set
+	if os.Getenv("GOPATH") == "" {
+		out, err := exec.Command("go", "env", "GOPATH").Output()
+		if err == nil {
+			gp := strings.TrimSpace(string(out))
+			os.Setenv("GOPATH", gp)
+		}
+	}
+
+	for _, e := range os.Environ() {
+		pair := strings.Split(e, "=")
+		env[pair[0]] = os.Getenv(pair[0])
+	}
+}
+
+func Mods() bool {
+	return Get(GO111MODULE, "off") == "on"
+}
+
+// Reload the ENV variables. Useful if
+// an external ENV manager has been used
+func Reload() {
+	env = map[string]string{}
+	loadEnv()
+}
+
+// Load .env files. Files will be loaded in the same order that are received.
+// Redefined vars will override previously existing values.
+// IE: envy.Load(".env", "test_env/.env") will result in DIR=test_env
+// If no arg passed, it will try to load a .env file.
+func Load(files ...string) error {
+
+	// If no files received, load the default one
+	if len(files) == 0 {
+		err := godotenv.Overload()
+		if err == nil {
+			Reload()
+		}
+		return err
+	}
+
+	// We received a list of files
+	for _, file := range files {
+
+		// Check if it exists or we can access
+		if _, err := os.Stat(file); err != nil {
+			// It does not exist or we can not access.
+			// Return and stop loading
+			return err
+		}
+
+		// It exists and we have permission. Load it
+		if err := godotenv.Overload(file); err != nil {
+			return err
+		}
+
+		// Reload the env so all new changes are noticed
+		Reload()
+
+	}
+	return nil
+}
+
+// Get a value from the ENV. If it doesn't exist the
+// default value will be returned.
+func Get(key string, value string) string {
+	gil.RLock()
+	defer gil.RUnlock()
+	if v, ok := env[key]; ok {
+		return v
+	}
+	return value
+}
+
+// Get a value from the ENV. If it doesn't exist
+// an error will be returned
+func MustGet(key string) (string, error) {
+	gil.RLock()
+	defer gil.RUnlock()
+	if v, ok := env[key]; ok {
+		return v, nil
+	}
+	return "", fmt.Errorf("could not find ENV var with %s", key)
+}
+
+// Set a value into the ENV. This is NOT permanent. It will
+// only affect values accessed through envy.
+func Set(key string, value string) {
+	gil.Lock()
+	defer gil.Unlock()
+	env[key] = value
+}
+
+// MustSet the value into the underlying ENV, as well as envy.
+// This may return an error if there is a problem setting the
+// underlying ENV value.
+func MustSet(key string, value string) error {
+	gil.Lock()
+	defer gil.Unlock()
+	err := os.Setenv(key, value)
+	if err != nil {
+		return err
+	}
+	env[key] = value
+	return nil
+}
+
+// Map all of the keys/values set in envy.
+func Map() map[string]string {
+	gil.RLock()
+	defer gil.RUnlock()
+	cp := map[string]string{}
+	for k, v := range env {
+		cp[k] = v
+	}
+	return env
+}
+
+// Temp makes a copy of the values and allows operation on
+// those values temporarily during the run of the function.
+// At the end of the function run the copy is discarded and
+// the original values are replaced. This is useful for testing.
+// Warning: This function is NOT safe to use from a goroutine or
+// from code which may access any Get or Set function from a goroutine
+func Temp(f func()) {
+	oenv := env
+	env = map[string]string{}
+	for k, v := range oenv {
+		env[k] = v
+	}
+	defer func() { env = oenv }()
+	f()
+}
+
+func GoPath() string {
+	return Get("GOPATH", "")
+}
+
+func GoBin() string {
+	return Get("GO_BIN", "go")
+}
+
+// GoPaths returns all possible GOPATHS that are set.
+func GoPaths() []string {
+	gp := Get("GOPATH", "")
+	if runtime.GOOS == "windows" {
+		return strings.Split(gp, ";") // Windows uses a different separator
+	}
+	return strings.Split(gp, ":")
+}
+
+func importPath(path string) string {
+	for _, gopath := range GoPaths() {
+		srcpath := filepath.Join(gopath, "src")
+		rel, err := filepath.Rel(srcpath, path)
+		if err == nil {
+			return filepath.ToSlash(rel)
+		}
+	}
+
+	// fallback to trim
+	rel := strings.TrimPrefix(path, filepath.Join(GoPath(), "src"))
+	rel = strings.TrimPrefix(rel, string(filepath.Separator))
+	return filepath.ToSlash(rel)
+}
+
+func CurrentPackage() string {
+	pwd, _ := os.Getwd()
+	return importPath(pwd)
+}
+
+func Environ() []string {
+	gil.RLock()
+	defer gil.RUnlock()
+	var e []string
+	for k, v := range env {
+		e = append(e, fmt.Sprintf("%s=%s", k, v))
+	}
+	return e
+}
diff --git a/vendor/github.com/gobuffalo/envy/version.go b/vendor/github.com/gobuffalo/envy/version.go
new file mode 100644
index 0000000..f41fdc0
--- /dev/null
+++ b/vendor/github.com/gobuffalo/envy/version.go
@@ -0,0 +1,3 @@
+package envy
+
+const Version = "v1.6.7"
diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/equate.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/equate.go
new file mode 100644
index 0000000..41bbddc
--- /dev/null
+++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/equate.go
@@ -0,0 +1,89 @@
+// Copyright 2017, 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.md file.
+
+// Package cmpopts provides common options for the cmp package.
+package cmpopts
+
+import (
+	"math"
+	"reflect"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func equateAlways(_, _ interface{}) bool { return true }
+
+// EquateEmpty returns a Comparer option that determines all maps and slices
+// with a length of zero to be equal, regardless of whether they are nil.
+//
+// EquateEmpty can be used in conjunction with SortSlices and SortMaps.
+func EquateEmpty() cmp.Option {
+	return cmp.FilterValues(isEmpty, cmp.Comparer(equateAlways))
+}
+
+func isEmpty(x, y interface{}) bool {
+	vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
+	return (x != nil && y != nil && vx.Type() == vy.Type()) &&
+		(vx.Kind() == reflect.Slice || vx.Kind() == reflect.Map) &&
+		(vx.Len() == 0 && vy.Len() == 0)
+}
+
+// EquateApprox returns a Comparer option that determines float32 or float64
+// values to be equal if they are within a relative fraction or absolute margin.
+// This option is not used when either x or y is NaN or infinite.
+//
+// The fraction determines that the difference of two values must be within the
+// smaller fraction of the two values, while the margin determines that the two
+// values must be within some absolute margin.
+// To express only a fraction or only a margin, use 0 for the other parameter.
+// The fraction and margin must be non-negative.
+//
+// The mathematical expression used is equivalent to:
+//	|x-y| ≤ max(fraction*min(|x|, |y|), margin)
+//
+// EquateApprox can be used in conjunction with EquateNaNs.
+func EquateApprox(fraction, margin float64) cmp.Option {
+	if margin < 0 || fraction < 0 || math.IsNaN(margin) || math.IsNaN(fraction) {
+		panic("margin or fraction must be a non-negative number")
+	}
+	a := approximator{fraction, margin}
+	return cmp.Options{
+		cmp.FilterValues(areRealF64s, cmp.Comparer(a.compareF64)),
+		cmp.FilterValues(areRealF32s, cmp.Comparer(a.compareF32)),
+	}
+}
+
+type approximator struct{ frac, marg float64 }
+
+func areRealF64s(x, y float64) bool {
+	return !math.IsNaN(x) && !math.IsNaN(y) && !math.IsInf(x, 0) && !math.IsInf(y, 0)
+}
+func areRealF32s(x, y float32) bool {
+	return areRealF64s(float64(x), float64(y))
+}
+func (a approximator) compareF64(x, y float64) bool {
+	relMarg := a.frac * math.Min(math.Abs(x), math.Abs(y))
+	return math.Abs(x-y) <= math.Max(a.marg, relMarg)
+}
+func (a approximator) compareF32(x, y float32) bool {
+	return a.compareF64(float64(x), float64(y))
+}
+
+// EquateNaNs returns a Comparer option that determines float32 and float64
+// NaN values to be equal.
+//
+// EquateNaNs can be used in conjunction with EquateApprox.
+func EquateNaNs() cmp.Option {
+	return cmp.Options{
+		cmp.FilterValues(areNaNsF64s, cmp.Comparer(equateAlways)),
+		cmp.FilterValues(areNaNsF32s, cmp.Comparer(equateAlways)),
+	}
+}
+
+func areNaNsF64s(x, y float64) bool {
+	return math.IsNaN(x) && math.IsNaN(y)
+}
+func areNaNsF32s(x, y float32) bool {
+	return areNaNsF64s(float64(x), float64(y))
+}
diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/ignore.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/ignore.go
new file mode 100644
index 0000000..e86554b
--- /dev/null
+++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/ignore.go
@@ -0,0 +1,145 @@
+// Copyright 2017, 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.md file.
+
+package cmpopts
+
+import (
+	"fmt"
+	"reflect"
+	"unicode"
+	"unicode/utf8"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+// IgnoreFields returns an Option that ignores exported fields of the
+// given names on a single struct type.
+// The struct type is specified by passing in a value of that type.
+//
+// The name may be a dot-delimited string (e.g., "Foo.Bar") to ignore a
+// specific sub-field that is embedded or nested within the parent struct.
+//
+// This does not handle unexported fields; use IgnoreUnexported instead.
+func IgnoreFields(typ interface{}, names ...string) cmp.Option {
+	sf := newStructFilter(typ, names...)
+	return cmp.FilterPath(sf.filter, cmp.Ignore())
+}
+
+// IgnoreTypes returns an Option that ignores all values assignable to
+// certain types, which are specified by passing in a value of each type.
+func IgnoreTypes(typs ...interface{}) cmp.Option {
+	tf := newTypeFilter(typs...)
+	return cmp.FilterPath(tf.filter, cmp.Ignore())
+}
+
+type typeFilter []reflect.Type
+
+func newTypeFilter(typs ...interface{}) (tf typeFilter) {
+	for _, typ := range typs {
+		t := reflect.TypeOf(typ)
+		if t == nil {
+			// This occurs if someone tries to pass in sync.Locker(nil)
+			panic("cannot determine type; consider using IgnoreInterfaces")
+		}
+		tf = append(tf, t)
+	}
+	return tf
+}
+func (tf typeFilter) filter(p cmp.Path) bool {
+	if len(p) < 1 {
+		return false
+	}
+	t := p.Last().Type()
+	for _, ti := range tf {
+		if t.AssignableTo(ti) {
+			return true
+		}
+	}
+	return false
+}
+
+// IgnoreInterfaces returns an Option that ignores all values or references of
+// values assignable to certain interface types. These interfaces are specified
+// by passing in an anonymous struct with the interface types embedded in it.
+// For example, to ignore sync.Locker, pass in struct{sync.Locker}{}.
+func IgnoreInterfaces(ifaces interface{}) cmp.Option {
+	tf := newIfaceFilter(ifaces)
+	return cmp.FilterPath(tf.filter, cmp.Ignore())
+}
+
+type ifaceFilter []reflect.Type
+
+func newIfaceFilter(ifaces interface{}) (tf ifaceFilter) {
+	t := reflect.TypeOf(ifaces)
+	if ifaces == nil || t.Name() != "" || t.Kind() != reflect.Struct {
+		panic("input must be an anonymous struct")
+	}
+	for i := 0; i < t.NumField(); i++ {
+		fi := t.Field(i)
+		switch {
+		case !fi.Anonymous:
+			panic("struct cannot have named fields")
+		case fi.Type.Kind() != reflect.Interface:
+			panic("embedded field must be an interface type")
+		case fi.Type.NumMethod() == 0:
+			// This matches everything; why would you ever want this?
+			panic("cannot ignore empty interface")
+		default:
+			tf = append(tf, fi.Type)
+		}
+	}
+	return tf
+}
+func (tf ifaceFilter) filter(p cmp.Path) bool {
+	if len(p) < 1 {
+		return false
+	}
+	t := p.Last().Type()
+	for _, ti := range tf {
+		if t.AssignableTo(ti) {
+			return true
+		}
+		if t.Kind() != reflect.Ptr && reflect.PtrTo(t).AssignableTo(ti) {
+			return true
+		}
+	}
+	return false
+}
+
+// IgnoreUnexported returns an Option that only ignores the immediate unexported
+// fields of a struct, including anonymous fields of unexported types.
+// In particular, unexported fields within the struct's exported fields
+// of struct types, including anonymous fields, will not be ignored unless the
+// type of the field itself is also passed to IgnoreUnexported.
+func IgnoreUnexported(typs ...interface{}) cmp.Option {
+	ux := newUnexportedFilter(typs...)
+	return cmp.FilterPath(ux.filter, cmp.Ignore())
+}
+
+type unexportedFilter struct{ m map[reflect.Type]bool }
+
+func newUnexportedFilter(typs ...interface{}) unexportedFilter {
+	ux := unexportedFilter{m: make(map[reflect.Type]bool)}
+	for _, typ := range typs {
+		t := reflect.TypeOf(typ)
+		if t == nil || t.Kind() != reflect.Struct {
+			panic(fmt.Sprintf("invalid struct type: %T", typ))
+		}
+		ux.m[t] = true
+	}
+	return ux
+}
+func (xf unexportedFilter) filter(p cmp.Path) bool {
+	sf, ok := p.Index(-1).(cmp.StructField)
+	if !ok {
+		return false
+	}
+	return xf.m[p.Index(-2).Type()] && !isExported(sf.Name())
+}
+
+// isExported reports whether the identifier is exported.
+func isExported(id string) bool {
+	r, _ := utf8.DecodeRuneInString(id)
+	return unicode.IsUpper(r)
+}
diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go
new file mode 100644
index 0000000..da17d74
--- /dev/null
+++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go
@@ -0,0 +1,146 @@
+// Copyright 2017, 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.md file.
+
+package cmpopts
+
+import (
+	"fmt"
+	"reflect"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/internal/function"
+)
+
+// SortSlices returns a Transformer option that sorts all []V.
+// The less function must be of the form "func(T, T) bool" which is used to
+// sort any slice with element type V that is assignable to T.
+//
+// The less function must be:
+//	• Deterministic: less(x, y) == less(x, y)
+//	• Irreflexive: !less(x, x)
+//	• Transitive: if !less(x, y) and !less(y, z), then !less(x, z)
+//
+// The less function does not have to be "total". That is, if !less(x, y) and
+// !less(y, x) for two elements x and y, their relative order is maintained.
+//
+// SortSlices can be used in conjunction with EquateEmpty.
+func SortSlices(less interface{}) cmp.Option {
+	vf := reflect.ValueOf(less)
+	if !function.IsType(vf.Type(), function.Less) || vf.IsNil() {
+		panic(fmt.Sprintf("invalid less function: %T", less))
+	}
+	ss := sliceSorter{vf.Type().In(0), vf}
+	return cmp.FilterValues(ss.filter, cmp.Transformer("Sort", ss.sort))
+}
+
+type sliceSorter struct {
+	in  reflect.Type  // T
+	fnc reflect.Value // func(T, T) bool
+}
+
+func (ss sliceSorter) filter(x, y interface{}) bool {
+	vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
+	if !(x != nil && y != nil && vx.Type() == vy.Type()) ||
+		!(vx.Kind() == reflect.Slice && vx.Type().Elem().AssignableTo(ss.in)) ||
+		(vx.Len() <= 1 && vy.Len() <= 1) {
+		return false
+	}
+	// Check whether the slices are already sorted to avoid an infinite
+	// recursion cycle applying the same transform to itself.
+	ok1 := sliceIsSorted(x, func(i, j int) bool { return ss.less(vx, i, j) })
+	ok2 := sliceIsSorted(y, func(i, j int) bool { return ss.less(vy, i, j) })
+	return !ok1 || !ok2
+}
+func (ss sliceSorter) sort(x interface{}) interface{} {
+	src := reflect.ValueOf(x)
+	dst := reflect.MakeSlice(src.Type(), src.Len(), src.Len())
+	for i := 0; i < src.Len(); i++ {
+		dst.Index(i).Set(src.Index(i))
+	}
+	sortSliceStable(dst.Interface(), func(i, j int) bool { return ss.less(dst, i, j) })
+	ss.checkSort(dst)
+	return dst.Interface()
+}
+func (ss sliceSorter) checkSort(v reflect.Value) {
+	start := -1 // Start of a sequence of equal elements.
+	for i := 1; i < v.Len(); i++ {
+		if ss.less(v, i-1, i) {
+			// Check that first and last elements in v[start:i] are equal.
+			if start >= 0 && (ss.less(v, start, i-1) || ss.less(v, i-1, start)) {
+				panic(fmt.Sprintf("incomparable values detected: want equal elements: %v", v.Slice(start, i)))
+			}
+			start = -1
+		} else if start == -1 {
+			start = i
+		}
+	}
+}
+func (ss sliceSorter) less(v reflect.Value, i, j int) bool {
+	vx, vy := v.Index(i), v.Index(j)
+	return ss.fnc.Call([]reflect.Value{vx, vy})[0].Bool()
+}
+
+// SortMaps returns a Transformer option that flattens map[K]V types to be a
+// sorted []struct{K, V}. The less function must be of the form
+// "func(T, T) bool" which is used to sort any map with key K that is
+// assignable to T.
+//
+// Flattening the map into a slice has the property that cmp.Equal is able to
+// use Comparers on K or the K.Equal method if it exists.
+//
+// The less function must be:
+//	• Deterministic: less(x, y) == less(x, y)
+//	• Irreflexive: !less(x, x)
+//	• Transitive: if !less(x, y) and !less(y, z), then !less(x, z)
+//	• Total: if x != y, then either less(x, y) or less(y, x)
+//
+// SortMaps can be used in conjunction with EquateEmpty.
+func SortMaps(less interface{}) cmp.Option {
+	vf := reflect.ValueOf(less)
+	if !function.IsType(vf.Type(), function.Less) || vf.IsNil() {
+		panic(fmt.Sprintf("invalid less function: %T", less))
+	}
+	ms := mapSorter{vf.Type().In(0), vf}
+	return cmp.FilterValues(ms.filter, cmp.Transformer("Sort", ms.sort))
+}
+
+type mapSorter struct {
+	in  reflect.Type  // T
+	fnc reflect.Value // func(T, T) bool
+}
+
+func (ms mapSorter) filter(x, y interface{}) bool {
+	vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
+	return (x != nil && y != nil && vx.Type() == vy.Type()) &&
+		(vx.Kind() == reflect.Map && vx.Type().Key().AssignableTo(ms.in)) &&
+		(vx.Len() != 0 || vy.Len() != 0)
+}
+func (ms mapSorter) sort(x interface{}) interface{} {
+	src := reflect.ValueOf(x)
+	outType := mapEntryType(src.Type())
+	dst := reflect.MakeSlice(reflect.SliceOf(outType), src.Len(), src.Len())
+	for i, k := range src.MapKeys() {
+		v := reflect.New(outType).Elem()
+		v.Field(0).Set(k)
+		v.Field(1).Set(src.MapIndex(k))
+		dst.Index(i).Set(v)
+	}
+	sortSlice(dst.Interface(), func(i, j int) bool { return ms.less(dst, i, j) })
+	ms.checkSort(dst)
+	return dst.Interface()
+}
+func (ms mapSorter) checkSort(v reflect.Value) {
+	for i := 1; i < v.Len(); i++ {
+		if !ms.less(v, i-1, i) {
+			panic(fmt.Sprintf("partial order detected: want %v < %v", v.Index(i-1), v.Index(i)))
+		}
+	}
+}
+func (ms mapSorter) less(v reflect.Value, i, j int) bool {
+	vx, vy := v.Index(i).Field(0), v.Index(j).Field(0)
+	if !hasReflectStructOf {
+		vx, vy = vx.Elem(), vy.Elem()
+	}
+	return ms.fnc.Call([]reflect.Value{vx, vy})[0].Bool()
+}
diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go17.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go17.go
new file mode 100644
index 0000000..839b88c
--- /dev/null
+++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go17.go
@@ -0,0 +1,46 @@
+// Copyright 2017, 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.md file.
+
+// +build !go1.8
+
+package cmpopts
+
+import (
+	"reflect"
+	"sort"
+)
+
+const hasReflectStructOf = false
+
+func mapEntryType(reflect.Type) reflect.Type {
+	return reflect.TypeOf(struct{ K, V interface{} }{})
+}
+
+func sliceIsSorted(slice interface{}, less func(i, j int) bool) bool {
+	return sort.IsSorted(reflectSliceSorter{reflect.ValueOf(slice), less})
+}
+func sortSlice(slice interface{}, less func(i, j int) bool) {
+	sort.Sort(reflectSliceSorter{reflect.ValueOf(slice), less})
+}
+func sortSliceStable(slice interface{}, less func(i, j int) bool) {
+	sort.Stable(reflectSliceSorter{reflect.ValueOf(slice), less})
+}
+
+type reflectSliceSorter struct {
+	slice reflect.Value
+	less  func(i, j int) bool
+}
+
+func (ss reflectSliceSorter) Len() int {
+	return ss.slice.Len()
+}
+func (ss reflectSliceSorter) Less(i, j int) bool {
+	return ss.less(i, j)
+}
+func (ss reflectSliceSorter) Swap(i, j int) {
+	vi := ss.slice.Index(i).Interface()
+	vj := ss.slice.Index(j).Interface()
+	ss.slice.Index(i).Set(reflect.ValueOf(vj))
+	ss.slice.Index(j).Set(reflect.ValueOf(vi))
+}
diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go18.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go18.go
new file mode 100644
index 0000000..8a59c0d
--- /dev/null
+++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go18.go
@@ -0,0 +1,31 @@
+// Copyright 2017, 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.md file.
+
+// +build go1.8
+
+package cmpopts
+
+import (
+	"reflect"
+	"sort"
+)
+
+const hasReflectStructOf = true
+
+func mapEntryType(t reflect.Type) reflect.Type {
+	return reflect.StructOf([]reflect.StructField{
+		{Name: "K", Type: t.Key()},
+		{Name: "V", Type: t.Elem()},
+	})
+}
+
+func sliceIsSorted(slice interface{}, less func(i, j int) bool) bool {
+	return sort.SliceIsSorted(slice, less)
+}
+func sortSlice(slice interface{}, less func(i, j int) bool) {
+	sort.Slice(slice, less)
+}
+func sortSliceStable(slice interface{}, less func(i, j int) bool) {
+	sort.SliceStable(slice, less)
+}
diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/struct_filter.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/struct_filter.go
new file mode 100644
index 0000000..97f7079
--- /dev/null
+++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/struct_filter.go
@@ -0,0 +1,182 @@
+// Copyright 2017, 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.md file.
+
+package cmpopts
+
+import (
+	"fmt"
+	"reflect"
+	"strings"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+// filterField returns a new Option where opt is only evaluated on paths that
+// include a specific exported field on a single struct type.
+// The struct type is specified by passing in a value of that type.
+//
+// The name may be a dot-delimited string (e.g., "Foo.Bar") to select a
+// specific sub-field that is embedded or nested within the parent struct.
+func filterField(typ interface{}, name string, opt cmp.Option) cmp.Option {
+	// TODO: This is currently unexported over concerns of how helper filters
+	// can be composed together easily.
+	// TODO: Add tests for FilterField.
+
+	sf := newStructFilter(typ, name)
+	return cmp.FilterPath(sf.filter, opt)
+}
+
+type structFilter struct {
+	t  reflect.Type // The root struct type to match on
+	ft fieldTree    // Tree of fields to match on
+}
+
+func newStructFilter(typ interface{}, names ...string) structFilter {
+	// TODO: Perhaps allow * as a special identifier to allow ignoring any
+	// number of path steps until the next field match?
+	// This could be useful when a concrete struct gets transformed into
+	// an anonymous struct where it is not possible to specify that by type,
+	// but the transformer happens to provide guarantees about the names of
+	// the transformed fields.
+
+	t := reflect.TypeOf(typ)
+	if t == nil || t.Kind() != reflect.Struct {
+		panic(fmt.Sprintf("%T must be a struct", typ))
+	}
+	var ft fieldTree
+	for _, name := range names {
+		cname, err := canonicalName(t, name)
+		if err != nil {
+			panic(fmt.Sprintf("%s: %v", strings.Join(cname, "."), err))
+		}
+		ft.insert(cname)
+	}
+	return structFilter{t, ft}
+}
+
+func (sf structFilter) filter(p cmp.Path) bool {
+	for i, ps := range p {
+		if ps.Type().AssignableTo(sf.t) && sf.ft.matchPrefix(p[i+1:]) {
+			return true
+		}
+	}
+	return false
+}
+
+// fieldTree represents a set of dot-separated identifiers.
+//
+// For example, inserting the following selectors:
+//	Foo
+//	Foo.Bar.Baz
+//	Foo.Buzz
+//	Nuka.Cola.Quantum
+//
+// Results in a tree of the form:
+//	{sub: {
+//		"Foo": {ok: true, sub: {
+//			"Bar": {sub: {
+//				"Baz": {ok: true},
+//			}},
+//			"Buzz": {ok: true},
+//		}},
+//		"Nuka": {sub: {
+//			"Cola": {sub: {
+//				"Quantum": {ok: true},
+//			}},
+//		}},
+//	}}
+type fieldTree struct {
+	ok  bool                 // Whether this is a specified node
+	sub map[string]fieldTree // The sub-tree of fields under this node
+}
+
+// insert inserts a sequence of field accesses into the tree.
+func (ft *fieldTree) insert(cname []string) {
+	if ft.sub == nil {
+		ft.sub = make(map[string]fieldTree)
+	}
+	if len(cname) == 0 {
+		ft.ok = true
+		return
+	}
+	sub := ft.sub[cname[0]]
+	sub.insert(cname[1:])
+	ft.sub[cname[0]] = sub
+}
+
+// matchPrefix reports whether any selector in the fieldTree matches
+// the start of path p.
+func (ft fieldTree) matchPrefix(p cmp.Path) bool {
+	for _, ps := range p {
+		switch ps := ps.(type) {
+		case cmp.StructField:
+			ft = ft.sub[ps.Name()]
+			if ft.ok {
+				return true
+			}
+			if len(ft.sub) == 0 {
+				return false
+			}
+		case cmp.Indirect:
+		default:
+			return false
+		}
+	}
+	return false
+}
+
+// canonicalName returns a list of identifiers where any struct field access
+// through an embedded field is expanded to include the names of the embedded
+// types themselves.
+//
+// For example, suppose field "Foo" is not directly in the parent struct,
+// but actually from an embedded struct of type "Bar". Then, the canonical name
+// of "Foo" is actually "Bar.Foo".
+//
+// Suppose field "Foo" is not directly in the parent struct, but actually
+// a field in two different embedded structs of types "Bar" and "Baz".
+// Then the selector "Foo" causes a panic since it is ambiguous which one it
+// refers to. The user must specify either "Bar.Foo" or "Baz.Foo".
+func canonicalName(t reflect.Type, sel string) ([]string, error) {
+	var name string
+	sel = strings.TrimPrefix(sel, ".")
+	if sel == "" {
+		return nil, fmt.Errorf("name must not be empty")
+	}
+	if i := strings.IndexByte(sel, '.'); i < 0 {
+		name, sel = sel, ""
+	} else {
+		name, sel = sel[:i], sel[i:]
+	}
+
+	// Type must be a struct or pointer to struct.
+	if t.Kind() == reflect.Ptr {
+		t = t.Elem()
+	}
+	if t.Kind() != reflect.Struct {
+		return nil, fmt.Errorf("%v must be a struct", t)
+	}
+
+	// Find the canonical name for this current field name.
+	// If the field exists in an embedded struct, then it will be expanded.
+	if !isExported(name) {
+		// Disallow unexported fields:
+		//	* To discourage people from actually touching unexported fields
+		//	* FieldByName is buggy (https://golang.org/issue/4876)
+		return []string{name}, fmt.Errorf("name must be exported")
+	}
+	sf, ok := t.FieldByName(name)
+	if !ok {
+		return []string{name}, fmt.Errorf("does not exist")
+	}
+	var ss []string
+	for i := range sf.Index {
+		ss = append(ss, t.FieldByIndex(sf.Index[:i+1]).Name)
+	}
+	if sel == "" {
+		return ss, nil
+	}
+	ssPost, err := canonicalName(sf.Type, sel)
+	return append(ss, ssPost...), err
+}
diff --git a/vendor/github.com/joho/godotenv/LICENCE b/vendor/github.com/joho/godotenv/LICENCE
new file mode 100644
index 0000000..e7ddd51
--- /dev/null
+++ b/vendor/github.com/joho/godotenv/LICENCE
@@ -0,0 +1,23 @@
+Copyright (c) 2013 John Barton
+
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/vendor/github.com/joho/godotenv/godotenv.go b/vendor/github.com/joho/godotenv/godotenv.go
new file mode 100644
index 0000000..29b436c
--- /dev/null
+++ b/vendor/github.com/joho/godotenv/godotenv.go
@@ -0,0 +1,346 @@
+// Package godotenv is a go port of the ruby dotenv library (https://github.com/bkeepers/dotenv)
+//
+// Examples/readme can be found on the github page at https://github.com/joho/godotenv
+//
+// The TL;DR is that you make a .env file that looks something like
+//
+// 		SOME_ENV_VAR=somevalue
+//
+// and then in your go code you can call
+//
+// 		godotenv.Load()
+//
+// and all the env vars declared in .env will be available through os.Getenv("SOME_ENV_VAR")
+package godotenv
+
+import (
+	"bufio"
+	"errors"
+	"fmt"
+	"io"
+	"os"
+	"os/exec"
+	"regexp"
+	"sort"
+	"strings"
+)
+
+const doubleQuoteSpecialChars = "\\\n\r\"!$`"
+
+// Load will read your env file(s) and load them into ENV for this process.
+//
+// Call this function as close as possible to the start of your program (ideally in main)
+//
+// If you call Load without any args it will default to loading .env in the current path
+//
+// You can otherwise tell it which files to load (there can be more than one) like
+//
+//		godotenv.Load("fileone", "filetwo")
+//
+// It's important to note that it WILL NOT OVERRIDE an env variable that already exists - consider the .env file to set dev vars or sensible defaults
+func Load(filenames ...string) (err error) {
+	filenames = filenamesOrDefault(filenames)
+
+	for _, filename := range filenames {
+		err = loadFile(filename, false)
+		if err != nil {
+			return // return early on a spazout
+		}
+	}
+	return
+}
+
+// Overload will read your env file(s) and load them into ENV for this process.
+//
+// Call this function as close as possible to the start of your program (ideally in main)
+//
+// If you call Overload without any args it will default to loading .env in the current path
+//
+// You can otherwise tell it which files to load (there can be more than one) like
+//
+//		godotenv.Overload("fileone", "filetwo")
+//
+// It's important to note this WILL OVERRIDE an env variable that already exists - consider the .env file to forcefilly set all vars.
+func Overload(filenames ...string) (err error) {
+	filenames = filenamesOrDefault(filenames)
+
+	for _, filename := range filenames {
+		err = loadFile(filename, true)
+		if err != nil {
+			return // return early on a spazout
+		}
+	}
+	return
+}
+
+// Read all env (with same file loading semantics as Load) but return values as
+// a map rather than automatically writing values into env
+func Read(filenames ...string) (envMap map[string]string, err error) {
+	filenames = filenamesOrDefault(filenames)
+	envMap = make(map[string]string)
+
+	for _, filename := range filenames {
+		individualEnvMap, individualErr := readFile(filename)
+
+		if individualErr != nil {
+			err = individualErr
+			return // return early on a spazout
+		}
+
+		for key, value := range individualEnvMap {
+			envMap[key] = value
+		}
+	}
+
+	return
+}
+
+// Parse reads an env file from io.Reader, returning a map of keys and values.
+func Parse(r io.Reader) (envMap map[string]string, err error) {
+	envMap = make(map[string]string)
+
+	var lines []string
+	scanner := bufio.NewScanner(r)
+	for scanner.Scan() {
+		lines = append(lines, scanner.Text())
+	}
+
+	if err = scanner.Err(); err != nil {
+		return
+	}
+
+	for _, fullLine := range lines {
+		if !isIgnoredLine(fullLine) {
+			var key, value string
+			key, value, err = parseLine(fullLine, envMap)
+
+			if err != nil {
+				return
+			}
+			envMap[key] = value
+		}
+	}
+	return
+}
+
+//Unmarshal reads an env file from a string, returning a map of keys and values.
+func Unmarshal(str string) (envMap map[string]string, err error) {
+	return Parse(strings.NewReader(str))
+}
+
+// Exec loads env vars from the specified filenames (empty map falls back to default)
+// then executes the cmd specified.
+//
+// Simply hooks up os.Stdin/err/out to the command and calls Run()
+//
+// If you want more fine grained control over your command it's recommended
+// that you use `Load()` or `Read()` and the `os/exec` package yourself.
+func Exec(filenames []string, cmd string, cmdArgs []string) error {
+	Load(filenames...)
+
+	command := exec.Command(cmd, cmdArgs...)
+	command.Stdin = os.Stdin
+	command.Stdout = os.Stdout
+	command.Stderr = os.Stderr
+	return command.Run()
+}
+
+// Write serializes the given environment and writes it to a file
+func Write(envMap map[string]string, filename string) error {
+	content, error := Marshal(envMap)
+	if error != nil {
+		return error
+	}
+	file, error := os.Create(filename)
+	if error != nil {
+		return error
+	}
+	_, err := file.WriteString(content)
+	return err
+}
+
+// Marshal outputs the given environment as a dotenv-formatted environment file.
+// Each line is in the format: KEY="VALUE" where VALUE is backslash-escaped.
+func Marshal(envMap map[string]string) (string, error) {
+	lines := make([]string, 0, len(envMap))
+	for k, v := range envMap {
+		lines = append(lines, fmt.Sprintf(`%s="%s"`, k, doubleQuoteEscape(v)))
+	}
+	sort.Strings(lines)
+	return strings.Join(lines, "\n"), nil
+}
+
+func filenamesOrDefault(filenames []string) []string {
+	if len(filenames) == 0 {
+		return []string{".env"}
+	}
+	return filenames
+}
+
+func loadFile(filename string, overload bool) error {
+	envMap, err := readFile(filename)
+	if err != nil {
+		return err
+	}
+
+	currentEnv := map[string]bool{}
+	rawEnv := os.Environ()
+	for _, rawEnvLine := range rawEnv {
+		key := strings.Split(rawEnvLine, "=")[0]
+		currentEnv[key] = true
+	}
+
+	for key, value := range envMap {
+		if !currentEnv[key] || overload {
+			os.Setenv(key, value)
+		}
+	}
+
+	return nil
+}
+
+func readFile(filename string) (envMap map[string]string, err error) {
+	file, err := os.Open(filename)
+	if err != nil {
+		return
+	}
+	defer file.Close()
+
+	return Parse(file)
+}
+
+func parseLine(line string, envMap map[string]string) (key string, value string, err error) {
+	if len(line) == 0 {
+		err = errors.New("zero length string")
+		return
+	}
+
+	// ditch the comments (but keep quoted hashes)
+	if strings.Contains(line, "#") {
+		segmentsBetweenHashes := strings.Split(line, "#")
+		quotesAreOpen := false
+		var segmentsToKeep []string
+		for _, segment := range segmentsBetweenHashes {
+			if strings.Count(segment, "\"") == 1 || strings.Count(segment, "'") == 1 {
+				if quotesAreOpen {
+					quotesAreOpen = false
+					segmentsToKeep = append(segmentsToKeep, segment)
+				} else {
+					quotesAreOpen = true
+				}
+			}
+
+			if len(segmentsToKeep) == 0 || quotesAreOpen {
+				segmentsToKeep = append(segmentsToKeep, segment)
+			}
+		}
+
+		line = strings.Join(segmentsToKeep, "#")
+	}
+
+	firstEquals := strings.Index(line, "=")
+	firstColon := strings.Index(line, ":")
+	splitString := strings.SplitN(line, "=", 2)
+	if firstColon != -1 && (firstColon < firstEquals || firstEquals == -1) {
+		//this is a yaml-style line
+		splitString = strings.SplitN(line, ":", 2)
+	}
+
+	if len(splitString) != 2 {
+		err = errors.New("Can't separate key from value")
+		return
+	}
+
+	// Parse the key
+	key = splitString[0]
+	if strings.HasPrefix(key, "export") {
+		key = strings.TrimPrefix(key, "export")
+	}
+	key = strings.Trim(key, " ")
+
+	// Parse the value
+	value = parseValue(splitString[1], envMap)
+	return
+}
+
+func parseValue(value string, envMap map[string]string) string {
+
+	// trim
+	value = strings.Trim(value, " ")
+
+	// check if we've got quoted values or possible escapes
+	if len(value) > 1 {
+		rs := regexp.MustCompile(`\A'(.*)'\z`)
+		singleQuotes := rs.FindStringSubmatch(value)
+
+		rd := regexp.MustCompile(`\A"(.*)"\z`)
+		doubleQuotes := rd.FindStringSubmatch(value)
+
+		if singleQuotes != nil || doubleQuotes != nil {
+			// pull the quotes off the edges
+			value = value[1 : len(value)-1]
+		}
+
+		if doubleQuotes != nil {
+			// expand newlines
+			escapeRegex := regexp.MustCompile(`\\.`)
+			value = escapeRegex.ReplaceAllStringFunc(value, func(match string) string {
+				c := strings.TrimPrefix(match, `\`)
+				switch c {
+				case "n":
+					return "\n"
+				case "r":
+					return "\r"
+				default:
+					return match
+				}
+			})
+			// unescape characters
+			e := regexp.MustCompile(`\\([^$])`)
+			value = e.ReplaceAllString(value, "$1")
+		}
+
+		if singleQuotes == nil {
+			value = expandVariables(value, envMap)
+		}
+	}
+
+	return value
+}
+
+func expandVariables(v string, m map[string]string) string {
+	r := regexp.MustCompile(`(\\)?(\$)(\()?\{?([A-Z0-9_]+)?\}?`)
+
+	return r.ReplaceAllStringFunc(v, func(s string) string {
+		submatch := r.FindStringSubmatch(s)
+
+		if submatch == nil {
+			return s
+		}
+		if submatch[1] == "\\" || submatch[2] == "(" {
+			return submatch[0][1:]
+		} else if submatch[4] != "" {
+			return m[submatch[4]]
+		}
+		return s
+	})
+}
+
+func isIgnoredLine(line string) bool {
+	trimmedLine := strings.Trim(line, " \n\t")
+	return len(trimmedLine) == 0 || strings.HasPrefix(trimmedLine, "#")
+}
+
+func doubleQuoteEscape(line string) string {
+	for _, c := range doubleQuoteSpecialChars {
+		toReplace := "\\" + string(c)
+		if c == '\n' {
+			toReplace = `\n`
+		}
+		if c == '\r' {
+			toReplace = `\r`
+		}
+		line = strings.Replace(line, string(c), toReplace, -1)
+	}
+	return line
+}
diff --git a/vendor/github.com/knative/eventing/AUTHORS b/vendor/github.com/knative/eventing/AUTHORS
new file mode 100644
index 0000000..2ac441f
--- /dev/null
+++ b/vendor/github.com/knative/eventing/AUTHORS
@@ -0,0 +1,8 @@
+# This is the list of Knative authors for copyright purposes.
+#
+# This does not necessarily list everyone who has contributed code, since in
+# some cases, their employer may be the copyright holder.  To see the full list
+# of contributors, see the revision history in source control.
+Google LLC
+Pivotal Software, Inc.
+Red Hat, Inc.
diff --git a/vendor/github.com/knative/eventing/LICENSE b/vendor/github.com/knative/eventing/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/vendor/github.com/knative/eventing/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/vendor/github.com/knative/eventing/cmd/controller/kodata/HEAD b/vendor/github.com/knative/eventing/cmd/controller/kodata/HEAD
new file mode 120000
index 0000000..8f63681
--- /dev/null
+++ b/vendor/github.com/knative/eventing/cmd/controller/kodata/HEAD
@@ -0,0 +1 @@
+../../../.git/HEAD
\ No newline at end of file
diff --git a/vendor/github.com/knative/eventing/cmd/controller/kodata/LICENSE b/vendor/github.com/knative/eventing/cmd/controller/kodata/LICENSE
new file mode 120000
index 0000000..5853aae
--- /dev/null
+++ b/vendor/github.com/knative/eventing/cmd/controller/kodata/LICENSE
@@ -0,0 +1 @@
+../../../LICENSE
\ No newline at end of file
diff --git a/vendor/github.com/knative/eventing/cmd/controller/kodata/VENDOR-LICENSE b/vendor/github.com/knative/eventing/cmd/controller/kodata/VENDOR-LICENSE
new file mode 120000
index 0000000..3cc8976
--- /dev/null
+++ b/vendor/github.com/knative/eventing/cmd/controller/kodata/VENDOR-LICENSE
@@ -0,0 +1 @@
+../../../third_party/VENDOR-LICENSE
\ No newline at end of file
diff --git a/vendor/github.com/knative/eventing/cmd/fanoutsidecar/kodata/HEAD b/vendor/github.com/knative/eventing/cmd/fanoutsidecar/kodata/HEAD
new file mode 120000
index 0000000..8f63681
--- /dev/null
+++ b/vendor/github.com/knative/eventing/cmd/fanoutsidecar/kodata/HEAD
@@ -0,0 +1 @@
+../../../.git/HEAD
\ No newline at end of file
diff --git a/vendor/github.com/knative/eventing/cmd/fanoutsidecar/kodata/LICENSE b/vendor/github.com/knative/eventing/cmd/fanoutsidecar/kodata/LICENSE
new file mode 120000
index 0000000..5853aae
--- /dev/null
+++ b/vendor/github.com/knative/eventing/cmd/fanoutsidecar/kodata/LICENSE
@@ -0,0 +1 @@
+../../../LICENSE
\ No newline at end of file
diff --git a/vendor/github.com/knative/eventing/cmd/fanoutsidecar/kodata/VENDOR-LICENSE b/vendor/github.com/knative/eventing/cmd/fanoutsidecar/kodata/VENDOR-LICENSE
new file mode 120000
index 0000000..3cc8976
--- /dev/null
+++ b/vendor/github.com/knative/eventing/cmd/fanoutsidecar/kodata/VENDOR-LICENSE
@@ -0,0 +1 @@
+../../../third_party/VENDOR-LICENSE
\ No newline at end of file
diff --git a/vendor/github.com/knative/eventing/cmd/sendevent/kodata/HEAD b/vendor/github.com/knative/eventing/cmd/sendevent/kodata/HEAD
new file mode 120000
index 0000000..8f63681
--- /dev/null
+++ b/vendor/github.com/knative/eventing/cmd/sendevent/kodata/HEAD
@@ -0,0 +1 @@
+../../../.git/HEAD
\ No newline at end of file
diff --git a/vendor/github.com/knative/eventing/cmd/sendevent/kodata/LICENSE b/vendor/github.com/knative/eventing/cmd/sendevent/kodata/LICENSE
new file mode 120000
index 0000000..5853aae
--- /dev/null
+++ b/vendor/github.com/knative/eventing/cmd/sendevent/kodata/LICENSE
@@ -0,0 +1 @@
+../../../LICENSE
\ No newline at end of file
diff --git a/vendor/github.com/knative/eventing/cmd/sendevent/kodata/VENDOR-LICENSE b/vendor/github.com/knative/eventing/cmd/sendevent/kodata/VENDOR-LICENSE
new file mode 120000
index 0000000..3cc8976
--- /dev/null
+++ b/vendor/github.com/knative/eventing/cmd/sendevent/kodata/VENDOR-LICENSE
@@ -0,0 +1 @@
+../../../third_party/VENDOR-LICENSE
\ No newline at end of file
diff --git a/vendor/github.com/knative/eventing/cmd/webhook/kodata/HEAD b/vendor/github.com/knative/eventing/cmd/webhook/kodata/HEAD
new file mode 120000
index 0000000..8f63681
--- /dev/null
+++ b/vendor/github.com/knative/eventing/cmd/webhook/kodata/HEAD
@@ -0,0 +1 @@
+../../../.git/HEAD
\ No newline at end of file
diff --git a/vendor/github.com/knative/eventing/cmd/webhook/kodata/LICENSE b/vendor/github.com/knative/eventing/cmd/webhook/kodata/LICENSE
new file mode 120000
index 0000000..5853aae
--- /dev/null
+++ b/vendor/github.com/knative/eventing/cmd/webhook/kodata/LICENSE
@@ -0,0 +1 @@
+../../../LICENSE
\ No newline at end of file
diff --git a/vendor/github.com/knative/eventing/cmd/webhook/kodata/VENDOR-LICENSE b/vendor/github.com/knative/eventing/cmd/webhook/kodata/VENDOR-LICENSE
new file mode 120000
index 0000000..3cc8976
--- /dev/null
+++ b/vendor/github.com/knative/eventing/cmd/webhook/kodata/VENDOR-LICENSE
@@ -0,0 +1 @@
+../../../third_party/VENDOR-LICENSE
\ No newline at end of file
diff --git a/vendor/github.com/knative/eventing/pkg/apis/duck/v1alpha1/doc.go b/vendor/github.com/knative/eventing/pkg/apis/duck/v1alpha1/doc.go
new file mode 100644
index 0000000..3e5d1e3
--- /dev/null
+++ b/vendor/github.com/knative/eventing/pkg/apis/duck/v1alpha1/doc.go
@@ -0,0 +1,21 @@
+/*
+Copyright 2018 The Knative Authors
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+    http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Api versions allow the api contract for a resource to be changed while keeping
+// backward compatibility by support multiple concurrent versions
+// of the same resource
+
+// Package v1alpha1 is the v1alpha1 version of the API.
+// +k8s:deepcopy-gen=package
+// +groupName=duck.knative.dev
+package v1alpha1
diff --git a/vendor/github.com/knative/eventing/pkg/apis/duck/v1alpha1/subscribable_types.go b/vendor/github.com/knative/eventing/pkg/apis/duck/v1alpha1/subscribable_types.go
new file mode 100644
index 0000000..b0ca54d
--- /dev/null
+++ b/vendor/github.com/knative/eventing/pkg/apis/duck/v1alpha1/subscribable_types.go
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2018 The Knative Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package v1alpha1
+
+import (
+	"github.com/knative/pkg/apis/duck"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+)
+
+// Subscribable is the schema for the subscribable portion of the spec
+// section of the resource.
+type Subscribable struct {
+	// TODO: What is actually required here for Channel spec.
+	// This is the list of subscriptions for this channel.
+	Subscribers []ChannelSubscriberSpec `json:"subscribers,omitempty"`
+}
+
+// ChannelSubscriberSpec defines a single subscriber to a Channel.
+// Ref is a reference to the Subscription this ChannelSubscriberSpec was created for
+// SubscriberURI is the endpoint for the subscriber
+// ReplyURI is the endpoint for the reply
+// At least one of SubscriberURI and ReplyURI must be present
+type ChannelSubscriberSpec struct {
+	// +optional
+	Ref *corev1.ObjectReference `json:"ref,omitempty"`
+	// +optional
+	SubscriberURI string `json:"subscriberURI,omitempty"`
+	// +optional
+	ReplyURI string `json:"replyURI,omitempty"`
+}
+
+// Channel is a skeleton type wrapping Subscribable in the manner we expect resource writers
+// defining compatible resources to embed it. We will typically use this type to deserialize
+// Channel ObjectReferences and access the Subscription data.  This is not a real resource.
+type Channel struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	// ChannelSpec is the part where Subscribable object is
+	// configured as to be compatible with Subscribable contract.
+	Spec ChannelSpec `json:"spec"`
+}
+
+// ChannelSpec shows how we expect folks to embed Subscribable in their Spec field.
+type ChannelSpec struct {
+	Subscribable *Subscribable `json:"subscribable,omitempty"`
+}
+
+// GetFullType implements duck.Implementable
+func (s *Subscribable) GetFullType() duck.Populatable {
+	return &Channel{}
+}
+
+// Populate implements duck.Populatable
+func (c *Channel) Populate() {
+	c.Spec.Subscribable = &Subscribable{
+		// Populate ALL fields
+		Subscribers: []ChannelSubscriberSpec{{
+			Ref: &corev1.ObjectReference{
+				APIVersion: "eventing.knative.dev/v1alpha1",
+				Kind:       "Subscription",
+				Name:       "subscription1",
+				Namespace:  "default",
+				UID:        "2f9b5e8e-deb6-11e8-9f32-f2801f1b9fd1",
+			},
+			SubscriberURI: "call1",
+			ReplyURI:      "sink2",
+		}, {
+			Ref: &corev1.ObjectReference{
+				APIVersion: "eventing.knative.dev/v1alpha1",
+				Kind:       "Subscription",
+				Name:       "subscription2",
+				Namespace:  "default",
+				UID:        "34c5aec8-deb6-11e8-9f32-f2801f1b9fd1",
+			},
+			SubscriberURI: "call2",
+			ReplyURI:      "sink2",
+		}},
+	}
+}
+
+// GetListType implements apis.Listable
+func (c *Channel) GetListType() runtime.Object {
+	return &ChannelList{}
+}
+
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+
+// ChannelList is a list of Channel resources
+type ChannelList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata"`
+
+	Items []Channel `json:"items"`
+}
diff --git a/vendor/github.com/knative/eventing/pkg/apis/duck/v1alpha1/zz_generated.deepcopy.go b/vendor/github.com/knative/eventing/pkg/apis/duck/v1alpha1/zz_generated.deepcopy.go
new file mode 100644
index 0000000..064638a
--- /dev/null
+++ b/vendor/github.com/knative/eventing/pkg/apis/duck/v1alpha1/zz_generated.deepcopy.go
@@ -0,0 +1,151 @@
+// +build !ignore_autogenerated
+
+/*
+Copyright 2018 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by deepcopy-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+	v1 "k8s.io/api/core/v1"
+	runtime "k8s.io/apimachinery/pkg/runtime"
+)
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Channel) DeepCopyInto(out *Channel) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Channel.
+func (in *Channel) DeepCopy() *Channel {
+	if in == nil {
+		return nil
+	}
+	out := new(Channel)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ChannelList) DeepCopyInto(out *ChannelList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	out.ListMeta = in.ListMeta
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]Channel, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelList.
+func (in *ChannelList) DeepCopy() *ChannelList {
+	if in == nil {
+		return nil
+	}
+	out := new(ChannelList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *ChannelList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ChannelSpec) DeepCopyInto(out *ChannelSpec) {
+	*out = *in
+	if in.Subscribable != nil {
+		in, out := &in.Subscribable, &out.Subscribable
+		if *in == nil {
+			*out = nil
+		} else {
+			*out = new(Subscribable)
+			(*in).DeepCopyInto(*out)
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelSpec.
+func (in *ChannelSpec) DeepCopy() *ChannelSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(ChannelSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ChannelSubscriberSpec) DeepCopyInto(out *ChannelSubscriberSpec) {
+	*out = *in
+	if in.Ref != nil {
+		in, out := &in.Ref, &out.Ref
+		if *in == nil {
+			*out = nil
+		} else {
+			*out = new(v1.ObjectReference)
+			**out = **in
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelSubscriberSpec.
+func (in *ChannelSubscriberSpec) DeepCopy() *ChannelSubscriberSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(ChannelSubscriberSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Subscribable) DeepCopyInto(out *Subscribable) {
+	*out = *in
+	if in.Subscribers != nil {
+		in, out := &in.Subscribers, &out.Subscribers
+		*out = make([]ChannelSubscriberSpec, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Subscribable.
+func (in *Subscribable) DeepCopy() *Subscribable {
+	if in == nil {
+		return nil
+	}
+	out := new(Subscribable)
+	in.DeepCopyInto(out)
+	return out
+}
diff --git a/vendor/github.com/knative/eventing/pkg/apis/eventing/register.go b/vendor/github.com/knative/eventing/pkg/apis/eventing/register.go
new file mode 100644
index 0000000..49493ed
--- /dev/null
+++ b/vendor/github.com/knative/eventing/pkg/apis/eventing/register.go
@@ -0,0 +1,21 @@
+/*
+Copyright 2018 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package eventing
+
+const (
+	GroupName = "eventing.knative.dev"
+)
diff --git a/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/channel_defaults.go b/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/channel_defaults.go
new file mode 100644
index 0000000..a95fb22
--- /dev/null
+++ b/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/channel_defaults.go
@@ -0,0 +1,26 @@
+/*
+Copyright 2018 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1alpha1
+
+//TODO replace this with openapi defaults when
+// https://github.com/kubernetes/features/issues/575 lands (scheduled for 1.13)
+func (c *Channel) SetDefaults() {
+	c.Spec.SetDefaults()
+}
+
+func (fs *ChannelSpec) SetDefaults() {
+}
diff --git a/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/channel_types.go b/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/channel_types.go
new file mode 100644
index 0000000..2b6606b
--- /dev/null
+++ b/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/channel_types.go
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2018 The Knative Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package v1alpha1
+
+import (
+	eventingduck "github.com/knative/eventing/pkg/apis/duck/v1alpha1"
+	"github.com/knative/pkg/apis"
+	duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
+	"github.com/knative/pkg/webhook"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+)
+
+// +genclient
+// +genclient:noStatus
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+
+// Channel is an abstract resource that implements the Addressable contract.
+// The Provisioner provisions infrastructure to accepts events and
+// deliver to Subscriptions.
+type Channel struct {
+	metav1.TypeMeta `json:",inline"`
+	// +optional
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	// Spec defines the desired state of the Channel.
+	Spec ChannelSpec `json:"spec,omitempty"`
+
+	// Status represents the current state of the Channel. This data may be out of
+	// date.
+	// +optional
+	Status ChannelStatus `json:"status,omitempty"`
+}
+
+// Check that Channel can be validated, can be defaulted, and has immutable fields.
+var _ apis.Validatable = (*Channel)(nil)
+var _ apis.Defaultable = (*Channel)(nil)
+var _ apis.Immutable = (*Channel)(nil)
+var _ runtime.Object = (*Channel)(nil)
+var _ webhook.GenericCRD = (*Channel)(nil)
+
+// ChannelSpec specifies the Provisioner backing a channel and the configuration
+// arguments for a Channel.
+type ChannelSpec struct {
+	// TODO: Generation does not work correctly with CRD. They are scrubbed
+	// by the APIserver (https://github.com/kubernetes/kubernetes/issues/58778)
+	// So, we add Generation here. Once that gets fixed, remove this and use
+	// ObjectMeta.Generation instead.
+	// +optional
+	Generation int64 `json:"generation,omitempty"`
+
+	// Provisioner defines the name of the Provisioner backing this channel.
+	Provisioner *corev1.ObjectReference `json:"provisioner,omitempty"`
+
+	// Arguments defines the arguments to pass to the Provisioner which
+	// provisions this Channel.
+	// +optional
+	Arguments *runtime.RawExtension `json:"arguments,omitempty"`
+
+	// Channel conforms to Duck type Subscribable.
+	Subscribable *eventingduck.Subscribable `json:"subscribable,omitempty"`
+}
+
+var chanCondSet = duckv1alpha1.NewLivingConditionSet(ChannelConditionProvisioned, ChannelConditionAddressable)
+
+// ChannelStatus represents the current state of a Channel.
+type ChannelStatus struct {
+	// ObservedGeneration is the most recent generation observed for this Channel.
+	// It corresponds to the Channel's generation, which is updated on mutation by
+	// the API Server.
+	// TODO: The above comment is only true once
+	// https://github.com/kubernetes/kubernetes/issues/58778 is fixed.
+	// +optional
+	ObservedGeneration int64 `json:"observedGeneration,omitempty"`
+
+	// Channel is Addressable. It currently exposes the endpoint as a
+	// fully-qualified DNS name which will distribute traffic over the
+	// provided targets from inside the cluster.
+	//
+	// It generally has the form {channel}.{namespace}.svc.cluster.local
+	Address duckv1alpha1.Addressable `json:"address,omitempty"`
+
+	// Represents the latest available observations of a channel's current state.
+	// +optional
+	// +patchMergeKey=type
+	// +patchStrategy=merge
+	Conditions duckv1alpha1.Conditions `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
+}
+
+const (
+	// ChannelConditionReady has status True when the Channel is ready to
+	// accept traffic.
+	ChannelConditionReady = duckv1alpha1.ConditionReady
+
+	// ChannelConditionProvisioned has status True when the Channel's
+	// backing resources have been provisioned.
+	ChannelConditionProvisioned duckv1alpha1.ConditionType = "Provisioned"
+
+	// ChannelConditionAddressable has status true when this Channel meets
+	// the Addressable contract and has a non-empty hostname.
+	ChannelConditionAddressable duckv1alpha1.ConditionType = "Addressable"
+)
+
+// GetCondition returns the condition currently associated with the given type, or nil.
+func (cs *ChannelStatus) GetCondition(t duckv1alpha1.ConditionType) *duckv1alpha1.Condition {
+	return chanCondSet.Manage(cs).GetCondition(t)
+}
+
+// IsReady returns true if the resource is ready overall.
+func (cs *ChannelStatus) IsReady() bool {
+	return chanCondSet.Manage(cs).IsHappy()
+}
+
+// InitializeConditions sets relevant unset conditions to Unknown state.
+func (cs *ChannelStatus) InitializeConditions() {
+	chanCondSet.Manage(cs).InitializeConditions()
+}
+
+// MarkProvisioned sets ChannelConditionProvisioned condition to True state.
+func (cs *ChannelStatus) MarkProvisioned() {
+	chanCondSet.Manage(cs).MarkTrue(ChannelConditionProvisioned)
+}
+
+// MarkNotProvisioned sets ChannelConditionProvisioned condition to False state.
+func (cs *ChannelStatus) MarkNotProvisioned(reason, messageFormat string, messageA ...interface{}) {
+	chanCondSet.Manage(cs).MarkFalse(ChannelConditionProvisioned, reason, messageFormat, messageA...)
+}
+
+// SetAddress makes this Channel addressable by setting the hostname. It also
+// sets the ChannelConditionAddressable to true.
+func (cs *ChannelStatus) SetAddress(hostname string) {
+	cs.Address.Hostname = hostname
+	if hostname != "" {
+		chanCondSet.Manage(cs).MarkTrue(ChannelConditionAddressable)
+	} else {
+		chanCondSet.Manage(cs).MarkFalse(ChannelConditionAddressable, "emptyHostname", "hostname is the empty string")
+	}
+}
+
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+
+// ChannelList is a collection of Channels.
+type ChannelList struct {
+	metav1.TypeMeta `json:",inline"`
+	// +optional
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []Channel `json:"items"`
+}
diff --git a/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/channel_validation.go b/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/channel_validation.go
new file mode 100644
index 0000000..054526b
--- /dev/null
+++ b/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/channel_validation.go
@@ -0,0 +1,66 @@
+/*
+Copyright 2018 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1alpha1
+
+import (
+	"fmt"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
+	"github.com/knative/pkg/apis"
+)
+
+func (c *Channel) Validate() *apis.FieldError {
+	return c.Spec.Validate().ViaField("spec")
+}
+
+func (cs *ChannelSpec) Validate() *apis.FieldError {
+	var errs *apis.FieldError
+	if cs.Provisioner == nil {
+		errs = errs.Also(apis.ErrMissingField("provisioner"))
+	}
+
+	if cs.Subscribable != nil {
+		for i, subscriber := range cs.Subscribable.Subscribers {
+			if subscriber.ReplyURI == "" && subscriber.SubscriberURI == "" {
+				fe := apis.ErrMissingField("replyURI", "subscriberURI")
+				fe.Details = "expected at least one of, got none"
+				errs = errs.Also(fe.ViaField(fmt.Sprintf("subscriber[%d]", i)).ViaField("subscribable"))
+			}
+		}
+	}
+
+	return errs
+}
+
+func (current *Channel) CheckImmutableFields(og apis.Immutable) *apis.FieldError {
+	if og == nil {
+		return nil
+	}
+	original, ok := og.(*Channel)
+	if !ok {
+		return &apis.FieldError{Message: "The provided resource was not a Channel"}
+	}
+	ignoreArguments := cmpopts.IgnoreFields(ChannelSpec{}, "Arguments", "Subscribable")
+	if diff := cmp.Diff(original.Spec, current.Spec, ignoreArguments); diff != "" {
+		return &apis.FieldError{
+			Message: "Immutable fields changed",
+			Paths:   []string{"spec.provisioner"},
+		}
+	}
+	return nil
+}
diff --git a/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/cluster_channel_provisioner_defaults.go b/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/cluster_channel_provisioner_defaults.go
new file mode 100644
index 0000000..59a8089
--- /dev/null
+++ b/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/cluster_channel_provisioner_defaults.go
@@ -0,0 +1,27 @@
+/*
+Copyright 2018 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1alpha1
+
+// SetDefaults defaults
+func (p *ClusterChannelProvisioner) SetDefaults() {
+	p.Spec.SetDefaults()
+}
+
+// SetDefaults defaults the ClusterChannelProvisioner spec.
+func (ps *ClusterChannelProvisionerSpec) SetDefaults() {
+	// no defaults
+}
diff --git a/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/cluster_channel_provisioner_types.go b/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/cluster_channel_provisioner_types.go
new file mode 100644
index 0000000..751925c
--- /dev/null
+++ b/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/cluster_channel_provisioner_types.go
@@ -0,0 +1,122 @@
+/*
+Copyright 2018 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1alpha1
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+
+	"github.com/knative/pkg/apis"
+	duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
+	"github.com/knative/pkg/webhook"
+)
+
+// +genclient
+// +genclient:noStatus
+// +genclient:nonNamespaced
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+
+// ClusterChannelProvisioner encapsulates a provisioning strategy for the
+// backing resources required to realize a particular resource type.
+type ClusterChannelProvisioner struct {
+	metav1.TypeMeta `json:",inline"`
+	// +optional
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	// Spec defines the Types provisioned by this Provisioner.
+	Spec ClusterChannelProvisionerSpec `json:"spec"`
+
+	// Status is the current status of the Provisioner.
+	// +optional
+	Status ClusterChannelProvisionerStatus `json:"status,omitempty"`
+}
+
+// Check that ClusterChannelProvisioner can be validated and can be defaulted.
+var _ apis.Validatable = (*ClusterChannelProvisioner)(nil)
+var _ apis.Defaultable = (*ClusterChannelProvisioner)(nil)
+var _ runtime.Object = (*ClusterChannelProvisioner)(nil)
+var _ webhook.GenericCRD = (*ClusterChannelProvisioner)(nil)
+
+// ClusterChannelProvisionerSpec is the spec for a ClusterChannelProvisioner resource.
+type ClusterChannelProvisionerSpec struct {
+	// TODO: Generation does not work correctly with CRD. They are scrubbed
+	// by the APIserver (https://github.com/kubernetes/kubernetes/issues/58778)
+	// So, we add Generation here. Once that gets fixed, remove this and use
+	// ObjectMeta.Generation instead.
+	// +optional
+	Generation int64 `json:"generation,omitempty"`
+}
+
+var ccProvCondSet = duckv1alpha1.NewLivingConditionSet()
+
+// ClusterChannelProvisionerStatus is the status for a ClusterChannelProvisioner resource
+type ClusterChannelProvisionerStatus struct {
+	// Conditions holds the state of a cluster provisioner at a point in time.
+	// +optional
+	// +patchMergeKey=type
+	// +patchStrategy=merge
+	Conditions duckv1alpha1.Conditions `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
+
+	// ObservedGeneration is the 'Generation' of the ClusterChannelProvisioner that
+	// was last reconciled by the controller.
+	// +optional
+	ObservedGeneration int64 `json:"observedGeneration,omitempty"`
+}
+
+const (
+	// ClusterChannelProvisionerConditionReady has status True when the Controller reconciling objects
+	// controlled by it is ready to control them.
+	ClusterChannelProvisionerConditionReady = duckv1alpha1.ConditionReady
+)
+
+// GetCondition returns the condition currently associated with the given type, or nil.
+func (ps *ClusterChannelProvisionerStatus) GetCondition(t duckv1alpha1.ConditionType) *duckv1alpha1.Condition {
+	return ccProvCondSet.Manage(ps).GetCondition(t)
+}
+
+// IsReady returns true if the resource is ready overall.
+func (ps *ClusterChannelProvisionerStatus) IsReady() bool {
+	return ccProvCondSet.Manage(ps).IsHappy()
+}
+
+// MarkProvisionerNotReady sets the condition that the provisioner is not ready to provision backing resource.
+func (ps *ClusterChannelProvisionerStatus) MarkNotReady(reason, messageFormat string, messageA ...interface{}) {
+	ccProvCondSet.Manage(ps).MarkFalse(ClusterChannelProvisionerConditionReady, reason, messageFormat, messageA...)
+}
+
+// InitializeConditions sets relevant unset conditions to Unknown state.
+func (ps *ClusterChannelProvisionerStatus) InitializeConditions() {
+	ccProvCondSet.Manage(ps).InitializeConditions()
+}
+
+// MarkReady marks this ClusterChannelProvisioner as Ready=true.
+//
+// Note that this is not the normal pattern for duck conditions, but because there is (currently)
+// no other condition on ClusterChannelProvisioners, the normal IsReady() logic doesn't work well.
+func (ps *ClusterChannelProvisionerStatus) MarkReady() {
+	ccProvCondSet.Manage(ps).MarkTrue(ClusterChannelProvisionerConditionReady)
+}
+
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+
+// ClusterChannelProvisionerList is a list of ClusterChannelProvisioner resources
+type ClusterChannelProvisionerList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata"`
+
+	Items []ClusterChannelProvisioner `json:"items"`
+}
diff --git a/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/cluster_channel_provisioner_validation.go b/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/cluster_channel_provisioner_validation.go
new file mode 100644
index 0000000..7487867
--- /dev/null
+++ b/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/cluster_channel_provisioner_validation.go
@@ -0,0 +1,33 @@
+/*
+Copyright 2018 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1alpha1
+
+import (
+	"github.com/knative/pkg/apis"
+)
+
+// Validate validates the ClusterChannelProvisioner resource.
+func (p *ClusterChannelProvisioner) Validate() *apis.FieldError {
+	return p.Spec.Validate().ViaField("spec")
+}
+
+// Validate validates the ClusterChannelProvisioner spec
+func (ps *ClusterChannelProvisionerSpec) Validate() *apis.FieldError {
+	var errs *apis.FieldError
+
+	return errs
+}
diff --git a/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/doc.go b/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/doc.go
new file mode 100644
index 0000000..e080fe7
--- /dev/null
+++ b/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/doc.go
@@ -0,0 +1,17 @@
+/*
+Copyright 2018 The Knative Authors
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+    http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package v1alpha1 is the v1alpha1 version of the API.
+// +k8s:deepcopy-gen=package
+// +groupName=eventing.knative.dev
+package v1alpha1
diff --git a/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/register.go b/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/register.go
new file mode 100644
index 0000000..df6338f
--- /dev/null
+++ b/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/register.go
@@ -0,0 +1,57 @@
+/*
+Copyright 2018 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1alpha1
+
+import (
+	"github.com/knative/eventing/pkg/apis/eventing"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+)
+
+// SchemeGroupVersion is group version used to register these objects
+var SchemeGroupVersion = schema.GroupVersion{Group: eventing.GroupName, Version: "v1alpha1"}
+
+// Kind takes an unqualified kind and returns back a Group qualified GroupKind
+func Kind(kind string) schema.GroupKind {
+	return SchemeGroupVersion.WithKind(kind).GroupKind()
+}
+
+// Resource takes an unqualified resource and returns a Group qualified GroupResource
+func Resource(resource string) schema.GroupResource {
+	return SchemeGroupVersion.WithResource(resource).GroupResource()
+}
+
+var (
+	SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
+	AddToScheme   = SchemeBuilder.AddToScheme
+)
+
+// Adds the list of known types to Scheme.
+func addKnownTypes(scheme *runtime.Scheme) error {
+	scheme.AddKnownTypes(SchemeGroupVersion,
+		&Channel{},
+		&ChannelList{},
+		&ClusterChannelProvisioner{},
+		&ClusterChannelProvisionerList{},
+		&Subscription{},
+		&SubscriptionList{},
+	)
+	metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
+	return nil
+}
diff --git a/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/subscribable_channelable_validation.go b/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/subscribable_channelable_validation.go
new file mode 100644
index 0000000..b415b68
--- /dev/null
+++ b/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/subscribable_channelable_validation.go
@@ -0,0 +1,101 @@
+/*
+Copyright 2018 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1alpha1
+
+import (
+	"reflect"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/knative/pkg/apis"
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/equality"
+)
+
+func isChannelEmpty(f corev1.ObjectReference) bool {
+	return equality.Semantic.DeepEqual(f, corev1.ObjectReference{})
+}
+
+// Valid from only contains the following fields:
+// - Kind       == 'Channel'
+// - APIVersion == 'eventing.knative.dev/v1alpha1'
+// - Name       == not empty
+func isValidChannel(f corev1.ObjectReference) *apis.FieldError {
+	errs := isValidObjectReference(f)
+
+	if f.Kind != "Channel" {
+		fe := apis.ErrInvalidValue(f.Kind, "kind")
+		fe.Paths = []string{"kind"}
+		fe.Details = "only 'Channel' kind is allowed"
+		errs = errs.Also(fe)
+	}
+	if f.APIVersion != "eventing.knative.dev/v1alpha1" {
+		fe := apis.ErrInvalidValue(f.APIVersion, "apiVersion")
+		fe.Details = "only eventing.knative.dev/v1alpha1 is allowed for apiVersion"
+		errs = errs.Also(fe)
+	}
+	return errs
+}
+
+func isValidObjectReference(f corev1.ObjectReference) *apis.FieldError {
+	return checkRequiredObjectReferenceFields(f).
+		Also(checkDisallowedObjectReferenceFields(f))
+}
+
+// Check the corev1.ObjectReference to make sure it has the required fields. They
+// are not checked for anything more except that they are set.
+func checkRequiredObjectReferenceFields(f corev1.ObjectReference) *apis.FieldError {
+	var errs *apis.FieldError
+	if f.Name == "" {
+		errs = errs.Also(apis.ErrMissingField("name"))
+	}
+	if f.APIVersion == "" {
+		errs = errs.Also(apis.ErrMissingField("apiVersion"))
+	}
+	if f.Kind == "" {
+		errs = errs.Also(apis.ErrMissingField("kind"))
+	}
+	return errs
+}
+
+// Check the corev1.ObjectReference to make sure it only has the following fields set:
+// Name, Kind, APIVersion
+// If any other fields are set and is not the Zero value, returns an apis.FieldError
+// with the fieldpaths for all those fields.
+func checkDisallowedObjectReferenceFields(f corev1.ObjectReference) *apis.FieldError {
+	disallowedFields := []string{}
+	// See if there are any fields that have been set that should not be.
+	// TODO: Hoist this kind of stuff into pkg repository.
+	s := reflect.ValueOf(f)
+	typeOf := s.Type()
+	for i := 0; i < s.NumField(); i++ {
+		field := s.Field(i)
+		fieldName := typeOf.Field(i).Name
+		if fieldName == "Name" || fieldName == "Kind" || fieldName == "APIVersion" {
+			continue
+		}
+		if !cmp.Equal(field.Interface(), reflect.Zero(field.Type()).Interface()) {
+			disallowedFields = append(disallowedFields, fieldName)
+		}
+	}
+	if len(disallowedFields) > 0 {
+		fe := apis.ErrDisallowedFields(disallowedFields...)
+		fe.Details = "only name, apiVersion and kind are supported fields"
+		return fe
+	}
+	return nil
+
+}
diff --git a/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/subscription_defaults.go b/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/subscription_defaults.go
new file mode 100644
index 0000000..ed8fdf5
--- /dev/null
+++ b/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/subscription_defaults.go
@@ -0,0 +1,25 @@
+/*
+Copyright 2018 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1alpha1
+
+func (s *Subscription) SetDefaults() {
+	s.Spec.SetDefaults()
+}
+
+func (ss *SubscriptionSpec) SetDefaults() {
+	// TODO anything?
+}
diff --git a/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/subscription_types.go b/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/subscription_types.go
new file mode 100644
index 0000000..1d7b295
--- /dev/null
+++ b/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/subscription_types.go
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2018 The Knative Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package v1alpha1
+
+import (
+	"github.com/knative/pkg/apis"
+	duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
+	"github.com/knative/pkg/webhook"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+)
+
+// +genclient
+// +genclient:noStatus
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+// +k8s:defaulter-gen=true
+
+// Subscription routes events received on a Channel to a DNS name and
+// corresponds to the subscriptions.channels.knative.dev CRD.
+type Subscription struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata"`
+	Spec              SubscriptionSpec   `json:"spec"`
+	Status            SubscriptionStatus `json:"status,omitempty"`
+}
+
+// Check that Subscription can be validated, can be defaulted, and has immutable fields.
+var _ apis.Validatable = (*Subscription)(nil)
+var _ apis.Defaultable = (*Subscription)(nil)
+var _ apis.Immutable = (*Subscription)(nil)
+var _ runtime.Object = (*Subscription)(nil)
+var _ webhook.GenericCRD = (*Subscription)(nil)
+
+// SubscriptionSpec specifies the Channel for incoming events, a Subscriber target
+// for processing those events and where to put the result of the processing. Only
+// From (where the events are coming from) is always required. You can optionally
+// only Process the events (results in no output events) by leaving out the Result.
+// You can also perform an identity transformation on the invoming events by leaving
+// out the Subscriber and only specifying Result.
+//
+// The following are all valid specifications:
+// channel --[subscriber]--> reply
+// Sink, no outgoing events:
+// channel -- subscriber
+// no-op function (identity transformation):
+// channel --> reply
+type SubscriptionSpec struct {
+	// TODO: Generation used to not work correctly with CRD. They were scrubbed
+	// by the APIserver (https://github.com/kubernetes/kubernetes/issues/58778)
+	// So, we add Generation here. Once the above bug gets rolled out to production
+	// clusters, remove this and use ObjectMeta.Generation instead.
+	// +optional
+	Generation int64 `json:"generation,omitempty"`
+
+	// Reference to a channel that will be used to create the subscription
+	// for receiving events. The channel must have spec.subscriptions
+	// list which will then be modified accordingly.
+	//
+	// You can specify only the following fields of the ObjectReference:
+	//   - Kind
+	//   - APIVersion
+	//   - Name
+	// Kind must be "Channel" and APIVersion must be
+	// "eventing.knative.dev/v1alpha1"
+	//
+	// This field is immutable. We have no good answer on what happens to
+	// the events that are currently in the channel being consumed from
+	// and what the semantics there should be. For now, you can always
+	// delete the Subscription and recreate it to point to a different
+	// channel, giving the user more control over what semantics should
+	// be used (drain the channel first, possibly have events dropped,
+	// etc.)
+	Channel corev1.ObjectReference `json:"channel"`
+
+	// Subscriber is reference to (optional) function for processing events.
+	// Events from the Channel will be delivered here and replies are
+	// sent to a channel as specified by the Reply.
+	// +optional
+	Subscriber *SubscriberSpec `json:"subscriber,omitempty"`
+
+	// Reply specifies (optionally) how to handle events returned from
+	// the Subscriber target.
+	// +optional
+	Reply *ReplyStrategy `json:"reply,omitempty"`
+}
+
+// SubscriberSpec specifies the reference to an object that's expected to
+// provide the resolved target of the action.
+// Currently we inspect the objects Status and see if there's a predefined
+// Status field that we will then use to dispatch events to be processed by
+// the target. Currently must resolve to a k8s service or Istio virtual
+// service.
+// Note that in the future we should try to utilize subresources (/resolve ?) to
+// make this cleaner, but CRDs do not support subresources yet, so we need
+// to rely on a specified Status field today. By relying on this behaviour
+// we can utilize a dynamic client instead of having to understand all
+// kinds of different types of objects. As long as they adhere to this
+// particular contract, they can be used as a Target.
+//
+// This ensures that we can support external targets and for ease of use
+// we also allow for an URI to be specified.
+// There of course is also a requirement for the resolved SubscriberSpec to
+// behave properly at the data plane level.
+// TODO: Add a pointer to a real spec for this.
+// For now, this means: Receive an event payload, and respond with one of:
+// success and an optional response event, or failure.
+// Delivery failures may be retried by the channel
+type SubscriberSpec struct {
+	// Only one of these can be specified
+
+	// Reference to an object that will be used to find the target
+	// endpoint, which should implement the Addressable duck type.
+	// For example, this could be a reference to a Route resource
+	// or a Knative Service resource.
+	// TODO: Specify the required fields the target object must
+	// have in the status.
+	// You can specify only the following fields of the ObjectReference:
+	//   - Kind
+	//   - APIVersion
+	//   - Name
+	// +optional
+	Ref *corev1.ObjectReference `json:"ref,omitempty"`
+
+	// Reference to a 'known' endpoint where no resolving is done.
+	// http://k8s-service for example
+	// http://myexternalhandler.example.com/foo/bar
+	// +optional
+	DNSName *string `json:"dnsName,omitempty"`
+}
+
+// ReplyStrategy specifies the handling of the SubscriberSpec's returned replies.
+// If no SubscriberSpec is specified, the identity function is assumed.
+type ReplyStrategy struct {
+	// This object must be a Channel.
+	//
+	// You can specify only the following fields of the ObjectReference:
+	//   - Kind
+	//   - APIVersion
+	//   - Name
+	// Kind must be "Channel" and APIVersion must be
+	// "eventing.knative.dev/v1alpha1"
+	// +optional
+	Channel *corev1.ObjectReference `json:"channel,omitempty"`
+}
+
+// subCondSet is a condition set with Ready as the happy condition and
+// ReferencesResolved and ChannelReady as the dependent conditions.
+var subCondSet = duckv1alpha1.NewLivingConditionSet(SubscriptionConditionReferencesResolved, SubscriptionConditionChannelReady)
+
+// SubscriptionStatus (computed) for a subscription
+type SubscriptionStatus struct {
+	// Represents the latest available observations of a subscription's current state.
+	// +patchMergeKey=type
+	// +patchStrategy=merge
+	Conditions duckv1alpha1.Conditions `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
+
+	// PhysicalSubscription is the fully resolved values that this Subscription represents.
+	PhysicalSubscription SubscriptionStatusPhysicalSubscription `json:"physicalSubscription,omitEmpty"`
+}
+
+// SubscriptionStatusPhysicalSubscription represents the fully resolved values for this
+// Subscription.
+type SubscriptionStatusPhysicalSubscription struct {
+	// SubscriberURI is the fully resolved URI for spec.subscriber.
+	SubscriberURI string `json:"subscriberURI,omitEmpty"`
+
+	// ReplyURI is the fully resolved URI for the spec.reply.
+	ReplyURI string `json:"replyURI,omitEmpty"`
+}
+
+const (
+	// SubscriptionConditionReady has status True when all subconditions below have been set to True.
+	SubscriptionConditionReady = duckv1alpha1.ConditionReady
+
+	// SubscriptionConditionReferencesResolved has status True when all the specified references have been successfully
+	// resolved.
+	SubscriptionConditionReferencesResolved duckv1alpha1.ConditionType = "Resolved"
+
+	// SubscriptionConditionChannelReady has status True when controller has successfully added a
+	// subscription to the spec.channel resource.
+	SubscriptionConditionChannelReady duckv1alpha1.ConditionType = "ChannelReady"
+)
+
+// GetCondition returns the condition currently associated with the given type, or nil.
+func (ss *SubscriptionStatus) GetCondition(t duckv1alpha1.ConditionType) *duckv1alpha1.Condition {
+	return subCondSet.Manage(ss).GetCondition(t)
+}
+
+// IsReady returns true if the resource is ready overall.
+func (ss *SubscriptionStatus) IsReady() bool {
+	return subCondSet.Manage(ss).IsHappy()
+}
+
+// InitializeConditions sets relevant unset conditions to Unknown state.
+func (ss *SubscriptionStatus) InitializeConditions() {
+	subCondSet.Manage(ss).InitializeConditions()
+}
+
+// MarkReferencesResolved sets the ReferencesResolved condition to True state.
+func (ss *SubscriptionStatus) MarkReferencesResolved() {
+	subCondSet.Manage(ss).MarkTrue(SubscriptionConditionReferencesResolved)
+}
+
+// MarkChannelReady sets the ChannelReady condition to True state.
+func (ss *SubscriptionStatus) MarkChannelReady() {
+	subCondSet.Manage(ss).MarkTrue(SubscriptionConditionChannelReady)
+}
+
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+
+// SubscriptionList returned in list operations
+type SubscriptionList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata"`
+	Items           []Subscription `json:"items"`
+}
diff --git a/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/subscription_validation.go b/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/subscription_validation.go
new file mode 100644
index 0000000..c74f1a4
--- /dev/null
+++ b/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/subscription_validation.go
@@ -0,0 +1,129 @@
+/*
+Copyright 2018 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1alpha1
+
+import (
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
+	"github.com/knative/pkg/apis"
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/equality"
+)
+
+func (s *Subscription) Validate() *apis.FieldError {
+	return s.Spec.Validate().ViaField("spec")
+}
+
+// We require always Channel
+// Also at least one of 'subscriber' and 'reply' must be defined (non-nill and non-empty)
+func (ss *SubscriptionSpec) Validate() *apis.FieldError {
+	var errs *apis.FieldError
+	if isChannelEmpty(ss.Channel) {
+		fe := apis.ErrMissingField("channel")
+		fe.Details = "the Subscription must reference a channel"
+		return fe
+	} else if fe := isValidChannel(ss.Channel); fe != nil {
+		errs = errs.Also(fe.ViaField("channel"))
+	}
+
+	missingSubscriber := isSubscriberSpecNilOrEmpty(ss.Subscriber)
+	missingReply := isReplyStrategyNilOrEmpty(ss.Reply)
+	if missingSubscriber && missingReply {
+		fe := apis.ErrMissingField("reply", "subscriber")
+		fe.Details = "the Subscription must reference at least one of (reply or a subscriber)"
+		errs = errs.Also(fe)
+	}
+
+	if !missingSubscriber {
+		if fe := isValidSubscriberSpec(*ss.Subscriber); fe != nil {
+			errs = errs.Also(fe.ViaField("subscriber"))
+		}
+	}
+
+	if !missingReply {
+		if fe := isValidReply(*ss.Reply); fe != nil {
+			errs = errs.Also(fe.ViaField("reply"))
+		}
+	}
+
+	return errs
+}
+
+func isSubscriberSpecNilOrEmpty(s *SubscriberSpec) bool {
+	return s == nil || equality.Semantic.DeepEqual(s, &SubscriberSpec{}) ||
+		(equality.Semantic.DeepEqual(s.Ref, &corev1.ObjectReference{}) && s.DNSName == nil)
+
+}
+
+func isValidSubscriberSpec(s SubscriberSpec) *apis.FieldError {
+	var errs *apis.FieldError
+	if s.DNSName != nil && *s.DNSName != "" && s.Ref != nil && !equality.Semantic.DeepEqual(s.Ref, &corev1.ObjectReference{}) {
+		errs = errs.Also(apis.ErrMultipleOneOf("ref", "dnsName"))
+	}
+
+	// If Ref given, check the fields.
+	if s.Ref != nil && !equality.Semantic.DeepEqual(s.Ref, &corev1.ObjectReference{}) {
+		fe := isValidObjectReference(*s.Ref)
+		if fe != nil {
+			errs = errs.Also(fe.ViaField("ref"))
+		}
+	}
+	return errs
+}
+
+func isReplyStrategyNilOrEmpty(r *ReplyStrategy) bool {
+	return r == nil || equality.Semantic.DeepEqual(r, &ReplyStrategy{}) || equality.Semantic.DeepEqual(r.Channel, &corev1.ObjectReference{})
+}
+
+func isValidReply(r ReplyStrategy) *apis.FieldError {
+	if fe := isValidObjectReference(*r.Channel); fe != nil {
+		return fe.ViaField("channel")
+	}
+	if r.Channel.Kind != "Channel" {
+		fe := apis.ErrInvalidValue(r.Channel.Kind, "kind")
+		fe.Paths = []string{"kind"}
+		fe.Details = "only 'Channel' kind is allowed"
+		return fe
+	}
+	if r.Channel.APIVersion != "eventing.knative.dev/v1alpha1" {
+		fe := apis.ErrInvalidValue(r.Channel.APIVersion, "apiVersion")
+		fe.Details = "only eventing.knative.dev/v1alpha1 is allowed for apiVersion"
+		return fe
+	}
+	return nil
+}
+
+func (current *Subscription) CheckImmutableFields(og apis.Immutable) *apis.FieldError {
+	original, ok := og.(*Subscription)
+	if !ok {
+		return &apis.FieldError{Message: "The provided original was not a Subscription"}
+	}
+	if original == nil {
+		return nil
+	}
+
+	// Only Subscriber and Reply are mutable.
+	ignoreArguments := cmpopts.IgnoreFields(SubscriptionSpec{}, "Subscriber", "Reply")
+	if diff := cmp.Diff(original.Spec, current.Spec, ignoreArguments); diff != "" {
+		return &apis.FieldError{
+			Message: "Immutable fields changed (-old +new)",
+			Paths:   []string{"spec"},
+			Details: diff,
+		}
+	}
+	return nil
+}
diff --git a/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go b/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go
new file mode 100644
index 0000000..56b5019
--- /dev/null
+++ b/vendor/github.com/knative/eventing/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go
@@ -0,0 +1,451 @@
+// +build !ignore_autogenerated
+
+/*
+Copyright 2018 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by deepcopy-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+	duck_v1alpha1 "github.com/knative/eventing/pkg/apis/duck/v1alpha1"
+	apis_duck_v1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
+	v1 "k8s.io/api/core/v1"
+	runtime "k8s.io/apimachinery/pkg/runtime"
+)
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Channel) DeepCopyInto(out *Channel) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+	in.Status.DeepCopyInto(&out.Status)
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Channel.
+func (in *Channel) DeepCopy() *Channel {
+	if in == nil {
+		return nil
+	}
+	out := new(Channel)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *Channel) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ChannelList) DeepCopyInto(out *ChannelList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	out.ListMeta = in.ListMeta
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]Channel, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelList.
+func (in *ChannelList) DeepCopy() *ChannelList {
+	if in == nil {
+		return nil
+	}
+	out := new(ChannelList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *ChannelList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ChannelSpec) DeepCopyInto(out *ChannelSpec) {
+	*out = *in
+	if in.Provisioner != nil {
+		in, out := &in.Provisioner, &out.Provisioner
+		if *in == nil {
+			*out = nil
+		} else {
+			*out = new(v1.ObjectReference)
+			**out = **in
+		}
+	}
+	if in.Arguments != nil {
+		in, out := &in.Arguments, &out.Arguments
+		if *in == nil {
+			*out = nil
+		} else {
+			*out = new(runtime.RawExtension)
+			(*in).DeepCopyInto(*out)
+		}
+	}
+	if in.Subscribable != nil {
+		in, out := &in.Subscribable, &out.Subscribable
+		if *in == nil {
+			*out = nil
+		} else {
+			*out = new(duck_v1alpha1.Subscribable)
+			(*in).DeepCopyInto(*out)
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelSpec.
+func (in *ChannelSpec) DeepCopy() *ChannelSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(ChannelSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ChannelStatus) DeepCopyInto(out *ChannelStatus) {
+	*out = *in
+	out.Address = in.Address
+	if in.Conditions != nil {
+		in, out := &in.Conditions, &out.Conditions
+		*out = make(apis_duck_v1alpha1.Conditions, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelStatus.
+func (in *ChannelStatus) DeepCopy() *ChannelStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(ChannelStatus)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ClusterChannelProvisioner) DeepCopyInto(out *ClusterChannelProvisioner) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	out.Spec = in.Spec
+	in.Status.DeepCopyInto(&out.Status)
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterChannelProvisioner.
+func (in *ClusterChannelProvisioner) DeepCopy() *ClusterChannelProvisioner {
+	if in == nil {
+		return nil
+	}
+	out := new(ClusterChannelProvisioner)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *ClusterChannelProvisioner) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ClusterChannelProvisionerList) DeepCopyInto(out *ClusterChannelProvisionerList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	out.ListMeta = in.ListMeta
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]ClusterChannelProvisioner, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterChannelProvisionerList.
+func (in *ClusterChannelProvisionerList) DeepCopy() *ClusterChannelProvisionerList {
+	if in == nil {
+		return nil
+	}
+	out := new(ClusterChannelProvisionerList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *ClusterChannelProvisionerList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ClusterChannelProvisionerSpec) DeepCopyInto(out *ClusterChannelProvisionerSpec) {
+	*out = *in
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterChannelProvisionerSpec.
+func (in *ClusterChannelProvisionerSpec) DeepCopy() *ClusterChannelProvisionerSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(ClusterChannelProvisionerSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ClusterChannelProvisionerStatus) DeepCopyInto(out *ClusterChannelProvisionerStatus) {
+	*out = *in
+	if in.Conditions != nil {
+		in, out := &in.Conditions, &out.Conditions
+		*out = make(apis_duck_v1alpha1.Conditions, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterChannelProvisionerStatus.
+func (in *ClusterChannelProvisionerStatus) DeepCopy() *ClusterChannelProvisionerStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(ClusterChannelProvisionerStatus)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ReplyStrategy) DeepCopyInto(out *ReplyStrategy) {
+	*out = *in
+	if in.Channel != nil {
+		in, out := &in.Channel, &out.Channel
+		if *in == nil {
+			*out = nil
+		} else {
+			*out = new(v1.ObjectReference)
+			**out = **in
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplyStrategy.
+func (in *ReplyStrategy) DeepCopy() *ReplyStrategy {
+	if in == nil {
+		return nil
+	}
+	out := new(ReplyStrategy)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SubscriberSpec) DeepCopyInto(out *SubscriberSpec) {
+	*out = *in
+	if in.Ref != nil {
+		in, out := &in.Ref, &out.Ref
+		if *in == nil {
+			*out = nil
+		} else {
+			*out = new(v1.ObjectReference)
+			**out = **in
+		}
+	}
+	if in.DNSName != nil {
+		in, out := &in.DNSName, &out.DNSName
+		if *in == nil {
+			*out = nil
+		} else {
+			*out = new(string)
+			**out = **in
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubscriberSpec.
+func (in *SubscriberSpec) DeepCopy() *SubscriberSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(SubscriberSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Subscription) DeepCopyInto(out *Subscription) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+	in.Status.DeepCopyInto(&out.Status)
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Subscription.
+func (in *Subscription) DeepCopy() *Subscription {
+	if in == nil {
+		return nil
+	}
+	out := new(Subscription)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *Subscription) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SubscriptionList) DeepCopyInto(out *SubscriptionList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	out.ListMeta = in.ListMeta
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]Subscription, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubscriptionList.
+func (in *SubscriptionList) DeepCopy() *SubscriptionList {
+	if in == nil {
+		return nil
+	}
+	out := new(SubscriptionList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *SubscriptionList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SubscriptionSpec) DeepCopyInto(out *SubscriptionSpec) {
+	*out = *in
+	out.Channel = in.Channel
+	if in.Subscriber != nil {
+		in, out := &in.Subscriber, &out.Subscriber
+		if *in == nil {
+			*out = nil
+		} else {
+			*out = new(SubscriberSpec)
+			(*in).DeepCopyInto(*out)
+		}
+	}
+	if in.Reply != nil {
+		in, out := &in.Reply, &out.Reply
+		if *in == nil {
+			*out = nil
+		} else {
+			*out = new(ReplyStrategy)
+			(*in).DeepCopyInto(*out)
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubscriptionSpec.
+func (in *SubscriptionSpec) DeepCopy() *SubscriptionSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(SubscriptionSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SubscriptionStatus) DeepCopyInto(out *SubscriptionStatus) {
+	*out = *in
+	if in.Conditions != nil {
+		in, out := &in.Conditions, &out.Conditions
+		*out = make(apis_duck_v1alpha1.Conditions, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	out.PhysicalSubscription = in.PhysicalSubscription
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubscriptionStatus.
+func (in *SubscriptionStatus) DeepCopy() *SubscriptionStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(SubscriptionStatus)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SubscriptionStatusPhysicalSubscription) DeepCopyInto(out *SubscriptionStatusPhysicalSubscription) {
+	*out = *in
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubscriptionStatusPhysicalSubscription.
+func (in *SubscriptionStatusPhysicalSubscription) DeepCopy() *SubscriptionStatusPhysicalSubscription {
+	if in == nil {
+		return nil
+	}
+	out := new(SubscriptionStatusPhysicalSubscription)
+	in.DeepCopyInto(out)
+	return out
+}
diff --git a/vendor/github.com/knative/eventing/pkg/controller/eventing/inmemory/controller/kodata/LICENSE b/vendor/github.com/knative/eventing/pkg/controller/eventing/inmemory/controller/kodata/LICENSE
new file mode 120000
index 0000000..ee8b6fe
--- /dev/null
+++ b/vendor/github.com/knative/eventing/pkg/controller/eventing/inmemory/controller/kodata/LICENSE
@@ -0,0 +1 @@
+../../../../../../LICENSE
\ No newline at end of file
diff --git a/vendor/github.com/knative/eventing/pkg/controller/eventing/inmemory/controller/kodata/VENDOR-LICENSE b/vendor/github.com/knative/eventing/pkg/controller/eventing/inmemory/controller/kodata/VENDOR-LICENSE
new file mode 120000
index 0000000..423e2b2
--- /dev/null
+++ b/vendor/github.com/knative/eventing/pkg/controller/eventing/inmemory/controller/kodata/VENDOR-LICENSE
@@ -0,0 +1 @@
+../../../../../../third_party/VENDOR-LICENSE
\ No newline at end of file
diff --git a/vendor/github.com/knative/eventing/test/e2e/k8sevents/kodata/LICENSE b/vendor/github.com/knative/eventing/test/e2e/k8sevents/kodata/LICENSE
new file mode 120000
index 0000000..1477615
--- /dev/null
+++ b/vendor/github.com/knative/eventing/test/e2e/k8sevents/kodata/LICENSE
@@ -0,0 +1 @@
+../../../../LICENSE
\ No newline at end of file
diff --git a/vendor/github.com/knative/eventing/test/e2e/k8sevents/kodata/VENDOR-LICENSE b/vendor/github.com/knative/eventing/test/e2e/k8sevents/kodata/VENDOR-LICENSE
new file mode 120000
index 0000000..7322c09
--- /dev/null
+++ b/vendor/github.com/knative/eventing/test/e2e/k8sevents/kodata/VENDOR-LICENSE
@@ -0,0 +1 @@
+../../../../third_party/VENDOR-LICENSE
\ No newline at end of file
diff --git a/vendor/github.com/knative/pkg/logging/config.go b/vendor/github.com/knative/pkg/logging/config.go
new file mode 100644
index 0000000..8c0dfe5
--- /dev/null
+++ b/vendor/github.com/knative/pkg/logging/config.go
@@ -0,0 +1,150 @@
+/*
+Copyright 2018 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package logging
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+	corev1 "k8s.io/api/core/v1"
+
+	"github.com/knative/pkg/logging/logkey"
+)
+
+// NewLogger creates a logger with the supplied configuration.
+// In addition to the logger, it returns AtomicLevel that can
+// be used to change the logging level at runtime.
+// If configuration is empty, a fallback configuration is used.
+// If configuration cannot be used to instantiate a logger,
+// the same fallback configuration is used.
+func NewLogger(configJSON string, levelOverride string, opts ...zap.Option) (*zap.SugaredLogger, zap.AtomicLevel) {
+	logger, atomicLevel, err := newLoggerFromConfig(configJSON, levelOverride, opts)
+	if err == nil {
+		return logger.Sugar(), atomicLevel
+	}
+
+	loggingCfg := zap.NewProductionConfig()
+	if len(levelOverride) > 0 {
+		if level, err := levelFromString(levelOverride); err == nil {
+			loggingCfg.Level = zap.NewAtomicLevelAt(*level)
+		}
+	}
+
+	logger, err2 := loggingCfg.Build(opts...)
+	if err2 != nil {
+		panic(err2)
+	}
+	return logger.Named("fallback-logger").Sugar(), loggingCfg.Level
+}
+
+// NewLoggerFromConfig creates a logger using the provided Config
+func NewLoggerFromConfig(config *Config, name string, opts ...zap.Option) (*zap.SugaredLogger, zap.AtomicLevel) {
+	logger, level := NewLogger(config.LoggingConfig, config.LoggingLevel[name].String(), opts...)
+	return logger.Named(name), level
+}
+
+func newLoggerFromConfig(configJSON string, levelOverride string, opts []zap.Option) (*zap.Logger, zap.AtomicLevel, error) {
+	if len(configJSON) == 0 {
+		return nil, zap.AtomicLevel{}, errors.New("empty logging configuration")
+	}
+
+	var loggingCfg zap.Config
+	if err := json.Unmarshal([]byte(configJSON), &loggingCfg); err != nil {
+		return nil, zap.AtomicLevel{}, err
+	}
+
+	if len(levelOverride) > 0 {
+		if level, err := levelFromString(levelOverride); err == nil {
+			loggingCfg.Level = zap.NewAtomicLevelAt(*level)
+		}
+	}
+
+	logger, err := loggingCfg.Build(opts...)
+	if err != nil {
+		return nil, zap.AtomicLevel{}, err
+	}
+
+	logger.Info("Successfully created the logger.", zap.String(logkey.JSONConfig, configJSON))
+	logger.Sugar().Infof("Logging level set to %v", loggingCfg.Level)
+	return logger, loggingCfg.Level, nil
+}
+
+// Config contains the configuration defined in the logging ConfigMap.
+// +k8s:deepcopy-gen=true
+type Config struct {
+	LoggingConfig string
+	LoggingLevel  map[string]zapcore.Level
+}
+
+// NewConfigFromMap creates a LoggingConfig from the supplied map,
+// expecting the given list of components.
+func NewConfigFromMap(data map[string]string, components ...string) (*Config, error) {
+	lc := &Config{}
+	if zlc, ok := data["zap-logger-config"]; ok {
+		lc.LoggingConfig = zlc
+	}
+	lc.LoggingLevel = make(map[string]zapcore.Level)
+	for _, component := range components {
+		if ll, ok := data["loglevel."+component]; ok {
+			if len(ll) > 0 {
+				level, err := levelFromString(ll)
+				if err != nil {
+					return nil, err
+				}
+				lc.LoggingLevel[component] = *level
+			}
+		}
+	}
+	return lc, nil
+}
+
+// NewConfigFromConfigMap creates a LoggingConfig from the supplied ConfigMap,
+// expecting the given list of components.
+func NewConfigFromConfigMap(configMap *corev1.ConfigMap, components ...string) (*Config, error) {
+	return NewConfigFromMap(configMap.Data, components...)
+}
+
+func levelFromString(level string) (*zapcore.Level, error) {
+	var zapLevel zapcore.Level
+	if err := zapLevel.UnmarshalText([]byte(level)); err != nil {
+		return nil, fmt.Errorf("invalid logging level: %v", level)
+	}
+	return &zapLevel, nil
+}
+
+// UpdateLevelFromConfigMap returns a helper func that can be used to update the logging level
+// when a config map is updated
+func UpdateLevelFromConfigMap(logger *zap.SugaredLogger, atomicLevel zap.AtomicLevel,
+	levelKey string, components ...string) func(configMap *corev1.ConfigMap) {
+	return func(configMap *corev1.ConfigMap) {
+		loggingConfig, err := NewConfigFromConfigMap(configMap, components...)
+		if err != nil {
+			logger.Error("Failed to parse the logging configmap. Previous config map will be used.", zap.Error(err))
+			return
+		}
+
+		if level, ok := loggingConfig.LoggingLevel[levelKey]; ok {
+			if atomicLevel.Level() != level {
+				logger.Infof("Updating logging level for %v from %v to %v.", levelKey, atomicLevel.Level(), level)
+				atomicLevel.SetLevel(level)
+			}
+		}
+	}
+}
diff --git a/vendor/github.com/knative/pkg/logging/logger.go b/vendor/github.com/knative/pkg/logging/logger.go
new file mode 100644
index 0000000..903dc58
--- /dev/null
+++ b/vendor/github.com/knative/pkg/logging/logger.go
@@ -0,0 +1,57 @@
+/*
+Copyright 2018 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package logging
+
+import (
+	"context"
+
+	"go.uber.org/zap"
+)
+
+type loggerKey struct{}
+
+// This logger is used when there is no logger attached to the context.
+// Rather than returning nil and causing a panic, we will use the fallback
+// logger. Fallback logger is tagged with logger=fallback to make sure
+// that code that doesn't set the logger correctly can be caught at runtime.
+var fallbackLogger *zap.SugaredLogger
+
+func init() {
+	if logger, err := zap.NewProduction(); err != nil {
+		// We failed to create a fallback logger. Our fallback
+		// unfortunately falls back to noop.
+		fallbackLogger = zap.NewNop().Sugar()
+	} else {
+		fallbackLogger = logger.Named("fallback").Sugar()
+	}
+}
+
+// WithLogger returns a copy of parent context in which the
+// value associated with logger key is the supplied logger.
+func WithLogger(ctx context.Context, logger *zap.SugaredLogger) context.Context {
+	return context.WithValue(ctx, loggerKey{}, logger)
+}
+
+// FromContext returns the logger stored in context.
+// Returns nil if no logger is set in context, or if the stored value is
+// not of correct type.
+func FromContext(ctx context.Context) *zap.SugaredLogger {
+	if logger, ok := ctx.Value(loggerKey{}).(*zap.SugaredLogger); ok {
+		return logger
+	}
+	return fallbackLogger
+}
diff --git a/vendor/github.com/knative/pkg/logging/logkey/constants.go b/vendor/github.com/knative/pkg/logging/logkey/constants.go
new file mode 100644
index 0000000..809b101
--- /dev/null
+++ b/vendor/github.com/knative/pkg/logging/logkey/constants.go
@@ -0,0 +1,58 @@
+/*
+Copyright 2018 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package logkey
+
+const (
+	// ControllerType is the key used for controller type in structured logs
+	ControllerType = "knative.dev/controller"
+
+	// Key is the key (namespace/name) being reconciled.
+	Key = "knative.dev/key"
+
+	// Namespace is the key used for namespace in structured logs
+	Namespace = "knative.dev/namespace"
+
+	// JSONConfig is the key used for JSON configurations (not to be confused by the Configuration object)
+	JSONConfig = "knative.dev/jsonconfig"
+
+	// Kind is the key used to represent kind of an object in logs
+	Kind = "knative.dev/kind"
+
+	// Name is the key used to represent name of an object in logs
+	Name = "knative.dev/name"
+
+	// Operation is the key used to represent an operation in logs
+	Operation = "knative.dev/operation"
+
+	// Resource is the key used to represent a resource in logs
+	Resource = "knative.dev/resource"
+
+	// SubResource is a generic key used to represent a sub-resource in logs
+	SubResource = "knative.dev/subresource"
+
+	// UserInfo is the key used to represent a user information in logs
+	UserInfo = "knative.dev/userinfo"
+
+	// Pod is the key used to represent a pod's name in logs
+	Pod = "knative.dev/pod"
+
+	// Deployment is the key used to represent a deployment's name in logs
+	Deployment = "knative.dev/deployment"
+
+	// KubernetesService is the key used to represent a Kubernetes service name in logs
+	KubernetesService = "knative.dev/k8sservice"
+)
diff --git a/vendor/github.com/knative/pkg/logging/zz_generated.deepcopy.go b/vendor/github.com/knative/pkg/logging/zz_generated.deepcopy.go
new file mode 100644
index 0000000..19b3ddc
--- /dev/null
+++ b/vendor/github.com/knative/pkg/logging/zz_generated.deepcopy.go
@@ -0,0 +1,48 @@
+// +build !ignore_autogenerated
+
+/*
+Copyright 2018 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by deepcopy-gen. DO NOT EDIT.
+
+package logging
+
+import (
+	zapcore "go.uber.org/zap/zapcore"
+)
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Config) DeepCopyInto(out *Config) {
+	*out = *in
+	if in.LoggingLevel != nil {
+		in, out := &in.LoggingLevel, &out.LoggingLevel
+		*out = make(map[string]zapcore.Level, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Config.
+func (in *Config) DeepCopy() *Config {
+	if in == nil {
+		return nil
+	}
+	out := new(Config)
+	in.DeepCopyInto(out)
+	return out
+}
diff --git a/vendor/github.com/knative/pkg/webhook/certs.go b/vendor/github.com/knative/pkg/webhook/certs.go
new file mode 100644
index 0000000..510783b
--- /dev/null
+++ b/vendor/github.com/knative/pkg/webhook/certs.go
@@ -0,0 +1,166 @@
+/*
+Copyright 2018 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package webhook
+
+import (
+	"context"
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/pem"
+	"errors"
+	"math/big"
+	"time"
+
+	"go.uber.org/zap"
+
+	"github.com/knative/pkg/logging"
+)
+
+const (
+	// TODO(vaikas): Add / change other parts of the cert we might care about
+	organization = "kube"
+)
+
+// Create the common parts of the cert. These don't change between
+// the root/CA cert and the server cert.
+func createCertTemplate(name, namespace string) (*x509.Certificate, error) {
+	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
+	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
+	if err != nil {
+		return nil, errors.New("failed to generate serial number: " + err.Error())
+	}
+
+	serviceName := name + "." + namespace
+	serviceNames := []string{
+		name,
+		serviceName,
+		serviceName + ".svc",
+		serviceName + ".svc.cluster.local",
+	}
+
+	tmpl := x509.Certificate{
+		SerialNumber:          serialNumber,
+		Subject:               pkix.Name{Organization: []string{organization}},
+		SignatureAlgorithm:    x509.SHA256WithRSA,
+		NotBefore:             time.Now(),
+		NotAfter:              time.Now().AddDate(1, 0, 0), // valid for 1 years
+		BasicConstraintsValid: true,
+		DNSNames:              serviceNames,
+	}
+	return &tmpl, nil
+}
+
+// Create cert template suitable for CA and hence signing
+func createCACertTemplate(name, namespace string) (*x509.Certificate, error) {
+	rootCert, err := createCertTemplate(name, namespace)
+	if err != nil {
+		return nil, err
+	}
+	// Make it into a CA cert and change it so we can use it to sign certs
+	rootCert.IsCA = true
+	rootCert.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature
+	rootCert.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}
+	return rootCert, nil
+}
+
+// Create cert template that we can use on the server for TLS
+func createServerCertTemplate(name, namespace string) (*x509.Certificate, error) {
+	serverCert, err := createCertTemplate(name, namespace)
+	if err != nil {
+		return nil, err
+	}
+	serverCert.KeyUsage = x509.KeyUsageDigitalSignature
+	serverCert.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
+	return serverCert, err
+}
+
+// Actually sign the cert and return things in a form that we can use later on
+func createCert(template, parent *x509.Certificate, pub interface{}, parentPriv interface{}) (
+	cert *x509.Certificate, certPEM []byte, err error) {
+
+	certDER, err := x509.CreateCertificate(rand.Reader, template, parent, pub, parentPriv)
+	if err != nil {
+		return
+	}
+	cert, err = x509.ParseCertificate(certDER)
+	if err != nil {
+		return
+	}
+	b := pem.Block{Type: "CERTIFICATE", Bytes: certDER}
+	certPEM = pem.EncodeToMemory(&b)
+	return
+}
+
+func createCA(ctx context.Context, name, namespace string) (*rsa.PrivateKey, *x509.Certificate, []byte, error) {
+	logger := logging.FromContext(ctx)
+	rootKey, err := rsa.GenerateKey(rand.Reader, 2048)
+	if err != nil {
+		logger.Error("error generating random key", zap.Error(err))
+		return nil, nil, nil, err
+	}
+
+	rootCertTmpl, err := createCACertTemplate(name, namespace)
+	if err != nil {
+		logger.Error("error generating CA cert", zap.Error(err))
+		return nil, nil, nil, err
+	}
+
+	rootCert, rootCertPEM, err := createCert(rootCertTmpl, rootCertTmpl, &rootKey.PublicKey, rootKey)
+	if err != nil {
+		logger.Error("error signing the CA cert", zap.Error(err))
+		return nil, nil, nil, err
+	}
+	return rootKey, rootCert, rootCertPEM, nil
+}
+
+// CreateCerts creates and returns a CA certificate and certificate and
+// key for the server. serverKey and serverCert are used by the server
+// to establish trust for clients, CA certificate is used by the
+// client to verify the server authentication chain.
+func CreateCerts(ctx context.Context, name, namespace string) (serverKey, serverCert, caCert []byte, err error) {
+	logger := logging.FromContext(ctx)
+	// First create a CA certificate and private key
+	caKey, caCertificate, caCertificatePEM, err := createCA(ctx, name, namespace)
+	if err != nil {
+		return nil, nil, nil, err
+	}
+
+	// Then create the private key for the serving cert
+	servKey, err := rsa.GenerateKey(rand.Reader, 2048)
+	if err != nil {
+		logger.Error("error generating random key", zap.Error(err))
+		return nil, nil, nil, err
+	}
+	servCertTemplate, err := createServerCertTemplate(name, namespace)
+	if err != nil {
+		logger.Error("failed to create the server certificate template", zap.Error(err))
+		return nil, nil, nil, err
+	}
+
+	// create a certificate which wraps the server's public key, sign it with the CA private key
+	_, servCertPEM, err := createCert(servCertTemplate, caCertificate, &servKey.PublicKey, caKey)
+	if err != nil {
+		logger.Error("error signing server certificate template", zap.Error(err))
+		return nil, nil, nil, err
+	}
+	servKeyPEM := pem.EncodeToMemory(&pem.Block{
+		Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(servKey),
+	})
+	return servKeyPEM, servCertPEM, caCertificatePEM, nil
+}
diff --git a/vendor/github.com/knative/pkg/webhook/webhook.go b/vendor/github.com/knative/pkg/webhook/webhook.go
new file mode 100644
index 0000000..6ca4182
--- /dev/null
+++ b/vendor/github.com/knative/pkg/webhook/webhook.go
@@ -0,0 +1,668 @@
+/*
+Copyright 2017 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package webhook
+
+import (
+	"bytes"
+	"context"
+	"crypto/tls"
+	"crypto/x509"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+	"reflect"
+	"sort"
+	"strings"
+	"time"
+
+	"go.uber.org/zap"
+
+	"github.com/knative/pkg/apis"
+	"github.com/knative/pkg/apis/duck"
+	duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
+	"github.com/knative/pkg/logging"
+	"github.com/knative/pkg/logging/logkey"
+
+	"github.com/markbates/inflect"
+	"github.com/mattbaird/jsonpatch"
+	admissionv1beta1 "k8s.io/api/admission/v1beta1"
+	admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
+	corev1 "k8s.io/api/core/v1"
+	v1beta1 "k8s.io/api/extensions/v1beta1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/client-go/kubernetes"
+	clientadmissionregistrationv1beta1 "k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1"
+)
+
+const (
+	secretServerKey  = "server-key.pem"
+	secretServerCert = "server-cert.pem"
+	secretCACert     = "ca-cert.pem"
+)
+
+var (
+	deploymentKind      = v1beta1.SchemeGroupVersion.WithKind("Deployment")
+	errMissingNewObject = errors.New("the new object may not be nil")
+)
+
+// ControllerOptions contains the configuration for the webhook
+type ControllerOptions struct {
+	// WebhookName is the name of the webhook we create to handle
+	// mutations before they get stored in the storage.
+	WebhookName string
+
+	// ServiceName is the service name of the webhook.
+	ServiceName string
+
+	// DeploymentName is the service name of the webhook.
+	DeploymentName string
+
+	// SecretName is the name of k8s secret that contains the webhook
+	// server key/cert and corresponding CA cert that signed them. The
+	// server key/cert are used to serve the webhook and the CA cert
+	// is provided to k8s apiserver during admission controller
+	// registration.
+	SecretName string
+
+	// Namespace is the namespace in which everything above lives
+	Namespace string
+
+	// Port where the webhook is served. Per k8s admission
+	// registration requirements this should be 443 unless there is
+	// only a single port for the service.
+	Port int
+
+	// RegistrationDelay controls how long admission registration
+	// occurs after the webhook is started. This is used to avoid
+	// potential races where registration completes and k8s apiserver
+	// invokes the webhook before the HTTP server is started.
+	RegistrationDelay time.Duration
+
+	// ClientAuthType declares the policy the webhook server will follow for
+	// TLS Client Authentication.
+	// The default value is tls.NoClientCert.
+	ClientAuth tls.ClientAuthType
+}
+
+// ResourceCallback defines a signature for resource specific (Route, Configuration, etc.)
+// handlers that can validate and mutate an object. If non-nil error is returned, object creation
+// is denied. Mutations should be appended to the patches operations.
+type ResourceCallback func(patches *[]jsonpatch.JsonPatchOperation, old GenericCRD, new GenericCRD) error
+
+// ResourceDefaulter defines a signature for resource specific (Route, Configuration, etc.)
+// handlers that can set defaults on an object. If non-nil error is returned, object creation
+// is denied. Mutations should be appended to the patches operations.
+type ResourceDefaulter func(patches *[]jsonpatch.JsonPatchOperation, crd GenericCRD) error
+
+// AdmissionController implements the external admission webhook for validation of
+// pilot configuration.
+type AdmissionController struct {
+	Client   kubernetes.Interface
+	Options  ControllerOptions
+	Handlers map[schema.GroupVersionKind]GenericCRD
+	Logger   *zap.SugaredLogger
+}
+
+// GenericCRD is the interface definition that allows us to perform the generic
+// CRD actions like deciding whether to increment generation and so forth.
+type GenericCRD interface {
+	apis.Defaultable
+	apis.Validatable
+	runtime.Object
+}
+
+// GetAPIServerExtensionCACert gets the Kubernetes aggregate apiserver
+// client CA cert used by validator.
+//
+// NOTE: this certificate is provided kubernetes. We do not control
+// its name or location.
+func getAPIServerExtensionCACert(cl kubernetes.Interface) ([]byte, error) {
+	const name = "extension-apiserver-authentication"
+	c, err := cl.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(name, metav1.GetOptions{})
+	if err != nil {
+		return nil, err
+	}
+	const caFileName = "requestheader-client-ca-file"
+	pem, ok := c.Data[caFileName]
+	if !ok {
+		return nil, fmt.Errorf("cannot find %s in ConfigMap %s: ConfigMap.Data is %#v", caFileName, name, c.Data)
+	}
+	return []byte(pem), nil
+}
+
+// MakeTLSConfig makes a TLS configuration suitable for use with the server
+func makeTLSConfig(serverCert, serverKey, caCert []byte, clientAuthType tls.ClientAuthType) (*tls.Config, error) {
+	caCertPool := x509.NewCertPool()
+	caCertPool.AppendCertsFromPEM(caCert)
+	cert, err := tls.X509KeyPair(serverCert, serverKey)
+	if err != nil {
+		return nil, err
+	}
+	return &tls.Config{
+		Certificates: []tls.Certificate{cert},
+		ClientCAs:    caCertPool,
+		ClientAuth:   clientAuthType,
+	}, nil
+}
+
+func getOrGenerateKeyCertsFromSecret(ctx context.Context, client kubernetes.Interface,
+	options *ControllerOptions) (serverKey, serverCert, caCert []byte, err error) {
+	logger := logging.FromContext(ctx)
+	secret, err := client.CoreV1().Secrets(options.Namespace).Get(options.SecretName, metav1.GetOptions{})
+	if err != nil {
+		if !apierrors.IsNotFound(err) {
+			return nil, nil, nil, err
+		}
+		logger.Info("Did not find existing secret, creating one")
+		newSecret, err := generateSecret(ctx, options)
+		if err != nil {
+			return nil, nil, nil, err
+		}
+		secret, err = client.CoreV1().Secrets(newSecret.Namespace).Create(newSecret)
+		if err != nil && !apierrors.IsAlreadyExists(err) {
+			return nil, nil, nil, err
+		}
+		// Ok, so something else might have created, try fetching it one more time
+		secret, err = client.CoreV1().Secrets(options.Namespace).Get(options.SecretName, metav1.GetOptions{})
+		if err != nil {
+			return nil, nil, nil, err
+		}
+	}
+
+	var ok bool
+	if serverKey, ok = secret.Data[secretServerKey]; !ok {
+		return nil, nil, nil, errors.New("server key missing")
+	}
+	if serverCert, ok = secret.Data[secretServerCert]; !ok {
+		return nil, nil, nil, errors.New("server cert missing")
+	}
+	if caCert, ok = secret.Data[secretCACert]; !ok {
+		return nil, nil, nil, errors.New("ca cert missing")
+	}
+	return serverKey, serverCert, caCert, nil
+}
+
+// Validate checks whether "new" and "old" implement HasImmutableFields and checks them,
+// it then delegates validation to apis.Validatable on "new".
+func Validate(ctx context.Context) ResourceCallback {
+	return func(patches *[]jsonpatch.JsonPatchOperation, old GenericCRD, new GenericCRD) error {
+		if hifNew, ok := new.(apis.Immutable); ok && old != nil {
+			hifOld, ok := old.(apis.Immutable)
+			if !ok {
+				return fmt.Errorf("unexpected type mismatch %T vs. %T", old, new)
+			}
+			if err := hifNew.CheckImmutableFields(hifOld); err != nil {
+				return err
+			}
+		}
+		// Can't just `return new.Validate()` because it doesn't properly nil-check.
+		if err := new.Validate(); err != nil {
+			return err
+		}
+		return nil
+	}
+}
+
+// SetDefaults simply leverages apis.Defaultable to set defaults.
+func SetDefaults(ctx context.Context) ResourceDefaulter {
+	return func(patches *[]jsonpatch.JsonPatchOperation, crd GenericCRD) error {
+		before, after := crd.DeepCopyObject(), crd
+		after.SetDefaults()
+
+		patch, err := duck.CreatePatch(before, after)
+		if err != nil {
+			return err
+		}
+		*patches = append(*patches, patch...)
+		return nil
+	}
+}
+
+func configureCerts(ctx context.Context, client kubernetes.Interface, options *ControllerOptions) (*tls.Config, []byte, error) {
+	var apiServerCACert []byte
+	if options.ClientAuth >= tls.VerifyClientCertIfGiven {
+		var err error
+		apiServerCACert, err = getAPIServerExtensionCACert(client)
+		if err != nil {
+			return nil, nil, err
+		}
+	}
+
+	serverKey, serverCert, caCert, err := getOrGenerateKeyCertsFromSecret(ctx, client, options)
+	if err != nil {
+		return nil, nil, err
+	}
+	tlsConfig, err := makeTLSConfig(serverCert, serverKey, apiServerCACert, options.ClientAuth)
+	if err != nil {
+		return nil, nil, err
+	}
+	return tlsConfig, caCert, nil
+}
+
+// Run implements the admission controller run loop.
+func (ac *AdmissionController) Run(stop <-chan struct{}) error {
+	logger := ac.Logger
+	ctx := logging.WithLogger(context.TODO(), logger)
+	tlsConfig, caCert, err := configureCerts(ctx, ac.Client, &ac.Options)
+	if err != nil {
+		logger.Error("Could not configure admission webhook certs", zap.Error(err))
+		return err
+	}
+
+	server := &http.Server{
+		Handler:   ac,
+		Addr:      fmt.Sprintf(":%v", ac.Options.Port),
+		TLSConfig: tlsConfig,
+	}
+
+	logger.Info("Found certificates for webhook...")
+	if ac.Options.RegistrationDelay != 0 {
+		logger.Infof("Delaying admission webhook registration for %v", ac.Options.RegistrationDelay)
+	}
+
+	// Verify that each of the types we are given implements the Generation duck type.
+	for _, crd := range ac.Handlers {
+		cp := crd.DeepCopyObject()
+		var emptyGen duckv1alpha1.Generation
+		if err := duck.VerifyType(cp, &emptyGen); err != nil {
+			return err
+		}
+	}
+
+	select {
+	case <-time.After(ac.Options.RegistrationDelay):
+		cl := ac.Client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations()
+		if err := ac.register(ctx, cl, caCert); err != nil {
+			logger.Error("Failed to register webhook", zap.Error(err))
+			return err
+		}
+		logger.Info("Successfully registered webhook")
+	case <-stop:
+		return nil
+	}
+
+	serverBootstrapErrCh := make(chan struct{})
+	go func() {
+		if err := server.ListenAndServeTLS("", ""); err != nil {
+			logger.Error("ListenAndServeTLS for admission webhook returned error", zap.Error(err))
+			close(serverBootstrapErrCh)
+		}
+	}()
+
+	select {
+	case <-stop:
+		return server.Close()
+	case <-serverBootstrapErrCh:
+		return errors.New("webhook server bootstrap failed")
+	}
+}
+
+// Register registers the external admission webhook for pilot
+// configuration types.
+func (ac *AdmissionController) register(
+	ctx context.Context, client clientadmissionregistrationv1beta1.MutatingWebhookConfigurationInterface, caCert []byte) error { // nolint: lll
+	logger := logging.FromContext(ctx)
+	failurePolicy := admissionregistrationv1beta1.Fail
+
+	var rules []admissionregistrationv1beta1.RuleWithOperations
+	for gvk := range ac.Handlers {
+		plural := strings.ToLower(inflect.Pluralize(gvk.Kind))
+
+		rules = append(rules, admissionregistrationv1beta1.RuleWithOperations{
+			Operations: []admissionregistrationv1beta1.OperationType{
+				admissionregistrationv1beta1.Create,
+				admissionregistrationv1beta1.Update,
+			},
+			Rule: admissionregistrationv1beta1.Rule{
+				APIGroups:   []string{gvk.Group},
+				APIVersions: []string{gvk.Version},
+				Resources:   []string{plural},
+			},
+		})
+	}
+
+	// Sort the rules by Group, Version, Kind so that things are deterministically ordered.
+	sort.Slice(rules, func(i, j int) bool {
+		lhs, rhs := rules[i], rules[j]
+		if lhs.APIGroups[0] != rhs.APIGroups[0] {
+			return lhs.APIGroups[0] < rhs.APIGroups[0]
+		}
+		if lhs.APIVersions[0] != rhs.APIVersions[0] {
+			return lhs.APIVersions[0] < rhs.APIVersions[0]
+		}
+		return lhs.Resources[0] < rhs.Resources[0]
+	})
+
+	webhook := &admissionregistrationv1beta1.MutatingWebhookConfiguration{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: ac.Options.WebhookName,
+		},
+		Webhooks: []admissionregistrationv1beta1.Webhook{{
+			Name:  ac.Options.WebhookName,
+			Rules: rules,
+			ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{
+				Service: &admissionregistrationv1beta1.ServiceReference{
+					Namespace: ac.Options.Namespace,
+					Name:      ac.Options.ServiceName,
+				},
+				CABundle: caCert,
+			},
+			FailurePolicy: &failurePolicy,
+		}},
+	}
+
+	// Set the owner to our deployment
+	deployment, err := ac.Client.ExtensionsV1beta1().Deployments(ac.Options.Namespace).Get(ac.Options.DeploymentName, metav1.GetOptions{})
+	if err != nil {
+		return fmt.Errorf("Failed to fetch our deployment: %s", err)
+	}
+	deploymentRef := metav1.NewControllerRef(deployment, deploymentKind)
+	webhook.OwnerReferences = append(webhook.OwnerReferences, *deploymentRef)
+
+	// Try to create the webhook and if it already exists validate webhook rules
+	_, err = client.Create(webhook)
+	if err != nil {
+		if !apierrors.IsAlreadyExists(err) {
+			return fmt.Errorf("Failed to create a webhook: %s", err)
+		}
+		logger.Info("Webhook already exists")
+		configuredWebhook, err := client.Get(ac.Options.WebhookName, metav1.GetOptions{})
+		if err != nil {
+			return fmt.Errorf("Error retrieving webhook: %s", err)
+		}
+		if !reflect.DeepEqual(configuredWebhook.Webhooks, webhook.Webhooks) {
+			logger.Info("Updating webhook")
+			// Set the ResourceVersion as required by update.
+			webhook.ObjectMeta.ResourceVersion = configuredWebhook.ObjectMeta.ResourceVersion
+			if _, err := client.Update(webhook); err != nil {
+				return fmt.Errorf("Failed to update webhook: %s", err)
+			}
+		} else {
+			logger.Info("Webhook is already valid")
+		}
+	} else {
+		logger.Info("Created a webhook")
+	}
+	return nil
+}
+
+// ServeHTTP implements the external admission webhook for mutating
+// serving resources.
+func (ac *AdmissionController) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	logger := ac.Logger
+	logger.Infof("Webhook ServeHTTP request=%#v", r)
+
+	// verify the content type is accurate
+	contentType := r.Header.Get("Content-Type")
+	if contentType != "application/json" {
+		http.Error(w, "invalid Content-Type, want `application/json`", http.StatusUnsupportedMediaType)
+		return
+	}
+
+	var review admissionv1beta1.AdmissionReview
+	defer r.Body.Close()
+	if err := json.NewDecoder(r.Body).Decode(&review); err != nil {
+		http.Error(w, fmt.Sprintf("could not decode body: %v", err), http.StatusBadRequest)
+		return
+	}
+
+	logger = logger.With(
+		zap.String(logkey.Kind, fmt.Sprint(review.Request.Kind)),
+		zap.String(logkey.Namespace, review.Request.Namespace),
+		zap.String(logkey.Name, review.Request.Name),
+		zap.String(logkey.Operation, fmt.Sprint(review.Request.Operation)),
+		zap.String(logkey.Resource, fmt.Sprint(review.Request.Resource)),
+		zap.String(logkey.SubResource, fmt.Sprint(review.Request.SubResource)),
+		zap.String(logkey.UserInfo, fmt.Sprint(review.Request.UserInfo)))
+	reviewResponse := ac.admit(logging.WithLogger(r.Context(), logger), review.Request)
+	var response admissionv1beta1.AdmissionReview
+	if reviewResponse != nil {
+		response.Response = reviewResponse
+		response.Response.UID = review.Request.UID
+	}
+
+	logger.Infof("AdmissionReview for %s: %v/%v response=%v",
+		review.Request.Kind, review.Request.Namespace, review.Request.Name, reviewResponse)
+
+	if err := json.NewEncoder(w).Encode(response); err != nil {
+		http.Error(w, fmt.Sprintf("could encode response: %v", err), http.StatusInternalServerError)
+		return
+	}
+}
+
+func makeErrorStatus(reason string, args ...interface{}) *admissionv1beta1.AdmissionResponse {
+	result := apierrors.NewBadRequest(fmt.Sprintf(reason, args...)).Status()
+	return &admissionv1beta1.AdmissionResponse{
+		Result:  &result,
+		Allowed: false,
+	}
+}
+
+func (ac *AdmissionController) admit(ctx context.Context, request *admissionv1beta1.AdmissionRequest) *admissionv1beta1.AdmissionResponse {
+	logger := logging.FromContext(ctx)
+	switch request.Operation {
+	case admissionv1beta1.Create, admissionv1beta1.Update:
+	default:
+		logger.Infof("Unhandled webhook operation, letting it through %v", request.Operation)
+		return &admissionv1beta1.AdmissionResponse{Allowed: true}
+	}
+
+	patchBytes, err := ac.mutate(ctx, request.Kind, request.OldObject.Raw, request.Object.Raw)
+	if err != nil {
+		return makeErrorStatus("mutation failed: %v", err)
+	}
+	logger.Infof("Kind: %q PatchBytes: %v", request.Kind, string(patchBytes))
+
+	return &admissionv1beta1.AdmissionResponse{
+		Patch:   patchBytes,
+		Allowed: true,
+		PatchType: func() *admissionv1beta1.PatchType {
+			pt := admissionv1beta1.PatchTypeJSONPatch
+			return &pt
+		}(),
+	}
+}
+
+func (ac *AdmissionController) mutate(ctx context.Context, kind metav1.GroupVersionKind, oldBytes []byte, newBytes []byte) ([]byte, error) {
+	// Why, oh why are these different types...
+	gvk := schema.GroupVersionKind{
+		Group:   kind.Group,
+		Version: kind.Version,
+		Kind:    kind.Kind,
+	}
+
+	logger := logging.FromContext(ctx)
+	handler, ok := ac.Handlers[gvk]
+	if !ok {
+		logger.Errorf("Unhandled kind %v", gvk)
+		return nil, fmt.Errorf("unhandled kind: %v", gvk)
+	}
+
+	oldObj := handler.DeepCopyObject().(GenericCRD)
+	newObj := handler.DeepCopyObject().(GenericCRD)
+
+	if len(newBytes) != 0 {
+		newDecoder := json.NewDecoder(bytes.NewBuffer(newBytes))
+		if err := newDecoder.Decode(&newObj); err != nil {
+			return nil, fmt.Errorf("cannot decode incoming new object: %v", err)
+		}
+	} else {
+		// Use nil to denote the absence of a new object (delete)
+		newObj = nil
+	}
+
+	if len(oldBytes) != 0 {
+		oldDecoder := json.NewDecoder(bytes.NewBuffer(oldBytes))
+		if err := oldDecoder.Decode(&oldObj); err != nil {
+			return nil, fmt.Errorf("cannot decode incoming old object: %v", err)
+		}
+	} else {
+		// Use nil to denote the absence of an old object (create)
+		oldObj = nil
+	}
+
+	var patches []jsonpatch.JsonPatchOperation
+
+	err := updateGeneration(ctx, &patches, oldObj, newObj)
+	if err != nil {
+		logger.Error("Failed to update generation", zap.Error(err))
+		return nil, fmt.Errorf("Failed to update generation: %s", err)
+	}
+
+	if defaulter := SetDefaults(ctx); defaulter != nil {
+		if err := defaulter(&patches, newObj); err != nil {
+			logger.Error("Failed the resource specific defaulter", zap.Error(err))
+			// Return the error message as-is to give the defaulter callback
+			// discretion over (our portion of) the message that the user sees.
+			return nil, err
+		}
+	}
+
+	// None of the validators will accept a nil value for newObj.
+	if newObj == nil {
+		return nil, errMissingNewObject
+	}
+	if validator := Validate(ctx); validator != nil {
+		if err := validator(&patches, oldObj, newObj); err != nil {
+			logger.Error("Failed the resource specific validation", zap.Error(err))
+			// Return the error message as-is to give the validation callback
+			// discretion over (our portion of) the message that the user sees.
+			return nil, err
+		}
+	}
+
+	return json.Marshal(patches)
+}
+
+// updateGeneration sets the generation by following this logic:
+// if there's no old object, it's create, set generation to 1
+// if there's an old object and spec has changed, set generation to oldGeneration + 1
+// appends the patch to patches if changes are necessary.
+// TODO: Generation does not work correctly with CRD. They are scrubbed
+// by the APIserver (https://github.com/kubernetes/kubernetes/issues/58778)
+// So, we add Generation here. Once that gets fixed, remove this and use
+// ObjectMeta.Generation instead.
+func updateGeneration(ctx context.Context, patches *[]jsonpatch.JsonPatchOperation, old GenericCRD, new GenericCRD) error {
+	logger := logging.FromContext(ctx)
+
+	if chg, err := hasChanged(ctx, old, new); err != nil {
+		return err
+	} else if !chg {
+		logger.Info("No changes in the spec, not bumping generation")
+		return nil
+	}
+
+	// Leverage Spec duck typing to bump the Generation of the resource.
+	before, err := asGenerational(ctx, new)
+	if err != nil {
+		return err
+	}
+	after := before.DeepCopyObject().(*duckv1alpha1.Generational)
+	after.Spec.Generation = after.Spec.Generation + 1
+
+	genBump, err := duck.CreatePatch(before, after)
+	if err != nil {
+		return err
+	}
+	*patches = append(*patches, genBump...)
+	return nil
+}
+
+// Not worth fully duck typing since there's no shared schema.
+type hasSpec struct {
+	Spec json.RawMessage `json:"spec"`
+}
+
+func getSpecJSON(crd GenericCRD) ([]byte, error) {
+	b, err := json.Marshal(crd)
+	if err != nil {
+		return nil, err
+	}
+	hs := hasSpec{}
+	if err := json.Unmarshal(b, &hs); err != nil {
+		return nil, err
+	}
+	return []byte(hs.Spec), nil
+}
+
+func hasChanged(ctx context.Context, old, new GenericCRD) (bool, error) {
+	if old == nil {
+		return true, nil
+	}
+	logger := logging.FromContext(ctx)
+
+	oldSpecJSON, err := getSpecJSON(old)
+	if err != nil {
+		logger.Error("Failed to get Spec JSON for old", zap.Error(err))
+		return false, err
+	}
+	newSpecJSON, err := getSpecJSON(new)
+	if err != nil {
+		logger.Error("Failed to get Spec JSON for new", zap.Error(err))
+		return false, err
+	}
+
+	specPatches, err := jsonpatch.CreatePatch(oldSpecJSON, newSpecJSON)
+	if err != nil {
+		fmt.Printf("Error creating JSON patch:%v", err)
+		return false, err
+	}
+	if len(specPatches) == 0 {
+		return false, nil
+	}
+	specPatchesJSON, err := json.Marshal(specPatches)
+	if err != nil {
+		logger.Error("Failed to marshal spec patches", zap.Error(err))
+		return false, err
+	}
+	logger.Infof("Specs differ:\n%+v\n", string(specPatchesJSON))
+	return true, nil
+}
+
+func asGenerational(ctx context.Context, crd GenericCRD) (*duckv1alpha1.Generational, error) {
+	raw, err := json.Marshal(crd)
+	if err != nil {
+		return nil, err
+	}
+	kr := &duckv1alpha1.Generational{}
+	if err := json.Unmarshal(raw, kr); err != nil {
+		return nil, err
+	}
+	return kr, nil
+}
+
+func generateSecret(ctx context.Context, options *ControllerOptions) (*corev1.Secret, error) {
+	serverKey, serverCert, caCert, err := CreateCerts(ctx, options.ServiceName, options.Namespace)
+	if err != nil {
+		return nil, err
+	}
+	return &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      options.SecretName,
+			Namespace: options.Namespace,
+		},
+		Data: map[string][]byte{
+			secretServerKey:  serverKey,
+			secretServerCert: serverCert,
+			secretCACert:     caCert,
+		},
+	}, nil
+}
diff --git a/vendor/github.com/markbates/inflect/LICENCE b/vendor/github.com/markbates/inflect/LICENCE
new file mode 100644
index 0000000..8a36b94
--- /dev/null
+++ b/vendor/github.com/markbates/inflect/LICENCE
@@ -0,0 +1,7 @@
+Copyright (c) 2011 Chris Farmiloe
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/github.com/markbates/inflect/helpers.go b/vendor/github.com/markbates/inflect/helpers.go
new file mode 100644
index 0000000..24050c7
--- /dev/null
+++ b/vendor/github.com/markbates/inflect/helpers.go
@@ -0,0 +1,19 @@
+package inflect
+
+//Helpers is a map of the helper names with its corresponding inflect function
+var Helpers = map[string]interface{}{
+	"asciffy":             Asciify,
+	"camelize":            Camelize,
+	"camelize_down_first": CamelizeDownFirst,
+	"capitalize":          Capitalize,
+	"dasherize":           Dasherize,
+	"humanize":            Humanize,
+	"ordinalize":          Ordinalize,
+	"parameterize":        Parameterize,
+	"pluralize":           Pluralize,
+	"pluralize_with_size": PluralizeWithSize,
+	"singularize":         Singularize,
+	"tableize":            Tableize,
+	"typeify":             Typeify,
+	"underscore":          Underscore,
+}
diff --git a/vendor/github.com/markbates/inflect/inflect.go b/vendor/github.com/markbates/inflect/inflect.go
new file mode 100644
index 0000000..9b6776c
--- /dev/null
+++ b/vendor/github.com/markbates/inflect/inflect.go
@@ -0,0 +1,892 @@
+package inflect
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"regexp"
+	"strconv"
+	"strings"
+	"unicode"
+	"unicode/utf8"
+)
+
+// baseAcronyms comes from https://en.wikipedia.org/wiki/List_of_information_technology_acronymss
+const baseAcronyms = `JSON,JWT,ID,UUID,SQL,ACK,ACL,ADSL,AES,ANSI,API,ARP,ATM,BGP,BSS,CAT,CCITT,CHAP,CIDR,CIR,CLI,CPE,CPU,CRC,CRT,CSMA,CMOS,DCE,DEC,DES,DHCP,DNS,DRAM,DSL,DSLAM,DTE,DMI,EHA,EIA,EIGRP,EOF,ESS,FCC,FCS,FDDI,FTP,GBIC,gbps,GEPOF,HDLC,HTTP,HTTPS,IANA,ICMP,IDF,IDS,IEEE,IETF,IMAP,IP,IPS,ISDN,ISP,kbps,LACP,LAN,LAPB,LAPF,LLC,MAC,MAN,Mbps,MC,MDF,MIB,MoCA,MPLS,MTU,NAC,NAT,NBMA,NIC,NRZ,NRZI,NVRAM,OSI,OSPF,OUI,PAP,PAT,PC,PIM,PIM,PCM,PDU,POP3,POP,POTS,PPP,PPTP,PTT,PVST,RADIUS,RAM,RARP,RFC [...]
+
+// Rule used by rulesets
+type Rule struct {
+	suffix      string
+	replacement string
+	exact       bool
+}
+
+// Ruleset a Ruleset is the config of pluralization rules
+// you can extend the rules with the Add* methods
+type Ruleset struct {
+	uncountables map[string]bool
+	plurals      []*Rule
+	singulars    []*Rule
+	humans       []*Rule
+	acronyms     []*Rule
+}
+
+// NewRuleset creates a blank ruleset. Unless you are going to
+// build your own rules from scratch you probably
+// won't need this and can just use the defaultRuleset
+// via the global inflect.* methods
+func NewRuleset() *Ruleset {
+	rs := new(Ruleset)
+	rs.uncountables = make(map[string]bool)
+	rs.plurals = make([]*Rule, 0)
+	rs.singulars = make([]*Rule, 0)
+	rs.humans = make([]*Rule, 0)
+	rs.acronyms = make([]*Rule, 0)
+	return rs
+}
+
+// NewDefaultRuleset creates a new ruleset and load it with the default
+// set of common English pluralization rules
+func NewDefaultRuleset() *Ruleset {
+	rs := NewRuleset()
+	rs.AddPlural("movie", "movies")
+	rs.AddPlural("s", "s")
+	rs.AddPlural("testis", "testes")
+	rs.AddPlural("axis", "axes")
+	rs.AddPlural("octopus", "octopi")
+	rs.AddPlural("virus", "viri")
+	rs.AddPlural("octopi", "octopi")
+	rs.AddPlural("viri", "viri")
+	rs.AddPlural("alias", "aliases")
+	rs.AddPlural("status", "statuses")
+	rs.AddPlural("Status", "Statuses")
+	rs.AddPlural("campus", "campuses")
+	rs.AddPlural("bus", "buses")
+	rs.AddPlural("buffalo", "buffaloes")
+	rs.AddPlural("tomato", "tomatoes")
+	rs.AddPlural("tum", "ta")
+	rs.AddPlural("ium", "ia")
+	rs.AddPlural("ta", "ta")
+	rs.AddPlural("ia", "ia")
+	rs.AddPlural("sis", "ses")
+	rs.AddPlural("lf", "lves")
+	rs.AddPlural("rf", "rves")
+	rs.AddPlural("afe", "aves")
+	rs.AddPlural("bfe", "bves")
+	rs.AddPlural("cfe", "cves")
+	rs.AddPlural("dfe", "dves")
+	rs.AddPlural("efe", "eves")
+	rs.AddPlural("gfe", "gves")
+	rs.AddPlural("hfe", "hves")
+	rs.AddPlural("ife", "ives")
+	rs.AddPlural("jfe", "jves")
+	rs.AddPlural("kfe", "kves")
+	rs.AddPlural("lfe", "lves")
+	rs.AddPlural("mfe", "mves")
+	rs.AddPlural("nfe", "nves")
+	rs.AddPlural("ofe", "oves")
+	rs.AddPlural("pfe", "pves")
+	rs.AddPlural("qfe", "qves")
+	rs.AddPlural("rfe", "rves")
+	rs.AddPlural("sfe", "sves")
+	rs.AddPlural("tfe", "tves")
+	rs.AddPlural("ufe", "uves")
+	rs.AddPlural("vfe", "vves")
+	rs.AddPlural("wfe", "wves")
+	rs.AddPlural("xfe", "xves")
+	rs.AddPlural("yfe", "yves")
+	rs.AddPlural("zfe", "zves")
+	rs.AddPlural("hive", "hives")
+	rs.AddPlural("quy", "quies")
+	rs.AddPlural("by", "bies")
+	rs.AddPlural("cy", "cies")
+	rs.AddPlural("dy", "dies")
+	rs.AddPlural("fy", "fies")
+	rs.AddPlural("gy", "gies")
+	rs.AddPlural("hy", "hies")
+	rs.AddPlural("jy", "jies")
+	rs.AddPlural("ky", "kies")
+	rs.AddPlural("ly", "lies")
+	rs.AddPlural("my", "mies")
+	rs.AddPlural("ny", "nies")
+	rs.AddPlural("py", "pies")
+	rs.AddPlural("qy", "qies")
+	rs.AddPlural("ry", "ries")
+	rs.AddPlural("sy", "sies")
+	rs.AddPlural("ty", "ties")
+	rs.AddPlural("vy", "vies")
+	rs.AddPlural("wy", "wies")
+	rs.AddPlural("xy", "xies")
+	rs.AddPlural("zy", "zies")
+	rs.AddPlural("x", "xes")
+	rs.AddPlural("ch", "ches")
+	rs.AddPlural("ss", "sses")
+	rs.AddPlural("sh", "shes")
+	rs.AddPlural("matrix", "matrices")
+	rs.AddPlural("vertix", "vertices")
+	rs.AddPlural("indix", "indices")
+	rs.AddPlural("matrex", "matrices")
+	rs.AddPlural("vertex", "vertices")
+	rs.AddPlural("index", "indices")
+	rs.AddPlural("mouse", "mice")
+	rs.AddPlural("louse", "lice")
+	rs.AddPlural("mice", "mice")
+	rs.AddPlural("lice", "lice")
+	rs.AddPlural("ress", "resses")
+	rs.AddPluralExact("ox", "oxen", true)
+	rs.AddPluralExact("oxen", "oxen", true)
+	rs.AddPluralExact("quiz", "quizzes", true)
+	rs.AddSingular("s", "")
+	rs.AddSingular("ss", "ss")
+	rs.AddSingular("news", "news")
+	rs.AddSingular("ta", "tum")
+	rs.AddSingular("ia", "ium")
+	rs.AddSingular("analyses", "analysis")
+	rs.AddSingular("bases", "basis")
+	rs.AddSingularExact("basis", "basis", true)
+	rs.AddSingular("diagnoses", "diagnosis")
+	rs.AddSingularExact("diagnosis", "diagnosis", true)
+	rs.AddSingular("parentheses", "parenthesis")
+	rs.AddSingular("prognoses", "prognosis")
+	rs.AddSingular("synopses", "synopsis")
+	rs.AddSingular("theses", "thesis")
+	rs.AddSingular("analyses", "analysis")
+	rs.AddSingularExact("analysis", "analysis", true)
+	rs.AddSingular("ovies", "ovie")
+	rs.AddSingular("aves", "afe")
+	rs.AddSingular("bves", "bfe")
+	rs.AddSingular("cves", "cfe")
+	rs.AddSingular("dves", "dfe")
+	rs.AddSingular("eves", "efe")
+	rs.AddSingular("gves", "gfe")
+	rs.AddSingular("hves", "hfe")
+	rs.AddSingular("ives", "ife")
+	rs.AddSingular("jves", "jfe")
+	rs.AddSingular("kves", "kfe")
+	rs.AddSingular("lves", "lfe")
+	rs.AddSingular("mves", "mfe")
+	rs.AddSingular("nves", "nfe")
+	rs.AddSingular("oves", "ofe")
+	rs.AddSingular("pves", "pfe")
+	rs.AddSingular("qves", "qfe")
+	rs.AddSingular("rves", "rfe")
+	rs.AddSingular("sves", "sfe")
+	rs.AddSingular("tves", "tfe")
+	rs.AddSingular("uves", "ufe")
+	rs.AddSingular("vves", "vfe")
+	rs.AddSingular("wves", "wfe")
+	rs.AddSingular("xves", "xfe")
+	rs.AddSingular("yves", "yfe")
+	rs.AddSingular("zves", "zfe")
+	rs.AddSingular("hives", "hive")
+	rs.AddSingular("tives", "tive")
+	rs.AddSingular("lves", "lf")
+	rs.AddSingular("rves", "rf")
+	rs.AddSingular("quies", "quy")
+	rs.AddSingular("bies", "by")
+	rs.AddSingular("cies", "cy")
+	rs.AddSingular("dies", "dy")
+	rs.AddSingular("fies", "fy")
+	rs.AddSingular("gies", "gy")
+	rs.AddSingular("hies", "hy")
+	rs.AddSingular("jies", "jy")
+	rs.AddSingular("kies", "ky")
+	rs.AddSingular("lies", "ly")
+	rs.AddSingular("mies", "my")
+	rs.AddSingular("nies", "ny")
+	rs.AddSingular("pies", "py")
+	rs.AddSingular("qies", "qy")
+	rs.AddSingular("ries", "ry")
+	rs.AddSingular("sies", "sy")
+	rs.AddSingular("ties", "ty")
+	// rs.AddSingular("vies", "vy")
+	rs.AddSingular("wies", "wy")
+	rs.AddSingular("xies", "xy")
+	rs.AddSingular("zies", "zy")
+	rs.AddSingular("series", "series")
+	rs.AddSingular("xes", "x")
+	rs.AddSingular("ches", "ch")
+	rs.AddSingular("sses", "ss")
+	rs.AddSingular("shes", "sh")
+	rs.AddSingular("mice", "mouse")
+	rs.AddSingular("lice", "louse")
+	rs.AddSingular("buses", "bus")
+	rs.AddSingularExact("bus", "bus", true)
+	rs.AddSingular("oes", "o")
+	rs.AddSingular("shoes", "shoe")
+	rs.AddSingular("crises", "crisis")
+	rs.AddSingularExact("crisis", "crisis", true)
+	rs.AddSingular("axes", "axis")
+	rs.AddSingularExact("axis", "axis", true)
+	rs.AddSingular("testes", "testis")
+	rs.AddSingularExact("testis", "testis", true)
+	rs.AddSingular("octopi", "octopus")
+	rs.AddSingularExact("octopus", "octopus", true)
+	rs.AddSingular("viri", "virus")
+	rs.AddSingularExact("virus", "virus", true)
+	rs.AddSingular("statuses", "status")
+	rs.AddSingular("Statuses", "Status")
+	rs.AddSingular("campuses", "campus")
+	rs.AddSingularExact("status", "status", true)
+	rs.AddSingularExact("Status", "Status", true)
+	rs.AddSingularExact("campus", "campus", true)
+	rs.AddSingular("aliases", "alias")
+	rs.AddSingularExact("alias", "alias", true)
+	rs.AddSingularExact("oxen", "ox", true)
+	rs.AddSingular("vertices", "vertex")
+	rs.AddSingular("indices", "index")
+	rs.AddSingular("matrices", "matrix")
+	rs.AddSingularExact("quizzes", "quiz", true)
+	rs.AddSingular("databases", "database")
+	rs.AddSingular("resses", "ress")
+	rs.AddSingular("ress", "ress")
+	rs.AddIrregular("person", "people")
+	rs.AddIrregular("man", "men")
+	rs.AddIrregular("child", "children")
+	rs.AddIrregular("sex", "sexes")
+	rs.AddIrregular("move", "moves")
+	rs.AddIrregular("zombie", "zombies")
+	rs.AddIrregular("Status", "Statuses")
+	rs.AddIrregular("status", "statuses")
+	rs.AddIrregular("campus", "campuses")
+	rs.AddIrregular("human", "humans")
+	rs.AddUncountable("equipment")
+	rs.AddUncountable("information")
+	rs.AddUncountable("rice")
+	rs.AddUncountable("money")
+	rs.AddUncountable("species")
+	rs.AddUncountable("series")
+	rs.AddUncountable("fish")
+	rs.AddUncountable("sheep")
+	rs.AddUncountable("jeans")
+	rs.AddUncountable("police")
+
+	acronyms := strings.Split(baseAcronyms, ",")
+	for _, acr := range acronyms {
+		rs.AddAcronym(acr)
+	}
+
+	return rs
+}
+
+// Uncountables returns a map of uncountables in the ruleset
+func (rs *Ruleset) Uncountables() map[string]bool {
+	return rs.uncountables
+}
+
+// AddPlural add a pluralization rule
+func (rs *Ruleset) AddPlural(suffix, replacement string) {
+	rs.AddPluralExact(suffix, replacement, false)
+}
+
+// AddPluralExact add a pluralization rule with full string match
+func (rs *Ruleset) AddPluralExact(suffix, replacement string, exact bool) {
+	// remove uncountable
+	delete(rs.uncountables, suffix)
+	// create rule
+	r := new(Rule)
+	r.suffix = suffix
+	r.replacement = replacement
+	r.exact = exact
+	// prepend
+	rs.plurals = append([]*Rule{r}, rs.plurals...)
+}
+
+// AddSingular add a singular rule
+func (rs *Ruleset) AddSingular(suffix, replacement string) {
+	rs.AddSingularExact(suffix, replacement, false)
+}
+
+// AddSingularExact same as AddSingular but you can set `exact` to force
+// a full string match
+func (rs *Ruleset) AddSingularExact(suffix, replacement string, exact bool) {
+	// remove from uncountable
+	delete(rs.uncountables, suffix)
+	// create rule
+	r := new(Rule)
+	r.suffix = suffix
+	r.replacement = replacement
+	r.exact = exact
+	rs.singulars = append([]*Rule{r}, rs.singulars...)
+}
+
+// AddHuman Human rules are applied by humanize to show more friendly
+// versions of words
+func (rs *Ruleset) AddHuman(suffix, replacement string) {
+	r := new(Rule)
+	r.suffix = suffix
+	r.replacement = replacement
+	rs.humans = append([]*Rule{r}, rs.humans...)
+}
+
+// AddIrregular Add any inconsistent pluralizing/singularizing rules
+// to the set here.
+func (rs *Ruleset) AddIrregular(singular, plural string) {
+	delete(rs.uncountables, singular)
+	delete(rs.uncountables, plural)
+	rs.AddPlural(singular, plural)
+	rs.AddPlural(plural, plural)
+	rs.AddSingular(plural, singular)
+}
+
+// AddAcronym if you use acronym you may need to add them to the ruleset
+// to prevent Underscored words of things like "HTML" coming out
+// as "h_t_m_l"
+func (rs *Ruleset) AddAcronym(word string) {
+	r := new(Rule)
+	r.suffix = word
+	r.replacement = rs.Titleize(strings.ToLower(word))
+	rs.acronyms = append(rs.acronyms, r)
+}
+
+// AddUncountable add a word to this ruleset that has the same singular and plural form
+// for example: "rice"
+func (rs *Ruleset) AddUncountable(word string) {
+	rs.uncountables[strings.ToLower(word)] = true
+}
+
+func (rs *Ruleset) isUncountable(word string) bool {
+	// handle multiple words by using the last one
+	words := strings.Split(word, " ")
+	if _, exists := rs.uncountables[strings.ToLower(words[len(words)-1])]; exists {
+		return true
+	}
+	return false
+}
+
+//isAcronym returns if a word is acronym or not.
+func (rs *Ruleset) isAcronym(word string) bool {
+	for _, rule := range rs.acronyms {
+		if strings.ToUpper(rule.suffix) == strings.ToUpper(word) {
+			return true
+		}
+	}
+
+	return false
+}
+
+//PluralizeWithSize pluralize with taking number into account
+func (rs *Ruleset) PluralizeWithSize(word string, size int) string {
+	if size == 1 {
+		return rs.Singularize(word)
+	}
+	return rs.Pluralize(word)
+}
+
+// Pluralize returns the plural form of a singular word
+func (rs *Ruleset) Pluralize(word string) string {
+	if len(word) == 0 {
+		return word
+	}
+	lWord := strings.ToLower(word)
+	if rs.isUncountable(lWord) {
+		return word
+	}
+
+	var candidate string
+	for _, rule := range rs.plurals {
+		if rule.exact {
+			if lWord == rule.suffix {
+				// Capitalized word
+				if lWord[0] != word[0] && lWord[1:] == word[1:] {
+					return rs.Capitalize(rule.replacement)
+				}
+				return rule.replacement
+			}
+			continue
+		}
+
+		if strings.EqualFold(word, rule.suffix) {
+			candidate = rule.replacement
+		}
+
+		if strings.HasSuffix(word, rule.suffix) {
+			return replaceLast(word, rule.suffix, rule.replacement)
+		}
+	}
+
+	if candidate != "" {
+		return candidate
+	}
+	return word + "s"
+}
+
+//Singularize returns the singular form of a plural word
+func (rs *Ruleset) Singularize(word string) string {
+	if len(word) <= 1 {
+		return word
+	}
+	lWord := strings.ToLower(word)
+	if rs.isUncountable(lWord) {
+		return word
+	}
+
+	var candidate string
+
+	for _, rule := range rs.singulars {
+		if rule.exact {
+			if lWord == rule.suffix {
+				// Capitalized word
+				if lWord[0] != word[0] && lWord[1:] == word[1:] {
+					return rs.Capitalize(rule.replacement)
+				}
+				return rule.replacement
+			}
+			continue
+		}
+
+		if strings.EqualFold(word, rule.suffix) {
+			candidate = rule.replacement
+		}
+
+		if strings.HasSuffix(word, rule.suffix) {
+			return replaceLast(word, rule.suffix, rule.replacement)
+		}
+	}
+
+	if candidate != "" {
+		return candidate
+	}
+
+	return word
+}
+
+//Capitalize uppercase first character
+func (rs *Ruleset) Capitalize(word string) string {
+	if rs.isAcronym(word) {
+		return strings.ToUpper(word)
+	}
+	return strings.ToUpper(word[:1]) + word[1:]
+}
+
+//Camelize "dino_party" -> "DinoParty"
+func (rs *Ruleset) Camelize(word string) string {
+	if rs.isAcronym(word) {
+		return strings.ToUpper(word)
+	}
+	words := splitAtCaseChangeWithTitlecase(word)
+	return strings.Join(words, "")
+}
+
+//CamelizeDownFirst same as Camelcase but with first letter downcased
+func (rs *Ruleset) CamelizeDownFirst(word string) string {
+	word = Camelize(word)
+	return strings.ToLower(word[:1]) + word[1:]
+}
+
+//Titleize Capitalize every word in sentence "hello there" -> "Hello There"
+func (rs *Ruleset) Titleize(word string) string {
+	words := splitAtCaseChangeWithTitlecase(word)
+	result := strings.Join(words, " ")
+
+	var acronymWords []string
+	for index, word := range words {
+		if len(word) == 1 {
+			acronymWords = append(acronymWords, word)
+		}
+
+		if len(word) > 1 || index == len(words)-1 || len(acronymWords) > 1 {
+			acronym := strings.Join(acronymWords, "")
+			if !rs.isAcronym(acronym) {
+				acronymWords = acronymWords[:len(acronymWords)]
+				continue
+			}
+
+			result = strings.Replace(result, strings.Join(acronymWords, " "), acronym, 1)
+			acronymWords = []string{}
+		}
+	}
+
+	return result
+}
+
+func (rs *Ruleset) safeCaseAcronyms(word string) string {
+	// convert an acronym like HTML into Html
+	for _, rule := range rs.acronyms {
+		word = strings.Replace(word, rule.suffix, rule.replacement, -1)
+	}
+	return word
+}
+
+func (rs *Ruleset) separatedWords(word, sep string) string {
+	word = rs.safeCaseAcronyms(word)
+	words := splitAtCaseChange(word)
+	return strings.Join(words, sep)
+}
+
+//Underscore lowercase underscore version "BigBen" -> "big_ben"
+func (rs *Ruleset) Underscore(word string) string {
+	return rs.separatedWords(word, "_")
+}
+
+//Humanize First letter of sentence capitalized
+// Uses custom friendly replacements via AddHuman()
+func (rs *Ruleset) Humanize(word string) string {
+	word = replaceLast(word, "_id", "") // strip foreign key kinds
+	// replace and strings in humans list
+	for _, rule := range rs.humans {
+		word = strings.Replace(word, rule.suffix, rule.replacement, -1)
+	}
+	sentence := rs.separatedWords(word, " ")
+
+	r, n := utf8.DecodeRuneInString(sentence)
+	return string(unicode.ToUpper(r)) + sentence[n:]
+}
+
+//ForeignKey an underscored foreign key name "Person" -> "person_id"
+func (rs *Ruleset) ForeignKey(word string) string {
+	return rs.Underscore(rs.Singularize(word)) + "_id"
+}
+
+//ForeignKeyCondensed a foreign key (with an underscore) "Person" -> "personid"
+func (rs *Ruleset) ForeignKeyCondensed(word string) string {
+	return rs.Underscore(word) + "id"
+}
+
+//Tableize Rails style pluralized table names: "SuperPerson" -> "super_people"
+func (rs *Ruleset) Tableize(word string) string {
+	return rs.Pluralize(rs.Underscore(rs.Typeify(word)))
+}
+
+var notUrlSafe *regexp.Regexp = regexp.MustCompile(`[^\w\d\-_ ]`)
+
+//Parameterize param safe dasherized names like "my-param"
+func (rs *Ruleset) Parameterize(word string) string {
+	return ParameterizeJoin(word, "-")
+}
+
+//ParameterizeJoin param safe dasherized names with custom separator
+func (rs *Ruleset) ParameterizeJoin(word, sep string) string {
+	word = strings.ToLower(word)
+	word = rs.Asciify(word)
+	word = notUrlSafe.ReplaceAllString(word, "")
+	word = strings.Replace(word, " ", sep, -1)
+	if len(sep) > 0 {
+		squash, err := regexp.Compile(sep + "+")
+		if err == nil {
+			word = squash.ReplaceAllString(word, sep)
+		}
+	}
+	word = strings.Trim(word, sep+" ")
+	return word
+}
+
+var lookalikes = map[string]*regexp.Regexp{
+	"A":  regexp.MustCompile(`À|Á|Â|Ã|Ä|Å`),
+	"AE": regexp.MustCompile(`Æ`),
+	"C":  regexp.MustCompile(`Ç`),
+	"E":  regexp.MustCompile(`È|É|Ê|Ë`),
+	"G":  regexp.MustCompile(`Ğ`),
+	"I":  regexp.MustCompile(`Ì|Í|Î|Ï|İ`),
+	"N":  regexp.MustCompile(`Ñ`),
+	"O":  regexp.MustCompile(`Ò|Ó|Ô|Õ|Ö|Ø`),
+	"S":  regexp.MustCompile(`Ş`),
+	"U":  regexp.MustCompile(`Ù|Ú|Û|Ü`),
+	"Y":  regexp.MustCompile(`Ý`),
+	"ss": regexp.MustCompile(`ß`),
+	"a":  regexp.MustCompile(`à|á|â|ã|ä|å`),
+	"ae": regexp.MustCompile(`æ`),
+	"c":  regexp.MustCompile(`ç`),
+	"e":  regexp.MustCompile(`è|é|ê|ë`),
+	"g":  regexp.MustCompile(`ğ`),
+	"i":  regexp.MustCompile(`ì|í|î|ï|ı`),
+	"n":  regexp.MustCompile(`ñ`),
+	"o":  regexp.MustCompile(`ò|ó|ô|õ|ö|ø`),
+	"s":  regexp.MustCompile(`ş`),
+	"u":  regexp.MustCompile(`ù|ú|û|ü|ũ|ū|ŭ|ů|ű|ų`),
+	"y":  regexp.MustCompile(`ý|ÿ`),
+}
+
+//Asciify transforms Latin characters like é -> e
+func (rs *Ruleset) Asciify(word string) string {
+	for repl, regex := range lookalikes {
+		word = regex.ReplaceAllString(word, repl)
+	}
+	return word
+}
+
+var tablePrefix = regexp.MustCompile(`^[^.]*\.`)
+
+//Typeify "something_like_this" -> "SomethingLikeThis"
+func (rs *Ruleset) Typeify(word string) string {
+	word = tablePrefix.ReplaceAllString(word, "")
+	return rs.Camelize(rs.Singularize(word))
+}
+
+//Dasherize "SomeText" -> "some-text"
+func (rs *Ruleset) Dasherize(word string) string {
+	return rs.separatedWords(word, "-")
+}
+
+//Ordinalize "1031" -> "1031st"
+func (rs *Ruleset) Ordinalize(str string) string {
+	number, err := strconv.Atoi(str)
+	if err != nil {
+		return str
+	}
+	switch abs(number) % 100 {
+	case 11, 12, 13:
+		return fmt.Sprintf("%dth", number)
+	default:
+		switch abs(number) % 10 {
+		case 1:
+			return fmt.Sprintf("%dst", number)
+		case 2:
+			return fmt.Sprintf("%dnd", number)
+		case 3:
+			return fmt.Sprintf("%drd", number)
+		}
+	}
+	return fmt.Sprintf("%dth", number)
+}
+
+//ForeignKeyToAttribute returns the attribute name from the foreign key
+func (rs *Ruleset) ForeignKeyToAttribute(str string) string {
+	w := rs.Camelize(str)
+	if strings.HasSuffix(w, "Id") {
+		return strings.TrimSuffix(w, "Id") + "ID"
+	}
+	return w
+}
+
+//LoadReader loads rules from io.Reader param
+func (rs *Ruleset) LoadReader(r io.Reader) error {
+	m := map[string]string{}
+	err := json.NewDecoder(r).Decode(&m)
+	if err != nil {
+		return fmt.Errorf("could not decode inflection JSON from reader: %s", err)
+	}
+	for s, p := range m {
+		defaultRuleset.AddIrregular(s, p)
+	}
+	return nil
+}
+
+/////////////////////////////////////////
+// the default global ruleset
+//////////////////////////////////////////
+
+var defaultRuleset *Ruleset
+
+//LoadReader loads rules from io.Reader param
+func LoadReader(r io.Reader) error {
+	return defaultRuleset.LoadReader(r)
+}
+
+func init() {
+	defaultRuleset = NewDefaultRuleset()
+
+	pwd, _ := os.Getwd()
+	cfg := filepath.Join(pwd, "inflections.json")
+	if p := os.Getenv("INFLECT_PATH"); p != "" {
+		cfg = p
+	}
+	if _, err := os.Stat(cfg); err == nil {
+		b, err := ioutil.ReadFile(cfg)
+		if err != nil {
+			fmt.Printf("could not read inflection file %s (%s)\n", cfg, err)
+			return
+		}
+		if err = defaultRuleset.LoadReader(bytes.NewReader(b)); err != nil {
+			fmt.Println(err)
+		}
+	}
+}
+
+//Uncountables returns a list of uncountables rules
+func Uncountables() map[string]bool {
+	return defaultRuleset.Uncountables()
+}
+
+//AddPlural adds plural to the ruleset
+func AddPlural(suffix, replacement string) {
+	defaultRuleset.AddPlural(suffix, replacement)
+}
+
+//AddSingular adds singular to the ruleset
+func AddSingular(suffix, replacement string) {
+	defaultRuleset.AddSingular(suffix, replacement)
+}
+
+//AddHuman adds human
+func AddHuman(suffix, replacement string) {
+	defaultRuleset.AddHuman(suffix, replacement)
+}
+
+func AddIrregular(singular, plural string) {
+	defaultRuleset.AddIrregular(singular, plural)
+}
+
+func AddAcronym(word string) {
+	defaultRuleset.AddAcronym(word)
+}
+
+func AddUncountable(word string) {
+	defaultRuleset.AddUncountable(word)
+}
+
+func Pluralize(word string) string {
+	return defaultRuleset.Pluralize(word)
+}
+
+func PluralizeWithSize(word string, size int) string {
+	return defaultRuleset.PluralizeWithSize(word, size)
+}
+
+func Singularize(word string) string {
+	return defaultRuleset.Singularize(word)
+}
+
+func Capitalize(word string) string {
+	return defaultRuleset.Capitalize(word)
+}
+
+func Camelize(word string) string {
+	return defaultRuleset.Camelize(word)
+}
+
+func CamelizeDownFirst(word string) string {
+	return defaultRuleset.CamelizeDownFirst(word)
+}
+
+func Titleize(word string) string {
+	return defaultRuleset.Titleize(word)
+}
+
+func Underscore(word string) string {
+	return defaultRuleset.Underscore(word)
+}
+
+func Humanize(word string) string {
+	return defaultRuleset.Humanize(word)
+}
+
+func ForeignKey(word string) string {
+	return defaultRuleset.ForeignKey(word)
+}
+
+func ForeignKeyCondensed(word string) string {
+	return defaultRuleset.ForeignKeyCondensed(word)
+}
+
+func Tableize(word string) string {
+	return defaultRuleset.Tableize(word)
+}
+
+func Parameterize(word string) string {
+	return defaultRuleset.Parameterize(word)
+}
+
+func ParameterizeJoin(word, sep string) string {
+	return defaultRuleset.ParameterizeJoin(word, sep)
+}
+
+func Typeify(word string) string {
+	return defaultRuleset.Typeify(word)
+}
+
+func Dasherize(word string) string {
+	return defaultRuleset.Dasherize(word)
+}
+
+func Ordinalize(word string) string {
+	return defaultRuleset.Ordinalize(word)
+}
+
+func Asciify(word string) string {
+	return defaultRuleset.Asciify(word)
+}
+
+func ForeignKeyToAttribute(word string) string {
+	return defaultRuleset.ForeignKeyToAttribute(word)
+}
+
+// helper funcs
+
+func reverse(s string) string {
+	o := make([]rune, utf8.RuneCountInString(s))
+	i := len(o)
+	for _, c := range s {
+		i--
+		o[i] = c
+	}
+	return string(o)
+}
+
+func isSpacerChar(c rune) bool {
+	switch {
+	case c == rune("_"[0]):
+		return true
+	case c == rune(" "[0]):
+		return true
+	case c == rune(":"[0]):
+		return true
+	case c == rune("-"[0]):
+		return true
+	}
+	return false
+}
+
+func splitAtCaseChange(s string) []string {
+	words := make([]string, 0)
+	word := make([]rune, 0)
+	for _, c := range s {
+		spacer := isSpacerChar(c)
+		if len(word) > 0 {
+			if unicode.IsUpper(c) || spacer {
+				words = append(words, string(word))
+				word = make([]rune, 0)
+			}
+		}
+		if !spacer {
+			word = append(word, unicode.ToLower(c))
+		}
+	}
+	words = append(words, string(word))
+	return words
+}
+
+func splitAtCaseChangeWithTitlecase(s string) []string {
+	words := make([]string, 0)
+	word := make([]rune, 0)
+
+	for _, c := range s {
+		spacer := isSpacerChar(c)
+		if len(word) > 0 {
+			if unicode.IsUpper(c) || spacer {
+				words = append(words, string(word))
+				word = make([]rune, 0)
+			}
+		}
+		if !spacer {
+			if len(word) > 0 {
+				word = append(word, unicode.ToLower(c))
+			} else {
+				word = append(word, unicode.ToUpper(c))
+			}
+		}
+	}
+
+	words = append(words, string(word))
+	return words
+}
+
+func replaceLast(s, match, repl string) string {
+	// reverse strings
+	srev := reverse(s)
+	mrev := reverse(match)
+	rrev := reverse(repl)
+	// match first and reverse back
+	return reverse(strings.Replace(srev, mrev, rrev, 1))
+}
+
+func abs(x int) int {
+	if x < 0 {
+		return -x
+	}
+	return x
+}
diff --git a/vendor/github.com/markbates/inflect/name.go b/vendor/github.com/markbates/inflect/name.go
new file mode 100644
index 0000000..e6863e2
--- /dev/null
+++ b/vendor/github.com/markbates/inflect/name.go
@@ -0,0 +1,163 @@
+package inflect
+
+import (
+	"fmt"
+	"path/filepath"
+	"strings"
+
+	"github.com/gobuffalo/envy"
+)
+
+// Name is a string that represents the "name" of a thing, like an app, model, etc...
+type Name string
+
+// Title version of a name. ie. "foo_bar" => "Foo Bar"
+func (n Name) Title() string {
+	x := strings.Split(string(n), "/")
+	for i, s := range x {
+		x[i] = Titleize(s)
+	}
+
+	return strings.Join(x, " ")
+}
+
+// Underscore version of a name. ie. "FooBar" => "foo_bar"
+func (n Name) Underscore() string {
+	w := string(n)
+	if strings.ToUpper(w) == w {
+		return strings.ToLower(w)
+	}
+	return Underscore(w)
+}
+
+// Plural version of a name
+func (n Name) Plural() string {
+	return Pluralize(string(n))
+}
+
+// Singular version of a name
+func (n Name) Singular() string {
+	return Singularize(string(n))
+}
+
+// Camel version of a name
+func (n Name) Camel() string {
+	c := Camelize(string(n))
+	if strings.HasSuffix(c, "Id") {
+		c = strings.TrimSuffix(c, "Id")
+		c += "ID"
+	}
+	return c
+}
+
+// Model version of a name. ie. "user" => "User"
+func (n Name) Model() string {
+	x := strings.Split(string(n), "/")
+	for i, s := range x {
+		x[i] = Camelize(Singularize(s))
+	}
+
+	return strings.Join(x, "")
+}
+
+// Resource version of a name
+func (n Name) Resource() string {
+	name := n.Underscore()
+	x := strings.FieldsFunc(name, func(r rune) bool {
+		return r == '_' || r == '/'
+	})
+
+	for i, w := range x {
+		if i == len(x)-1 {
+			x[i] = Camelize(Pluralize(strings.ToLower(w)))
+			continue
+		}
+
+		x[i] = Camelize(w)
+	}
+
+	return strings.Join(x, "")
+}
+
+// ModelPlural version of a name. ie. "user" => "Users"
+func (n Name) ModelPlural() string {
+	return Camelize(Pluralize(n.Model()))
+}
+
+// File version of a name
+func (n Name) File() string {
+	return Underscore(Camelize(string(n)))
+}
+
+// Table version of a name
+func (n Name) Table() string {
+	return Underscore(Pluralize(string(n)))
+}
+
+// UnderSingular version of a name
+func (n Name) UnderSingular() string {
+	return Underscore(Singularize(string(n)))
+}
+
+// PluralCamel version of a name
+func (n Name) PluralCamel() string {
+	return Pluralize(Camelize(string(n)))
+}
+
+// PluralUnder version of a name
+func (n Name) PluralUnder() string {
+	return Pluralize(Underscore(string(n)))
+}
+
+// URL version of a name
+func (n Name) URL() string {
+	return n.PluralUnder()
+}
+
+// CamelSingular version of a name
+func (n Name) CamelSingular() string {
+	return Camelize(Singularize(string(n)))
+}
+
+// VarCaseSingular version of a name. ie. "FooBar" => "fooBar"
+func (n Name) VarCaseSingular() string {
+	return CamelizeDownFirst(Singularize(Underscore(n.Resource())))
+}
+
+// VarCasePlural version of a name. ie. "FooBar" => "fooBar"
+func (n Name) VarCasePlural() string {
+	return CamelizeDownFirst(n.Resource())
+}
+
+// Lower case version of a string
+func (n Name) Lower() string {
+	return strings.ToLower(string(n))
+}
+
+// ParamID returns foo_bar_id
+func (n Name) ParamID() string {
+	return fmt.Sprintf("%s_id", strings.Replace(n.UnderSingular(), "/", "_", -1))
+}
+
+// Package returns go package
+func (n Name) Package() string {
+	key := string(n)
+
+	for _, gp := range envy.GoPaths() {
+		key = strings.TrimPrefix(key, filepath.Join(gp, "src"))
+		key = strings.TrimPrefix(key, gp)
+	}
+	key = strings.TrimPrefix(key, string(filepath.Separator))
+
+	key = strings.Replace(key, "\\", "/", -1)
+	return key
+}
+
+// Char returns first character in lower case, this is useful for methods inside a struct.
+func (n Name) Char() string {
+	return strings.ToLower(string(n[0]))
+}
+
+func (n Name) String() string {
+	return string(n)
+}
diff --git a/vendor/github.com/markbates/inflect/version.go b/vendor/github.com/markbates/inflect/version.go
new file mode 100644
index 0000000..a167449
--- /dev/null
+++ b/vendor/github.com/markbates/inflect/version.go
@@ -0,0 +1,3 @@
+package inflect
+
+const Version = "v1.0.4"
diff --git a/vendor/go.uber.org/atomic/LICENSE.txt b/vendor/go.uber.org/atomic/LICENSE.txt
new file mode 100644
index 0000000..8765c9f
--- /dev/null
+++ b/vendor/go.uber.org/atomic/LICENSE.txt
@@ -0,0 +1,19 @@
+Copyright (c) 2016 Uber Technologies, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/go.uber.org/atomic/atomic.go b/vendor/go.uber.org/atomic/atomic.go
new file mode 100644
index 0000000..1db6849
--- /dev/null
+++ b/vendor/go.uber.org/atomic/atomic.go
@@ -0,0 +1,351 @@
+// Copyright (c) 2016 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+// Package atomic provides simple wrappers around numerics to enforce atomic
+// access.
+package atomic
+
+import (
+	"math"
+	"sync/atomic"
+	"time"
+)
+
+// Int32 is an atomic wrapper around an int32.
+type Int32 struct{ v int32 }
+
+// NewInt32 creates an Int32.
+func NewInt32(i int32) *Int32 {
+	return &Int32{i}
+}
+
+// Load atomically loads the wrapped value.
+func (i *Int32) Load() int32 {
+	return atomic.LoadInt32(&i.v)
+}
+
+// Add atomically adds to the wrapped int32 and returns the new value.
+func (i *Int32) Add(n int32) int32 {
+	return atomic.AddInt32(&i.v, n)
+}
+
+// Sub atomically subtracts from the wrapped int32 and returns the new value.
+func (i *Int32) Sub(n int32) int32 {
+	return atomic.AddInt32(&i.v, -n)
+}
+
+// Inc atomically increments the wrapped int32 and returns the new value.
+func (i *Int32) Inc() int32 {
+	return i.Add(1)
+}
+
+// Dec atomically decrements the wrapped int32 and returns the new value.
+func (i *Int32) Dec() int32 {
+	return i.Sub(1)
+}
+
+// CAS is an atomic compare-and-swap.
+func (i *Int32) CAS(old, new int32) bool {
+	return atomic.CompareAndSwapInt32(&i.v, old, new)
+}
+
+// Store atomically stores the passed value.
+func (i *Int32) Store(n int32) {
+	atomic.StoreInt32(&i.v, n)
+}
+
+// Swap atomically swaps the wrapped int32 and returns the old value.
+func (i *Int32) Swap(n int32) int32 {
+	return atomic.SwapInt32(&i.v, n)
+}
+
+// Int64 is an atomic wrapper around an int64.
+type Int64 struct{ v int64 }
+
+// NewInt64 creates an Int64.
+func NewInt64(i int64) *Int64 {
+	return &Int64{i}
+}
+
+// Load atomically loads the wrapped value.
+func (i *Int64) Load() int64 {
+	return atomic.LoadInt64(&i.v)
+}
+
+// Add atomically adds to the wrapped int64 and returns the new value.
+func (i *Int64) Add(n int64) int64 {
+	return atomic.AddInt64(&i.v, n)
+}
+
+// Sub atomically subtracts from the wrapped int64 and returns the new value.
+func (i *Int64) Sub(n int64) int64 {
+	return atomic.AddInt64(&i.v, -n)
+}
+
+// Inc atomically increments the wrapped int64 and returns the new value.
+func (i *Int64) Inc() int64 {
+	return i.Add(1)
+}
+
+// Dec atomically decrements the wrapped int64 and returns the new value.
+func (i *Int64) Dec() int64 {
+	return i.Sub(1)
+}
+
+// CAS is an atomic compare-and-swap.
+func (i *Int64) CAS(old, new int64) bool {
+	return atomic.CompareAndSwapInt64(&i.v, old, new)
+}
+
+// Store atomically stores the passed value.
+func (i *Int64) Store(n int64) {
+	atomic.StoreInt64(&i.v, n)
+}
+
+// Swap atomically swaps the wrapped int64 and returns the old value.
+func (i *Int64) Swap(n int64) int64 {
+	return atomic.SwapInt64(&i.v, n)
+}
+
+// Uint32 is an atomic wrapper around an uint32.
+type Uint32 struct{ v uint32 }
+
+// NewUint32 creates a Uint32.
+func NewUint32(i uint32) *Uint32 {
+	return &Uint32{i}
+}
+
+// Load atomically loads the wrapped value.
+func (i *Uint32) Load() uint32 {
+	return atomic.LoadUint32(&i.v)
+}
+
+// Add atomically adds to the wrapped uint32 and returns the new value.
+func (i *Uint32) Add(n uint32) uint32 {
+	return atomic.AddUint32(&i.v, n)
+}
+
+// Sub atomically subtracts from the wrapped uint32 and returns the new value.
+func (i *Uint32) Sub(n uint32) uint32 {
+	return atomic.AddUint32(&i.v, ^(n - 1))
+}
+
+// Inc atomically increments the wrapped uint32 and returns the new value.
+func (i *Uint32) Inc() uint32 {
+	return i.Add(1)
+}
+
+// Dec atomically decrements the wrapped int32 and returns the new value.
+func (i *Uint32) Dec() uint32 {
+	return i.Sub(1)
+}
+
+// CAS is an atomic compare-and-swap.
+func (i *Uint32) CAS(old, new uint32) bool {
+	return atomic.CompareAndSwapUint32(&i.v, old, new)
+}
+
+// Store atomically stores the passed value.
+func (i *Uint32) Store(n uint32) {
+	atomic.StoreUint32(&i.v, n)
+}
+
+// Swap atomically swaps the wrapped uint32 and returns the old value.
+func (i *Uint32) Swap(n uint32) uint32 {
+	return atomic.SwapUint32(&i.v, n)
+}
+
+// Uint64 is an atomic wrapper around a uint64.
+type Uint64 struct{ v uint64 }
+
+// NewUint64 creates a Uint64.
+func NewUint64(i uint64) *Uint64 {
+	return &Uint64{i}
+}
+
+// Load atomically loads the wrapped value.
+func (i *Uint64) Load() uint64 {
+	return atomic.LoadUint64(&i.v)
+}
+
+// Add atomically adds to the wrapped uint64 and returns the new value.
+func (i *Uint64) Add(n uint64) uint64 {
+	return atomic.AddUint64(&i.v, n)
+}
+
+// Sub atomically subtracts from the wrapped uint64 and returns the new value.
+func (i *Uint64) Sub(n uint64) uint64 {
+	return atomic.AddUint64(&i.v, ^(n - 1))
+}
+
+// Inc atomically increments the wrapped uint64 and returns the new value.
+func (i *Uint64) Inc() uint64 {
+	return i.Add(1)
+}
+
+// Dec atomically decrements the wrapped uint64 and returns the new value.
+func (i *Uint64) Dec() uint64 {
+	return i.Sub(1)
+}
+
+// CAS is an atomic compare-and-swap.
+func (i *Uint64) CAS(old, new uint64) bool {
+	return atomic.CompareAndSwapUint64(&i.v, old, new)
+}
+
+// Store atomically stores the passed value.
+func (i *Uint64) Store(n uint64) {
+	atomic.StoreUint64(&i.v, n)
+}
+
+// Swap atomically swaps the wrapped uint64 and returns the old value.
+func (i *Uint64) Swap(n uint64) uint64 {
+	return atomic.SwapUint64(&i.v, n)
+}
+
+// Bool is an atomic Boolean.
+type Bool struct{ v uint32 }
+
+// NewBool creates a Bool.
+func NewBool(initial bool) *Bool {
+	return &Bool{boolToInt(initial)}
+}
+
+// Load atomically loads the Boolean.
+func (b *Bool) Load() bool {
+	return truthy(atomic.LoadUint32(&b.v))
+}
+
+// CAS is an atomic compare-and-swap.
+func (b *Bool) CAS(old, new bool) bool {
+	return atomic.CompareAndSwapUint32(&b.v, boolToInt(old), boolToInt(new))
+}
+
+// Store atomically stores the passed value.
+func (b *Bool) Store(new bool) {
+	atomic.StoreUint32(&b.v, boolToInt(new))
+}
+
+// Swap sets the given value and returns the previous value.
+func (b *Bool) Swap(new bool) bool {
+	return truthy(atomic.SwapUint32(&b.v, boolToInt(new)))
+}
+
+// Toggle atomically negates the Boolean and returns the previous value.
+func (b *Bool) Toggle() bool {
+	return truthy(atomic.AddUint32(&b.v, 1) - 1)
+}
+
+func truthy(n uint32) bool {
+	return n&1 == 1
+}
+
+func boolToInt(b bool) uint32 {
+	if b {
+		return 1
+	}
+	return 0
+}
+
+// Float64 is an atomic wrapper around float64.
+type Float64 struct {
+	v uint64
+}
+
+// NewFloat64 creates a Float64.
+func NewFloat64(f float64) *Float64 {
+	return &Float64{math.Float64bits(f)}
+}
+
+// Load atomically loads the wrapped value.
+func (f *Float64) Load() float64 {
+	return math.Float64frombits(atomic.LoadUint64(&f.v))
+}
+
+// Store atomically stores the passed value.
+func (f *Float64) Store(s float64) {
+	atomic.StoreUint64(&f.v, math.Float64bits(s))
+}
+
+// Add atomically adds to the wrapped float64 and returns the new value.
+func (f *Float64) Add(s float64) float64 {
+	for {
+		old := f.Load()
+		new := old + s
+		if f.CAS(old, new) {
+			return new
+		}
+	}
+}
+
+// Sub atomically subtracts from the wrapped float64 and returns the new value.
+func (f *Float64) Sub(s float64) float64 {
+	return f.Add(-s)
+}
+
+// CAS is an atomic compare-and-swap.
+func (f *Float64) CAS(old, new float64) bool {
+	return atomic.CompareAndSwapUint64(&f.v, math.Float64bits(old), math.Float64bits(new))
+}
+
+// Duration is an atomic wrapper around time.Duration
+// https://godoc.org/time#Duration
+type Duration struct {
+	v Int64
+}
+
+// NewDuration creates a Duration.
+func NewDuration(d time.Duration) *Duration {
+	return &Duration{v: *NewInt64(int64(d))}
+}
+
+// Load atomically loads the wrapped value.
+func (d *Duration) Load() time.Duration {
+	return time.Duration(d.v.Load())
+}
+
+// Store atomically stores the passed value.
+func (d *Duration) Store(n time.Duration) {
+	d.v.Store(int64(n))
+}
+
+// Add atomically adds to the wrapped time.Duration and returns the new value.
+func (d *Duration) Add(n time.Duration) time.Duration {
+	return time.Duration(d.v.Add(int64(n)))
+}
+
+// Sub atomically subtracts from the wrapped time.Duration and returns the new value.
+func (d *Duration) Sub(n time.Duration) time.Duration {
+	return time.Duration(d.v.Sub(int64(n)))
+}
+
+// Swap atomically swaps the wrapped time.Duration and returns the old value.
+func (d *Duration) Swap(n time.Duration) time.Duration {
+	return time.Duration(d.v.Swap(int64(n)))
+}
+
+// CAS is an atomic compare-and-swap.
+func (d *Duration) CAS(old, new time.Duration) bool {
+	return d.v.CAS(int64(old), int64(new))
+}
+
+// Value shadows the type of the same name from sync/atomic
+// https://godoc.org/sync/atomic#Value
+type Value struct{ atomic.Value }
diff --git a/vendor/go.uber.org/atomic/string.go b/vendor/go.uber.org/atomic/string.go
new file mode 100644
index 0000000..ede8136
--- /dev/null
+++ b/vendor/go.uber.org/atomic/string.go
@@ -0,0 +1,49 @@
+// Copyright (c) 2016 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package atomic
+
+// String is an atomic type-safe wrapper around Value for strings.
+type String struct{ v Value }
+
+// NewString creates a String.
+func NewString(str string) *String {
+	s := &String{}
+	if str != "" {
+		s.Store(str)
+	}
+	return s
+}
+
+// Load atomically loads the wrapped string.
+func (s *String) Load() string {
+	v := s.v.Load()
+	if v == nil {
+		return ""
+	}
+	return v.(string)
+}
+
+// Store atomically stores the passed string.
+// Note: Converting the string to an interface{} to store in the Value
+// requires an allocation.
+func (s *String) Store(str string) {
+	s.v.Store(str)
+}
diff --git a/vendor/go.uber.org/multierr/LICENSE.txt b/vendor/go.uber.org/multierr/LICENSE.txt
new file mode 100644
index 0000000..858e024
--- /dev/null
+++ b/vendor/go.uber.org/multierr/LICENSE.txt
@@ -0,0 +1,19 @@
+Copyright (c) 2017 Uber Technologies, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/go.uber.org/multierr/error.go b/vendor/go.uber.org/multierr/error.go
new file mode 100644
index 0000000..de6ce47
--- /dev/null
+++ b/vendor/go.uber.org/multierr/error.go
@@ -0,0 +1,401 @@
+// Copyright (c) 2017 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+// Package multierr allows combining one or more errors together.
+//
+// Overview
+//
+// Errors can be combined with the use of the Combine function.
+//
+// 	multierr.Combine(
+// 		reader.Close(),
+// 		writer.Close(),
+// 		conn.Close(),
+// 	)
+//
+// If only two errors are being combined, the Append function may be used
+// instead.
+//
+// 	err = multierr.Combine(reader.Close(), writer.Close())
+//
+// This makes it possible to record resource cleanup failures from deferred
+// blocks with the help of named return values.
+//
+// 	func sendRequest(req Request) (err error) {
+// 		conn, err := openConnection()
+// 		if err != nil {
+// 			return err
+// 		}
+// 		defer func() {
+// 			err = multierr.Append(err, conn.Close())
+// 		}()
+// 		// ...
+// 	}
+//
+// The underlying list of errors for a returned error object may be retrieved
+// with the Errors function.
+//
+// 	errors := multierr.Errors(err)
+// 	if len(errors) > 0 {
+// 		fmt.Println("The following errors occurred:")
+// 	}
+//
+// Advanced Usage
+//
+// Errors returned by Combine and Append MAY implement the following
+// interface.
+//
+// 	type errorGroup interface {
+// 		// Returns a slice containing the underlying list of errors.
+// 		//
+// 		// This slice MUST NOT be modified by the caller.
+// 		Errors() []error
+// 	}
+//
+// Note that if you need access to list of errors behind a multierr error, you
+// should prefer using the Errors function. That said, if you need cheap
+// read-only access to the underlying errors slice, you can attempt to cast
+// the error to this interface. You MUST handle the failure case gracefully
+// because errors returned by Combine and Append are not guaranteed to
+// implement this interface.
+//
+// 	var errors []error
+// 	group, ok := err.(errorGroup)
+// 	if ok {
+// 		errors = group.Errors()
+// 	} else {
+// 		errors = []error{err}
+// 	}
+package multierr // import "go.uber.org/multierr"
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"strings"
+	"sync"
+
+	"go.uber.org/atomic"
+)
+
+var (
+	// Separator for single-line error messages.
+	_singlelineSeparator = []byte("; ")
+
+	_newline = []byte("\n")
+
+	// Prefix for multi-line messages
+	_multilinePrefix = []byte("the following errors occurred:")
+
+	// Prefix for the first and following lines of an item in a list of
+	// multi-line error messages.
+	//
+	// For example, if a single item is:
+	//
+	// 	foo
+	// 	bar
+	//
+	// It will become,
+	//
+	// 	 -  foo
+	// 	    bar
+	_multilineSeparator = []byte("\n -  ")
+	_multilineIndent    = []byte("    ")
+)
+
+// _bufferPool is a pool of bytes.Buffers.
+var _bufferPool = sync.Pool{
+	New: func() interface{} {
+		return &bytes.Buffer{}
+	},
+}
+
+type errorGroup interface {
+	Errors() []error
+}
+
+// Errors returns a slice containing zero or more errors that the supplied
+// error is composed of. If the error is nil, the returned slice is empty.
+//
+// 	err := multierr.Append(r.Close(), w.Close())
+// 	errors := multierr.Errors(err)
+//
+// If the error is not composed of other errors, the returned slice contains
+// just the error that was passed in.
+//
+// Callers of this function are free to modify the returned slice.
+func Errors(err error) []error {
+	if err == nil {
+		return nil
+	}
+
+	// Note that we're casting to multiError, not errorGroup. Our contract is
+	// that returned errors MAY implement errorGroup. Errors, however, only
+	// has special behavior for multierr-specific error objects.
+	//
+	// This behavior can be expanded in the future but I think it's prudent to
+	// start with as little as possible in terms of contract and possibility
+	// of misuse.
+	eg, ok := err.(*multiError)
+	if !ok {
+		return []error{err}
+	}
+
+	errors := eg.Errors()
+	result := make([]error, len(errors))
+	copy(result, errors)
+	return result
+}
+
+// multiError is an error that holds one or more errors.
+//
+// An instance of this is guaranteed to be non-empty and flattened. That is,
+// none of the errors inside multiError are other multiErrors.
+//
+// multiError formats to a semi-colon delimited list of error messages with
+// %v and with a more readable multi-line format with %+v.
+type multiError struct {
+	copyNeeded atomic.Bool
+	errors     []error
+}
+
+var _ errorGroup = (*multiError)(nil)
+
+// Errors returns the list of underlying errors.
+//
+// This slice MUST NOT be modified.
+func (merr *multiError) Errors() []error {
+	if merr == nil {
+		return nil
+	}
+	return merr.errors
+}
+
+func (merr *multiError) Error() string {
+	if merr == nil {
+		return ""
+	}
+
+	buff := _bufferPool.Get().(*bytes.Buffer)
+	buff.Reset()
+
+	merr.writeSingleline(buff)
+
+	result := buff.String()
+	_bufferPool.Put(buff)
+	return result
+}
+
+func (merr *multiError) Format(f fmt.State, c rune) {
+	if c == 'v' && f.Flag('+') {
+		merr.writeMultiline(f)
+	} else {
+		merr.writeSingleline(f)
+	}
+}
+
+func (merr *multiError) writeSingleline(w io.Writer) {
+	first := true
+	for _, item := range merr.errors {
+		if first {
+			first = false
+		} else {
+			w.Write(_singlelineSeparator)
+		}
+		io.WriteString(w, item.Error())
+	}
+}
+
+func (merr *multiError) writeMultiline(w io.Writer) {
+	w.Write(_multilinePrefix)
+	for _, item := range merr.errors {
+		w.Write(_multilineSeparator)
+		writePrefixLine(w, _multilineIndent, fmt.Sprintf("%+v", item))
+	}
+}
+
+// Writes s to the writer with the given prefix added before each line after
+// the first.
+func writePrefixLine(w io.Writer, prefix []byte, s string) {
+	first := true
+	for len(s) > 0 {
+		if first {
+			first = false
+		} else {
+			w.Write(prefix)
+		}
+
+		idx := strings.IndexByte(s, '\n')
+		if idx < 0 {
+			idx = len(s) - 1
+		}
+
+		io.WriteString(w, s[:idx+1])
+		s = s[idx+1:]
+	}
+}
+
+type inspectResult struct {
+	// Number of top-level non-nil errors
+	Count int
+
+	// Total number of errors including multiErrors
+	Capacity int
+
+	// Index of the first non-nil error in the list. Value is meaningless if
+	// Count is zero.
+	FirstErrorIdx int
+
+	// Whether the list contains at least one multiError
+	ContainsMultiError bool
+}
+
+// Inspects the given slice of errors so that we can efficiently allocate
+// space for it.
+func inspect(errors []error) (res inspectResult) {
+	first := true
+	for i, err := range errors {
+		if err == nil {
+			continue
+		}
+
+		res.Count++
+		if first {
+			first = false
+			res.FirstErrorIdx = i
+		}
+
+		if merr, ok := err.(*multiError); ok {
+			res.Capacity += len(merr.errors)
+			res.ContainsMultiError = true
+		} else {
+			res.Capacity++
+		}
+	}
+	return
+}
+
+// fromSlice converts the given list of errors into a single error.
+func fromSlice(errors []error) error {
+	res := inspect(errors)
+	switch res.Count {
+	case 0:
+		return nil
+	case 1:
+		// only one non-nil entry
+		return errors[res.FirstErrorIdx]
+	case len(errors):
+		if !res.ContainsMultiError {
+			// already flat
+			return &multiError{errors: errors}
+		}
+	}
+
+	nonNilErrs := make([]error, 0, res.Capacity)
+	for _, err := range errors[res.FirstErrorIdx:] {
+		if err == nil {
+			continue
+		}
+
+		if nested, ok := err.(*multiError); ok {
+			nonNilErrs = append(nonNilErrs, nested.errors...)
+		} else {
+			nonNilErrs = append(nonNilErrs, err)
+		}
+	}
+
+	return &multiError{errors: nonNilErrs}
+}
+
+// Combine combines the passed errors into a single error.
+//
+// If zero arguments were passed or if all items are nil, a nil error is
+// returned.
+//
+// 	Combine(nil, nil)  // == nil
+//
+// If only a single error was passed, it is returned as-is.
+//
+// 	Combine(err)  // == err
+//
+// Combine skips over nil arguments so this function may be used to combine
+// together errors from operations that fail independently of each other.
+//
+// 	multierr.Combine(
+// 		reader.Close(),
+// 		writer.Close(),
+// 		pipe.Close(),
+// 	)
+//
+// If any of the passed errors is a multierr error, it will be flattened along
+// with the other errors.
+//
+// 	multierr.Combine(multierr.Combine(err1, err2), err3)
+// 	// is the same as
+// 	multierr.Combine(err1, err2, err3)
+//
+// The returned error formats into a readable multi-line error message if
+// formatted with %+v.
+//
+// 	fmt.Sprintf("%+v", multierr.Combine(err1, err2))
+func Combine(errors ...error) error {
+	return fromSlice(errors)
+}
+
+// Append appends the given errors together. Either value may be nil.
+//
+// This function is a specialization of Combine for the common case where
+// there are only two errors.
+//
+// 	err = multierr.Append(reader.Close(), writer.Close())
+//
+// The following pattern may also be used to record failure of deferred
+// operations without losing information about the original error.
+//
+// 	func doSomething(..) (err error) {
+// 		f := acquireResource()
+// 		defer func() {
+// 			err = multierr.Append(err, f.Close())
+// 		}()
+func Append(left error, right error) error {
+	switch {
+	case left == nil:
+		return right
+	case right == nil:
+		return left
+	}
+
+	if _, ok := right.(*multiError); !ok {
+		if l, ok := left.(*multiError); ok && !l.copyNeeded.Swap(true) {
+			// Common case where the error on the left is constantly being
+			// appended to.
+			errs := append(l.errors, right)
+			return &multiError{errors: errs}
+		} else if !ok {
+			// Both errors are single errors.
+			return &multiError{errors: []error{left, right}}
+		}
+	}
+
+	// Either right or both, left and right, are multiErrors. Rely on usual
+	// expensive logic.
+	errors := [2]error{left, right}
+	return fromSlice(errors[0:])
+}
diff --git a/vendor/go.uber.org/zap/LICENSE.txt b/vendor/go.uber.org/zap/LICENSE.txt
new file mode 100644
index 0000000..6652bed
--- /dev/null
+++ b/vendor/go.uber.org/zap/LICENSE.txt
@@ -0,0 +1,19 @@
+Copyright (c) 2016-2017 Uber Technologies, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/go.uber.org/zap/array.go b/vendor/go.uber.org/zap/array.go
new file mode 100644
index 0000000..5be3704
--- /dev/null
+++ b/vendor/go.uber.org/zap/array.go
@@ -0,0 +1,320 @@
+// Copyright (c) 2016 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package zap
+
+import (
+	"time"
+
+	"go.uber.org/zap/zapcore"
+)
+
+// Array constructs a field with the given key and ArrayMarshaler. It provides
+// a flexible, but still type-safe and efficient, way to add array-like types
+// to the logging context. The struct's MarshalLogArray method is called lazily.
+func Array(key string, val zapcore.ArrayMarshaler) Field {
+	return Field{Key: key, Type: zapcore.ArrayMarshalerType, Interface: val}
+}
+
+// Bools constructs a field that carries a slice of bools.
+func Bools(key string, bs []bool) Field {
+	return Array(key, bools(bs))
+}
+
+// ByteStrings constructs a field that carries a slice of []byte, each of which
+// must be UTF-8 encoded text.
+func ByteStrings(key string, bss [][]byte) Field {
+	return Array(key, byteStringsArray(bss))
+}
+
+// Complex128s constructs a field that carries a slice of complex numbers.
+func Complex128s(key string, nums []complex128) Field {
+	return Array(key, complex128s(nums))
+}
+
+// Complex64s constructs a field that carries a slice of complex numbers.
+func Complex64s(key string, nums []complex64) Field {
+	return Array(key, complex64s(nums))
+}
+
+// Durations constructs a field that carries a slice of time.Durations.
+func Durations(key string, ds []time.Duration) Field {
+	return Array(key, durations(ds))
+}
+
+// Float64s constructs a field that carries a slice of floats.
+func Float64s(key string, nums []float64) Field {
+	return Array(key, float64s(nums))
+}
+
+// Float32s constructs a field that carries a slice of floats.
+func Float32s(key string, nums []float32) Field {
+	return Array(key, float32s(nums))
+}
+
+// Ints constructs a field that carries a slice of integers.
+func Ints(key string, nums []int) Field {
+	return Array(key, ints(nums))
+}
+
+// Int64s constructs a field that carries a slice of integers.
+func Int64s(key string, nums []int64) Field {
+	return Array(key, int64s(nums))
+}
+
+// Int32s constructs a field that carries a slice of integers.
+func Int32s(key string, nums []int32) Field {
+	return Array(key, int32s(nums))
+}
+
+// Int16s constructs a field that carries a slice of integers.
+func Int16s(key string, nums []int16) Field {
+	return Array(key, int16s(nums))
+}
+
+// Int8s constructs a field that carries a slice of integers.
+func Int8s(key string, nums []int8) Field {
+	return Array(key, int8s(nums))
+}
+
+// Strings constructs a field that carries a slice of strings.
+func Strings(key string, ss []string) Field {
+	return Array(key, stringArray(ss))
+}
+
+// Times constructs a field that carries a slice of time.Times.
+func Times(key string, ts []time.Time) Field {
+	return Array(key, times(ts))
+}
+
+// Uints constructs a field that carries a slice of unsigned integers.
+func Uints(key string, nums []uint) Field {
+	return Array(key, uints(nums))
+}
+
+// Uint64s constructs a field that carries a slice of unsigned integers.
+func Uint64s(key string, nums []uint64) Field {
+	return Array(key, uint64s(nums))
+}
+
+// Uint32s constructs a field that carries a slice of unsigned integers.
+func Uint32s(key string, nums []uint32) Field {
+	return Array(key, uint32s(nums))
+}
+
+// Uint16s constructs a field that carries a slice of unsigned integers.
+func Uint16s(key string, nums []uint16) Field {
+	return Array(key, uint16s(nums))
+}
+
+// Uint8s constructs a field that carries a slice of unsigned integers.
+func Uint8s(key string, nums []uint8) Field {
+	return Array(key, uint8s(nums))
+}
+
+// Uintptrs constructs a field that carries a slice of pointer addresses.
+func Uintptrs(key string, us []uintptr) Field {
+	return Array(key, uintptrs(us))
+}
+
+// Errors constructs a field that carries a slice of errors.
+func Errors(key string, errs []error) Field {
+	return Array(key, errArray(errs))
+}
+
+type bools []bool
+
+func (bs bools) MarshalLogArray(arr zapcore.ArrayEncoder) error {
+	for i := range bs {
+		arr.AppendBool(bs[i])
+	}
+	return nil
+}
+
+type byteStringsArray [][]byte
+
+func (bss byteStringsArray) MarshalLogArray(arr zapcore.ArrayEncoder) error {
+	for i := range bss {
+		arr.AppendByteString(bss[i])
+	}
+	return nil
+}
+
+type complex128s []complex128
+
+func (nums complex128s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
+	for i := range nums {
+		arr.AppendComplex128(nums[i])
+	}
+	return nil
+}
+
+type complex64s []complex64
+
+func (nums complex64s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
+	for i := range nums {
+		arr.AppendComplex64(nums[i])
+	}
+	return nil
+}
+
+type durations []time.Duration
+
+func (ds durations) MarshalLogArray(arr zapcore.ArrayEncoder) error {
+	for i := range ds {
+		arr.AppendDuration(ds[i])
+	}
+	return nil
+}
+
+type float64s []float64
+
+func (nums float64s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
+	for i := range nums {
+		arr.AppendFloat64(nums[i])
+	}
+	return nil
+}
+
+type float32s []float32
+
+func (nums float32s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
+	for i := range nums {
+		arr.AppendFloat32(nums[i])
+	}
+	return nil
+}
+
+type ints []int
+
+func (nums ints) MarshalLogArray(arr zapcore.ArrayEncoder) error {
+	for i := range nums {
+		arr.AppendInt(nums[i])
+	}
+	return nil
+}
+
+type int64s []int64
+
+func (nums int64s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
+	for i := range nums {
+		arr.AppendInt64(nums[i])
+	}
+	return nil
+}
+
+type int32s []int32
+
+func (nums int32s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
+	for i := range nums {
+		arr.AppendInt32(nums[i])
+	}
+	return nil
+}
+
+type int16s []int16
+
+func (nums int16s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
+	for i := range nums {
+		arr.AppendInt16(nums[i])
+	}
+	return nil
+}
+
+type int8s []int8
+
+func (nums int8s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
+	for i := range nums {
+		arr.AppendInt8(nums[i])
+	}
+	return nil
+}
+
+type stringArray []string
+
+func (ss stringArray) MarshalLogArray(arr zapcore.ArrayEncoder) error {
+	for i := range ss {
+		arr.AppendString(ss[i])
+	}
+	return nil
+}
+
+type times []time.Time
+
+func (ts times) MarshalLogArray(arr zapcore.ArrayEncoder) error {
+	for i := range ts {
+		arr.AppendTime(ts[i])
+	}
+	return nil
+}
+
+type uints []uint
+
+func (nums uints) MarshalLogArray(arr zapcore.ArrayEncoder) error {
+	for i := range nums {
+		arr.AppendUint(nums[i])
+	}
+	return nil
+}
+
+type uint64s []uint64
+
+func (nums uint64s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
+	for i := range nums {
+		arr.AppendUint64(nums[i])
+	}
+	return nil
+}
+
+type uint32s []uint32
+
+func (nums uint32s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
+	for i := range nums {
+		arr.AppendUint32(nums[i])
+	}
+	return nil
+}
+
+type uint16s []uint16
+
+func (nums uint16s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
+	for i := range nums {
+		arr.AppendUint16(nums[i])
+	}
+	return nil
+}
+
+type uint8s []uint8
+
+func (nums uint8s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
+	for i := range nums {
+		arr.AppendUint8(nums[i])
+	}
+	return nil
+}
+
+type uintptrs []uintptr
+
+func (nums uintptrs) MarshalLogArray(arr zapcore.ArrayEncoder) error {
+	for i := range nums {
+		arr.AppendUintptr(nums[i])
+	}
+	return nil
+}
diff --git a/vendor/go.uber.org/zap/buffer/buffer.go b/vendor/go.uber.org/zap/buffer/buffer.go
new file mode 100644
index 0000000..7592e8c
--- /dev/null
+++ b/vendor/go.uber.org/zap/buffer/buffer.go
@@ -0,0 +1,115 @@
+// Copyright (c) 2016 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+// Package buffer provides a thin wrapper around a byte slice. Unlike the
+// standard library's bytes.Buffer, it supports a portion of the strconv
+// package's zero-allocation formatters.
+package buffer // import "go.uber.org/zap/buffer"
+
+import "strconv"
+
+const _size = 1024 // by default, create 1 KiB buffers
+
+// Buffer is a thin wrapper around a byte slice. It's intended to be pooled, so
+// the only way to construct one is via a Pool.
+type Buffer struct {
+	bs   []byte
+	pool Pool
+}
+
+// AppendByte writes a single byte to the Buffer.
+func (b *Buffer) AppendByte(v byte) {
+	b.bs = append(b.bs, v)
+}
+
+// AppendString writes a string to the Buffer.
+func (b *Buffer) AppendString(s string) {
+	b.bs = append(b.bs, s...)
+}
+
+// AppendInt appends an integer to the underlying buffer (assuming base 10).
+func (b *Buffer) AppendInt(i int64) {
+	b.bs = strconv.AppendInt(b.bs, i, 10)
+}
+
+// AppendUint appends an unsigned integer to the underlying buffer (assuming
+// base 10).
+func (b *Buffer) AppendUint(i uint64) {
+	b.bs = strconv.AppendUint(b.bs, i, 10)
+}
+
+// AppendBool appends a bool to the underlying buffer.
+func (b *Buffer) AppendBool(v bool) {
+	b.bs = strconv.AppendBool(b.bs, v)
+}
+
+// AppendFloat appends a float to the underlying buffer. It doesn't quote NaN
+// or +/- Inf.
+func (b *Buffer) AppendFloat(f float64, bitSize int) {
+	b.bs = strconv.AppendFloat(b.bs, f, 'f', -1, bitSize)
+}
+
+// Len returns the length of the underlying byte slice.
+func (b *Buffer) Len() int {
+	return len(b.bs)
+}
+
+// Cap returns the capacity of the underlying byte slice.
+func (b *Buffer) Cap() int {
+	return cap(b.bs)
+}
+
+// Bytes returns a mutable reference to the underlying byte slice.
+func (b *Buffer) Bytes() []byte {
+	return b.bs
+}
+
+// String returns a string copy of the underlying byte slice.
+func (b *Buffer) String() string {
+	return string(b.bs)
+}
+
+// Reset resets the underlying byte slice. Subsequent writes re-use the slice's
+// backing array.
+func (b *Buffer) Reset() {
+	b.bs = b.bs[:0]
+}
+
+// Write implements io.Writer.
+func (b *Buffer) Write(bs []byte) (int, error) {
+	b.bs = append(b.bs, bs...)
+	return len(bs), nil
+}
+
+// TrimNewline trims any final "\n" byte from the end of the buffer.
+func (b *Buffer) TrimNewline() {
+	if i := len(b.bs) - 1; i >= 0 {
+		if b.bs[i] == '\n' {
+			b.bs = b.bs[:i]
+		}
+	}
+}
+
+// Free returns the Buffer to its Pool.
+//
+// Callers must not retain references to the Buffer after calling Free.
+func (b *Buffer) Free() {
+	b.pool.put(b)
+}
diff --git a/vendor/go.uber.org/zap/buffer/pool.go b/vendor/go.uber.org/zap/buffer/pool.go
new file mode 100644
index 0000000..8fb3e20
--- /dev/null
+++ b/vendor/go.uber.org/zap/buffer/pool.go
@@ -0,0 +1,49 @@
+// Copyright (c) 2016 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package buffer
+
+import "sync"
+
+// A Pool is a type-safe wrapper around a sync.Pool.
+type Pool struct {
+	p *sync.Pool
+}
+
+// NewPool constructs a new Pool.
+func NewPool() Pool {
+	return Pool{p: &sync.Pool{
+		New: func() interface{} {
+			return &Buffer{bs: make([]byte, 0, _size)}
+		},
+	}}
+}
+
+// Get retrieves a Buffer from the pool, creating one if necessary.
+func (p Pool) Get() *Buffer {
+	buf := p.p.Get().(*Buffer)
+	buf.Reset()
+	buf.pool = p
+	return buf
+}
+
+func (p Pool) put(buf *Buffer) {
+	p.p.Put(buf)
+}
diff --git a/vendor/go.uber.org/zap/config.go b/vendor/go.uber.org/zap/config.go
new file mode 100644
index 0000000..6fe17d9
--- /dev/null
+++ b/vendor/go.uber.org/zap/config.go
@@ -0,0 +1,243 @@
+// Copyright (c) 2016 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package zap
+
+import (
+	"sort"
+	"time"
+
+	"go.uber.org/zap/zapcore"
+)
+
+// SamplingConfig sets a sampling strategy for the logger. Sampling caps the
+// global CPU and I/O load that logging puts on your process while attempting
+// to preserve a representative subset of your logs.
+//
+// Values configured here are per-second. See zapcore.NewSampler for details.
+type SamplingConfig struct {
+	Initial    int `json:"initial" yaml:"initial"`
+	Thereafter int `json:"thereafter" yaml:"thereafter"`
+}
+
+// Config offers a declarative way to construct a logger. It doesn't do
+// anything that can't be done with New, Options, and the various
+// zapcore.WriteSyncer and zapcore.Core wrappers, but it's a simpler way to
+// toggle common options.
+//
+// Note that Config intentionally supports only the most common options. More
+// unusual logging setups (logging to network connections or message queues,
+// splitting output between multiple files, etc.) are possible, but require
+// direct use of the zapcore package. For sample code, see the package-level
+// BasicConfiguration and AdvancedConfiguration examples.
+//
+// For an example showing runtime log level changes, see the documentation for
+// AtomicLevel.
+type Config struct {
+	// Level is the minimum enabled logging level. Note that this is a dynamic
+	// level, so calling Config.Level.SetLevel will atomically change the log
+	// level of all loggers descended from this config.
+	Level AtomicLevel `json:"level" yaml:"level"`
+	// Development puts the logger in development mode, which changes the
+	// behavior of DPanicLevel and takes stacktraces more liberally.
+	Development bool `json:"development" yaml:"development"`
+	// DisableCaller stops annotating logs with the calling function's file
+	// name and line number. By default, all logs are annotated.
+	DisableCaller bool `json:"disableCaller" yaml:"disableCaller"`
+	// DisableStacktrace completely disables automatic stacktrace capturing. By
+	// default, stacktraces are captured for WarnLevel and above logs in
+	// development and ErrorLevel and above in production.
+	DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"`
+	// Sampling sets a sampling policy. A nil SamplingConfig disables sampling.
+	Sampling *SamplingConfig `json:"sampling" yaml:"sampling"`
+	// Encoding sets the logger's encoding. Valid values are "json" and
+	// "console", as well as any third-party encodings registered via
+	// RegisterEncoder.
+	Encoding string `json:"encoding" yaml:"encoding"`
+	// EncoderConfig sets options for the chosen encoder. See
+	// zapcore.EncoderConfig for details.
+	EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"`
+	// OutputPaths is a list of URLs or file paths to write logging output to.
+	// See Open for details.
+	OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
+	// ErrorOutputPaths is a list of URLs to write internal logger errors to.
+	// The default is standard error.
+	//
+	// Note that this setting only affects internal errors; for sample code that
+	// sends error-level logs to a different location from info- and debug-level
+	// logs, see the package-level AdvancedConfiguration example.
+	ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"`
+	// InitialFields is a collection of fields to add to the root logger.
+	InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"`
+}
+
+// NewProductionEncoderConfig returns an opinionated EncoderConfig for
+// production environments.
+func NewProductionEncoderConfig() zapcore.EncoderConfig {
+	return zapcore.EncoderConfig{
+		TimeKey:        "ts",
+		LevelKey:       "level",
+		NameKey:        "logger",
+		CallerKey:      "caller",
+		MessageKey:     "msg",
+		StacktraceKey:  "stacktrace",
+		LineEnding:     zapcore.DefaultLineEnding,
+		EncodeLevel:    zapcore.LowercaseLevelEncoder,
+		EncodeTime:     zapcore.EpochTimeEncoder,
+		EncodeDuration: zapcore.SecondsDurationEncoder,
+		EncodeCaller:   zapcore.ShortCallerEncoder,
+	}
+}
+
+// NewProductionConfig is a reasonable production logging configuration.
+// Logging is enabled at InfoLevel and above.
+//
+// It uses a JSON encoder, writes to standard error, and enables sampling.
+// Stacktraces are automatically included on logs of ErrorLevel and above.
+func NewProductionConfig() Config {
+	return Config{
+		Level:       NewAtomicLevelAt(InfoLevel),
+		Development: false,
+		Sampling: &SamplingConfig{
+			Initial:    100,
+			Thereafter: 100,
+		},
+		Encoding:         "json",
+		EncoderConfig:    NewProductionEncoderConfig(),
+		OutputPaths:      []string{"stderr"},
+		ErrorOutputPaths: []string{"stderr"},
+	}
+}
+
+// NewDevelopmentEncoderConfig returns an opinionated EncoderConfig for
+// development environments.
+func NewDevelopmentEncoderConfig() zapcore.EncoderConfig {
+	return zapcore.EncoderConfig{
+		// Keys can be anything except the empty string.
+		TimeKey:        "T",
+		LevelKey:       "L",
+		NameKey:        "N",
+		CallerKey:      "C",
+		MessageKey:     "M",
+		StacktraceKey:  "S",
+		LineEnding:     zapcore.DefaultLineEnding,
+		EncodeLevel:    zapcore.CapitalLevelEncoder,
+		EncodeTime:     zapcore.ISO8601TimeEncoder,
+		EncodeDuration: zapcore.StringDurationEncoder,
+		EncodeCaller:   zapcore.ShortCallerEncoder,
+	}
+}
+
+// NewDevelopmentConfig is a reasonable development logging configuration.
+// Logging is enabled at DebugLevel and above.
+//
+// It enables development mode (which makes DPanicLevel logs panic), uses a
+// console encoder, writes to standard error, and disables sampling.
+// Stacktraces are automatically included on logs of WarnLevel and above.
+func NewDevelopmentConfig() Config {
+	return Config{
+		Level:            NewAtomicLevelAt(DebugLevel),
+		Development:      true,
+		Encoding:         "console",
+		EncoderConfig:    NewDevelopmentEncoderConfig(),
+		OutputPaths:      []string{"stderr"},
+		ErrorOutputPaths: []string{"stderr"},
+	}
+}
+
+// Build constructs a logger from the Config and Options.
+func (cfg Config) Build(opts ...Option) (*Logger, error) {
+	enc, err := cfg.buildEncoder()
+	if err != nil {
+		return nil, err
+	}
+
+	sink, errSink, err := cfg.openSinks()
+	if err != nil {
+		return nil, err
+	}
+
+	log := New(
+		zapcore.NewCore(enc, sink, cfg.Level),
+		cfg.buildOptions(errSink)...,
+	)
+	if len(opts) > 0 {
+		log = log.WithOptions(opts...)
+	}
+	return log, nil
+}
+
+func (cfg Config) buildOptions(errSink zapcore.WriteSyncer) []Option {
+	opts := []Option{ErrorOutput(errSink)}
+
+	if cfg.Development {
+		opts = append(opts, Development())
+	}
+
+	if !cfg.DisableCaller {
+		opts = append(opts, AddCaller())
+	}
+
+	stackLevel := ErrorLevel
+	if cfg.Development {
+		stackLevel = WarnLevel
+	}
+	if !cfg.DisableStacktrace {
+		opts = append(opts, AddStacktrace(stackLevel))
+	}
+
+	if cfg.Sampling != nil {
+		opts = append(opts, WrapCore(func(core zapcore.Core) zapcore.Core {
+			return zapcore.NewSampler(core, time.Second, int(cfg.Sampling.Initial), int(cfg.Sampling.Thereafter))
+		}))
+	}
+
+	if len(cfg.InitialFields) > 0 {
+		fs := make([]Field, 0, len(cfg.InitialFields))
+		keys := make([]string, 0, len(cfg.InitialFields))
+		for k := range cfg.InitialFields {
+			keys = append(keys, k)
+		}
+		sort.Strings(keys)
+		for _, k := range keys {
+			fs = append(fs, Any(k, cfg.InitialFields[k]))
+		}
+		opts = append(opts, Fields(fs...))
+	}
+
+	return opts
+}
+
+func (cfg Config) openSinks() (zapcore.WriteSyncer, zapcore.WriteSyncer, error) {
+	sink, closeOut, err := Open(cfg.OutputPaths...)
+	if err != nil {
+		return nil, nil, err
+	}
+	errSink, _, err := Open(cfg.ErrorOutputPaths...)
+	if err != nil {
+		closeOut()
+		return nil, nil, err
+	}
+	return sink, errSink, nil
+}
+
+func (cfg Config) buildEncoder() (zapcore.Encoder, error) {
+	return newEncoder(cfg.Encoding, cfg.EncoderConfig)
+}
diff --git a/vendor/go.uber.org/zap/doc.go b/vendor/go.uber.org/zap/doc.go
new file mode 100644
index 0000000..8638dd1
--- /dev/null
+++ b/vendor/go.uber.org/zap/doc.go
@@ -0,0 +1,113 @@
+// Copyright (c) 2016 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+// Package zap provides fast, structured, leveled logging.
+//
+// For applications that log in the hot path, reflection-based serialization
+// and string formatting are prohibitively expensive - they're CPU-intensive
+// and make many small allocations. Put differently, using json.Marshal and
+// fmt.Fprintf to log tons of interface{} makes your application slow.
+//
+// Zap takes a different approach. It includes a reflection-free,
+// zero-allocation JSON encoder, and the base Logger strives to avoid
+// serialization overhead and allocations wherever possible. By building the
+// high-level SugaredLogger on that foundation, zap lets users choose when
+// they need to count every allocation and when they'd prefer a more familiar,
+// loosely typed API.
+//
+// Choosing a Logger
+//
+// In contexts where performance is nice, but not critical, use the
+// SugaredLogger. It's 4-10x faster than other structured logging packages and
+// supports both structured and printf-style logging. Like log15 and go-kit,
+// the SugaredLogger's structured logging APIs are loosely typed and accept a
+// variadic number of key-value pairs. (For more advanced use cases, they also
+// accept strongly typed fields - see the SugaredLogger.With documentation for
+// details.)
+//  sugar := zap.NewExample().Sugar()
+//  defer sugar.Sync()
+//  sugar.Infow("failed to fetch URL",
+//    "url", "http://example.com",
+//    "attempt", 3,
+//    "backoff", time.Second,
+//  )
+//  sugar.Infof("failed to fetch URL: %s", "http://example.com")
+//
+// By default, loggers are unbuffered. However, since zap's low-level APIs
+// allow buffering, calling Sync before letting your process exit is a good
+// habit.
+//
+// In the rare contexts where every microsecond and every allocation matter,
+// use the Logger. It's even faster than the SugaredLogger and allocates far
+// less, but it only supports strongly-typed, structured logging.
+//  logger := zap.NewExample()
+//  defer logger.Sync()
+//  logger.Info("failed to fetch URL",
+//    zap.String("url", "http://example.com"),
+//    zap.Int("attempt", 3),
+//    zap.Duration("backoff", time.Second),
+//  )
+//
+// Choosing between the Logger and SugaredLogger doesn't need to be an
+// application-wide decision: converting between the two is simple and
+// inexpensive.
+//   logger := zap.NewExample()
+//   defer logger.Sync()
+//   sugar := logger.Sugar()
+//   plain := sugar.Desugar()
+//
+// Configuring Zap
+//
+// The simplest way to build a Logger is to use zap's opinionated presets:
+// NewExample, NewProduction, and NewDevelopment. These presets build a logger
+// with a single function call:
+//  logger, err := zap.NewProduction()
+//  if err != nil {
+//    log.Fatalf("can't initialize zap logger: %v", err)
+//  }
+//  defer logger.Sync()
+//
+// Presets are fine for small projects, but larger projects and organizations
+// naturally require a bit more customization. For most users, zap's Config
+// struct strikes the right balance between flexibility and convenience. See
+// the package-level BasicConfiguration example for sample code.
+//
+// More unusual configurations (splitting output between files, sending logs
+// to a message queue, etc.) are possible, but require direct use of
+// go.uber.org/zap/zapcore. See the package-level AdvancedConfiguration
+// example for sample code.
+//
+// Extending Zap
+//
+// The zap package itself is a relatively thin wrapper around the interfaces
+// in go.uber.org/zap/zapcore. Extending zap to support a new encoding (e.g.,
+// BSON), a new log sink (e.g., Kafka), or something more exotic (perhaps an
+// exception aggregation service, like Sentry or Rollbar) typically requires
+// implementing the zapcore.Encoder, zapcore.WriteSyncer, or zapcore.Core
+// interfaces. See the zapcore documentation for details.
+//
+// Similarly, package authors can use the high-performance Encoder and Core
+// implementations in the zapcore package to build their own loggers.
+//
+// Frequently Asked Questions
+//
+// An FAQ covering everything from installation errors to design decisions is
+// available at https://github.com/uber-go/zap/blob/master/FAQ.md.
+package zap // import "go.uber.org/zap"
diff --git a/vendor/go.uber.org/zap/encoder.go b/vendor/go.uber.org/zap/encoder.go
new file mode 100644
index 0000000..2e9d3c3
--- /dev/null
+++ b/vendor/go.uber.org/zap/encoder.go
@@ -0,0 +1,75 @@
+// Copyright (c) 2016 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package zap
+
+import (
+	"errors"
+	"fmt"
+	"sync"
+
+	"go.uber.org/zap/zapcore"
+)
+
+var (
+	errNoEncoderNameSpecified = errors.New("no encoder name specified")
+
+	_encoderNameToConstructor = map[string]func(zapcore.EncoderConfig) (zapcore.Encoder, error){
+		"console": func(encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) {
+			return zapcore.NewConsoleEncoder(encoderConfig), nil
+		},
+		"json": func(encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) {
+			return zapcore.NewJSONEncoder(encoderConfig), nil
+		},
+	}
+	_encoderMutex sync.RWMutex
+)
+
+// RegisterEncoder registers an encoder constructor, which the Config struct
+// can then reference. By default, the "json" and "console" encoders are
+// registered.
+//
+// Attempting to register an encoder whose name is already taken returns an
+// error.
+func RegisterEncoder(name string, constructor func(zapcore.EncoderConfig) (zapcore.Encoder, error)) error {
+	_encoderMutex.Lock()
+	defer _encoderMutex.Unlock()
+	if name == "" {
+		return errNoEncoderNameSpecified
+	}
+	if _, ok := _encoderNameToConstructor[name]; ok {
+		return fmt.Errorf("encoder already registered for name %q", name)
+	}
+	_encoderNameToConstructor[name] = constructor
+	return nil
+}
+
+func newEncoder(name string, encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) {
+	_encoderMutex.RLock()
+	defer _encoderMutex.RUnlock()
+	if name == "" {
+		return nil, errNoEncoderNameSpecified
+	}
+	constructor, ok := _encoderNameToConstructor[name]
+	if !ok {
+		return nil, fmt.Errorf("no encoder registered for name %q", name)
+	}
+	return constructor(encoderConfig)
+}
diff --git a/vendor/go.uber.org/zap/error.go b/vendor/go.uber.org/zap/error.go
new file mode 100644
index 0000000..65982a5
--- /dev/null
+++ b/vendor/go.uber.org/zap/error.go
@@ -0,0 +1,80 @@
+// Copyright (c) 2017 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package zap
+
+import (
+	"sync"
+
+	"go.uber.org/zap/zapcore"
+)
+
+var _errArrayElemPool = sync.Pool{New: func() interface{} {
+	return &errArrayElem{}
+}}
+
+// Error is shorthand for the common idiom NamedError("error", err).
+func Error(err error) Field {
+	return NamedError("error", err)
+}
+
+// NamedError constructs a field that lazily stores err.Error() under the
+// provided key. Errors which also implement fmt.Formatter (like those produced
+// by github.com/pkg/errors) will also have their verbose representation stored
+// under key+"Verbose". If passed a nil error, the field is a no-op.
+//
+// For the common case in which the key is simply "error", the Error function
+// is shorter and less repetitive.
+func NamedError(key string, err error) Field {
+	if err == nil {
+		return Skip()
+	}
+	return Field{Key: key, Type: zapcore.ErrorType, Interface: err}
+}
+
+type errArray []error
+
+func (errs errArray) MarshalLogArray(arr zapcore.ArrayEncoder) error {
+	for i := range errs {
+		if errs[i] == nil {
+			continue
+		}
+		// To represent each error as an object with an "error" attribute and
+		// potentially an "errorVerbose" attribute, we need to wrap it in a
+		// type that implements LogObjectMarshaler. To prevent this from
+		// allocating, pool the wrapper type.
+		elem := _errArrayElemPool.Get().(*errArrayElem)
+		elem.error = errs[i]
+		arr.AppendObject(elem)
+		elem.error = nil
+		_errArrayElemPool.Put(elem)
+	}
+	return nil
+}
+
+type errArrayElem struct {
+	error
+}
+
+func (e *errArrayElem) MarshalLogObject(enc zapcore.ObjectEncoder) error {
+	// Re-use the error field's logic, which supports non-standard error types.
+	Error(e.error).AddTo(enc)
+	return nil
+}
diff --git a/vendor/go.uber.org/zap/field.go b/vendor/go.uber.org/zap/field.go
new file mode 100644
index 0000000..5130e13
--- /dev/null
+++ b/vendor/go.uber.org/zap/field.go
@@ -0,0 +1,310 @@
+// Copyright (c) 2016 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package zap
+
+import (
+	"fmt"
+	"math"
+	"time"
+
+	"go.uber.org/zap/zapcore"
+)
+
+// Field is an alias for Field. Aliasing this type dramatically
+// improves the navigability of this package's API documentation.
+type Field = zapcore.Field
+
+// Skip constructs a no-op field, which is often useful when handling invalid
+// inputs in other Field constructors.
+func Skip() Field {
+	return Field{Type: zapcore.SkipType}
+}
+
+// Binary constructs a field that carries an opaque binary blob.
+//
+// Binary data is serialized in an encoding-appropriate format. For example,
+// zap's JSON encoder base64-encodes binary blobs. To log UTF-8 encoded text,
+// use ByteString.
+func Binary(key string, val []byte) Field {
+	return Field{Key: key, Type: zapcore.BinaryType, Interface: val}
+}
+
+// Bool constructs a field that carries a bool.
+func Bool(key string, val bool) Field {
+	var ival int64
+	if val {
+		ival = 1
+	}
+	return Field{Key: key, Type: zapcore.BoolType, Integer: ival}
+}
+
+// ByteString constructs a field that carries UTF-8 encoded text as a []byte.
+// To log opaque binary blobs (which aren't necessarily valid UTF-8), use
+// Binary.
+func ByteString(key string, val []byte) Field {
+	return Field{Key: key, Type: zapcore.ByteStringType, Interface: val}
+}
+
+// Complex128 constructs a field that carries a complex number. Unlike most
+// numeric fields, this costs an allocation (to convert the complex128 to
+// interface{}).
+func Complex128(key string, val complex128) Field {
+	return Field{Key: key, Type: zapcore.Complex128Type, Interface: val}
+}
+
+// Complex64 constructs a field that carries a complex number. Unlike most
+// numeric fields, this costs an allocation (to convert the complex64 to
+// interface{}).
+func Complex64(key string, val complex64) Field {
+	return Field{Key: key, Type: zapcore.Complex64Type, Interface: val}
+}
+
+// Float64 constructs a field that carries a float64. The way the
+// floating-point value is represented is encoder-dependent, so marshaling is
+// necessarily lazy.
+func Float64(key string, val float64) Field {
+	return Field{Key: key, Type: zapcore.Float64Type, Integer: int64(math.Float64bits(val))}
+}
+
+// Float32 constructs a field that carries a float32. The way the
+// floating-point value is represented is encoder-dependent, so marshaling is
+// necessarily lazy.
+func Float32(key string, val float32) Field {
+	return Field{Key: key, Type: zapcore.Float32Type, Integer: int64(math.Float32bits(val))}
+}
+
+// Int constructs a field with the given key and value.
+func Int(key string, val int) Field {
+	return Int64(key, int64(val))
+}
+
+// Int64 constructs a field with the given key and value.
+func Int64(key string, val int64) Field {
+	return Field{Key: key, Type: zapcore.Int64Type, Integer: val}
+}
+
+// Int32 constructs a field with the given key and value.
+func Int32(key string, val int32) Field {
+	return Field{Key: key, Type: zapcore.Int32Type, Integer: int64(val)}
+}
+
+// Int16 constructs a field with the given key and value.
+func Int16(key string, val int16) Field {
+	return Field{Key: key, Type: zapcore.Int16Type, Integer: int64(val)}
+}
+
+// Int8 constructs a field with the given key and value.
+func Int8(key string, val int8) Field {
+	return Field{Key: key, Type: zapcore.Int8Type, Integer: int64(val)}
+}
+
+// String constructs a field with the given key and value.
+func String(key string, val string) Field {
+	return Field{Key: key, Type: zapcore.StringType, String: val}
+}
+
+// Uint constructs a field with the given key and value.
+func Uint(key string, val uint) Field {
+	return Uint64(key, uint64(val))
+}
+
+// Uint64 constructs a field with the given key and value.
+func Uint64(key string, val uint64) Field {
+	return Field{Key: key, Type: zapcore.Uint64Type, Integer: int64(val)}
+}
+
+// Uint32 constructs a field with the given key and value.
+func Uint32(key string, val uint32) Field {
+	return Field{Key: key, Type: zapcore.Uint32Type, Integer: int64(val)}
+}
+
+// Uint16 constructs a field with the given key and value.
+func Uint16(key string, val uint16) Field {
+	return Field{Key: key, Type: zapcore.Uint16Type, Integer: int64(val)}
+}
+
+// Uint8 constructs a field with the given key and value.
+func Uint8(key string, val uint8) Field {
+	return Field{Key: key, Type: zapcore.Uint8Type, Integer: int64(val)}
+}
+
+// Uintptr constructs a field with the given key and value.
+func Uintptr(key string, val uintptr) Field {
+	return Field{Key: key, Type: zapcore.UintptrType, Integer: int64(val)}
+}
+
+// Reflect constructs a field with the given key and an arbitrary object. It uses
+// an encoding-appropriate, reflection-based function to lazily serialize nearly
+// any object into the logging context, but it's relatively slow and
+// allocation-heavy. Outside tests, Any is always a better choice.
+//
+// If encoding fails (e.g., trying to serialize a map[int]string to JSON), Reflect
+// includes the error message in the final log output.
+func Reflect(key string, val interface{}) Field {
+	return Field{Key: key, Type: zapcore.ReflectType, Interface: val}
+}
+
+// Namespace creates a named, isolated scope within the logger's context. All
+// subsequent fields will be added to the new namespace.
+//
+// This helps prevent key collisions when injecting loggers into sub-components
+// or third-party libraries.
+func Namespace(key string) Field {
+	return Field{Key: key, Type: zapcore.NamespaceType}
+}
+
+// Stringer constructs a field with the given key and the output of the value's
+// String method. The Stringer's String method is called lazily.
+func Stringer(key string, val fmt.Stringer) Field {
+	return Field{Key: key, Type: zapcore.StringerType, Interface: val}
+}
+
+// Time constructs a Field with the given key and value. The encoder
+// controls how the time is serialized.
+func Time(key string, val time.Time) Field {
+	return Field{Key: key, Type: zapcore.TimeType, Integer: val.UnixNano(), Interface: val.Location()}
+}
+
+// Stack constructs a field that stores a stacktrace of the current goroutine
+// under provided key. Keep in mind that taking a stacktrace is eager and
+// expensive (relatively speaking); this function both makes an allocation and
+// takes about two microseconds.
+func Stack(key string) Field {
+	// Returning the stacktrace as a string costs an allocation, but saves us
+	// from expanding the zapcore.Field union struct to include a byte slice. Since
+	// taking a stacktrace is already so expensive (~10us), the extra allocation
+	// is okay.
+	return String(key, takeStacktrace())
+}
+
+// Duration constructs a field with the given key and value. The encoder
+// controls how the duration is serialized.
+func Duration(key string, val time.Duration) Field {
+	return Field{Key: key, Type: zapcore.DurationType, Integer: int64(val)}
+}
+
+// Object constructs a field with the given key and ObjectMarshaler. It
+// provides a flexible, but still type-safe and efficient, way to add map- or
+// struct-like user-defined types to the logging context. The struct's
+// MarshalLogObject method is called lazily.
+func Object(key string, val zapcore.ObjectMarshaler) Field {
+	return Field{Key: key, Type: zapcore.ObjectMarshalerType, Interface: val}
+}
+
+// Any takes a key and an arbitrary value and chooses the best way to represent
+// them as a field, falling back to a reflection-based approach only if
+// necessary.
+//
+// Since byte/uint8 and rune/int32 are aliases, Any can't differentiate between
+// them. To minimize surprises, []byte values are treated as binary blobs, byte
+// values are treated as uint8, and runes are always treated as integers.
+func Any(key string, value interface{}) Field {
+	switch val := value.(type) {
+	case zapcore.ObjectMarshaler:
+		return Object(key, val)
+	case zapcore.ArrayMarshaler:
+		return Array(key, val)
+	case bool:
+		return Bool(key, val)
+	case []bool:
+		return Bools(key, val)
+	case complex128:
+		return Complex128(key, val)
+	case []complex128:
+		return Complex128s(key, val)
+	case complex64:
+		return Complex64(key, val)
+	case []complex64:
+		return Complex64s(key, val)
+	case float64:
+		return Float64(key, val)
+	case []float64:
+		return Float64s(key, val)
+	case float32:
+		return Float32(key, val)
+	case []float32:
+		return Float32s(key, val)
+	case int:
+		return Int(key, val)
+	case []int:
+		return Ints(key, val)
+	case int64:
+		return Int64(key, val)
+	case []int64:
+		return Int64s(key, val)
+	case int32:
+		return Int32(key, val)
+	case []int32:
+		return Int32s(key, val)
+	case int16:
+		return Int16(key, val)
+	case []int16:
+		return Int16s(key, val)
+	case int8:
+		return Int8(key, val)
+	case []int8:
+		return Int8s(key, val)
+	case string:
+		return String(key, val)
+	case []string:
+		return Strings(key, val)
+	case uint:
+		return Uint(key, val)
+	case []uint:
+		return Uints(key, val)
+	case uint64:
+		return Uint64(key, val)
+	case []uint64:
+		return Uint64s(key, val)
+	case uint32:
+		return Uint32(key, val)
+	case []uint32:
+		return Uint32s(key, val)
+	case uint16:
+		return Uint16(key, val)
+	case []uint16:
+		return Uint16s(key, val)
+	case uint8:
+		return Uint8(key, val)
+	case []byte:
+		return Binary(key, val)
+	case uintptr:
+		return Uintptr(key, val)
+	case []uintptr:
+		return Uintptrs(key, val)
+	case time.Time:
+		return Time(key, val)
+	case []time.Time:
+		return Times(key, val)
+	case time.Duration:
+		return Duration(key, val)
+	case []time.Duration:
+		return Durations(key, val)
+	case error:
+		return NamedError(key, val)
+	case []error:
+		return Errors(key, val)
+	case fmt.Stringer:
+		return Stringer(key, val)
+	default:
+		return Reflect(key, val)
+	}
+}
diff --git a/vendor/go.uber.org/zap/flag.go b/vendor/go.uber.org/zap/flag.go
new file mode 100644
index 0000000..1312875
--- /dev/null
+++ b/vendor/go.uber.org/zap/flag.go
@@ -0,0 +1,39 @@
+// Copyright (c) 2016 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package zap
+
+import (
+	"flag"
+
+	"go.uber.org/zap/zapcore"
+)
+
+// LevelFlag uses the standard library's flag.Var to declare a global flag
+// with the specified name, default, and usage guidance. The returned value is
+// a pointer to the value of the flag.
+//
+// If you don't want to use the flag package's global state, you can use any
+// non-nil *Level as a flag.Value with your own *flag.FlagSet.
+func LevelFlag(name string, defaultLevel zapcore.Level, usage string) *zapcore.Level {
+	lvl := defaultLevel
+	flag.Var(&lvl, name, usage)
+	return &lvl
+}
diff --git a/vendor/go.uber.org/zap/global.go b/vendor/go.uber.org/zap/global.go
new file mode 100644
index 0000000..d02232e
--- /dev/null
+++ b/vendor/go.uber.org/zap/global.go
@@ -0,0 +1,169 @@
+// Copyright (c) 2016 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package zap
+
+import (
+	"bytes"
+	"fmt"
+	"log"
+	"os"
+	"sync"
+
+	"go.uber.org/zap/zapcore"
+)
+
+const (
+	_stdLogDefaultDepth      = 2
+	_loggerWriterDepth       = 2
+	_programmerErrorTemplate = "You've found a bug in zap! Please file a bug at " +
+		"https://github.com/uber-go/zap/issues/new and reference this error: %v"
+)
+
+var (
+	_globalMu sync.RWMutex
+	_globalL  = NewNop()
+	_globalS  = _globalL.Sugar()
+)
+
+// L returns the global Logger, which can be reconfigured with ReplaceGlobals.
+// It's safe for concurrent use.
+func L() *Logger {
+	_globalMu.RLock()
+	l := _globalL
+	_globalMu.RUnlock()
+	return l
+}
+
+// S returns the global SugaredLogger, which can be reconfigured with
+// ReplaceGlobals. It's safe for concurrent use.
+func S() *SugaredLogger {
+	_globalMu.RLock()
+	s := _globalS
+	_globalMu.RUnlock()
+	return s
+}
+
+// ReplaceGlobals replaces the global Logger and SugaredLogger, and returns a
+// function to restore the original values. It's safe for concurrent use.
+func ReplaceGlobals(logger *Logger) func() {
+	_globalMu.Lock()
+	prev := _globalL
+	_globalL = logger
+	_globalS = logger.Sugar()
+	_globalMu.Unlock()
+	return func() { ReplaceGlobals(prev) }
+}
+
+// NewStdLog returns a *log.Logger which writes to the supplied zap Logger at
+// InfoLevel. To redirect the standard library's package-global logging
+// functions, use RedirectStdLog instead.
+func NewStdLog(l *Logger) *log.Logger {
+	logger := l.WithOptions(AddCallerSkip(_stdLogDefaultDepth + _loggerWriterDepth))
+	f := logger.Info
+	return log.New(&loggerWriter{f}, "" /* prefix */, 0 /* flags */)
+}
+
+// NewStdLogAt returns *log.Logger which writes to supplied zap logger at
+// required level.
+func NewStdLogAt(l *Logger, level zapcore.Level) (*log.Logger, error) {
+	logger := l.WithOptions(AddCallerSkip(_stdLogDefaultDepth + _loggerWriterDepth))
+	logFunc, err := levelToFunc(logger, level)
+	if err != nil {
+		return nil, err
+	}
+	return log.New(&loggerWriter{logFunc}, "" /* prefix */, 0 /* flags */), nil
+}
+
+// RedirectStdLog redirects output from the standard library's package-global
+// logger to the supplied logger at InfoLevel. Since zap already handles caller
+// annotations, timestamps, etc., it automatically disables the standard
+// library's annotations and prefixing.
+//
+// It returns a function to restore the original prefix and flags and reset the
+// standard library's output to os.Stderr.
+func RedirectStdLog(l *Logger) func() {
+	f, err := redirectStdLogAt(l, InfoLevel)
+	if err != nil {
+		// Can't get here, since passing InfoLevel to redirectStdLogAt always
+		// works.
+		panic(fmt.Sprintf(_programmerErrorTemplate, err))
+	}
+	return f
+}
+
+// RedirectStdLogAt redirects output from the standard library's package-global
+// logger to the supplied logger at the specified level. Since zap already
+// handles caller annotations, timestamps, etc., it automatically disables the
+// standard library's annotations and prefixing.
+//
+// It returns a function to restore the original prefix and flags and reset the
+// standard library's output to os.Stderr.
+func RedirectStdLogAt(l *Logger, level zapcore.Level) (func(), error) {
+	return redirectStdLogAt(l, level)
+}
+
+func redirectStdLogAt(l *Logger, level zapcore.Level) (func(), error) {
+	flags := log.Flags()
+	prefix := log.Prefix()
+	log.SetFlags(0)
+	log.SetPrefix("")
+	logger := l.WithOptions(AddCallerSkip(_stdLogDefaultDepth + _loggerWriterDepth))
+	logFunc, err := levelToFunc(logger, level)
+	if err != nil {
+		return nil, err
+	}
+	log.SetOutput(&loggerWriter{logFunc})
+	return func() {
+		log.SetFlags(flags)
+		log.SetPrefix(prefix)
+		log.SetOutput(os.Stderr)
+	}, nil
+}
+
+func levelToFunc(logger *Logger, lvl zapcore.Level) (func(string, ...Field), error) {
+	switch lvl {
+	case DebugLevel:
+		return logger.Debug, nil
+	case InfoLevel:
+		return logger.Info, nil
+	case WarnLevel:
+		return logger.Warn, nil
+	case ErrorLevel:
+		return logger.Error, nil
+	case DPanicLevel:
+		return logger.DPanic, nil
+	case PanicLevel:
+		return logger.Panic, nil
+	case FatalLevel:
+		return logger.Fatal, nil
+	}
+	return nil, fmt.Errorf("unrecognized level: %q", lvl)
+}
+
+type loggerWriter struct {
+	logFunc func(msg string, fields ...Field)
+}
+
+func (l *loggerWriter) Write(p []byte) (int, error) {
+	p = bytes.TrimSpace(p)
+	l.logFunc(string(p))
+	return len(p), nil
+}
diff --git a/vendor/go.uber.org/zap/http_handler.go b/vendor/go.uber.org/zap/http_handler.go
new file mode 100644
index 0000000..1b0ecac
--- /dev/null
+++ b/vendor/go.uber.org/zap/http_handler.go
@@ -0,0 +1,81 @@
+// Copyright (c) 2016 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package zap
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+
+	"go.uber.org/zap/zapcore"
+)
+
+// ServeHTTP is a simple JSON endpoint that can report on or change the current
+// logging level.
+//
+// GET requests return a JSON description of the current logging level. PUT
+// requests change the logging level and expect a payload like:
+//   {"level":"info"}
+//
+// It's perfectly safe to change the logging level while a program is running.
+func (lvl AtomicLevel) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	type errorResponse struct {
+		Error string `json:"error"`
+	}
+	type payload struct {
+		Level *zapcore.Level `json:"level"`
+	}
+
+	enc := json.NewEncoder(w)
+
+	switch r.Method {
+
+	case http.MethodGet:
+		current := lvl.Level()
+		enc.Encode(payload{Level: &current})
+
+	case http.MethodPut:
+		var req payload
+
+		if errmess := func() string {
+			if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+				return fmt.Sprintf("Request body must be well-formed JSON: %v", err)
+			}
+			if req.Level == nil {
+				return "Must specify a logging level."
+			}
+			return ""
+		}(); errmess != "" {
+			w.WriteHeader(http.StatusBadRequest)
+			enc.Encode(errorResponse{Error: errmess})
+			return
+		}
+
+		lvl.SetLevel(*req.Level)
+		enc.Encode(req)
+
+	default:
+		w.WriteHeader(http.StatusMethodNotAllowed)
+		enc.Encode(errorResponse{
+			Error: "Only GET and PUT are supported.",
+		})
+	}
+}
diff --git a/vendor/go.uber.org/zap/internal/bufferpool/bufferpool.go b/vendor/go.uber.org/zap/internal/bufferpool/bufferpool.go
new file mode 100644
index 0000000..dad583a
--- /dev/null
+++ b/vendor/go.uber.org/zap/internal/bufferpool/bufferpool.go
@@ -0,0 +1,31 @@
+// Copyright (c) 2016 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+// Package bufferpool houses zap's shared internal buffer pool. Third-party
+// packages can recreate the same functionality with buffers.NewPool.
+package bufferpool
+
+import "go.uber.org/zap/buffer"
+
+var (
+	_pool = buffer.NewPool()
+	// Get retrieves a buffer from the pool, creating one if necessary.
+	Get = _pool.Get
+)
diff --git a/vendor/go.uber.org/zap/internal/color/color.go b/vendor/go.uber.org/zap/internal/color/color.go
new file mode 100644
index 0000000..c4d5d02
--- /dev/null
+++ b/vendor/go.uber.org/zap/internal/color/color.go
@@ -0,0 +1,44 @@
+// Copyright (c) 2016 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+// Package color adds coloring functionality for TTY output.
+package color
+
+import "fmt"
+
+// Foreground colors.
+const (
+	Black Color = iota + 30
+	Red
+	Green
+	Yellow
+	Blue
+	Magenta
+	Cyan
+	White
+)
+
+// Color represents a text color.
+type Color uint8
+
+// Add adds the coloring to the given string.
+func (c Color) Add(s string) string {
+	return fmt.Sprintf("\x1b[%dm%s\x1b[0m", uint8(c), s)
+}
diff --git a/vendor/go.uber.org/zap/internal/exit/exit.go b/vendor/go.uber.org/zap/internal/exit/exit.go
new file mode 100644
index 0000000..dfc5b05
--- /dev/null
+++ b/vendor/go.uber.org/zap/internal/exit/exit.go
@@ -0,0 +1,64 @@
+// Copyright (c) 2016 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+// Package exit provides stubs so that unit tests can exercise code that calls
+// os.Exit(1).
+package exit
+
+import "os"
+
+var real = func() { os.Exit(1) }
+
+// Exit normally terminates the process by calling os.Exit(1). If the package
+// is stubbed, it instead records a call in the testing spy.
+func Exit() {
+	real()
+}
+
+// A StubbedExit is a testing fake for os.Exit.
+type StubbedExit struct {
+	Exited bool
+	prev   func()
+}
+
+// Stub substitutes a fake for the call to os.Exit(1).
+func Stub() *StubbedExit {
+	s := &StubbedExit{prev: real}
+	real = s.exit
+	return s
+}
+
+// WithStub runs the supplied function with Exit stubbed. It returns the stub
+// used, so that users can test whether the process would have crashed.
+func WithStub(f func()) *StubbedExit {
+	s := Stub()
+	defer s.Unstub()
+	f()
+	return s
+}
+
+// Unstub restores the previous exit function.
+func (se *StubbedExit) Unstub() {
+	real = se.prev
+}
+
+func (se *StubbedExit) exit() {
+	se.Exited = true
+}
diff --git a/vendor/go.uber.org/zap/level.go b/vendor/go.uber.org/zap/level.go
new file mode 100644
index 0000000..3567a9a
--- /dev/null
+++ b/vendor/go.uber.org/zap/level.go
@@ -0,0 +1,132 @@
+// Copyright (c) 2016 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package zap
+
+import (
+	"go.uber.org/atomic"
+	"go.uber.org/zap/zapcore"
+)
+
+const (
+	// DebugLevel logs are typically voluminous, and are usually disabled in
+	// production.
+	DebugLevel = zapcore.DebugLevel
+	// InfoLevel is the default logging priority.
+	InfoLevel = zapcore.InfoLevel
+	// WarnLevel logs are more important than Info, but don't need individual
+	// human review.
+	WarnLevel = zapcore.WarnLevel
+	// ErrorLevel logs are high-priority. If an application is running smoothly,
+	// it shouldn't generate any error-level logs.
+	ErrorLevel = zapcore.ErrorLevel
+	// DPanicLevel logs are particularly important errors. In development the
+	// logger panics after writing the message.
+	DPanicLevel = zapcore.DPanicLevel
+	// PanicLevel logs a message, then panics.
+	PanicLevel = zapcore.PanicLevel
+	// FatalLevel logs a message, then calls os.Exit(1).
+	FatalLevel = zapcore.FatalLevel
+)
+
+// LevelEnablerFunc is a convenient way to implement zapcore.LevelEnabler with
+// an anonymous function.
+//
+// It's particularly useful when splitting log output between different
+// outputs (e.g., standard error and standard out). For sample code, see the
+// package-level AdvancedConfiguration example.
+type LevelEnablerFunc func(zapcore.Level) bool
+
+// Enabled calls the wrapped function.
+func (f LevelEnablerFunc) Enabled(lvl zapcore.Level) bool { return f(lvl) }
+
+// An AtomicLevel is an atomically changeable, dynamic logging level. It lets
+// you safely change the log level of a tree of loggers (the root logger and
+// any children created by adding context) at runtime.
+//
+// The AtomicLevel itself is an http.Handler that serves a JSON endpoint to
+// alter its level.
+//
+// AtomicLevels must be created with the NewAtomicLevel constructor to allocate
+// their internal atomic pointer.
+type AtomicLevel struct {
+	l *atomic.Int32
+}
+
+// NewAtomicLevel creates an AtomicLevel with InfoLevel and above logging
+// enabled.
+func NewAtomicLevel() AtomicLevel {
+	return AtomicLevel{
+		l: atomic.NewInt32(int32(InfoLevel)),
+	}
+}
+
+// NewAtomicLevelAt is a convenience function that creates an AtomicLevel
+// and then calls SetLevel with the given level.
+func NewAtomicLevelAt(l zapcore.Level) AtomicLevel {
+	a := NewAtomicLevel()
+	a.SetLevel(l)
+	return a
+}
+
+// Enabled implements the zapcore.LevelEnabler interface, which allows the
+// AtomicLevel to be used in place of traditional static levels.
+func (lvl AtomicLevel) Enabled(l zapcore.Level) bool {
+	return lvl.Level().Enabled(l)
+}
+
+// Level returns the minimum enabled log level.
+func (lvl AtomicLevel) Level() zapcore.Level {
+	return zapcore.Level(int8(lvl.l.Load()))
+}
+
+// SetLevel alters the logging level.
+func (lvl AtomicLevel) SetLevel(l zapcore.Level) {
+	lvl.l.Store(int32(l))
+}
+
+// String returns the string representation of the underlying Level.
+func (lvl AtomicLevel) String() string {
+	return lvl.Level().String()
+}
+
+// UnmarshalText unmarshals the text to an AtomicLevel. It uses the same text
+// representations as the static zapcore.Levels ("debug", "info", "warn",
+// "error", "dpanic", "panic", and "fatal").
+func (lvl *AtomicLevel) UnmarshalText(text []byte) error {
+	if lvl.l == nil {
+		lvl.l = &atomic.Int32{}
+	}
+
+	var l zapcore.Level
+	if err := l.UnmarshalText(text); err != nil {
+		return err
+	}
+
+	lvl.SetLevel(l)
+	return nil
+}
+
+// MarshalText marshals the AtomicLevel to a byte slice. It uses the same
+// text representation as the static zapcore.Levels ("debug", "info", "warn",
+// "error", "dpanic", "panic", and "fatal").
+func (lvl AtomicLevel) MarshalText() (text []byte, err error) {
+	return lvl.Level().MarshalText()
+}
diff --git a/vendor/go.uber.org/zap/logger.go b/vendor/go.uber.org/zap/logger.go
new file mode 100644
index 0000000..dc8f6e3
--- /dev/null
+++ b/vendor/go.uber.org/zap/logger.go
@@ -0,0 +1,305 @@
+// Copyright (c) 2016 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package zap
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"runtime"
+	"strings"
+	"time"
+
+	"go.uber.org/zap/zapcore"
+)
+
+// A Logger provides fast, leveled, structured logging. All methods are safe
+// for concurrent use.
+//
+// The Logger is designed for contexts in which every microsecond and every
+// allocation matters, so its API intentionally favors performance and type
+// safety over brevity. For most applications, the SugaredLogger strikes a
+// better balance between performance and ergonomics.
+type Logger struct {
+	core zapcore.Core
+
+	development bool
+	name        string
+	errorOutput zapcore.WriteSyncer
+
+	addCaller bool
+	addStack  zapcore.LevelEnabler
+
+	callerSkip int
+}
+
+// New constructs a new Logger from the provided zapcore.Core and Options. If
+// the passed zapcore.Core is nil, it falls back to using a no-op
+// implementation.
+//
+// This is the most flexible way to construct a Logger, but also the most
+// verbose. For typical use cases, the highly-opinionated presets
+// (NewProduction, NewDevelopment, and NewExample) or the Config struct are
+// more convenient.
+//
+// For sample code, see the package-level AdvancedConfiguration example.
+func New(core zapcore.Core, options ...Option) *Logger {
+	if core == nil {
+		return NewNop()
+	}
+	log := &Logger{
+		core:        core,
+		errorOutput: zapcore.Lock(os.Stderr),
+		addStack:    zapcore.FatalLevel + 1,
+	}
+	return log.WithOptions(options...)
+}
+
+// NewNop returns a no-op Logger. It never writes out logs or internal errors,
+// and it never runs user-defined hooks.
+//
+// Using WithOptions to replace the Core or error output of a no-op Logger can
+// re-enable logging.
+func NewNop() *Logger {
+	return &Logger{
+		core:        zapcore.NewNopCore(),
+		errorOutput: zapcore.AddSync(ioutil.Discard),
+		addStack:    zapcore.FatalLevel + 1,
+	}
+}
+
+// NewProduction builds a sensible production Logger that writes InfoLevel and
+// above logs to standard error as JSON.
+//
+// It's a shortcut for NewProductionConfig().Build(...Option).
+func NewProduction(options ...Option) (*Logger, error) {
+	return NewProductionConfig().Build(options...)
+}
+
+// NewDevelopment builds a development Logger that writes DebugLevel and above
+// logs to standard error in a human-friendly format.
+//
+// It's a shortcut for NewDevelopmentConfig().Build(...Option).
+func NewDevelopment(options ...Option) (*Logger, error) {
+	return NewDevelopmentConfig().Build(options...)
+}
+
+// NewExample builds a Logger that's designed for use in zap's testable
+// examples. It writes DebugLevel and above logs to standard out as JSON, but
+// omits the timestamp and calling function to keep example output
+// short and deterministic.
+func NewExample(options ...Option) *Logger {
+	encoderCfg := zapcore.EncoderConfig{
+		MessageKey:     "msg",
+		LevelKey:       "level",
+		NameKey:        "logger",
+		EncodeLevel:    zapcore.LowercaseLevelEncoder,
+		EncodeTime:     zapcore.ISO8601TimeEncoder,
+		EncodeDuration: zapcore.StringDurationEncoder,
+	}
+	core := zapcore.NewCore(zapcore.NewJSONEncoder(encoderCfg), os.Stdout, DebugLevel)
+	return New(core).WithOptions(options...)
+}
+
+// Sugar wraps the Logger to provide a more ergonomic, but slightly slower,
+// API. Sugaring a Logger is quite inexpensive, so it's reasonable for a
+// single application to use both Loggers and SugaredLoggers, converting
+// between them on the boundaries of performance-sensitive code.
+func (log *Logger) Sugar() *SugaredLogger {
+	core := log.clone()
+	core.callerSkip += 2
+	return &SugaredLogger{core}
+}
+
+// Named adds a new path segment to the logger's name. Segments are joined by
+// periods. By default, Loggers are unnamed.
+func (log *Logger) Named(s string) *Logger {
+	if s == "" {
+		return log
+	}
+	l := log.clone()
+	if log.name == "" {
+		l.name = s
+	} else {
+		l.name = strings.Join([]string{l.name, s}, ".")
+	}
+	return l
+}
+
+// WithOptions clones the current Logger, applies the supplied Options, and
+// returns the resulting Logger. It's safe to use concurrently.
+func (log *Logger) WithOptions(opts ...Option) *Logger {
+	c := log.clone()
+	for _, opt := range opts {
+		opt.apply(c)
+	}
+	return c
+}
+
+// With creates a child logger and adds structured context to it. Fields added
+// to the child don't affect the parent, and vice versa.
+func (log *Logger) With(fields ...Field) *Logger {
+	if len(fields) == 0 {
+		return log
+	}
+	l := log.clone()
+	l.core = l.core.With(fields)
+	return l
+}
+
+// Check returns a CheckedEntry if logging a message at the specified level
+// is enabled. It's a completely optional optimization; in high-performance
+// applications, Check can help avoid allocating a slice to hold fields.
+func (log *Logger) Check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry {
+	return log.check(lvl, msg)
+}
+
+// Debug logs a message at DebugLevel. The message includes any fields passed
+// at the log site, as well as any fields accumulated on the logger.
+func (log *Logger) Debug(msg string, fields ...Field) {
+	if ce := log.check(DebugLevel, msg); ce != nil {
+		ce.Write(fields...)
+	}
+}
+
+// Info logs a message at InfoLevel. The message includes any fields passed
+// at the log site, as well as any fields accumulated on the logger.
+func (log *Logger) Info(msg string, fields ...Field) {
+	if ce := log.check(InfoLevel, msg); ce != nil {
+		ce.Write(fields...)
+	}
+}
+
+// Warn logs a message at WarnLevel. The message includes any fields passed
+// at the log site, as well as any fields accumulated on the logger.
+func (log *Logger) Warn(msg string, fields ...Field) {
+	if ce := log.check(WarnLevel, msg); ce != nil {
+		ce.Write(fields...)
+	}
+}
+
+// Error logs a message at ErrorLevel. The message includes any fields passed
+// at the log site, as well as any fields accumulated on the logger.
+func (log *Logger) Error(msg string, fields ...Field) {
+	if ce := log.check(ErrorLevel, msg); ce != nil {
+		ce.Write(fields...)
+	}
+}
+
+// DPanic logs a message at DPanicLevel. The message includes any fields
+// passed at the log site, as well as any fields accumulated on the logger.
+//
+// If the logger is in development mode, it then panics (DPanic means
+// "development panic"). This is useful for catching errors that are
+// recoverable, but shouldn't ever happen.
+func (log *Logger) DPanic(msg string, fields ...Field) {
+	if ce := log.check(DPanicLevel, msg); ce != nil {
+		ce.Write(fields...)
+	}
+}
+
+// Panic logs a message at PanicLevel. The message includes any fields passed
+// at the log site, as well as any fields accumulated on the logger.
+//
+// The logger then panics, even if logging at PanicLevel is disabled.
+func (log *Logger) Panic(msg string, fields ...Field) {
+	if ce := log.check(PanicLevel, msg); ce != nil {
+		ce.Write(fields...)
+	}
+}
+
+// Fatal logs a message at FatalLevel. The message includes any fields passed
+// at the log site, as well as any fields accumulated on the logger.
+//
+// The logger then calls os.Exit(1), even if logging at FatalLevel is
+// disabled.
+func (log *Logger) Fatal(msg string, fields ...Field) {
+	if ce := log.check(FatalLevel, msg); ce != nil {
+		ce.Write(fields...)
+	}
+}
+
+// Sync calls the underlying Core's Sync method, flushing any buffered log
+// entries. Applications should take care to call Sync before exiting.
+func (log *Logger) Sync() error {
+	return log.core.Sync()
+}
+
+// Core returns the Logger's underlying zapcore.Core.
+func (log *Logger) Core() zapcore.Core {
+	return log.core
+}
+
+func (log *Logger) clone() *Logger {
+	copy := *log
+	return &copy
+}
+
+func (log *Logger) check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry {
+	// check must always be called directly by a method in the Logger interface
+	// (e.g., Check, Info, Fatal).
+	const callerSkipOffset = 2
+
+	// Create basic checked entry thru the core; this will be non-nil if the
+	// log message will actually be written somewhere.
+	ent := zapcore.Entry{
+		LoggerName: log.name,
+		Time:       time.Now(),
+		Level:      lvl,
+		Message:    msg,
+	}
+	ce := log.core.Check(ent, nil)
+	willWrite := ce != nil
+
+	// Set up any required terminal behavior.
+	switch ent.Level {
+	case zapcore.PanicLevel:
+		ce = ce.Should(ent, zapcore.WriteThenPanic)
+	case zapcore.FatalLevel:
+		ce = ce.Should(ent, zapcore.WriteThenFatal)
+	case zapcore.DPanicLevel:
+		if log.development {
+			ce = ce.Should(ent, zapcore.WriteThenPanic)
+		}
+	}
+
+	// Only do further annotation if we're going to write this message; checked
+	// entries that exist only for terminal behavior don't benefit from
+	// annotation.
+	if !willWrite {
+		return ce
+	}
+
+	// Thread the error output through to the CheckedEntry.
+	ce.ErrorOutput = log.errorOutput
+	if log.addCaller {
+		ce.Entry.Caller = zapcore.NewEntryCaller(runtime.Caller(log.callerSkip + callerSkipOffset))
+		if !ce.Entry.Caller.Defined {
+			fmt.Fprintf(log.errorOutput, "%v Logger.check error: failed to get caller\n", time.Now().UTC())
+			log.errorOutput.Sync()
+		}
+	}
+	if log.addStack.Enabled(ce.Entry.Level) {
+		ce.Entry.Stack = Stack("").String
+	}
+
+	return ce
+}
diff --git a/vendor/go.uber.org/zap/options.go b/vendor/go.uber.org/zap/options.go
new file mode 100644
index 0000000..7a6b0fc
--- /dev/null
+++ b/vendor/go.uber.org/zap/options.go
@@ -0,0 +1,109 @@
+// Copyright (c) 2016 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package zap
+
+import "go.uber.org/zap/zapcore"
+
+// An Option configures a Logger.
+type Option interface {
+	apply(*Logger)
+}
+
+// optionFunc wraps a func so it satisfies the Option interface.
+type optionFunc func(*Logger)
+
+func (f optionFunc) apply(log *Logger) {
+	f(log)
+}
+
+// WrapCore wraps or replaces the Logger's underlying zapcore.Core.
+func WrapCore(f func(zapcore.Core) zapcore.Core) Option {
+	return optionFunc(func(log *Logger) {
+		log.core = f(log.core)
+	})
+}
+
+// Hooks registers functions which will be called each time the Logger writes
+// out an Entry. Repeated use of Hooks is additive.
+//
+// Hooks are useful for simple side effects, like capturing metrics for the
+// number of emitted logs. More complex side effects, including anything that
+// requires access to the Entry's structured fields, should be implemented as
+// a zapcore.Core instead. See zapcore.RegisterHooks for details.
+func Hooks(hooks ...func(zapcore.Entry) error) Option {
+	return optionFunc(func(log *Logger) {
+		log.core = zapcore.RegisterHooks(log.core, hooks...)
+	})
+}
+
+// Fields adds fields to the Logger.
+func Fields(fs ...Field) Option {
+	return optionFunc(func(log *Logger) {
+		log.core = log.core.With(fs)
+	})
+}
+
+// ErrorOutput sets the destination for errors generated by the Logger. Note
+// that this option only affects internal errors; for sample code that sends
+// error-level logs to a different location from info- and debug-level logs,
+// see the package-level AdvancedConfiguration example.
+//
+// The supplied WriteSyncer must be safe for concurrent use. The Open and
+// zapcore.Lock functions are the simplest ways to protect files with a mutex.
+func ErrorOutput(w zapcore.WriteSyncer) Option {
+	return optionFunc(func(log *Logger) {
+		log.errorOutput = w
+	})
+}
+
+// Development puts the logger in development mode, which makes DPanic-level
+// logs panic instead of simply logging an error.
+func Development() Option {
+	return optionFunc(func(log *Logger) {
+		log.development = true
+	})
+}
+
+// AddCaller configures the Logger to annotate each message with the filename
+// and line number of zap's caller.
+func AddCaller() Option {
+	return optionFunc(func(log *Logger) {
+		log.addCaller = true
+	})
+}
+
+// AddCallerSkip increases the number of callers skipped by caller annotation
+// (as enabled by the AddCaller option). When building wrappers around the
+// Logger and SugaredLogger, supplying this Option prevents zap from always
+// reporting the wrapper code as the caller.
+func AddCallerSkip(skip int) Option {
+	return optionFunc(func(log *Logger) {
+		log.callerSkip += skip
+	})
+}
+
+// AddStacktrace configures the Logger to record a stack trace for all messages at
+// or above a given level.
+func AddStacktrace(lvl zapcore.LevelEnabler) Option {
+	return optionFunc(func(log *Logger) {
+		log.addStack = lvl
+	})
+}
diff --git a/vendor/go.uber.org/zap/sink.go b/vendor/go.uber.org/zap/sink.go
new file mode 100644
index 0000000..ff0becf
--- /dev/null
+++ b/vendor/go.uber.org/zap/sink.go
@@ -0,0 +1,161 @@
+// Copyright (c) 2016 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package zap
+
+import (
+	"errors"
+	"fmt"
+	"io"
+	"net/url"
+	"os"
+	"strings"
+	"sync"
+
+	"go.uber.org/zap/zapcore"
+)
+
+const schemeFile = "file"
+
+var (
+	_sinkMutex     sync.RWMutex
+	_sinkFactories map[string]func(*url.URL) (Sink, error) // keyed by scheme
+)
+
+func init() {
+	resetSinkRegistry()
+}
+
+func resetSinkRegistry() {
+	_sinkMutex.Lock()
+	defer _sinkMutex.Unlock()
+
+	_sinkFactories = map[string]func(*url.URL) (Sink, error){
+		schemeFile: newFileSink,
+	}
+}
+
+// Sink defines the interface to write to and close logger destinations.
+type Sink interface {
+	zapcore.WriteSyncer
+	io.Closer
+}
+
+type nopCloserSink struct{ zapcore.WriteSyncer }
+
+func (nopCloserSink) Close() error { return nil }
+
+type errSinkNotFound struct {
+	scheme string
+}
+
+func (e *errSinkNotFound) Error() string {
+	return fmt.Sprintf("no sink found for scheme %q", e.scheme)
+}
+
+// RegisterSink registers a user-supplied factory for all sinks with a
+// particular scheme.
+//
+// All schemes must be ASCII, valid under section 3.1 of RFC 3986
+// (https://tools.ietf.org/html/rfc3986#section-3.1), and must not already
+// have a factory registered. Zap automatically registers a factory for the
+// "file" scheme.
+func RegisterSink(scheme string, factory func(*url.URL) (Sink, error)) error {
+	_sinkMutex.Lock()
+	defer _sinkMutex.Unlock()
+
+	if scheme == "" {
+		return errors.New("can't register a sink factory for empty string")
+	}
+	normalized, err := normalizeScheme(scheme)
+	if err != nil {
+		return fmt.Errorf("%q is not a valid scheme: %v", scheme, err)
+	}
+	if _, ok := _sinkFactories[normalized]; ok {
+		return fmt.Errorf("sink factory already registered for scheme %q", normalized)
+	}
+	_sinkFactories[normalized] = factory
+	return nil
+}
+
+func newSink(rawURL string) (Sink, error) {
+	u, err := url.Parse(rawURL)
+	if err != nil {
+		return nil, fmt.Errorf("can't parse %q as a URL: %v", rawURL, err)
+	}
+	if u.Scheme == "" {
+		u.Scheme = schemeFile
+	}
+
+	_sinkMutex.RLock()
+	factory, ok := _sinkFactories[u.Scheme]
+	_sinkMutex.RUnlock()
+	if !ok {
+		return nil, &errSinkNotFound{u.Scheme}
+	}
+	return factory(u)
+}
+
+func newFileSink(u *url.URL) (Sink, error) {
+	if u.User != nil {
+		return nil, fmt.Errorf("user and password not allowed with file URLs: got %v", u)
+	}
+	if u.Fragment != "" {
+		return nil, fmt.Errorf("fragments not allowed with file URLs: got %v", u)
+	}
+	if u.RawQuery != "" {
+		return nil, fmt.Errorf("query parameters not allowed with file URLs: got %v", u)
+	}
+	// Error messages are better if we check hostname and port separately.
+	if u.Port() != "" {
+		return nil, fmt.Errorf("ports not allowed with file URLs: got %v", u)
+	}
+	if hn := u.Hostname(); hn != "" && hn != "localhost" {
+		return nil, fmt.Errorf("file URLs must leave host empty or use localhost: got %v", u)
... 4909 lines suppressed ...