Repository: knative/pkg
Pull request #1275 (2020-05-01 22:25:07)

Create an interface for retrieving duck-type fields
Issue #1226

- This creates a new interface for receiving the core fields of KResource duck resources
- KResource implements the interface, but I intend to use this to implement for the basic API types
    - Unblocks the ability to cast to the interface and reason generically about the resource. I intend to use this to detect and run common logic for these resources.

knative-prow-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/knative/pkg/pull/1275#" title="Author self-approved">whaught</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **mattmoor**
You can assign the PR to them by writing `/assign @mattmoor` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=knative%2Fpkg).

<details open>
Needs approval from an approver in each of these files:

- **[apis/duck/OWNERS](https://github.com/knative/pkg/blob/master/apis/duck/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["mattmoor"]} -->
knative-prow-robot: @whaught: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-pkg-unit-tests | 894810650c6f2063fac0588b6383fb0d6fb4070c | [link](https://prow.knative.dev/view/gcs/knative-prow/pr-logs/pull/knative_pkg/1275/pull-knative-pkg-unit-tests/1256349335805759489) | `/test pull-knative-pkg-unit-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_pkg/1275). [Your PR dashboard](https://gubernator.knative.dev/pr/whaught).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
knative-prow-robot: @whaught: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-pkg-unit-tests | 894810650c6f2063fac0588b6383fb0d6fb4070c | [link](https://prow.knative.dev/view/gcs/knative-prow/pr-logs/pull/knative_pkg/1275/pull-knative-pkg-unit-tests/1256349335805759489) | `/test pull-knative-pkg-unit-tests`
pull-knative-pkg-build-tests | 894810650c6f2063fac0588b6383fb0d6fb4070c | [link](https://prow.knative.dev/view/gcs/knative-prow/pr-logs/pull/knative_pkg/1275/pull-knative-pkg-build-tests/1256349335805759488) | `/test pull-knative-pkg-build-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_pkg/1275). [Your PR dashboard](https://gubernator.knative.dev/pr/whaught).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-pkg-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[apis/duck/v1/kresource_type.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1275/pull-knative-pkg-go-coverage/1256352230055874562/artifacts/line-cov.html#file0) | Do not exist | 50.0% | 

whaught: /assign @vaikas @n3wscott 
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-pkg-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[apis/duck/v1/kresource_type.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1275/pull-knative-pkg-go-coverage/1256354997482819584/artifacts/line-cov.html#file0) | Do not exist | 50.0% | 

whaught: /hold I'm not sure if this is going to work without some reflection.

There isn't a way in golang to dynamically test if a resource implements an interface and then run logic on it that way.

The advantage of this would be instead of #1261 we'd reflect an instance of KRShaped from our type and from there we could create common logic (instead of duplicating that logic with the generator)
whaught: /unhold nah let's keep going with this. As long as we still create a new tag to genreconciler we'll only do this for KRShaped things. At least now we can create unit-testable functions on this type more easily instead of doing it all directly in the generated code.

Patch:
diff --git a/apis/duck/v1/kresource_type.go b/apis/duck/v1/kresource_type.go
new file mode 100644
index 000000000..22a3b33ec
--- /dev/null
+++ b/apis/duck/v1/kresource_type.go
@@ -0,0 +1,95 @@
+/*
+Copyright 2020 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 v1
+
+import (
+	"time"
+
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+
+	"knative.dev/pkg/apis"
+)
+
+// KRShaped is an interface for retrieving the duck elements of an arbitraty resource.
+type KRShaped interface {
+	GetTypeMeta() *metav1.TypeMeta
+
+	GetObjectMeta() *metav1.ObjectMeta
+
+	GetStatus() *Status
+}
+
+// Asserts KResource conformance with KRShaped
+var _ KRShaped = (*KResource)(nil)
+
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+
+// KResource is a skeleton type wrapping Conditions in the manner we expect
+// resource writers defining compatible resources to embed it.  We will
+// typically use this type to deserialize Conditions ObjectReferences and
+// access the Conditions data.  This is not a real resource.
+type KResource struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Status Status `json:"status"`
+}
+
+// Populate implements duck.Populatable
+func (t *KResource) Populate() {
+	t.Status.ObservedGeneration = 42
+	t.Status.Conditions = Conditions{{
+		// Populate ALL fields
+		Type:               "Birthday",
+		Status:             corev1.ConditionTrue,
+		LastTransitionTime: apis.VolatileTime{Inner: metav1.NewTime(time.Date(1984, 02, 28, 18, 52, 00, 00, time.UTC))},
+		Reason:             "Celebrate",
+		Message:            "n3wScott, find your party hat :tada:",
+	}}
+}
+
+// GetListType implements apis.Listable
+func (*KResource) GetListType() runtime.Object {
+	return &KResourceList{}
+}
+
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+
+// KResourceList is a list of KResource resources
+type KResourceList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata"`
+
+	Items []KResource `json:"items"`
+}
+
+// GetTypeMeta retrieves the ObjectMeta of the KResource. Implements the KRShaped interface.
+func (t *KResource) GetTypeMeta() *metav1.TypeMeta {
+	return &t.TypeMeta
+}
+
+// GetObjectMeta retrieves the ObjectMeta of the KResource. Implements the KRShaped interface.
+func (t *KResource) GetObjectMeta() *metav1.ObjectMeta {
+	return &t.ObjectMeta
+}
+
+// GetStatus retrieves the status of the KResource. Implements the KRShaped interface.
+func (t *KResource) GetStatus() *Status {
+	return &t.Status
+}
diff --git a/apis/duck/v1/status_types.go b/apis/duck/v1/status_types.go
index 2165e7838..9186e961f 100644
--- a/apis/duck/v1/status_types.go
+++ b/apis/duck/v1/status_types.go
@@ -18,11 +18,6 @@ package v1
 
 import (
 	"context"
-	"time"
-
-	corev1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/runtime"
 
 	"knative.dev/pkg/apis"
 	"knative.dev/pkg/apis/duck"
@@ -36,19 +31,6 @@ type Conditions apis.Conditions
 // Conditions is an Implementable "duck type".
 var _ duck.Implementable = (*Conditions)(nil)
 
-// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
-
-// KResource is a skeleton type wrapping Conditions in the manner we expect
-// resource writers defining compatible resources to embed it.  We will
-// typically use this type to deserialize Conditions ObjectReferences and
-// access the Conditions data.  This is not a real resource.
-type KResource struct {
-	metav1.TypeMeta   `json:",inline"`
-	metav1.ObjectMeta `json:"metadata,omitempty"`
-
-	Status Status `json:"status"`
-}
-
 // Status shows how we expect folks to embed Conditions in
 // their Status field.
 // WARNING: Adding fields to this struct will add them to all Knative resources.
@@ -126,31 +108,3 @@ func (source *Status) ConvertTo(ctx context.Context, sink *Status, predicates ..
 
 	sink.SetConditions(conditions)
 }
-
-// Populate implements duck.Populatable
-func (t *KResource) Populate() {
-	t.Status.ObservedGeneration = 42
-	t.Status.Conditions = Conditions{{
-		// Populate ALL fields
-		Type:               "Birthday",
-		Status:             corev1.ConditionTrue,
-		LastTransitionTime: apis.VolatileTime{Inner: metav1.NewTime(time.Date(1984, 02, 28, 18, 52, 00, 00, time.UTC))},
-		Reason:             "Celebrate",
-		Message:            "n3wScott, find your party hat :tada:",
-	}}
-}
-
-// GetListType implements apis.Listable
-func (*KResource) GetListType() runtime.Object {
-	return &KResourceList{}
-}
-
-// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
-
-// KResourceList is a list of KResource resources
-type KResourceList struct {
-	metav1.TypeMeta `json:",inline"`
-	metav1.ListMeta `json:"metadata"`
-
-	Items []KResource `json:"items"`
-}
diff --git a/codegen/cmd/injection-gen/generators/comment_parser.go b/codegen/cmd/injection-gen/generators/comment_parser.go
new file mode 100644
index 000000000..ada0dff3e
--- /dev/null
+++ b/codegen/cmd/injection-gen/generators/comment_parser.go
@@ -0,0 +1,75 @@
+/*
+Copyright 2020 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 generators
+
+import "strings"
+
+// Adapted from the k8s.io comment parser https://github.com/kubernetes/gengo/blob/master/types/comments.go
+
+// ExtractCommentTags parses comments for lines of the form:
+//
+//   'marker' + ':' "key=value,key2=value2".
+//
+// Values are optional; empty map is the default.  A tag can be specified more than
+// one time and all values are returned.  If the resulting map has an entry for
+// a key, the value (a slice) is guaranteed to have at least 1 element.
+//
+// Example: if you pass "+" for 'marker', and the following lines are in
+// the comments:
+//   +foo:key=value1,key2=value2
+//   +bar
+//
+// Then this function will return:
+//   map[string]map[string]string{"foo":{"key":value1","key2":"value2"}, "bar": nil}
+//
+// Users are not expected to repeat values.
+func ExtractCommentTags(marker string, lines []string) map[string]map[string]string {
+	out := map[string]map[string]string{}
+	for _, line := range lines {
+		line = strings.TrimSpace(line)
+		if len(line) == 0 || !strings.HasPrefix(line, marker) {
+			continue
+		}
+
+		options := strings.SplitN(line[len(marker):], ":", 2)
+		if len(options) == 2 {
+			vals := strings.Split(options[1], ",")
+
+			opts := out[options[0]]
+			if opts == nil {
+				opts = make(map[string]string, len(vals))
+			}
+
+			for _, pair := range vals {
+				if kv := strings.SplitN(pair, "=", 2); len(kv) == 2 {
+					opts[kv[0]] = kv[1]
+				} else if kv[0] != "" {
+					opts[kv[0]] = ""
+				}
+			}
+			if len(opts) == 0 {
+				out[options[0]] = nil
+			} else {
+				out[options[0]] = opts
+			}
+		} else if len(options) == 1 && options[0] != "" {
+			if _, has := out[options[0]]; !has {
+				out[options[0]] = nil
+			}
+		}
+	}
+	return out
+}
diff --git a/codegen/cmd/injection-gen/generators/packages.go b/codegen/cmd/injection-gen/generators/packages.go
index ce8b6dbc6..012e6c0b6 100644
--- a/codegen/cmd/injection-gen/generators/packages.go
+++ b/codegen/cmd/injection-gen/generators/packages.go
@@ -188,22 +188,27 @@ func MustParseClientGenTags(lines []string) Tags {
 	return ret
 }
 
-func extractReconcilerClassTag(t *types.Type) (string, bool) {
+func extractCommentTags(t *types.Type) map[string]map[string]string {
 	comments := append(append([]string{}, t.SecondClosestCommentLines...), t.CommentLines...)
-	values := types.ExtractCommentTags("+", comments)["genreconciler:class"]
-	for _, v := range values {
-		if len(v) == 0 {
-			continue
-		}
-		return v, true
+	return ExtractCommentTags("+", comments)
+}
+
+func extractReconcilerClassTag(tags map[string]map[string]string) (classname string, has bool) {
+	vals, has := tags["genreconciler"]
+	if !has {
+		return
 	}
-	return "", false
+	classname, _ = vals["class"]
+	return
 }
 
-func isNonNamespaced(t *types.Type) bool {
-	comments := append(append([]string{}, t.SecondClosestCommentLines...), t.CommentLines...)
-	_, nonNamespaced := types.ExtractCommentTags("+", comments)["genclient:nonNamespaced"]
-	return nonNamespaced
+func isNonNamespaced(tags map[string]map[string]string) bool {
+	vals, has := tags["genclient"]
+	if !has {
+		return false
+	}
+	_, has = vals["nonNamespaced"]
+	return has
 }
 
 func vendorless(p string) string {
@@ -416,8 +421,9 @@ func reconcilerPackages(basePackage string, groupPkgName string, gv clientgentyp
 		// Fix for golang iterator bug.
 		t := t
 
-		reconcilerClass, hasReconcilerClass := extractReconcilerClassTag(t)
-		nonNamespaced := isNonNamespaced(t)
+		extracted := extractCommentTags(t)
+		reconcilerClass, hasReconcilerClass := extractReconcilerClassTag(extracted)
+		nonNamespaced := isNonNamespaced(extracted)
 
 		packagePath := filepath.Join(packagePath, strings.ToLower(t.Name.Name))
 
diff --git a/codegen/cmd/injection-gen/generators/reconciler_controller.go b/codegen/cmd/injection-gen/generators/reconciler_controller.go
index 16ec66935..bf3ca04fe 100644
--- a/codegen/cmd/injection-gen/generators/reconciler_controller.go
+++ b/codegen/cmd/injection-gen/generators/reconciler_controller.go
@@ -185,29 +185,9 @@ func NewImpl(ctx {{.contextContext|raw}}, r Interface{{if .hasClass}}, classValu
 
 	{{.type|lowercaseSingular}}Informer := {{.informerGet|raw}}(ctx)
 
-	recorder := {{.controllerGetEventRecorder|raw}}(ctx)
-	if recorder == nil {
-		// Create event broadcaster
-		logger.Debug("Creating event broadcaster")
-		eventBroadcaster := {{.recordNewBroadcaster|raw}}()
-		watches := []{{.watchInterface|raw}}{
-			eventBroadcaster.StartLogging(logger.Named("event-broadcaster").Infof),
-			eventBroadcaster.StartRecordingToSink(
-				&{{.typedcorev1EventSinkImpl|raw}}{Interface: {{.kubeclientGet|raw}}(ctx).CoreV1().Events("")}),
-		}
-		recorder = eventBroadcaster.NewRecorder({{.schemeScheme|raw}}, {{.corev1EventSource|raw}}{Component: defaultControllerAgentName})
-		go func() {
-			<-ctx.Done()
-			for _, w := range watches {
-				w.Stop()
-			}
-		}()
-	}
-
 	rec := &reconcilerImpl{
 		Client:  {{.clientGet|raw}}(ctx),
 		Lister:  {{.type|lowercaseSingular}}Informer.Lister(),
-		Recorder: recorder,
 		reconciler:    r,
 		finalizerName: defaultFinalizerName,
 		{{if .hasClass}}classValue: classValue,{{end}}
@@ -217,6 +197,7 @@ func NewImpl(ctx {{.contextContext|raw}}, r Interface{{if .hasClass}}, classValu
 	queueName := {{.fmtSprintf|raw}}("%s.%s", {{.stringsReplaceAll|raw}}(t.PkgPath(), "/", "-"), t.Name())
 
 	impl := {{.controllerNewImpl|raw}}(rec, logger, queueName)
+	agentName := defaultControllerAgentName
 
 	// Pass impl to the options. Save any optional results.
 	for _, fn := range optionsFns {
@@ -227,11 +208,41 @@ func NewImpl(ctx {{.contextContext|raw}}, r Interface{{if .hasClass}}, classValu
 		if opts.FinalizerName != "" {
 			rec.finalizerName = opts.FinalizerName
 		}
+		if opts.AgentName != "" {
+			agentName = opts.AgentName
+		}
 	}
 
+	rec.Recorder = createRecorder(ctx, agentName)
+
 	return impl
 }
 
+func createRecorder(ctx context.Context, agentName string) record.EventRecorder {
+	logger := {{.loggingFromContext|raw}}(ctx)
+
+	recorder := {{.controllerGetEventRecorder|raw}}(ctx)
+	if recorder == nil {
+		// Create event broadcaster
+		logger.Debug("Creating event broadcaster")
+		eventBroadcaster := {{.recordNewBroadcaster|raw}}()
+		watches := []{{.watchInterface|raw}}{
+			eventBroadcaster.StartLogging(logger.Named("event-broadcaster").Infof),
+			eventBroadcaster.StartRecordingToSink(
+				&{{.typedcorev1EventSinkImpl|raw}}{Interface: {{.kubeclientGet|raw}}(ctx).CoreV1().Events("")}),
+		}
+		recorder = eventBroadcaster.NewRecorder({{.schemeScheme|raw}}, {{.corev1EventSource|raw}}{Component: agentName})
+		go func() {
+			<-ctx.Done()
+			for _, w := range watches {
+				w.Stop()
+			}
+		}()
+	}
+
+	return recorder
+}
+
 func init() {
 	{{.schemeAddToScheme|raw}}({{.schemeScheme|raw}})
 }
diff --git a/controller/controller.go b/controller/controller.go
index 166d109c8..4713335d1 100644
--- a/controller/controller.go
+++ b/controller/controller.go
@@ -96,7 +96,7 @@ func Filter(gvk schema.GroupVersionKind) func(obj interface{}) bool {
 // cache.FilteringResourceEventHandler that filter based on the
 // schema.GroupVersionKind of the controlling resources.
 //
-// Deprecated: Use FilterControlledByGVK instead.
+// Deprecated: Use FilterControllerGVK instead.
 func FilterGroupVersionKind(gvk schema.GroupVersionKind) func(obj interface{}) bool {
 	return FilterControllerGVK(gvk)
 }
@@ -122,7 +122,7 @@ func FilterControllerGVK(gvk schema.GroupVersionKind) func(obj interface{}) bool
 // cache.FilteringResourceEventHandler that filter based on the
 // schema.GroupKind of the controlling resources.
 //
-// Deprecated: Use FilterControlledByGK instead
+// Deprecated: Use FilterControllerGK instead
 func FilterGroupKind(gk schema.GroupKind) func(obj interface{}) bool {
 	return FilterControllerGK(gk)
 }
diff --git a/controller/options.go b/controller/options.go
index 5b7b8a405..6853839dc 100644
--- a/controller/options.go
+++ b/controller/options.go
@@ -27,6 +27,10 @@ type Options struct {
 	// FinalizerName is the name of the finalizer this reconciler uses. This
 	// overrides a default finalizer name assigned by the generator if needed.
 	FinalizerName string
+
+	// AgentName is the name of the agent this reconciler uses. This overrides
+	// the default controller's agent name.
+	AgentName string
 }
 
 // OptionsFn is a callback method signature that accepts an Impl and returns
diff --git a/go.mod b/go.mod
index 219e83140..b89424ace 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module knative.dev/pkg
 
-go 1.13
+go 1.14
 
 require (
 	cloud.google.com/go v0.55.0
diff --git a/network/transports.go b/network/transports.go
index 1ff6f4615..019f4ba87 100644
--- a/network/transports.go
+++ b/network/transports.go
@@ -19,6 +19,7 @@ package network
 import (
 	"context"
 	"errors"
+	"fmt"
 	"net"
 	"net/http"
 	"time"
@@ -74,6 +75,7 @@ func dialBackOffHelper(ctx context.Context, network, address string, bo wait.Bac
 		KeepAlive: 5 * time.Second,
 		DualStack: true,
 	}
+	start := time.Now()
 	for {
 		c, err := dialer.DialContext(ctx, network, address)
 		if err != nil {
@@ -89,7 +91,8 @@ func dialBackOffHelper(ctx context.Context, network, address string, bo wait.Bac
 		}
 		return c, nil
 	}
-	return nil, errDialTimeout
+	elapsed := time.Now().Sub(start)
+	return nil, fmt.Errorf("timed out dialing after %.2fs", elapsed.Seconds())
 }
 
 func newHTTPTransport(connTimeout time.Duration, disableKeepAlives bool) http.RoundTripper {
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 75d2c8c39..ad74e98a2 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -1,4 +1,5 @@
 # cloud.google.com/go v0.55.0
+## explicit
 cloud.google.com/go
 cloud.google.com/go/compute/metadata
 cloud.google.com/go/container/apiv1
@@ -10,15 +11,20 @@ cloud.google.com/go/internal/version
 cloud.google.com/go/monitoring/apiv3
 cloud.google.com/go/trace/apiv2
 # cloud.google.com/go/storage v1.6.0
+## explicit
 cloud.google.com/go/storage
 # contrib.go.opencensus.io/exporter/ocagent v0.6.0
+## explicit
 contrib.go.opencensus.io/exporter/ocagent
 # contrib.go.opencensus.io/exporter/prometheus v0.1.0
+## explicit
 contrib.go.opencensus.io/exporter/prometheus
 # contrib.go.opencensus.io/exporter/stackdriver v0.12.9-0.20191108183826-59d068f8d8ff
+## explicit
 contrib.go.opencensus.io/exporter/stackdriver
 contrib.go.opencensus.io/exporter/stackdriver/monitoredresource
 # contrib.go.opencensus.io/exporter/zipkin v0.1.1
+## explicit
 contrib.go.opencensus.io/exporter/zipkin
 # github.com/BurntSushi/toml v0.3.1
 github.com/BurntSushi/toml
@@ -27,6 +33,7 @@ github.com/PuerkitoBio/purell
 # github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578
 github.com/PuerkitoBio/urlesc
 # github.com/aws/aws-sdk-go v1.29.34
+## explicit
 github.com/aws/aws-sdk-go/aws
 github.com/aws/aws-sdk-go/aws/awserr
 github.com/aws/aws-sdk-go/aws/awsutil
@@ -65,7 +72,10 @@ github.com/aws/aws-sdk-go/service/sts/stsiface
 # github.com/beorn7/perks v1.0.1
 github.com/beorn7/perks/quantile
 # github.com/blang/semver v3.5.1+incompatible
+## explicit
 github.com/blang/semver
+# github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b
+## explicit
 # github.com/census-instrumentation/opencensus-proto v0.2.1
 github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1
 github.com/census-instrumentation/opencensus-proto/gen-go/agent/metrics/v1
@@ -74,15 +84,21 @@ github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1
 github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1
 github.com/census-instrumentation/opencensus-proto/gen-go/trace/v1
 # github.com/davecgh/go-spew v1.1.1
+## explicit
 github.com/davecgh/go-spew/spew
+# github.com/dgryski/go-gk v0.0.0-20200319235926-a69029f61654
+## explicit
 # github.com/emicklei/go-restful v2.9.5+incompatible
 github.com/emicklei/go-restful
 github.com/emicklei/go-restful/log
 # github.com/evanphx/json-patch v4.5.0+incompatible
+## explicit
 github.com/evanphx/json-patch
 # github.com/ghodss/yaml v1.0.0
+## explicit
 github.com/ghodss/yaml
 # github.com/go-logr/logr v0.1.0
+## explicit
 github.com/go-logr/logr
 # github.com/go-openapi/jsonpointer v0.19.3
 github.com/go-openapi/jsonpointer
@@ -93,8 +109,10 @@ github.com/go-openapi/spec
 # github.com/go-openapi/swag v0.19.5
 github.com/go-openapi/swag
 # github.com/gobuffalo/envy v1.7.0
+## explicit
 github.com/gobuffalo/envy
 # github.com/gogo/protobuf v1.3.1
+## explicit
 github.com/gogo/protobuf/proto
 github.com/gogo/protobuf/sortkeys
 # github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
@@ -102,6 +120,7 @@ github.com/golang/glog
 # github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
 github.com/golang/groupcache/lru
 # github.com/golang/protobuf v1.3.5
+## explicit
 github.com/golang/protobuf/descriptor
 github.com/golang/protobuf/jsonpb
 github.com/golang/protobuf/proto
@@ -119,6 +138,7 @@ github.com/golang/protobuf/ptypes/struct
 github.com/golang/protobuf/ptypes/timestamp
 github.com/golang/protobuf/ptypes/wrappers
 # github.com/google/go-cmp v0.4.0
+## explicit
 github.com/google/go-cmp/cmp
 github.com/google/go-cmp/cmp/cmpopts
 github.com/google/go-cmp/cmp/internal/diff
@@ -126,12 +146,15 @@ github.com/google/go-cmp/cmp/internal/flags
 github.com/google/go-cmp/cmp/internal/function
 github.com/google/go-cmp/cmp/internal/value
 # github.com/google/go-github/v27 v27.0.6
+## explicit
 github.com/google/go-github/v27/github
 # github.com/google/go-querystring v1.0.0
 github.com/google/go-querystring/query
 # github.com/google/gofuzz v1.1.0
+## explicit
 github.com/google/gofuzz
 # github.com/google/mako v0.0.0-20190821191249-122f8dcef9e3
+## explicit
 github.com/google/mako/clients/proto/analyzers/threshold_analyzer_go_proto
 github.com/google/mako/clients/proto/analyzers/utest_analyzer_go_proto
 github.com/google/mako/clients/proto/analyzers/window_deviation_go_proto
@@ -141,6 +164,7 @@ github.com/google/mako/internal/quickstore_microservice/proto/quickstore_go_prot
 github.com/google/mako/proto/quickstore/quickstore_go_proto
 github.com/google/mako/spec/proto/mako_go_proto
 # github.com/google/uuid v1.1.1
+## explicit
 github.com/google/uuid
 # github.com/googleapis/gax-go/v2 v2.0.5
 github.com/googleapis/gax-go/v2
@@ -149,14 +173,17 @@ github.com/googleapis/gnostic/OpenAPIv2
 github.com/googleapis/gnostic/compiler
 github.com/googleapis/gnostic/extensions
 # github.com/gorilla/websocket v1.4.0
+## explicit
 github.com/gorilla/websocket
 # github.com/grpc-ecosystem/grpc-gateway v1.12.1
+## explicit
 github.com/grpc-ecosystem/grpc-gateway/internal
 github.com/grpc-ecosystem/grpc-gateway/runtime
 github.com/grpc-ecosystem/grpc-gateway/utilities
 # github.com/hashicorp/errwrap v1.0.0
 github.com/hashicorp/errwrap
 # github.com/hashicorp/go-multierror v1.0.0
+## explicit
 github.com/hashicorp/go-multierror
 # github.com/hashicorp/golang-lru v0.5.3
 github.com/hashicorp/golang-lru
@@ -164,8 +191,10 @@ github.com/hashicorp/golang-lru/simplelru
 # github.com/imdario/mergo v0.3.8
 github.com/imdario/mergo
 # github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9
+## explicit
 github.com/influxdata/tdigest
 # github.com/jmespath/go-jmespath v0.3.0
+## explicit
 github.com/jmespath/go-jmespath
 # github.com/joho/godotenv v1.3.0
 github.com/joho/godotenv
@@ -176,8 +205,10 @@ github.com/jstemmer/go-junit-report
 github.com/jstemmer/go-junit-report/formatter
 github.com/jstemmer/go-junit-report/parser
 # github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
+## explicit
 github.com/kballard/go-shellquote
 # github.com/kelseyhightower/envconfig v1.4.0
+## explicit
 github.com/kelseyhightower/envconfig
 # github.com/konsorten/go-windows-terminal-sequences v1.0.2
 github.com/konsorten/go-windows-terminal-sequences
@@ -186,6 +217,7 @@ github.com/mailru/easyjson/buffer
 github.com/mailru/easyjson/jlexer
 github.com/mailru/easyjson/jwriter
 # github.com/markbates/inflect v1.0.4
+## explicit
 github.com/markbates/inflect
 # github.com/matttproud/golang_protobuf_extensions v1.0.1
 github.com/matttproud/golang_protobuf_extensions/pbutil
@@ -194,6 +226,7 @@ github.com/modern-go/concurrent
 # github.com/modern-go/reflect2 v1.0.1
 github.com/modern-go/reflect2
 # github.com/openzipkin/zipkin-go v0.2.2
+## explicit
 github.com/openzipkin/zipkin-go
 github.com/openzipkin/zipkin-go/idgenerator
 github.com/openzipkin/zipkin-go/model
@@ -204,6 +237,7 @@ github.com/openzipkin/zipkin-go/reporter/recorder
 # github.com/pkg/errors v0.9.1
 github.com/pkg/errors
 # github.com/prometheus/client_golang v1.0.0 => github.com/prometheus/client_golang v0.9.2
+## explicit
 github.com/prometheus/client_golang/api
 github.com/prometheus/client_golang/api/prometheus/v1
 github.com/prometheus/client_golang/prometheus
@@ -212,10 +246,12 @@ github.com/prometheus/client_golang/prometheus/promhttp
 # github.com/prometheus/client_model v0.2.0
 github.com/prometheus/client_model/go
 # github.com/prometheus/common v0.9.1
+## explicit
 github.com/prometheus/common/expfmt
 github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg
 github.com/prometheus/common/model
 # github.com/prometheus/procfs v0.0.11
+## explicit
 github.com/prometheus/procfs
 github.com/prometheus/procfs/internal/fs
 github.com/prometheus/procfs/internal/util
@@ -226,10 +262,15 @@ github.com/rogpeppe/go-internal/semver
 # github.com/sirupsen/logrus v1.4.2
 github.com/sirupsen/logrus
 # github.com/spf13/pflag v1.0.5
+## explicit
 github.com/spf13/pflag
+# github.com/streadway/quantile v0.0.0-20150917103942-b0c588724d25
+## explicit
 # github.com/tsenart/vegeta v12.7.1-0.20190725001342-b5f4fca92137+incompatible
+## explicit
 github.com/tsenart/vegeta/lib
 # go.opencensus.io v0.22.3
+## explicit
 go.opencensus.io
 go.opencensus.io/internal
 go.opencensus.io/internal/tagencoding
@@ -250,10 +291,13 @@ go.opencensus.io/trace/internal
 go.opencensus.io/trace/propagation
 go.opencensus.io/trace/tracestate
 # go.uber.org/atomic v1.4.0
+## explicit
 go.uber.org/atomic
 # go.uber.org/multierr v1.1.0
+## explicit
 go.uber.org/multierr
 # go.uber.org/zap v1.9.2-0.20180814183419-67bc79d13d15
+## explicit
 go.uber.org/zap
 go.uber.org/zap/buffer
 go.uber.org/zap/internal/bufferpool
@@ -278,6 +322,7 @@ golang.org/x/lint/golint
 golang.org/x/mod/module
 golang.org/x/mod/semver
 # golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e
+## explicit
 golang.org/x/net/context
 golang.org/x/net/context/ctxhttp
 golang.org/x/net/http/httpguts
@@ -288,15 +333,18 @@ golang.org/x/net/idna
 golang.org/x/net/internal/timeseries
 golang.org/x/net/trace
 # golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
+## explicit
 golang.org/x/oauth2
 golang.org/x/oauth2/google
 golang.org/x/oauth2/internal
 golang.org/x/oauth2/jws
 golang.org/x/oauth2/jwt
 # golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
+## explicit
 golang.org/x/sync/errgroup
 golang.org/x/sync/semaphore
 # golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775
+## explicit
 golang.org/x/sys/unix
 golang.org/x/sys/windows
 # golang.org/x/text v0.3.2
@@ -308,6 +356,7 @@ golang.org/x/text/width
 # golang.org/x/time v0.0.0-20191024005414-555d28b269f0
 golang.org/x/time/rate
 # golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65
+## explicit
 golang.org/x/tools/cmd/goimports
 golang.org/x/tools/go/analysis
 golang.org/x/tools/go/analysis/passes/inspect
@@ -333,6 +382,7 @@ golang.org/x/tools/internal/packagesinternal
 golang.org/x/xerrors
 golang.org/x/xerrors/internal
 # gomodules.xyz/jsonpatch/v2 v2.0.1
+## explicit
 gomodules.xyz/jsonpatch/v2
 # gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485
 gonum.org/v1/gonum/blas
@@ -360,6 +410,7 @@ gonum.org/v1/gonum/lapack/gonum
 gonum.org/v1/gonum/lapack/lapack64
 gonum.org/v1/gonum/mat
 # google.golang.org/api v0.20.0
+## explicit
 google.golang.org/api/container/v1beta1
 google.golang.org/api/googleapi
 google.golang.org/api/googleapi/transport
@@ -389,6 +440,7 @@ google.golang.org/appengine/internal/urlfetch
 google.golang.org/appengine/socket
 google.golang.org/appengine/urlfetch
 # google.golang.org/genproto v0.0.0-20200326112834-f447254575fd
+## explicit
 google.golang.org/genproto/googleapis/api
 google.golang.org/genproto/googleapis/api/annotations
 google.golang.org/genproto/googleapis/api/distribution
@@ -406,6 +458,7 @@ google.golang.org/genproto/googleapis/type/calendarperiod
 google.golang.org/genproto/googleapis/type/expr
 google.golang.org/genproto/protobuf/field_mask
 # google.golang.org/grpc v1.28.0
+## explicit
 google.golang.org/grpc
 google.golang.org/grpc/attributes
 google.golang.org/grpc/backoff
@@ -455,9 +508,12 @@ google.golang.org/grpc/serviceconfig
 google.golang.org/grpc/stats
 google.golang.org/grpc/status
 google.golang.org/grpc/tap
+# gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15
+## explicit
 # gopkg.in/inf.v0 v0.9.1
 gopkg.in/inf.v0
 # gopkg.in/yaml.v2 v2.2.7
+## explicit
 gopkg.in/yaml.v2
 # honnef.co/go/tools v0.0.1-2020.1.3
 honnef.co/go/tools/arg
@@ -490,6 +546,7 @@ honnef.co/go/tools/stylecheck
 honnef.co/go/tools/unused
 honnef.co/go/tools/version
 # k8s.io/api v0.16.4 => k8s.io/api v0.16.4
+## explicit
 k8s.io/api/admission/v1beta1
 k8s.io/api/admissionregistration/v1
 k8s.io/api/admissionregistration/v1beta1
@@ -530,6 +587,7 @@ k8s.io/api/storage/v1
 k8s.io/api/storage/v1alpha1
 k8s.io/api/storage/v1beta1
 # k8s.io/apiextensions-apiserver v0.16.4 => k8s.io/apiextensions-apiserver v0.16.4
+## explicit
 k8s.io/apiextensions-apiserver/pkg/apis/apiextensions
 k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1
 k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1
@@ -548,6 +606,7 @@ k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/internalint
 k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1
 k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1beta1
 # k8s.io/apimachinery v0.16.5-beta.1 => k8s.io/apimachinery v0.16.4
+## explicit
 k8s.io/apimachinery/pkg/api/apitesting
 k8s.io/apimachinery/pkg/api/apitesting/fuzzer
 k8s.io/apimachinery/pkg/api/apitesting/roundtrip
@@ -599,6 +658,7 @@ k8s.io/apimachinery/pkg/watch
 k8s.io/apimachinery/third_party/forked/golang/json
 k8s.io/apimachinery/third_party/forked/golang/reflect
 # k8s.io/client-go v11.0.1-0.20190805182717-6502b5e7b1b5+incompatible => k8s.io/client-go v0.16.4
+## explicit
 k8s.io/client-go/discovery
 k8s.io/client-go/discovery/fake
 k8s.io/client-go/dynamic
@@ -803,6 +863,7 @@ k8s.io/client-go/util/keyutil
 k8s.io/client-go/util/retry
 k8s.io/client-go/util/workqueue
 # k8s.io/code-generator v0.18.0 => k8s.io/code-generator v0.16.4
+## explicit
 k8s.io/code-generator
 k8s.io/code-generator/cmd/client-gen
 k8s.io/code-generator/cmd/client-gen/args
@@ -837,6 +898,7 @@ k8s.io/code-generator/pkg/namer
 k8s.io/code-generator/pkg/util
 k8s.io/code-generator/third_party/forked/golang/reflect
 # k8s.io/gengo v0.0.0-20200205140755-e0e292d8aa12
+## explicit
 k8s.io/gengo/args
 k8s.io/gengo/examples/deepcopy-gen/generators
 k8s.io/gengo/examples/defaulter-gen/generators
@@ -848,8 +910,10 @@ k8s.io/gengo/namer
 k8s.io/gengo/parser
 k8s.io/gengo/types
 # k8s.io/klog v1.0.0
+## explicit
 k8s.io/klog
 # k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf
+## explicit
 k8s.io/kube-openapi/cmd/openapi-gen
 k8s.io/kube-openapi/cmd/openapi-gen/args
 k8s.io/kube-openapi/pkg/common
@@ -858,17 +922,26 @@ k8s.io/kube-openapi/pkg/generators/rules
 k8s.io/kube-openapi/pkg/util/proto
 k8s.io/kube-openapi/pkg/util/sets
 # k8s.io/test-infra v0.0.0-20191212060232-70b0b49fe247
+## explicit
 k8s.io/test-infra/boskos/client
 k8s.io/test-infra/boskos/common
 k8s.io/test-infra/boskos/storage
 k8s.io/test-infra/prow/config/secret
 k8s.io/test-infra/prow/logrusutil
 # k8s.io/utils v0.0.0-20190907131718-3d4f5b7dea0b
+## explicit
 k8s.io/utils/buffer
 k8s.io/utils/integer
 k8s.io/utils/pointer
 k8s.io/utils/trace
 # knative.dev/test-infra v0.0.0-20200430225942-f7c1fafc1cde
+## explicit
 knative.dev/test-infra/scripts
 # sigs.k8s.io/yaml v1.1.0
 sigs.k8s.io/yaml
+# github.com/prometheus/client_golang => github.com/prometheus/client_golang v0.9.2
+# k8s.io/api => k8s.io/api v0.16.4
+# k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.16.4
+# k8s.io/apimachinery => k8s.io/apimachinery v0.16.4
+# k8s.io/client-go => k8s.io/client-go v0.16.4
+# k8s.io/code-generator => k8s.io/code-generator v0.16.4


Test patch:
diff --git a/codegen/cmd/injection-gen/generators/comment_parser_test.go b/codegen/cmd/injection-gen/generators/comment_parser_test.go
new file mode 100644
index 000000000..db2a7b757
--- /dev/null
+++ b/codegen/cmd/injection-gen/generators/comment_parser_test.go
@@ -0,0 +1,84 @@
+/*
+Copyright 2020 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 generators
+
+import "testing"
+
+func TestParseComments(t *testing.T) {
+	comment := []string{
+		"This is an example comment to parse",
+		"",
+		" +foo",
+		"+bar",
+		"+with:option",
+		"+pair:key=value",
+		"+manypairs:key1=value1,key2=value2",
+	}
+
+	extracted := ExtractCommentTags("+", comment)
+
+	if val, ok := extracted["foo"]; !ok || val != nil {
+		t.Errorf("Failed to extract single key got=%t,%v want=true,nil", ok, val)
+	}
+
+	if val, ok := extracted["bar"]; !ok || val != nil {
+		t.Errorf("Failed to extract single key got=%t,%v want=true,nil", ok, val)
+	}
+
+	if val, ok := extracted["with"]; !ok || val["option"] != "" {
+		t.Errorf("Failed to extract single key got=%t,%v want=true,{\"option\":\"\"}", ok, val)
+	}
+
+	if val, ok := extracted["pair"]; !ok || val["key"] != "value" {
+		t.Errorf("Failed to extract single key got=%t,%v want=true,{\"key\":\"value\"}", ok, val)
+	}
+
+	if val, ok := extracted["manypairs"]; !ok || val["key1"] != "value1" || val["key2"] != "value2" {
+		t.Errorf("Failed to extract single key got=%t,%v want=true,{\"key\":\"value\"}", ok, val)
+	}
+}
+
+func TestMergeDuplicates(t *testing.T) {
+	comment := []string{
+		"This is an example comment to parse",
+		"",
+		"+foo",
+		" +foo",
+		"+bar:key=value",
+		"+bar",
+		"+manypairs:key1=value1",
+		"+manypairs:key2=value2",
+		"+oops:,,,",
+	}
+
+	extracted := ExtractCommentTags("+", comment)
+
+	if val, ok := extracted["foo"]; !ok || val != nil {
+		t.Errorf("Failed to extract single key got=%t,%v want=true,nil", ok, val)
+	}
+
+	if val, ok := extracted["bar"]; !ok || val["key"] != "value" {
+		t.Errorf("Failed to extract single key got=%t,%v want=true,{\"key\":\"value\"}", ok, val)
+	}
+
+	if val, ok := extracted["manypairs"]; !ok || val["key1"] != "value1" || val["key2"] != "value2" {
+		t.Errorf("Failed to extract single key got=%t,%v want=true,{\"key\":\"value\"}", ok, val)
+	}
+
+	if val, ok := extracted["oops"]; !ok || val != nil {
+		t.Errorf("Failed to extract single key got=%t,%v want=true,{\"oops\":nil}", ok, val)
+	}
+}
diff --git a/network/transports_test.go b/network/transports_test.go
index b7d3ae97b..f8f2e837d 100644
--- a/network/transports_test.go
+++ b/network/transports_test.go
@@ -90,8 +90,9 @@ func TestDialWithBackoff(t *testing.T) {
 		c.Close()
 		t.Error("Unexpected success dialing")
 	}
-	if err != errDialTimeout {
-		t.Errorf("Error = %v, want: %v", err, errDialTimeout)
+	const expectedErrPrefix = "timed out dialing"
+	if err == nil || !strings.HasPrefix(err.Error(), expectedErrPrefix) {
+		t.Errorf("Error = %v, want: %s(...)", err, expectedErrPrefix)
 	}
 
 	s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
diff --git a/test/helpers/name.go b/test/helpers/name.go
index 5d75708c0..7bf14a3bf 100644
--- a/test/helpers/name.go
+++ b/test/helpers/name.go
@@ -17,7 +17,6 @@ limitations under the License.
 package helpers
 
 import (
-	"log"
 	"math/rand"
 	"strings"
 	"time"
@@ -39,7 +38,6 @@ func init() {
 	// Otherwise, rerunning tests will generate the same names for the test resources, causing conflicts with
 	// already existing resources.
 	seed := time.Now().UTC().UnixNano()
-	log.Printf("Using '%d' to seed the random number generator", seed)
 	rand.Seed(seed)
 }
 
diff --git a/testutils/metahelper/client/client.go b/testutils/metahelper/client/client.go
index 600a52564..3fe81d9be 100644
--- a/testutils/metahelper/client/client.go
+++ b/testutils/metahelper/client/client.go
@@ -32,9 +32,9 @@ const (
 	filename = "metadata.json"
 )
 
-// client holds metadata as a string:string map, as well as path for storing
+// Client holds metadata as a string:string map, as well as path for storing
 // metadata
-type client struct {
+type Client struct {
 	MetaData map[string]string
 	Path     string
 }
@@ -42,8 +42,8 @@ type client struct {
 // New creates a client, takes custom directory for storing `metadata.json`.
 // It reads existing `metadata.json` file if it exists, otherwise creates it.
 // Errors out if there is any file i/o problem other than file not exist error.
-func New(dir string) (*client, error) {
-	c := &client{
+func New(dir string) (*Client, error) {
+	c := &Client{
 		MetaData: make(map[string]string),
 	}
 	if dir == "" {
@@ -61,7 +61,7 @@ func New(dir string) (*client, error) {
 
 // sync is shared by Get and Set, invoked at the very beginning of each, makes
 // sure the file exists, and loads the content of file into c.MetaData
-func (c *client) sync() error {
+func (c *Client) sync() error {
 	_, err := os.Stat(c.Path)
 	if os.IsNotExist(err) {
 		body, _ := json.Marshal(&c.MetaData)
@@ -78,7 +78,7 @@ func (c *client) sync() error {
 }
 
 // Set sets key:val pair, and overrides if it exists
-func (c *client) Set(key, val string) error {
+func (c *Client) Set(key, val string) error {
 	err := c.sync()
 	if err != nil {
 		return err
@@ -92,7 +92,7 @@ func (c *client) Set(key, val string) error {
 }
 
 // Get gets val for key
-func (c *client) Get(key string) (string, error) {
+func (c *Client) Get(key string) (string, error) {
 	if _, err := os.Stat(c.Path); err != nil && os.IsNotExist(err) {
 		return "", fmt.Errorf("file %q doesn't exist", c.Path)
 	}