Repository: kubernetes/release
Pull request #963 (2019-12-10 13:11:00)

Fix unit tests for macOS in command package
This should at least make them not fail.

Refers to #961

k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes/release/pull/963#" title="Author self-approved">saschagrunert</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **idealhack**
You can assign the PR to them by writing `/assign @idealhack` 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=kubernetes%2Frelease).

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

- **[OWNERS](https://github.com/kubernetes/release/blob/master/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":["idealhack"]} -->
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes/release/pull/963#pullrequestreview-329826333" title="LGTM">cpanato</a>*, *<a href="https://github.com/kubernetes/release/pull/963#" title="Author self-approved">saschagrunert</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **idealhack**
You can assign the PR to them by writing `/assign @idealhack` 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=kubernetes%2Frelease).

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

- **[OWNERS](https://github.com/kubernetes/release/blob/master/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":["idealhack"]} -->
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes/release/pull/963#pullrequestreview-329826333" title="LGTM">cpanato</a>*, *<a href="https://github.com/kubernetes/release/pull/963#pullrequestreview-331422561" title="Approved">hoegaarden</a>*, *<a href="https://github.com/kubernetes/release/pull/963#" title="Author self-approved">saschagrunert</a>*

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

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

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

- ~~[OWNERS](https://github.com/kubernetes/release/blob/master/OWNERS)~~ [hoegaarden]

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":[]} -->

Patch:
diff --git a/.errcheck-excludes b/.errcheck-excludes
new file mode 100644
index 00000000..97b97714
--- /dev/null
+++ b/.errcheck-excludes
@@ -0,0 +1,1 @@
+(github.com/go-kit/kit/log.Logger).Log
diff --git a/.golangci.yml b/.golangci.yml
index 6778d357..2df76ada 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -47,6 +47,7 @@ linters-settings:
   errcheck:
     check-type-assertions: true
     check-blank: true
+    exclude: .errcheck-excludes
   gocritic:
     enabled-checks:
       # Diagnostic
diff --git a/BUILD.bazel b/BUILD.bazel
index e66b2cd6..a535fe9a 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -35,7 +35,6 @@ filegroup(
         "//pkg/command:all-srcs",
         "//pkg/git:all-srcs",
         "//pkg/notes:all-srcs",
-        "//pkg/release:all-srcs",
         "//pkg/util:all-srcs",
     ],
     tags = ["automanaged"],
diff --git a/cmd/krel/cmd/BUILD.bazel b/cmd/krel/cmd/BUILD.bazel
index 56c8edba..f972728d 100644
--- a/cmd/krel/cmd/BUILD.bazel
+++ b/cmd/krel/cmd/BUILD.bazel
@@ -11,12 +11,9 @@ go_library(
     visibility = ["//visibility:public"],
     deps = [
         "//pkg/git:go_default_library",
-        "//pkg/release:go_default_library",
         "//pkg/util:go_default_library",
         "@com_github_pkg_errors//:go_default_library",
-        "@com_github_sirupsen_logrus//:go_default_library",
         "@com_github_spf13_cobra//:go_default_library",
-        "@com_google_cloud_go//storage:go_default_library",
     ],
 )
 
diff --git a/cmd/krel/cmd/ff.go b/cmd/krel/cmd/ff.go
index 299b5d18..cbcff1d2 100644
--- a/cmd/krel/cmd/ff.go
+++ b/cmd/krel/cmd/ff.go
@@ -18,13 +18,13 @@ package cmd
 
 import (
 	"fmt"
-
-	kgit "k8s.io/release/pkg/git"
-	"k8s.io/release/pkg/util"
+	"log"
 
 	"github.com/pkg/errors"
-	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
+
+	kgit "k8s.io/release/pkg/git"
+	"k8s.io/release/pkg/util"
 )
 
 type ffOptions struct {
@@ -43,12 +43,14 @@ var ffCmd = &cobra.Command{
 (defaults to HEAD), and then prepares the branch as a Kubernetes release branch:
 
 - Run hack/update-all.sh to ensure compliance of generated files`,
-	Example:       "krel ff --branch release-1.17 --ref HEAD --cleanup",
-	SilenceUsage:  true,
-	SilenceErrors: true,
-	PreRunE:       initLogging,
+	Example:      "krel ff --branch release-1.17 --ref HEAD --cleanup",
+	SilenceUsage: true,
 	RunE: func(cmd *cobra.Command, args []string) error {
-		return runFf(ffOpts)
+		if err := runFf(ffOpts); err != nil {
+			return err
+		}
+
+		return nil
 	},
 }
 
@@ -71,28 +73,28 @@ func runFf(opts *ffOptions) error {
 	master := "master"
 	remoteMaster := kgit.Remotify(master)
 
-	logrus.Infof("Preparing to fast-forward master@%s onto the %s branch", masterRef, branch)
+	log.Printf("Preparing to fast-forward master@%s onto the %s branch", masterRef, branch)
 	repo, err := kgit.CloneOrOpenDefaultGitHubRepoSSH(rootOpts.repoPath, opts.org)
 	if err != nil {
 		return err
 	}
 
 	if !rootOpts.nomock {
-		logrus.Info("Using dry mode, which does not modify any remote content")
+		log.Printf("Using dry mode, which does not modify any remote content")
 		repo.SetDry()
 	}
 
-	logrus.Infof("Checking if %q is a release branch", branch)
+	log.Printf("Checking if %q is a release branch", branch)
 	if isReleaseBranch := kgit.IsReleaseBranch(branch); !isReleaseBranch {
-		return errors.Errorf("%s is not a release branch", branch)
+		log.Fatalf("%s is not a release branch", branch)
 	}
 
-	logrus.Info("Checking if branch is available on the default remote")
+	log.Printf("Checking if branch is available on the default remote")
 	if err := repo.HasRemoteBranch(branch); err != nil {
 		return err
 	}
 
-	logrus.Info("Checking out release branch")
+	log.Printf("Checking out release branch")
 	if err := repo.CheckoutBranch(branch); err != nil {
 		return err
 	}
@@ -102,7 +104,7 @@ func runFf(opts *ffOptions) error {
 		defer repo.Cleanup() // nolint: errcheck
 	}
 
-	logrus.Infof("Finding merge base between %q and %q", master, branch)
+	log.Printf("Finding merge base between %q and %q", master, branch)
 	mergeBase, err := repo.MergeBase(master, branch)
 	if err != nil {
 		return err
@@ -118,20 +120,22 @@ func runFf(opts *ffOptions) error {
 		return err
 	}
 	if masterTag != mergeBaseTag {
-		return errors.Errorf(
+		err := errors.Errorf(
 			"Unable to fast forward: tag %q does not match %q",
 			masterTag, mergeBaseTag,
 		)
+		log.Print(err)
+		return err
 	}
-	logrus.Infof("Last tag is: %s", masterTag)
+	log.Printf("Last tag is: %s", masterTag)
 
 	releaseRev, err := repo.Head()
 	if err != nil {
 		return err
 	}
-	logrus.Infof("Latest release branch revision is %s", releaseRev)
+	log.Printf("Latest release branch revision is %s", releaseRev)
 
-	logrus.Info("Merging master changes into release branch")
+	log.Printf("Merging master changes into release branch")
 	if err := repo.Merge(remoteMaster); err != nil {
 		return err
 	}
@@ -149,7 +153,7 @@ func runFf(opts *ffOptions) error {
 	}
 
 	if pushUpstream {
-		logrus.Infof("Pushing %s branch", branch)
+		log.Printf("Pushing %s branch", branch)
 		if err := repo.Push(branch); err != nil {
 			return err
 		}
@@ -159,7 +163,7 @@ func runFf(opts *ffOptions) error {
 }
 
 func prepushMessage(gitRoot, remote, branch, org, releaseRev, headRev string) {
-	fmt.Printf(`Go look around in %s to make sure things look okay before pushing...
+	log.Println(fmt.Sprintf(`Go look around in %s to make sure things look okay before pushing...
 
 Check for files left uncommitted using:
 
@@ -177,5 +181,5 @@ Once the branch fast-forward is complete, the diff will be available after push
 
 	https://github.com/%s/%s/compare/%s...%s"
 
-`, gitRoot, remote, branch, org, kgit.DefaultGithubRepo, releaseRev, headRev)
+`, gitRoot, remote, branch, org, kgit.DefaultGithubRepo, releaseRev, headRev))
 }
diff --git a/cmd/krel/cmd/push.go b/cmd/krel/cmd/push.go
index 1a0115ab..732eb7b5 100644
--- a/cmd/krel/cmd/push.go
+++ b/cmd/krel/cmd/push.go
@@ -17,16 +17,9 @@ limitations under the License.
 package cmd
 
 import (
-	"context"
-	"os"
-	"os/user"
+	"log"
 
-	"cloud.google.com/go/storage"
-	"github.com/pkg/errors"
-	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
-
-	"k8s.io/release/pkg/release"
 )
 
 const description = `
@@ -133,14 +126,14 @@ func init() {
 	pushBuildCmd.PersistentFlags().StringVar(
 		&pushBuildOpts.releaseKind,
 		"release-kind",
-		"kubernetes",
-		"Kind of release to push to GCS. Supported values are kubernetes (default) or federation",
+		"devel",
+		"Specify an alternate bucket for pushes (normally 'devel' or 'ci')",
 	)
 	pushBuildCmd.PersistentFlags().StringVar(
 		&pushBuildOpts.releaseType,
 		"release-type",
-		"devel",
-		"Specify an alternate bucket for pushes (normally 'devel' or 'ci')",
+		"kubernetes",
+		"Kind of release to push to GCS. Supported values are kubernetes (default) or federation",
 	)
 	pushBuildCmd.PersistentFlags().StringVar(
 		&pushBuildOpts.versionSuffix,
@@ -153,88 +146,6 @@ func init() {
 }
 
 func runPushBuild(opts *pushBuildOptions) error {
-	var latest string
-	releaseKind := opts.releaseKind
-
-	// Check if latest build uses bazel
-	dir, err := os.Getwd()
-	if err != nil {
-		return errors.Wrap(err, "Unable to get working directory")
-	}
-
-	isBazel, err := release.BuiltWithBazel(dir, releaseKind)
-	if err != nil {
-		return errors.Wrap(err, "Unable to identify if release built with Bazel")
-	}
-
-	if isBazel {
-		logrus.Info("Using Bazel build version")
-		version, err := release.ReadBazelVersion(dir)
-		if err != nil {
-			return errors.Wrap(err, "Unable to read Bazel build version")
-		}
-		latest = version
-	} else {
-		logrus.Info("Using Dockerized build version")
-		version, err := release.ReadDockerizedVersion(dir, releaseKind)
-		if err != nil {
-			return errors.Wrap(err, "Unable to read Dockerized build version")
-		}
-		latest = version
-	}
-
-	logrus.Infof("Found build version: %s", latest)
-
-	valid, err := release.IsValidReleaseBuild(latest)
-	if err != nil {
-		return errors.Wrap(err, "Unable to determine if release build version is valid")
-	}
-	if !valid {
-		return errors.Errorf("Build version %s is not valid for release", latest)
-	}
-
-	if opts.ci && release.IsDirtyBuild(latest) {
-		return errors.New(`Refusing to push dirty build with --ci flag given.\n
-			CI builds should always be performed from clean commits`)
-	}
-
-	if opts.versionSuffix != "" {
-		latest += "-" + opts.versionSuffix
-	}
-
-	gcsDest := opts.releaseType
-
-	// TODO: is this how we want to handle gcs dest args?
-	if opts.ci {
-		gcsDest = "ci"
-	}
-
-	gcsDest += opts.gcsSuffix
-
-	releaseBucket := opts.bucket
-	if !rootOpts.nomock {
-		u, err := user.Current()
-		if err != nil {
-			return errors.Wrap(err, "Unable to identify current user")
-		}
-
-		releaseBucket += "-" + u.Username
-	}
-
-	client, err := storage.NewClient(context.Background())
-	if err != nil {
-		return errors.Wrap(err, "error fetching gcloud credentials... try running \"gcloud auth application-default login\"")
-	}
-
-	bucket := client.Bucket(releaseBucket)
-	if bucket == nil {
-		return errors.Errorf("unable to identify specified bucket for artifacts: %s", releaseBucket)
-	}
-
-	// Check if bucket exists.
-	if _, err = bucket.Attrs(context.Background()); err != nil {
-		return errors.Wrap(err, "Unable to find release artifact bucket")
-	}
-
+	log.Println("unimplemented")
 	return nil
 }
diff --git a/cmd/krel/cmd/root.go b/cmd/krel/cmd/root.go
index ec9cbd33..c4f32b42 100644
--- a/cmd/krel/cmd/root.go
+++ b/cmd/krel/cmd/root.go
@@ -17,22 +17,21 @@ limitations under the License.
 package cmd
 
 import (
-	"github.com/sirupsen/logrus"
+	"log"
+
 	"github.com/spf13/cobra"
 )
 
 // rootCmd represents the base command when called without any subcommands
 var rootCmd = &cobra.Command{
-	Use:     "krel",
-	Short:   "krel",
-	PreRunE: initLogging,
+	Use:   "krel",
+	Short: "krel",
 }
 
 type rootOptions struct {
 	nomock   bool
 	cleanup  bool
 	repoPath string
-	logLevel string
 }
 
 var rootOpts = &rootOptions{}
@@ -41,7 +40,7 @@ var rootOpts = &rootOptions{}
 // This is called by main.main(). It only needs to happen once to the rootCmd.
 func Execute() {
 	if err := rootCmd.Execute(); err != nil {
-		logrus.Fatal(err)
+		log.Fatal(err)
 	}
 }
 
@@ -51,20 +50,8 @@ func init() {
 	rootCmd.PersistentFlags().BoolVar(&rootOpts.nomock, "nomock", false, "nomock flag")
 	rootCmd.PersistentFlags().BoolVar(&rootOpts.cleanup, "cleanup", false, "cleanup flag")
 	rootCmd.PersistentFlags().StringVar(&rootOpts.repoPath, "repo", "", "the local path to the repository to be used")
-	rootCmd.PersistentFlags().StringVar(&rootOpts.logLevel, "log-level", "info", "the logging verbosity, either 'panic', 'fatal', 'error', 'warn', 'warning', 'info', 'debug' or 'trace'")
 }
 
 // initConfig reads in config file and ENV variables if set.
 func initConfig() {
 }
-
-func initLogging(*cobra.Command, []string) error {
-	logrus.SetFormatter(&logrus.TextFormatter{DisableTimestamp: true})
-	lvl, err := logrus.ParseLevel(rootOpts.logLevel)
-	if err != nil {
-		return err
-	}
-	logrus.SetLevel(lvl)
-	logrus.Debugf("Using log level %q", lvl)
-	return nil
-}
diff --git a/cmd/release-notes/BUILD.bazel b/cmd/release-notes/BUILD.bazel
index 515def9f..726c4701 100644
--- a/cmd/release-notes/BUILD.bazel
+++ b/cmd/release-notes/BUILD.bazel
@@ -8,8 +8,10 @@ go_library(
     deps = [
         "//pkg/git:go_default_library",
         "//pkg/notes:go_default_library",
+        "@com_github_go_kit_kit//log:go_default_library",
+        "@com_github_go_kit_kit//log/level:go_default_library",
         "@com_github_google_go_github_v28//github:go_default_library",
-        "@com_github_sirupsen_logrus//:go_default_library",
+        "@com_github_kolide_kit//env:go_default_library",
         "@org_golang_x_oauth2//:go_default_library",
     ],
 )
diff --git a/cmd/release-notes/main.go b/cmd/release-notes/main.go
index 72cb78f9..122036a5 100644
--- a/cmd/release-notes/main.go
+++ b/cmd/release-notes/main.go
@@ -24,10 +24,11 @@ import (
 	"fmt"
 	"io/ioutil"
 	"os"
-	"strconv"
 
+	"github.com/go-kit/kit/log"
+	"github.com/go-kit/kit/log/level"
 	"github.com/google/go-github/v28/github"
-	"github.com/sirupsen/logrus"
+	"github.com/kolide/kit/env"
 	"golang.org/x/oauth2"
 
 	"k8s.io/release/pkg/git"
@@ -50,8 +51,7 @@ type options struct {
 	requiredAuthor string
 	debug          bool
 	discoverMode   string
-	releaseBucket  string
-	releaseTars    string
+	logger         log.Logger
 }
 
 const (
@@ -66,7 +66,7 @@ func (o *options) BindFlags() *flag.FlagSet {
 	flags.StringVar(
 		&o.githubToken,
 		"github-token",
-		envDefault("GITHUB_TOKEN", ""),
+		env.String("GITHUB_TOKEN", ""),
 		"A personal GitHub access token (required)",
 	)
 
@@ -74,7 +74,7 @@ func (o *options) BindFlags() *flag.FlagSet {
 	flags.StringVar(
 		&o.githubOrg,
 		"github-org",
-		envDefault("GITHUB_ORG", "kubernetes"),
+		env.String("GITHUB_ORG", "kubernetes"),
 		"Name of github organization",
 	)
 
@@ -82,7 +82,7 @@ func (o *options) BindFlags() *flag.FlagSet {
 	flags.StringVar(
 		&o.githubRepo,
 		"github-repo",
-		envDefault("GITHUB_REPO", "kubernetes"),
+		env.String("GITHUB_REPO", "kubernetes"),
 		"Name of github repository",
 	)
 
@@ -91,7 +91,7 @@ func (o *options) BindFlags() *flag.FlagSet {
 	flags.StringVar(
 		&o.output,
 		"output",
-		envDefault("OUTPUT", ""),
+		env.String("OUTPUT", ""),
 		"The path to the where the release notes will be printed",
 	)
 
@@ -99,7 +99,7 @@ func (o *options) BindFlags() *flag.FlagSet {
 	flags.StringVar(
 		&o.branch,
 		"branch",
-		envDefault("BRANCH", "master"),
+		env.String("BRANCH", "master"),
 		"Select which branch to scrape. Defaults to `master`",
 	)
 
@@ -108,7 +108,7 @@ func (o *options) BindFlags() *flag.FlagSet {
 	flags.StringVar(
 		&o.startSHA,
 		"start-sha",
-		envDefault("START_SHA", ""),
+		env.String("START_SHA", ""),
 		"The commit hash to start at",
 	)
 
@@ -116,7 +116,7 @@ func (o *options) BindFlags() *flag.FlagSet {
 	flags.StringVar(
 		&o.endSHA,
 		"end-sha",
-		envDefault("END_SHA", ""),
+		env.String("END_SHA", ""),
 		"The commit hash to end at",
 	)
 
@@ -125,7 +125,7 @@ func (o *options) BindFlags() *flag.FlagSet {
 	flags.StringVar(
 		&o.startRev,
 		"start-rev",
-		envDefault("START_REV", ""),
+		env.String("START_REV", ""),
 		"The git revision to start at. Can be used as alternative to start-sha.",
 	)
 
@@ -134,7 +134,7 @@ func (o *options) BindFlags() *flag.FlagSet {
 	flags.StringVar(
 		&o.endRev,
 		"end-rev",
-		envDefault("END_REV", ""),
+		env.String("END_REV", ""),
 		"The git revision to end at. Can be used as alternative to end-sha.",
 	)
 
@@ -143,7 +143,7 @@ func (o *options) BindFlags() *flag.FlagSet {
 	flags.StringVar(
 		&o.repoPath,
 		"repo-path",
-		envDefault("REPO_PATH", ""),
+		env.String("REPO_PATH", ""),
 		"Path to a the local Kubernetes repository",
 	)
 
@@ -151,7 +151,7 @@ func (o *options) BindFlags() *flag.FlagSet {
 	flags.StringVar(
 		&o.releaseVersion,
 		"release-version",
-		envDefault("RELEASE_VERSION", ""),
+		env.String("RELEASE_VERSION", ""),
 		"Which release version to tag the entries as.",
 	)
 
@@ -159,59 +159,36 @@ func (o *options) BindFlags() *flag.FlagSet {
 	flags.StringVar(
 		&o.format,
 		"format",
-		envDefault("FORMAT", "markdown"),
+		env.String("FORMAT", "markdown"),
 		"The format for notes output (options: markdown, json)",
 	)
 
 	flags.StringVar(
 		&o.requiredAuthor,
 		"requiredAuthor",
-		envDefault("REQUIRED_AUTHOR", "k8s-ci-robot"),
+		env.String("REQUIRED_AUTHOR", "k8s-ci-robot"),
 		"Only commits from this GitHub user are considered. Set to empty string to include all users",
 	)
 
-	debug, _ := strconv.ParseBool(envDefault("DEBUG", "false")) // nolint: errcheck
 	flags.BoolVar(
 		&o.debug,
 		"debug",
-		debug,
+		env.Bool("DEBUG", false),
 		"Enable debug logging",
 	)
 
 	flags.StringVar(
 		&o.discoverMode,
 		"discover",
-		envDefault("DISCOVER", revisionDiscoveryModeNONE),
+		env.String("DISCOVER", revisionDiscoveryModeNONE),
 		fmt.Sprintf("The revision discovery mode for automatic revision retrieval (options: %s, %s)",
 			revisionDiscoveryModeNONE,
 			revisionDiscoveryModeMinorToLatest),
 	)
 
-	flags.StringVar(
-		&o.releaseBucket,
-		"release-bucket",
-		envDefault("RELEASE_BUCKET", "kubernetes-release"),
-		"Specify gs bucket to point to in generated notes",
-	)
-
-	flags.StringVar(
-		&o.releaseTars,
-		"release-tars",
-		envDefault("RELEASE_TARS", ""),
-		"Directory of tars to sha512 sum for display",
-	)
-
 	return flags
 }
 
-func envDefault(key, def string) string {
-	value, ok := os.LookupEnv(key)
-	if !ok {
-		return def
-	}
-	return value
-}
-
 func (o *options) GetReleaseNotes() (notes.ReleaseNotes, notes.ReleaseNotesHistory, error) {
 	// Create the GitHub API client
 	ctx := context.Background()
@@ -221,7 +198,7 @@ func (o *options) GetReleaseNotes() (notes.ReleaseNotes, notes.ReleaseNotesHisto
 	githubClient := github.NewClient(httpClient)
 
 	// Fetch a list of fully-contextualized release notes
-	logrus.Info("fetching all commits. this might take a while...")
+	level.Info(o.logger).Log("msg", "fetching all commits. this might take a while...")
 
 	opts := []notes.GitHubAPIOption{notes.WithContext(ctx)}
 	if o.githubOrg != "" {
@@ -232,10 +209,10 @@ func (o *options) GetReleaseNotes() (notes.ReleaseNotes, notes.ReleaseNotesHisto
 	}
 
 	releaseNotes, history, err := notes.ListReleaseNotes(
-		githubClient, o.branch, o.startSHA, o.endSHA,
+		githubClient, o.logger, o.branch, o.startSHA, o.endSHA,
 		o.requiredAuthor, o.releaseVersion, opts...)
 	if err != nil {
-		logrus.Errorf("generating release notes: %v", err)
+		level.Error(o.logger).Log("msg", "error generating release notes", "err", err)
 		return nil, nil, err
 	}
 
@@ -243,7 +220,7 @@ func (o *options) GetReleaseNotes() (notes.ReleaseNotes, notes.ReleaseNotesHisto
 }
 
 func (o *options) WriteReleaseNotes(releaseNotes notes.ReleaseNotes, history notes.ReleaseNotesHistory) error {
-	logrus.Info("got the commits, performing rendering")
+	level.Info(o.logger).Log("msg", "got the commits, performing rendering")
 
 	// Open a handle to the file which will contain the release notes output
 	var output *os.File
@@ -253,13 +230,13 @@ func (o *options) WriteReleaseNotes(releaseNotes notes.ReleaseNotes, history not
 	if o.output != "" {
 		output, err = os.OpenFile(o.output, os.O_RDWR|os.O_CREATE, 0644)
 		if err != nil {
-			logrus.Errorf("opening the supplied output file: %v", err)
+			level.Error(o.logger).Log("msg", "error opening the supplied output file", "err", err)
 			return err
 		}
 	} else {
 		output, err = ioutil.TempFile("", "release-notes-")
 		if err != nil {
-			logrus.Errorf("creating a temporary file to write the release notes to: %v", err)
+			level.Error(o.logger).Log("msg", "error creating a temporary file to write the release notes to", "err", err)
 			return err
 		}
 	}
@@ -274,7 +251,7 @@ func (o *options) WriteReleaseNotes(releaseNotes notes.ReleaseNotes, history not
 
 		if len(byteValue) > 0 {
 			if err := json.Unmarshal(byteValue, &existingNotes); err != nil {
-				logrus.Errorf("unmarshalling existing notes: %v", err)
+				level.Error(o.logger).Log("msg", "error unmarshalling existing notes", "err", err)
 				return err
 			}
 		}
@@ -298,37 +275,36 @@ func (o *options) WriteReleaseNotes(releaseNotes notes.ReleaseNotes, history not
 		enc := json.NewEncoder(output)
 		enc.SetIndent("", "  ")
 		if err := enc.Encode(releaseNotes); err != nil {
-			logrus.Errorf("encoding JSON output: %v", err)
+			level.Error(o.logger).Log("msg", "error encoding JSON output", "err", err)
 			os.Exit(1)
 		}
 	case "markdown":
 		doc, err := notes.CreateDocument(releaseNotes, history)
 		if err != nil {
-			logrus.Errorf("creating release note document: %v", err)
+			level.Error(o.logger).Log("msg", "error creating release note document", "err", err)
 			return err
 		}
 
-		if err := notes.RenderMarkdown(
-			output, doc, o.releaseBucket, o.releaseTars, o.startRev, o.endRev,
-		); err != nil {
-			logrus.Errorf("rendering release note document to markdown: %v", err)
+		if err := notes.RenderMarkdown(doc, output); err != nil {
+			level.Error(o.logger).Log("msg", "error rendering release note document to markdown", "err", err)
 			return err
 		}
 
 	default:
 		errString := fmt.Sprintf("%q is an unsupported format", o.format)
-		logrus.Error(errString)
+		level.Error(o.logger).Log("msg", errString)
 		return errors.New(errString)
 	}
 
-	logrus.
-		WithField("path", output.Name()).
-		WithField("format", o.format).
-		Info("release notes written to file")
+	level.Info(o.logger).Log(
+		"msg", "release notes written to file",
+		"path", output.Name(),
+		"format", o.format,
+	)
 	return nil
 }
 
-func parseOptions(args []string) (*options, error) {
+func parseOptions(args []string, logger log.Logger) (*options, error) {
 	opts := &options{}
 	flags := opts.BindFlags()
 
@@ -359,8 +335,8 @@ func parseOptions(args []string) (*options, error) {
 		}
 		opts.startSHA = start
 		opts.endSHA = end
-		logrus.Infof("discovered start SHA %s", start)
-		logrus.Infof("discovered end SHA %s", end)
+		level.Info(logger).Log("msg", "discovered start SHA "+start)
+		level.Info(logger).Log("msg", "discovered end SHA "+end)
 	}
 
 	// The start SHA is required.
@@ -375,7 +351,7 @@ func parseOptions(args []string) (*options, error) {
 
 	// Check if we have to parse a revision
 	if opts.startRev != "" || opts.endRev != "" {
-		logrus.Info("cloning/updating repository to discover start or end sha")
+		level.Info(logger).Log("msg", "cloning/updating repository to discover start or end sha")
 		repo, err := git.CloneOrOpenGitHubRepo(
 			opts.repoPath,
 			opts.githubOrg,
@@ -390,7 +366,7 @@ func parseOptions(args []string) (*options, error) {
 			if err != nil {
 				return nil, err
 			}
-			logrus.Infof("using found start SHA: %s", sha)
+			level.Info(logger).Log("msg", "using found start SHA: "+sha)
 			opts.startSHA = sha
 		}
 		if opts.endRev != "" {
@@ -398,26 +374,31 @@ func parseOptions(args []string) (*options, error) {
 			if err != nil {
 				return nil, err
 			}
-			logrus.Infof("using found end SHA: %s", sha)
+			level.Info(logger).Log("msg", "using found end SHA: "+sha)
 			opts.endSHA = sha
 		}
 	}
 
 	// Add appropriate log filtering
 	if opts.debug {
-		logrus.SetLevel(logrus.DebugLevel)
+		logger = level.NewFilter(logger, level.AllowDebug())
+	} else {
+		logger = level.NewFilter(logger, level.AllowInfo())
 	}
+	logger = log.With(logger, "timestamp", log.DefaultTimestamp, "caller", log.DefaultCaller)
+	opts.logger = logger
 
 	return opts, nil
 }
 
-func run(args []string) error {
+func run(logger log.Logger, args []string) error {
 	// Parse the CLI options and enforce required defaults
-	opts, err := parseOptions(args)
+	opts, err := parseOptions(args, logger)
 	if err != nil {
-		logrus.Errorf("parsing options: %v", err)
+		level.Error(logger).Log("msg", "error parsing options", "err", err)
 		return err
 	}
+	logger = opts.logger
 
 	// get the release notes
 	releaseNotes, history, err := opts.GetReleaseNotes()
@@ -425,8 +406,9 @@ func run(args []string) error {
 		return err
 	}
 
-	if err := opts.WriteReleaseNotes(releaseNotes, history); err != nil {
-		logrus.Errorf("writing to file: %v", err)
+	err = opts.WriteReleaseNotes(releaseNotes, history)
+	if err != nil {
+		level.Error(logger).Log("msg", "error writing to file", "err", err)
 		return err
 	}
 
@@ -434,8 +416,12 @@ func run(args []string) error {
 }
 
 func main() {
-	logrus.SetFormatter(&logrus.TextFormatter{DisableTimestamp: true})
-	if err := run(os.Args[1:]); err != nil {
+	// Use the go-kit structured logger for logging. To learn more about structured
+	// logging see: https://github.com/go-kit/kit/tree/master/log#structured-logging
+	// https://godoc.org/github.com/go-kit/kit/log/level
+	logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
+
+	if err := run(logger, os.Args[1:]); err != nil {
 		os.Exit(-1)
 	}
 }
diff --git a/go.mod b/go.mod
index d25fa3fa..56df8bb5 100644
--- a/go.mod
+++ b/go.mod
@@ -3,18 +3,18 @@ module k8s.io/release
 go 1.13
 
 require (
-	cloud.google.com/go v0.38.0
+	cloud.google.com/go v0.38.0 // indirect
 	github.com/blang/semver v3.5.1+incompatible
-	github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d // indirect
+	github.com/go-kit/kit v0.9.0
 	github.com/golangci/golangci-lint v1.21.0
 	github.com/google/go-github/v28 v28.1.1
+	github.com/kolide/kit v0.0.0-20190123023048-c155a91098e3
 	github.com/pkg/errors v0.8.1
 	github.com/psampaz/go-mod-outdated v0.4.0
-	github.com/sirupsen/logrus v1.4.2
 	github.com/spf13/cobra v0.0.5
 	github.com/stretchr/testify v1.4.0
 	golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
-	google.golang.org/appengine v1.6.1 // indirect
+	google.golang.org/appengine v1.5.0 // indirect
 	google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873 // indirect
 	gopkg.in/src-d/go-git.v4 v4.13.1
 	k8s.io/release/build/debs v0.0.0-20191011003919-ca0d58d1459d
diff --git a/go.sum b/go.sum
index 52dc618e..1c340287 100644
--- a/go.sum
+++ b/go.sum
@@ -85,7 +85,6 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP
 github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
 github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
 github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
-github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
 github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
@@ -99,15 +98,16 @@ github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
 github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
 github.com/go-critic/go-critic v0.3.5-0.20190904082202-d79a9f0c64db h1:GYXWx7Vr3+zv833u+8IoXbNnQY0AdXsxAgI0kX7xcwA=
 github.com/go-critic/go-critic v0.3.5-0.20190904082202-d79a9f0c64db/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA=
-github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0=
+github.com/go-kit/kit v0.7.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk=
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-lintpack/lintpack v0.5.2 h1:DI5mA3+eKdWeJ40nU4d6Wc26qmdG8RCi/btYq0TuRN0=
 github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM=
 github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
-github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg=
 github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
 github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk=
 github.com/go-logr/zapr v0.1.1/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk=
@@ -117,6 +117,8 @@ github.com/go-openapi/jsonreference v0.0.0-20161105162150-36d33bfe519e/go.mod h1
 github.com/go-openapi/spec v0.0.0-20171219195406-fa03337d7da5/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
 github.com/go-openapi/swag v0.0.0-20171111214437-cf0bdb963811/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
 github.com/go-sql-driver/mysql v0.0.0-20160411075031-7ebe0a500653/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-stack/stack v1.7.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g=
@@ -152,8 +154,6 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
 github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
-github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I=
-github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -215,12 +215,12 @@ github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv3
 github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
-github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
 github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
@@ -228,7 +228,6 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
 github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
-github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk=
 github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
 github.com/gophercloud/gophercloud v0.0.0-20181215224939-bdd8b1ecd793/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4=
 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
@@ -244,6 +243,7 @@ github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc/go.mod h1:Fecb
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 github.com/grpc-ecosystem/grpc-gateway v1.4.1/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
+github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
 github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 github.com/hashicorp/go-multierror v0.0.0-20171204182908-b7773ae21874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
@@ -256,7 +256,6 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
-github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
 github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
@@ -268,18 +267,17 @@ github.com/jinzhu/gorm v0.0.0-20170316141641-572d0a0ab1eb/go.mod h1:Vla75njaFJ8c
 github.com/jinzhu/inflection v0.0.0-20190603042836-f5c5f50e6090/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
 github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
+github.com/jmoiron/sqlx v0.0.0-20180406164412-2aeb6a910c2b/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU=
 github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
-github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
 github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
 github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
-github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
 github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
@@ -291,6 +289,8 @@ github.com/klauspost/pgzip v1.2.1/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQ
 github.com/knative/build v0.1.2/go.mod h1:/sU74ZQkwlYA5FwYDJhYTy61i/Kn+5eWfln2jDbw3Qo=
 github.com/knative/build v0.3.1-0.20190330033454-38ace00371c7/go.mod h1:/sU74ZQkwlYA5FwYDJhYTy61i/Kn+5eWfln2jDbw3Qo=
 github.com/knative/pkg v0.0.0-20190330034653-916205998db9/go.mod h1:7Ijfhw7rfB+H9VtosIsDYvZQ+qYTz7auK3fHW/5z4ww=
+github.com/kolide/kit v0.0.0-20190123023048-c155a91098e3 h1:Ii9fpYpVTsnibfXFHctbDJ78azLTz5Qiph4o6zpIj4o=
+github.com/kolide/kit v0.0.0-20190123023048-c155a91098e3/go.mod h1:wdtviHFxzZ59XeVE7KnsKbj/F+g9J5Cbx1mcXEdLKgY=
 github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
@@ -327,6 +327,7 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp
 github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
 github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-sqlite3 v0.0.0-20160514122348-38ee283dabf1/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
 github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
 github.com/matttproud/golang_protobuf_extensions v1.0.0/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@@ -338,15 +339,14 @@ github.com/mitchellh/ioprogress v0.0.0-20180201004757-6a23b12fa88e/go.mod h1:waE
 github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1ORQBMvzfzBgpsctsbQikCVpvC+tX285E=
 github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
+github.com/oklog/ulid v0.3.0/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
 github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88=
@@ -359,6 +359,7 @@ github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
 github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/opencensus-integrations/ocsql v0.1.1/go.mod h1:ozPYpNVBHZsX33jfoQPO5TlI5lqh0/3R36kirEqJKAM=
 github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
 github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
 github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
@@ -457,6 +458,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
 github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@@ -492,6 +494,7 @@ go.etcd.io/bbolt v1.3.1-etcd.7/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XK
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/etcd v0.0.0-20181031231232-83304cfc808c/go.mod h1:weASp41xM3dk0YHg1s/W8ecdGP5G4teSTMBPpYAaUgA=
 go.opencensus.io v0.17.0/go.mod h1:mp1VrMQxhlqqDpKvH4UcQUa4YwlzNmymAjPrDdfxNpI=
+go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
 go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
 go.opencensus.io v0.20.2 h1:NAfh7zF0/3/HqtMvJNZ/RFrSlCE6ZTlHmKfhL/Dm1Jk=
 go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
@@ -511,7 +514,6 @@ golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnf
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk=
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 h1:ACG4HJsFiNMf47Y4PeRoebLNy/2lXT9EtprMuTFWt1M=
@@ -537,7 +539,6 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -568,7 +569,6 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -580,13 +580,11 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -597,7 +595,6 @@ golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3
 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
 golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a h1:mEQZbbaBjWyLNy0tmZmgEuQAR8XOQ3hL8GYi3J/NG64=
@@ -617,10 +614,11 @@ google.golang.org/api v0.4.0 h1:KKgc1aqhV8wDPbDzlDtpvyjZFY3vjz85FP7p4wcQUyI=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
-google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/genproto v0.0.0-20180608181217-32ee49c4dd80/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
@@ -652,7 +650,6 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
-gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
 gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5/go.mod h1:hiOFpYm0ZJbusNj2ywpbrXowU3G8U6GIQzqn2mw1UIE=
@@ -680,17 +677,13 @@ honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXe
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 k8s.io/api v0.0.0-20181018013834-843ad2d9b9ae/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
 k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
-k8s.io/api v0.0.0-20190816222004-e3a6b8045b0b h1:RXEExX+zpnAwh3WmvQMVy5albf6/wuIP1W2ehL2lTqc=
 k8s.io/api v0.0.0-20190816222004-e3a6b8045b0b/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
 k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE=
-k8s.io/apiextensions-apiserver v0.0.0-20190820064606-e49a3471dba5 h1:5hIafR5+cXejrePwdiaB04LgyXeJisYH5Apukak+XTA=
 k8s.io/apiextensions-apiserver v0.0.0-20190820064606-e49a3471dba5/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE=
 k8s.io/apimachinery v0.0.0-20181015213631-60666be32c5d/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
 k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
-k8s.io/apimachinery v0.0.0-20190816221834-a9f1d8a9c101 h1:QtHYUjIdgXTtJVdYQhWIQZZoXa32aF3O9BNX2up2plE=
 k8s.io/apimachinery v0.0.0-20190816221834-a9f1d8a9c101/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
 k8s.io/client-go v9.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
-k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible h1:U5Bt+dab9K8qaUmXINrkXO135kA11/i5Kg1RUydgaMQ=
 k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
 k8s.io/code-generator v0.0.0-20190311093542-50b561225d70 h1:lgPp615xLHxN84RBd+viA/oHzJfI0miFYFH4T9wpPQ4=
 k8s.io/code-generator v0.0.0-20190311093542-50b561225d70/go.mod h1:MYiN+ZJZ9HkETbgVZdWw2AsuAi9PZ4V80cwfuf2axe8=
@@ -717,11 +710,8 @@ mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jC
 mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f h1:Cq7MalBHYACRd6EesksG1Q8EoIAKOsiZviGKbOLIej4=
 mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw=
 mvdan.cc/xurls/v2 v2.0.0/go.mod h1:2/webFPYOXN9jp/lzuj0zuAVlF+9g4KPFJANH1oJhRU=
-sigs.k8s.io/controller-runtime v0.2.0 h1:5gL30PXOisGZl+Osi4CmLhvMUj77BO3wJeouKF2va50=
 sigs.k8s.io/controller-runtime v0.2.0/go.mod h1:ZHqrRDZi3f6BzONcvlUxkqCKgwasGk5FZrnSv9TVZF4=
-sigs.k8s.io/testing_frameworks v0.1.1 h1:cP2l8fkA3O9vekpy5Ks8mmA0NW/F7yBdXf8brkWhVrs=
 sigs.k8s.io/testing_frameworks v0.1.1/go.mod h1:VVBKrHmJ6Ekkfz284YKhQePcdycOzNH9qL6ht1zEr/U=
-sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
 sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
 sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4 h1:JPJh2pk3+X4lXAkZIk2RuE/7/FoK9maXw+TNPJhVS/c=
 sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
diff --git a/images/k8s-cloud-builder/Dockerfile b/images/k8s-cloud-builder/Dockerfile
index 0ec2201f..c409c0a5 100644
--- a/images/k8s-cloud-builder/Dockerfile
+++ b/images/k8s-cloud-builder/Dockerfile
@@ -16,85 +16,112 @@ FROM k8s.gcr.io/kube-cross:v1.13.4-1
 
 ARG DEBIAN_FRONTEND=noninteractive
 
-RUN apt-get -q update
-
 # We want to get rid of python2, we want only python3
 #
 # Right now, the image chain looks like this:
 #  k8s-cloud-builder <- k8s.gcr.io/kube-cross:v1.13.4-1 <- golang:1.13.4 <- buildpack-deps:buster-scm <- debian:buster
 # python2 comes in with buildpack-deps:buster-scm, because that image installs
 # mercurial which in turn has a hard dependency on python2
-RUN apt-get -qqy purge ".*python2.*" \
+RUN apt-get -q update \
+    && apt-get -qqy purge ".*python2.*" \
     && apt-get -qqy install \
         python3-minimal \
         python3-pip \
     && update-alternatives --install /usr/bin/python python /usr/bin/python3 90 \
     && update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 90
 
+# For now we do not re-install mercurial, we think we might not need it after all.
+#
+# # For that to work, we for now need to install mercurial via pip as opposed to
+# # apt/dpkg. As soon as debian has a mercurial package that has no hard
+# # dependency on python2, we can switch back to using the distributor's package
+# # again.
+# RUN pip3 install --no-cache-dir mercurial
+
 # Install packages
-RUN apt-get install -qqy \
+RUN apt-get -q update \
+    && apt-get install -qqy \
         apt-transport-https \
+        build-essential \
         ca-certificates \
         curl \
         gettext-base \
         git \
         gnupg2 \
-        grep \
         jq \
         lsb-release \
-        make \
-        net-tools \
         pandoc \
-        rsync \
         software-properties-common \
-        tzdata \
-        unzip
+        wget
 
 # Install Pip packages
-RUN pip3 install --no-cache-dir \
-        # for gcloud https://cloud.google.com/storage/docs/gsutil/addlhelp/CRC32CandInstallingcrcmod
-        crcmod \
-        # For now we do not re-install mercurial, we think we might not need it after all.
-        #
-        # # We for now need to install mercurial via pip as opposed to via
-        # # apt/dpkg to not pull in python2. As soon as debian has a mercurial package
-        # # that has no hard dependency on python2, we can switch back to using the
-        # # distributor's package again.
-        # mercurial \
-        yq
+RUN pip3 install --no-cache-dir yq
 
-# common::set_cloud_binaries() looks for it in this path
-ARG GOOGLE_DIR='/opt/google'
+# Packages required by the make in k8s
+# localtime
+RUN apt-get -q update \
+    && apt-get install -qqy tzdata
+
+# install net tools
+# required by common.sh
+RUN apt-get -q update \
+    && apt-get install -qqy \
+        grep \
+        net-tools \
+        rsync
 
 # Install gcloud
-RUN bash -c ' \
-        bash <(curl -sSL https://sdk.cloud.google.com) \
-            --install-dir="${GOOGLE_DIR}" \
-            --disable-prompts \
-            >/dev/null \
-    '
+# common::set_cloud_binaries() looks for it in this path
+RUN mkdir /opt/google
+RUN apt-get -y update \
+    && apt-get -y install \
+        gcc \
+        wget \
+        ca-certificates \
+        # These are necessary for add-apt-respository
+        software-properties-common \
+    # Install Git >2.0.1
+    && apt-get -y update \
+    && apt-get -y install git \
+    # Setup Google Cloud SDK (latest)
+    && curl -sSL https://sdk.cloud.google.com > /tmp/install.sh \
+    && bash /tmp/install.sh --install-dir=/opt/google --disable-prompts \
+    # install crcmod: https://cloud.google.com/storage/docs/gsutil/addlhelp/CRC32CandInstallingcrcmod
+    && pip3 install --no-cache-dir -U crcmod \
+    # Clean up
+    && apt-get -y remove \
+        gcc \
+        wget \
+    && rm -rf /var/lib/apt/lists/* \
+    && rm -rf ~/.config/gcloud
 
-ENV PATH="${GOOGLE_DIR}/google-cloud-sdk/bin:${PATH}"
+ENV PATH=/opt/google/google-cloud-sdk/bin:$PATH
 
-# Install docker cli
+# Install docker stuff
+#---------------------
+# Based on instructions from:
 # https://docs.docker.com/install/linux/docker-ce/debian/
-RUN curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - \
+RUN apt-get -y update \
+    && apt-get -y install \
+        apt-transport-https \
+        ca-certificates \
+        curl \
+        make \
+        software-properties-common \
+    && curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - \
     && apt-key fingerprint 0EBFCD88 \
     && add-apt-repository \
-        "deb [arch=amd64] https://download.docker.com/linux/debian \
-        $(lsb_release -cs) \
-        stable" \
+      "deb [arch=amd64] https://download.docker.com/linux/debian \
+      $(lsb_release -cs) \
+      stable" \
     && apt-get -y update \
-    && apt-get -qqy install \
-        docker-ce-cli
+    && apt-get -y install \
+        docker-ce \
+        docker-ce-cli \
+        containerd.io \
+        unzip
 
 # Cleanup a bit
-RUN apt-get -qqy remove \
-        gcc \
-        'cpp.*' \
-        wget \
-    && rm -rf -- \
-        /var/lib/apt/lists/* \
-        ~/.config/gcloud
+RUN rm -rf /var/lib/apt/lists/*
 
 ENTRYPOINT []
diff --git a/pkg/command/BUILD.bazel b/pkg/command/BUILD.bazel
index 9b0789d3..37a4c677 100644
--- a/pkg/command/BUILD.bazel
+++ b/pkg/command/BUILD.bazel
@@ -5,10 +5,7 @@ go_library(
     srcs = ["command.go"],
     importpath = "k8s.io/release/pkg/command",
     visibility = ["//visibility:public"],
-    deps = [
-        "@com_github_pkg_errors//:go_default_library",
-        "@com_github_sirupsen_logrus//:go_default_library",
-    ],
+    deps = ["@com_github_pkg_errors//:go_default_library"],
 )
 
 go_test(
diff --git a/pkg/command/command.go b/pkg/command/command.go
index 139e0820..28b015cb 100644
--- a/pkg/command/command.go
+++ b/pkg/command/command.go
@@ -19,6 +19,7 @@ package command
 import (
 	"bytes"
 	"io"
+	"log"
 	"os"
 	"os/exec"
 	"strings"
@@ -26,7 +27,6 @@ import (
 	"syscall"
 
 	"github.com/pkg/errors"
-	"github.com/sirupsen/logrus"
 )
 
 // A generic command abstraction
@@ -136,7 +136,7 @@ func (c *Command) RunSilentSuccess() (err error) {
 
 // run is the internal run method
 func (c *Command) run(printOutput bool) (res *Status, err error) {
-	logrus.Debugf("Running command: %v", c.String())
+	log.Printf("Running command: %v", c.String())
 	var runErr error
 	stdOutBuffer := &bytes.Buffer{}
 	stdErrBuffer := &bytes.Buffer{}
@@ -167,10 +167,10 @@ func (c *Command) run(printOutput bool) (res *Status, err error) {
 			go func() {
 				defer waitGroup.Done()
 				if _, err := io.Copy(stdOutWriter, stdout); err != nil {
-					logrus.Warn("unable to copy command stdout")
+					log.Println("unable to copy command stdout")
 				}
 				if _, err := io.Copy(stdErrWriter, stderr); err != nil {
-					logrus.Warn("unable to copy command stderr")
+					log.Println("unable to copy command stderr")
 				}
 			}()
 		}
@@ -254,7 +254,7 @@ func Available(commands ...string) (ok bool) {
 	ok = true
 	for _, command := range commands {
 		if _, err := exec.LookPath(command); err != nil {
-			logrus.Warnf("Unable to %v", err)
+			log.Printf("Unable to %v", err)
 			ok = false
 		}
 	}
diff --git a/pkg/git/BUILD.bazel b/pkg/git/BUILD.bazel
index fb3fbcef..5e566633 100644
--- a/pkg/git/BUILD.bazel
+++ b/pkg/git/BUILD.bazel
@@ -9,7 +9,6 @@ go_library(
         "//pkg/command:go_default_library",
         "@com_github_blang_semver//:go_default_library",
         "@com_github_pkg_errors//:go_default_library",
-        "@com_github_sirupsen_logrus//:go_default_library",
         "@in_gopkg_src_d_go_git_v4//:go_default_library",
         "@in_gopkg_src_d_go_git_v4//config:go_default_library",
         "@in_gopkg_src_d_go_git_v4//plumbing:go_default_library",
diff --git a/pkg/git/git.go b/pkg/git/git.go
index 86058948..867b1de2 100644
--- a/pkg/git/git.go
+++ b/pkg/git/git.go
@@ -19,6 +19,7 @@ package git
 import (
 	"fmt"
 	"io/ioutil"
+	"log"
 	"os"
 	"path/filepath"
 	"regexp"
@@ -28,7 +29,6 @@ import (
 
 	"github.com/blang/semver"
 	"github.com/pkg/errors"
-	"github.com/sirupsen/logrus"
 	"gopkg.in/src-d/go-git.v4"
 	"gopkg.in/src-d/go-git.v4/config"
 	"gopkg.in/src-d/go-git.v4/plumbing"
@@ -103,10 +103,10 @@ func CloneOrOpenRepo(repoPath, url string, useSSH bool) (*Repo, error) {
 		return nil, errors.New("git is needed to support all repository features")
 	}
 
-	logrus.Debugf("Using repository url %q", url)
+	log.Printf("Using repository url %q", url)
 	targetDir := ""
 	if repoPath != "" {
-		logrus.Debugf("Using existing repository path %q", repoPath)
+		log.Printf("Using existing repository path %q", repoPath)
 		_, err := os.Stat(repoPath)
 
 		if err == nil {
@@ -168,7 +168,7 @@ func updateRepo(repoPath string, useSSH bool) (*Repo, error) {
 }
 
 func (r *Repo) Cleanup() error {
-	logrus.Debugf("Deleting %s", r.dir)
+	log.Printf("Deleting %s...", r.dir)
 	return os.RemoveAll(r.dir)
 }
 
@@ -213,7 +213,7 @@ func (r *Repo) LatestNonPatchFinalToLatest() (start, end string, err error) {
 		return "", "", err
 	}
 	versionTag := "v" + version.String()
-	logrus.Infof("latest non patch version %s", versionTag)
+	log.Printf("latest non patch version %s", versionTag)
 	start, err = r.RevParse(versionTag)
 	if err != nil {
 		return "", "", err
@@ -263,13 +263,13 @@ func (r *Repo) releaseBranchOrMasterRev(major, minor uint64) (rev string, err er
 	relBranch := fmt.Sprintf("release-%d.%d", major, minor)
 	rev, err = r.RevParse(relBranch)
 	if err == nil {
-		logrus.Infof("found release branch %s", relBranch)
+		log.Printf("found release branch %s", relBranch)
 		return rev, nil
 	}
 
 	rev, err = r.RevParse("master")
 	if err == nil {
-		logrus.Infof("no release branch found, using master")
+		log.Println("no release branch found, using master")
 		return rev, nil
 	}
 
@@ -279,7 +279,7 @@ func (r *Repo) releaseBranchOrMasterRev(major, minor uint64) (rev string, err er
 // HasRemoteBranch takes a branch string and verifies that it exists
 // on the default remote
 func (r *Repo) HasRemoteBranch(branch string) error {
-	logrus.Infof("Verifying %s branch exists on the remote", branch)
+	log.Printf("Verifying %s branch exists on the remote", branch)
 
 	remote, err := r.inner.Remote(DefaultRemote)
 	if err != nil {
@@ -289,18 +289,19 @@ func (r *Repo) HasRemoteBranch(branch string) error {
 	// We can then use every Remote functions to retrieve wanted information
 	refs, err := remote.List(&git.ListOptions{Auth: r.auth})
 	if err != nil {
-		logrus.Warn("Could not list references on the remote repository.")
+		log.Printf("Could not list references on the remote repository.")
 		return err
 	}
 
 	for _, ref := range refs {
 		if ref.Name().IsBranch() {
 			if ref.Name().Short() == branch {
-				logrus.Infof("Found branch %s", ref.Name().Short())
+				log.Printf("Found branch %s", ref.Name().Short())
 				return nil
 			}
 		}
 	}
+	log.Printf("Could not find branch %s", branch)
 	return errors.Errorf("branch %v not found", branch)
 }
 
@@ -320,7 +321,7 @@ func (r *Repo) CheckoutBranch(name string) error {
 func IsReleaseBranch(branch string) bool {
 	re := regexp.MustCompile(branchRE)
 	if !re.MatchString(branch) {
-		logrus.Warnf("%s is not a release branch", branch)
+		log.Printf("%s is not a release branch", branch)
 		return false
 	}
 
@@ -331,7 +332,7 @@ func (r *Repo) MergeBase(from, to string) (string, error) {
 	masterRef := Remotify(from)
 	releaseRef := Remotify(to)
 
-	logrus.Debugf("masterRef: %s, releaseRef: %s", masterRef, releaseRef)
+	log.Printf("masterRef: %s, releaseRef: %s", masterRef, releaseRef)
 
 	commitRevs := []string{masterRef, releaseRef}
 	var res []*object.Commit
@@ -364,7 +365,7 @@ func (r *Repo) MergeBase(from, to string) (string, error) {
 	}
 
 	mergeBase := res[0].Hash.String()
-	logrus.Infof("merge base is %s", mergeBase)
+	log.Printf("merge base is %s", mergeBase)
 
 	return mergeBase, nil
 }
@@ -404,7 +405,7 @@ func (r *Repo) Merge(from string) error {
 func (r *Repo) Push(remoteBranch string) error {
 	args := []string{"push"}
 	if r.dryRun {
-		logrus.Infof("Won't push due to dry run repository")
+		log.Println("Won't push due to dry run repository")
 		args = append(args, "--dry-run")
 	}
 	args = append(args, DefaultRemote, remoteBranch)
diff --git a/pkg/notes/BUILD.bazel b/pkg/notes/BUILD.bazel
index 4240e2c1..b5b94cbc 100644
--- a/pkg/notes/BUILD.bazel
+++ b/pkg/notes/BUILD.bazel
@@ -9,9 +9,10 @@ go_library(
     importpath = "k8s.io/release/pkg/notes",
     visibility = ["//visibility:public"],
     deps = [
+        "@com_github_go_kit_kit//log:go_default_library",
+        "@com_github_go_kit_kit//log/level:go_default_library",
         "@com_github_google_go_github_v28//github:go_default_library",
         "@com_github_pkg_errors//:go_default_library",
-        "@com_github_sirupsen_logrus//:go_default_library",
     ],
 )
 
diff --git a/pkg/notes/document.go b/pkg/notes/document.go
index 0066dbb4..c99d490e 100644
--- a/pkg/notes/document.go
+++ b/pkg/notes/document.go
@@ -17,15 +17,10 @@ limitations under the License.
 package notes
 
 import (
-	"crypto/sha512"
 	"fmt"
 	"io"
-	"os"
-	"path/filepath"
 	"sort"
 	"strings"
-
-	"github.com/pkg/errors"
 )
 
 // Document represents the underlying structure of a release notes document.
@@ -111,11 +106,7 @@ func CreateDocument(notes ReleaseNotes, history ReleaseNotesHistory) (*Document,
 
 // RenderMarkdown accepts a Document and writes a version of that document to
 // supplied io.Writer in markdown format.
-func RenderMarkdown(w io.Writer, doc *Document, bucket, tars, prevTag, newTag string) error {
-	if err := createDownloadsTable(w, bucket, tars, prevTag, newTag); err != nil {
-		return err
-	}
-
+func RenderMarkdown(doc *Document, w io.Writer) error {
 	// we always want to render the document with SIGs in alphabetical order
 	sortedSIGs := []string{}
 	for sig := range doc.SIGs {
@@ -167,7 +158,7 @@ func RenderMarkdown(w io.Writer, doc *Document, bucket, tars, prevTag, newTag st
 
 	// the "API Changes" section
 	if len(doc.APIChanges) > 0 {
-		write("### API Changes\n\n")
+		write("## API Changes\n\n")
 		for _, note := range doc.APIChanges {
 			writeNote(note)
 		}
@@ -176,9 +167,9 @@ func RenderMarkdown(w io.Writer, doc *Document, bucket, tars, prevTag, newTag st
 
 	// the "Duplicate Notes" section
 	if len(doc.Duplicates) > 0 {
-		write("### Notes from Multiple SIGs\n\n")
+		write("## Notes From Multiple SIGs\n\n")
 		for header, notes := range doc.Duplicates {
-			write(fmt.Sprintf("#### %s\n\n", header))
+			write(fmt.Sprintf("### %s\n\n", header))
 			for _, note := range notes {
 				writeNote(note)
 			}
@@ -189,9 +180,9 @@ func RenderMarkdown(w io.Writer, doc *Document, bucket, tars, prevTag, newTag st
 
 	// each SIG gets a section (in alphabetical order)
 	if len(sortedSIGs) > 0 {
-		write("### Notes from Individual SIGs\n\n")
+		write("## Notes from Individual SIGs\n\n")
 		for _, sig := range sortedSIGs {
-			write("#### SIG " + prettySIG(sig) + "\n\n")
+			write("### SIG " + prettySIG(sig) + "\n\n")
 			for _, note := range doc.SIGs[sig] {
 				writeNote(note)
 			}
@@ -202,7 +193,7 @@ func RenderMarkdown(w io.Writer, doc *Document, bucket, tars, prevTag, newTag st
 
 	// the "Bug Fixes" section
 	if len(doc.BugFixes) > 0 {
-		write("### Bug Fixes\n\n")
+		write("## Bug Fixes\n\n")
 		for _, note := range doc.BugFixes {
 			writeNote(note)
 		}
@@ -212,7 +203,7 @@ func RenderMarkdown(w io.Writer, doc *Document, bucket, tars, prevTag, newTag st
 	// we call the uncategorized notes "Other Notable Changes". ideally these
 	// notes would at least have a SIG label.
 	if len(doc.Uncategorized) > 0 {
-		write("### Other Notable Changes\n\n")
+		write("## Other Notable Changes\n\n")
 		for _, note := range doc.Uncategorized {
 			writeNote(note)
 		}
@@ -262,74 +253,3 @@ func prettifySigList(sigs []string) string {
 
 	return sigList
 }
-
-// createDownloadsTable creates the markdown table with the links to the tarballs.
-// The function does nothing if the `tars` variable is empty.
-func createDownloadsTable(w io.Writer, bucket, tars, prevTag, newTag string) error {
-	// Do not add the table if not explicitely requested
-	if tars == "" {
-		return nil
-	}
-	if prevTag == "" || newTag == "" {
-		return errors.New("release tags not specified")
-	}
-
-	fmt.Fprintf(w, "# %s\n\n", newTag)
-	fmt.Fprintf(w, "[Documentation](https://docs.k8s.io)\n\n")
-
-	fmt.Fprintf(w, "## Downloads for %s\n\n", newTag)
-
-	urlPrefix := fmt.Sprintf("https://storage.googleapis.com/%s/release", bucket)
-	if bucket == "kubernetes-release" {
-		urlPrefix = "https://dl.k8s.io"
-	}
-
-	for _, item := range []struct {
-		heading  string
-		patterns []string
-	}{
-		{"", []string{"kubernetes.tar.gz", "kubernetes-src.tar.gz"}},
-		{"Client Binaries", []string{"kubernetes-client*.tar.gz"}},
-		{"Server Binaries", []string{"kubernetes-server*.tar.gz"}},
-		{"Node Binaries", []string{"kubernetes-node*.tar.gz"}},
-	} {
-		if item.heading != "" {
-			fmt.Fprintf(w, "### %s\n\n", item.heading)
-		}
-		fmt.Fprintln(w, "filename | sha512 hash")
-		fmt.Fprintln(w, "-------- | -----------")
-
-		for _, pattern := range item.patterns {
-			pattern := filepath.Join(tars, pattern)
-
-			matches, err := filepath.Glob(pattern)
-			if err != nil {
-				return err
-			}
-
-			for _, file := range matches {
-				f, err := os.Open(file)
-				if err != nil {
-					return err
-				}
-				defer f.Close()
-
-				h := sha512.New()
-				if _, err := io.Copy(h, f); err != nil {
-					return err
-				}
-
-				fileName := filepath.Base(file)
-				fmt.Fprintf(w,
-					"[%s](%s/%s/%s) | `%x`\n",
-					fileName, urlPrefix, newTag, fileName, h.Sum(nil),
-				)
-			}
-		}
-
-		fmt.Fprintln(w, "")
-	}
-
-	fmt.Fprintf(w, "## Changelog since %s\n\n", prevTag)
-	return nil
-}
diff --git a/pkg/notes/notes.go b/pkg/notes/notes.go
index 9b5691f9..2106dcbb 100644
--- a/pkg/notes/notes.go
+++ b/pkg/notes/notes.go
@@ -25,9 +25,10 @@ import (
 	"strconv"
 	"strings"
 
+	"github.com/go-kit/kit/log"
+	"github.com/go-kit/kit/log/level"
 	"github.com/google/go-github/v28/github"
 	"github.com/pkg/errors"
-	"github.com/sirupsen/logrus"
 )
 
 // ReleaseNote is the type that represents the total sum of all the information
@@ -165,6 +166,7 @@ type Result struct {
 // starting from a given commit SHA and ending at starting a given commit SHA.
 func ListReleaseNotes(
 	client *github.Client,
+	logger log.Logger,
 	branch,
 	start,
 	end,
@@ -172,7 +174,7 @@ func ListReleaseNotes(
 	relVer string,
 	opts ...GitHubAPIOption,
 ) (ReleaseNotes, ReleaseNotesHistory, error) {
-	results, err := ListCommitsWithNotes(client, branch, start, end, opts...)
+	results, err := ListCommitsWithNotes(client, logger, branch, start, end, opts...)
 	if err != nil {
 		return nil, nil, err
 	}
@@ -189,10 +191,11 @@ func ListReleaseNotes(
 
 		note, err := ReleaseNoteFromCommit(result, client, relVer, opts...)
 		if err != nil {
-			logrus.
-				WithField("err", err).
-				WithField("sha", result.commit.GetSHA()).
-				Error("getting the release note from commit while listing release notes")
+			level.Error(logger).Log(
+				"err", err,
+				"msg", "error getting the release note from commit while listing release notes",
+				"sha", result.commit.GetSHA(),
+			)
 			continue
 		}
 
@@ -209,11 +212,12 @@ func ListReleaseNotes(
 			}
 			if match {
 				excluded = true
-				logrus.
-					WithField("sha", result.commit.GetSHA()).
-					WithField("func", "ListReleaseNotes").
-					WithField("filter", filter).
-					Debug("Excluding notes that are deemed to have no content based on filter, and should NOT be added to release notes.")
+				level.Debug(logger).Log(
+					"msg", "Excluding notes that are deemed to have no content based on filter, and should NOT be added to release notes.",
+					"sha", result.commit.GetSHA(),
+					"func", "ListReleaseNotes",
+					"filter", filter,
+				)
 				break
 			}
 		}
@@ -238,10 +242,10 @@ func NoteTextFromString(s string) (string, error) {
 	exps := []*regexp.Regexp{
 		// (?s) is needed for '.' to be matching on newlines, by default that's disabled
 		// we need to match ungreedy 'U', because after the notes a `docs` block can occur
-		regexp.MustCompile("(?sU)```release-note[s]?\\r\\n(?P<note>.+)\\r\\n```"),
-		regexp.MustCompile("(?sU)```dev-release-note[s]?\\r\\n(?P<note>.+)"),
+		regexp.MustCompile("(?sU)```release-note\\r\\n(?P<note>.+)\\r\\n```"),
+		regexp.MustCompile("(?sU)```dev-release-note\\r\\n(?P<note>.+)"),
 		regexp.MustCompile("(?sU)```\\r\\n(?P<note>.+)\\r\\n```"),
-		regexp.MustCompile("(?sU)```release-note[s]?\n(?P<note>.+)\n```"),
+		regexp.MustCompile("(?sU)```release-note\n(?P<note>.+)\n```"),
 	}
 
 	for _, exp := range exps {
@@ -431,6 +435,7 @@ func ListCommits(client *github.Client, branch, start, end string, opts ...GitHu
 // returned.
 func ListCommitsWithNotes(
 	client *github.Client,
+	logger log.Logger,
 	branch,
 	start,
 	end string,
@@ -442,28 +447,32 @@ func ListCommitsWithNotes(
 	}
 
 	for i, commit := range commits {
-		logrus.Infof(
-			"[%d/%d - %0.2f%%]: %s",
-			i+1, len(commits), (float64(i+1)/float64(len(commits)))*100.0,
-			commit.GetSHA(),
+		level.Debug(logger).Log("msg", "################################################")
+		level.Info(logger).Log("msg", fmt.Sprintf("[%d/%d - %0.2f%%]", i+1, len(commits), (float64(i+1)/float64(len(commits)))*100.0))
+		level.Debug(logger).Log(
+			"msg", "Processing commit",
+			"func", "ListCommitsWithNotes",
+			"sha", commit.GetSHA(),
 		)
 
-		prs, err := PRsFromCommit(client, commit, opts...)
+		prs, err := PRsFromCommit(client, logger, commit, opts...)
 		if err != nil {
 			if err.Error() == "no matches found when parsing PR from commit" {
-				logrus.
-					WithField("func", "ListCommitsWithNotes").
-					Debugf("No matches found when parsing PR from commit sha %q", commit.GetSHA())
+				level.Debug(logger).Log(
+					"msg", fmt.Sprintf("No matches found when parsing PR from commit sha '%s'.", commit.GetSHA()),
+					"func", "ListCommitsWithNotes",
+				)
 				continue
 			}
 		}
 
 		for _, pr := range prs {
-			logrus.
-				WithField("func", "ListCommitsWithNotes").
-				WithField("pr no", pr.GetNumber()).
-				WithField("pr body", pr.GetBody()).
-				Debugf("Obtaining PR associated with commit sha %q", commit.GetSHA())
+			level.Debug(logger).Log(
+				"msg", fmt.Sprintf("Obtaining PR associated with commit sha '%s'.", commit.GetSHA()),
+				"func", "ListCommitsWithNotes",
+				"pr no", pr.GetNumber(),
+				"pr body", pr.GetBody(),
+			)
 
 			// exclusionFilters is a list of regular expressions that match commits that
 			// do NOT contain release notes. Notably, this is all of the variations of
@@ -492,10 +501,11 @@ func ListCommitsWithNotes(
 				}
 				if match {
 					excluded = true
-					logrus.
-						WithField("func", "ListCommitsWithNotes").
-						WithField("filter", filter).
-						Debug("Excluding notes for PR based on the exclusion filter.")
+					level.Debug(logger).Log(
+						"msg", "Excluding notes for PR based on the exclusion filter.",
+						"func", "ListCommitsWithNotes",
+						"filter", filter,
+					)
 					break
 				}
 			}
@@ -520,10 +530,11 @@ func ListCommitsWithNotes(
 				if match {
 					matched = true
 					filtered = append(filtered, &Result{commit: commit, pullRequest: pr})
-					logrus.
-						WithField("func", "ListCommitsWithNotes").
-						WithField("filter", filter).
-						Debug("Including notes for PR based on the inclusion filter.")
+					level.Debug(logger).Log(
+						"msg", "Including notes for PR based on the inclusion filter.",
+						"func", "ListCommitsWithNotes",
+						"filter", filter,
+					)
 				}
 			}
 
@@ -540,15 +551,16 @@ func ListCommitsWithNotes(
 // PRsFromCommit return an API Pull Request struct given a commit struct. This is
 // useful for going from a commit log to the PR (which contains useful info such
 // as labels).
-func PRsFromCommit(client *github.Client, commit *github.RepositoryCommit, opts ...GitHubAPIOption) (
+func PRsFromCommit(client *github.Client, logger log.Logger, commit *github.RepositoryCommit, opts ...GitHubAPIOption) (
 	[]*github.PullRequest, error,
 ) {
 	githubPRs, err := prsForCommitFromMessage(client, *commit.Commit.Message, opts...)
 	if err != nil {
-		logrus.
-			WithField("err", err).
-			WithField("sha", commit.GetSHA()).
-			Debug("getting the pr numbers from commit message")
+		level.Debug(logger).Log(
+			"err", err,
+			"msg", "error getting the pr numbers from commit message",
+			"sha", commit.GetSHA(),
+		)
 		return prsForCommitFromSHA(client, *commit.SHA, opts...)
 	}
 	return githubPRs, err
diff --git a/pkg/release/BUILD.bazel b/pkg/release/BUILD.bazel
deleted file mode 100644
index aa3726fa..00000000
--- a/pkg/release/BUILD.bazel
+++ /dev/null
@@ -1,30 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
-
-go_library(
-    name = "go_default_library",
-    srcs = ["release.go"],
-    importpath = "k8s.io/release/pkg/release",
-    visibility = ["//visibility:public"],
-    deps = ["//pkg/util:go_default_library"],
-)
-
-filegroup(
-    name = "package-srcs",
-    srcs = glob(["**"]),
-    tags = ["automanaged"],
-    visibility = ["//visibility:private"],
-)
-
-filegroup(
-    name = "all-srcs",
-    srcs = [":package-srcs"],
-    tags = ["automanaged"],
-    visibility = ["//visibility:public"],
-)
-
-go_test(
-    name = "go_default_test",
-    srcs = ["release_test.go"],
-    embed = [":go_default_library"],
-    deps = ["@com_github_stretchr_testify//require:go_default_library"],
-)
diff --git a/pkg/release/release.go b/pkg/release/release.go
deleted file mode 100644
index 00c55968..00000000
--- a/pkg/release/release.go
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
-Copyright 2019 The Kubernetes 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 release
-
-import (
-	"io/ioutil"
-	"path/filepath"
-	"regexp"
-	"strings"
-
-	"k8s.io/release/pkg/util"
-)
-
-const (
-	versionReleaseRE  = `v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[a-zA-Z0-9]+)*\.*(0|[1-9][0-9]*)?`
-	versionDotZeroRE  = `v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.0$`
-	versionBuildRE    = `([0-9]{1,})\+([0-9a-f]{5,40})`
-	versionDirtyRE    = `(-dirty)`
-	dockerBuildPath   = "_output/release-tars"
-	bazelBuildPath    = "bazel-bin/build/release-tars"
-	bazelVersionPath  = "bazel-genfiles/version"
-	dockerVersionPath = "version"
-	tarballExtension  = ".tar.gz"
-)
-
-// BuiltWithBazel determines whether the most recent release was built with Bazel.
-func BuiltWithBazel(path, releaseKind string) (bool, error) {
-	tar := releaseKind + tarballExtension
-	bazelBuild := filepath.Join(path, bazelBuildPath, tar)
-	dockerBuild := filepath.Join(path, dockerBuildPath, tar)
-	return util.MoreRecent(bazelBuild, dockerBuild)
-}
-
-// ReadBazelVersion reads the version from a Bazel build.
-func ReadBazelVersion(path string) (string, error) {
-	version, err := ioutil.ReadFile(filepath.Join(path, bazelVersionPath))
-	return string(version), err
-}
-
-// ReadDockerizedVersion reads the version from a Dockerized
-func ReadDockerizedVersion(path, releaseKind string) (string, error) {
-	tar := releaseKind + tarballExtension
-	dockerTarball := filepath.Join(path, dockerBuildPath, tar)
-	versionFile := filepath.Join(releaseKind, dockerVersionPath)
-	reader, err := util.ReadFileFromGzippedTar(dockerTarball, versionFile)
-	if err != nil {
-		return "", err
-	}
-	file, err := ioutil.ReadAll(reader)
-	return strings.TrimSuffix(string(file), "\n"), err
-}
-
-// IsValidReleaseBuild checks if build version is valid for release.
-func IsValidReleaseBuild(build string) (bool, error) {
-	return regexp.MatchString("("+versionReleaseRE+`(\.`+versionBuildRE+")?"+versionDirtyRE+"?)", build)
-}
-
-// IsDirtyBuild checks if build version is dirty.
-func IsDirtyBuild(build string) bool {
-	return strings.Contains(build, "dirty")
-}
diff --git a/pkg/util/BUILD.bazel b/pkg/util/BUILD.bazel
index ed835f42..05179352 100644
--- a/pkg/util/BUILD.bazel
+++ b/pkg/util/BUILD.bazel
@@ -1,11 +1,10 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
 
 go_library(
     name = "go_default_library",
     srcs = ["common.go"],
     importpath = "k8s.io/release/pkg/util",
     visibility = ["//visibility:public"],
-    deps = ["@com_github_sirupsen_logrus//:go_default_library"],
 )
 
 filegroup(
@@ -21,10 +20,3 @@ filegroup(
     tags = ["automanaged"],
     visibility = ["//visibility:public"],
 )
-
-go_test(
-    name = "go_default_test",
-    srcs = ["common_test.go"],
-    embed = [":go_default_library"],
-    deps = ["@com_github_stretchr_testify//require:go_default_library"],
-)
diff --git a/pkg/util/common.go b/pkg/util/common.go
index b4c588b9..3fded0ff 100644
--- a/pkg/util/common.go
+++ b/pkg/util/common.go
@@ -17,17 +17,13 @@ limitations under the License.
 package util
 
 import (
-	"archive/tar"
 	"bufio"
-	"compress/gzip"
 	"errors"
 	"fmt"
-	"io"
 	"io/ioutil"
+	"log"
 	"os"
 	"path/filepath"
-
-	"github.com/sirupsen/logrus"
 )
 
 /*
@@ -69,12 +65,12 @@ func Ask(question, expectedResponse string, retries int) (answer string, success
 	attempts := 1
 
 	if retries < 0 {
-		fmt.Printf("Retries was set to a number less than zero (%d). Please specify a positive number of retries or zero, if you want to ask unconditionally.", retries)
+		log.Printf("Retries was set to a number less than zero (%d). Please specify a positive number of retries or zero, if you want to ask unconditionally.", retries)
 	}
 
 	for attempts <= retries {
 		scanner := bufio.NewScanner(os.Stdin)
-		fmt.Printf("%s (%d/%d) ", question, attempts, retries)
+		log.Printf("%s (%d/%d) ", question, attempts, retries)
 
 		scanner.Scan()
 		answer = scanner.Text()
@@ -83,27 +79,28 @@ func Ask(question, expectedResponse string, retries int) (answer string, success
 			return answer, true, nil
 		}
 
-		fmt.Printf("Expected '%s', but got '%s'", expectedResponse, answer)
+		log.Printf("Expected '%s', but got '%s'", expectedResponse, answer)
 
 		attempts++
 	}
 
+	log.Printf("Expected response was not provided. Retries exceeded.")
 	return answer, false, errors.New("expected response was not input. Retries exceeded")
 }
 
 // FakeGOPATH creates a temp directory, links the base directory into it and
 // sets the GOPATH environment variable to it.
 func FakeGOPATH(srcDir string) (string, error) {
-	logrus.Debug("Linking repository into temp dir")
+	log.Printf("Linking repository into temp dir")
 	baseDir, err := ioutil.TempDir("", "ff-")
 	if err != nil {
 		return "", err
 	}
 
-	logrus.Infof("New working directory is %q", baseDir)
+	log.Printf("New working directory is %q", baseDir)
 
 	os.Setenv("GOPATH", baseDir)
-	logrus.Debugf("GOPATH: %s", os.Getenv("GOPATH"))
+	log.Printf("GOPATH: %s", os.Getenv("GOPATH"))
 
 	gitRoot := fmt.Sprintf("%s/src/k8s.io", baseDir)
 	if err := os.MkdirAll(gitRoot, 0o755); err != nil {
@@ -112,68 +109,15 @@ func FakeGOPATH(srcDir string) (string, error) {
 	gitRoot = filepath.Join(gitRoot, "kubernetes")
 
 	// link the repo into the working directory
-	logrus.Debugf("Creating symlink from %q to %q", srcDir, gitRoot)
+	log.Printf("Creating symlink from %q to %q", srcDir, gitRoot)
 	if err := os.Symlink(srcDir, gitRoot); err != nil {
 		return "", err
 	}
 
-	logrus.Infof("Changing working directory to %s", gitRoot)
+	log.Printf("Changing working directory to %s", gitRoot)
 	if err := os.Chdir(gitRoot); err != nil {
 		return "", err
 	}
 
 	return gitRoot, nil
 }
-
-// ReadFileFromGzippedTar opens a tarball and reads contents of a file inside.
-func ReadFileFromGzippedTar(tarPath, filePath string) (io.Reader, error) {
-	file, err := os.Open(tarPath)
-	if err != nil {
-		return nil, err
-	}
-
-	archive, err := gzip.NewReader(file)
-	if err != nil {
-		return nil, err
-	}
-	tr := tar.NewReader(archive)
-
-	for {
-		h, err := tr.Next()
-		if err == io.EOF {
-			break // End of archive
-		}
-
-		if h.Name == filePath {
-			return tr, nil
-		}
-	}
-
-	return nil, errors.New("unable to find file in tarball")
-}
-
-// MoreRecent determines if file at path a was modified more recently than file
-// at path b. If one file does not exist, the other will be treated as most
-// recent. If both files do not exist or an error occurs, an error is returned.
-func MoreRecent(a, b string) (bool, error) {
-	fileA, errA := os.Stat(a)
-	if errA != nil && !os.IsNotExist(errA) {
-		return false, errA
-	}
-
-	fileB, errB := os.Stat(b)
-	if errB != nil && !os.IsNotExist(errB) {
-		return false, errB
-	}
-
-	switch {
-	case os.IsNotExist(errA) && os.IsNotExist(errB):
-		return false, errors.New("neither file exists")
-	case os.IsNotExist(errA):
-		return false, nil
-	case os.IsNotExist(errB):
-		return true, nil
-	}
-
-	return (fileA.ModTime().Unix() >= fileB.ModTime().Unix()), nil
-}
diff --git a/repos.bzl b/repos.bzl
index 49cd5b86..2d8b8c7d 100644
--- a/repos.bzl
+++ b/repos.bzl
@@ -39,8 +39,8 @@ def go_repositories():
         build_file_generation = "on",
         build_file_proto_mode = "disable",
         importpath = "github.com/go-kit/kit",
-        sum = "h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0=",
-        version = "v0.8.0",
+        sum = "h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk=",
+        version = "v0.9.0",
     )
 
     go_repository(
@@ -79,6 +79,15 @@ def go_repositories():
         version = "v1.0.0",
     )
 
+    go_repository(
+        name = "com_github_kolide_kit",
+        build_file_generation = "on",
+        build_file_proto_mode = "disable",
+        importpath = "github.com/kolide/kit",
+        sum = "h1:Ii9fpYpVTsnibfXFHctbDJ78azLTz5Qiph4o6zpIj4o=",
+        version = "v0.0.0-20190123023048-c155a91098e3",
+    )
+
     go_repository(
         name = "com_github_kr_logfmt",
         build_file_generation = "on",
@@ -120,8 +129,8 @@ def go_repositories():
         build_file_generation = "on",
         build_file_proto_mode = "disable",
         importpath = "google.golang.org/appengine",
-        sum = "h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=",
-        version = "v1.6.1",
+        sum = "h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=",
+        version = "v1.5.0",
     )
 
     go_repository(
@@ -602,8 +611,8 @@ def go_repositories():
         build_file_generation = "on",
         build_file_proto_mode = "disable",
         importpath = "github.com/go-sql-driver/mysql",
-        sum = "h1:cRYCw9putX9pmpNdGkE/rWrY6gNGqZvdgGT1RPl6K18=",
-        version = "v0.0.0-20160411075031-7ebe0a500653",
+        sum = "h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=",
+        version = "v1.4.1",
     )
     go_repository(
         name = "com_github_go_stack_stack",
@@ -634,8 +643,8 @@ def go_repositories():
         build_file_generation = "on",
         build_file_proto_mode = "disable",
         importpath = "github.com/gogo/protobuf",
-        sum = "h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I=",
-        version = "v1.2.2-0.20190723190241-65acae22fc9d",
+        sum = "h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=",
+        version = "v1.2.1",
     )
     go_repository(
         name = "com_github_golang_glog",
@@ -966,7 +975,14 @@ def go_repositories():
         sum = "h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=",
         version = "v0.0.0-20180206201540-c2b33e8439af",
     )
-
+    go_repository(
+        name = "com_github_jmoiron_sqlx",
+        build_file_generation = "on",
+        build_file_proto_mode = "disable",
+        importpath = "github.com/jmoiron/sqlx",
+        sum = "h1:eR1qlND4ShQ9W/Q56oy9c/Jj6hpqS5heEruKQVbJGNo=",
+        version = "v0.0.0-20180406164412-2aeb6a910c2b",
+    )
     go_repository(
         name = "com_github_joho_godotenv",
         build_file_generation = "on",
@@ -1020,8 +1036,8 @@ def go_repositories():
         build_file_generation = "on",
         build_file_proto_mode = "disable",
         importpath = "github.com/kisielk/errcheck",
-        sum = "h1:reN85Pxc5larApoH1keMBiu2GWtPqXQ1nc9gx+jOU+E=",
-        version = "v1.2.0",
+        sum = "h1:ZqfnKyx9KGpRcW04j5nnPDgRgoXUeLh2YFBeFzphcA0=",
+        version = "v1.1.0",
     )
     go_repository(
         name = "com_github_kisielk_gotool",
@@ -1172,8 +1188,8 @@ def go_repositories():
         build_file_generation = "on",
         build_file_proto_mode = "disable",
         importpath = "github.com/mattn/go-sqlite3",
-        sum = "h1:LeyN6zuPxHQrv7ZGHbo2Ymtj+BfpbG02+5iAgKds/zU=",
-        version = "v0.0.0-20160514122348-38ee283dabf1",
+        sum = "h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=",
+        version = "v1.10.0",
     )
     go_repository(
         name = "com_github_mattn_go_zglob",
@@ -1263,7 +1279,6 @@ def go_repositories():
         sum = "h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=",
         version = "v1.3.1",
     )
-
     go_repository(
         name = "com_github_olekukonko_tablewriter",
         build_file_generation = "on",
@@ -1288,7 +1303,14 @@ def go_repositories():
         sum = "h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=",
         version = "v1.7.0",
     )
-
+    go_repository(
+        name = "com_github_opencensus_integrations_ocsql",
+        build_file_generation = "on",
+        build_file_proto_mode = "disable",
+        importpath = "github.com/opencensus-integrations/ocsql",
+        sum = "h1:+J5BmLX1kNWCH9/5wJdleej2oRyJrhVEt+FAjq1VqaI=",
+        version = "v0.1.1",
+    )
     go_repository(
         name = "com_github_opencontainers_go_digest",
         build_file_generation = "on",


Test patch:
diff --git a/images/k8s-cloud-builder/test.yaml b/images/k8s-cloud-builder/test.yaml
index 15191c6e..60fff8a9 100644
--- a/images/k8s-cloud-builder/test.yaml
+++ b/images/k8s-cloud-builder/test.yaml
@@ -16,54 +16,3 @@ commandTests:
   command:        pip
   args:           [ "--version" ]
   expectedOutput: [ "^pip.*python 3\\." ]
-- name:           bash is 4+
-  command:        bash
-  args:           [ '--version' ]
-  expectedOutput: [ "version [45]\\." ]
-- name:           google sdk is in the $PATH
-  command:        bash
-  args:
-  - -c
-  - |
-    echo "$PATH" | tr : '\n' | grep '^/opt/google/google-cloud-sdk/bin$'
-- name:           crcmod python module is installed
-  command:        bash
-  args:
-  - -c
-  - |
-    pip list | grep '^crcmod\b'
-
-- name:    commands are available
-  command: /test.sh
-  args:
-  - envsubst
-  - gcloud
-  - go
-  - gsutil
-  - yq
-  setup:
-  -
-    - bash
-    - -c
-    - |
-      cat <<'EOF' >/test.sh
-      #!/usr/bin/env bash
-      set -e
-      set -u
-      set -o pipefail
-
-      exitCode=0
-      for cmd in "$@"
-      do
-        if command -v "$cmd" >/dev/null 2>&1
-        then
-          echo -n .
-        else
-          exitCode=1
-          >&2 echo "${cmd} NOT available."
-        fi
-      done
-      exit $exitCode
-      EOF
-
-      chmod +x /test.sh
diff --git a/pkg/command/command_test.go b/pkg/command/command_test.go
index 7f82b557..fe307ee8 100644
--- a/pkg/command/command_test.go
+++ b/pkg/command/command_test.go
@@ -55,7 +55,7 @@ func TestFailurePipeWrongArgument(t *testing.T) {
 	require.Nil(t, err)
 	require.False(t, res.Success())
 	require.Empty(t, res.Output())
-	require.Contains(t, res.Error(), "unrecognized option")
+	require.NotEmpty(t, res.Error())
 }
 
 func TestSuccessWithWorkingDir(t *testing.T) {
diff --git a/pkg/notes/document_test.go b/pkg/notes/document_test.go
index 857bccc6..6fd76266 100644
--- a/pkg/notes/document_test.go
+++ b/pkg/notes/document_test.go
@@ -17,10 +17,6 @@ limitations under the License.
 package notes
 
 import (
-	"bytes"
-	"io/ioutil"
-	"os"
-	"path/filepath"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -41,97 +37,3 @@ func TestPrettySIG(t *testing.T) {
 		require.Equal(t, expected, (prettySIG(input)))
 	}
 }
-
-func TestCreateDownloadsTable(t *testing.T) {
-	// Given
-	output := &bytes.Buffer{}
-	dir, err := ioutil.TempDir("", "")
-	require.Nil(t, err)
-	defer os.RemoveAll(dir)
-
-	for _, file := range []string{
-		"kubernetes-client-darwin-386.tar.gz",
-		"kubernetes-client-darwin-amd64.tar.gz",
-		"kubernetes-client-linux-386.tar.gz",
-		"kubernetes-client-linux-amd64.tar.gz",
-		"kubernetes-client-linux-arm.tar.gz",
-		"kubernetes-client-linux-arm64.tar.gz",
-		"kubernetes-client-linux-ppc64le.tar.gz",
-		"kubernetes-client-linux-s390x.tar.gz",
-		"kubernetes-client-windows-386.tar.gz",
-		"kubernetes-client-windows-amd64.tar.gz",
-		"kubernetes-node-linux-amd64.tar.gz",
-		"kubernetes-node-linux-arm.tar.gz",
-		"kubernetes-node-linux-arm64.tar.gz",
-		"kubernetes-node-linux-ppc64le.tar.gz",
-		"kubernetes-node-linux-s390x.tar.gz",
-		"kubernetes-node-windows-amd64.tar.gz",
-		"kubernetes-server-linux-amd64.tar.gz",
-		"kubernetes-server-linux-arm.tar.gz",
-		"kubernetes-server-linux-arm64.tar.gz",
-		"kubernetes-server-linux-ppc64le.tar.gz",
-		"kubernetes-server-linux-s390x.tar.gz",
-		"kubernetes-src.tar.gz",
-		"kubernetes.tar.gz",
-	} {
-		require.Nil(t, ioutil.WriteFile(
-			filepath.Join(dir, file), []byte{1, 2, 3}, 0o0644,
-		))
-	}
-
-	// When
-	require.Nil(t, createDownloadsTable(output, "kubernetes-release", dir,
-		"v1.16.0", "v1.16.1"))
-
-	// Then
-	require.Equal(t, output.String(), `# v1.16.1
-
-[Documentation](https://docs.k8s.io)
-
-## Downloads for v1.16.1
-
-filename | sha512 hash
--------- | -----------
-[kubernetes.tar.gz](https://dl.k8s.io/v1.16.1/kubernetes.tar.gz) | `+"`"+`27864cc5219a951a7a6e52b8c8dddf6981d098da1658d96258c870b2c88dfbcb51841aea172a28bafa6a79731165584677066045c959ed0f9929688d04defc29`+"`"+`
-[kubernetes-src.tar.gz](https://dl.k8s.io/v1.16.1/kubernetes-src.tar.gz) | `+"`"+`27864cc5219a951a7a6e52b8c8dddf6981d098da1658d96258c870b2c88dfbcb51841aea172a28bafa6a79731165584677066045c959ed0f9929688d04defc29`+"`"+`
-
-### Client Binaries
-
-filename | sha512 hash
--------- | -----------
-[kubernetes-client-darwin-386.tar.gz](https://dl.k8s.io/v1.16.1/kubernetes-client-darwin-386.tar.gz) | `+"`"+`27864cc5219a951a7a6e52b8c8dddf6981d098da1658d96258c870b2c88dfbcb51841aea172a28bafa6a79731165584677066045c959ed0f9929688d04defc29`+"`"+`
-[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.16.1/kubernetes-client-darwin-amd64.tar.gz) | `+"`"+`27864cc5219a951a7a6e52b8c8dddf6981d098da1658d96258c870b2c88dfbcb51841aea172a28bafa6a79731165584677066045c959ed0f9929688d04defc29`+"`"+`
-[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.16.1/kubernetes-client-linux-386.tar.gz) | `+"`"+`27864cc5219a951a7a6e52b8c8dddf6981d098da1658d96258c870b2c88dfbcb51841aea172a28bafa6a79731165584677066045c959ed0f9929688d04defc29`+"`"+`
-[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.16.1/kubernetes-client-linux-amd64.tar.gz) | `+"`"+`27864cc5219a951a7a6e52b8c8dddf6981d098da1658d96258c870b2c88dfbcb51841aea172a28bafa6a79731165584677066045c959ed0f9929688d04defc29`+"`"+`
-[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.16.1/kubernetes-client-linux-arm.tar.gz) | `+"`"+`27864cc5219a951a7a6e52b8c8dddf6981d098da1658d96258c870b2c88dfbcb51841aea172a28bafa6a79731165584677066045c959ed0f9929688d04defc29`+"`"+`
-[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.16.1/kubernetes-client-linux-arm64.tar.gz) | `+"`"+`27864cc5219a951a7a6e52b8c8dddf6981d098da1658d96258c870b2c88dfbcb51841aea172a28bafa6a79731165584677066045c959ed0f9929688d04defc29`+"`"+`
-[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.16.1/kubernetes-client-linux-ppc64le.tar.gz) | `+"`"+`27864cc5219a951a7a6e52b8c8dddf6981d098da1658d96258c870b2c88dfbcb51841aea172a28bafa6a79731165584677066045c959ed0f9929688d04defc29`+"`"+`
-[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.16.1/kubernetes-client-linux-s390x.tar.gz) | `+"`"+`27864cc5219a951a7a6e52b8c8dddf6981d098da1658d96258c870b2c88dfbcb51841aea172a28bafa6a79731165584677066045c959ed0f9929688d04defc29`+"`"+`
-[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.16.1/kubernetes-client-windows-386.tar.gz) | `+"`"+`27864cc5219a951a7a6e52b8c8dddf6981d098da1658d96258c870b2c88dfbcb51841aea172a28bafa6a79731165584677066045c959ed0f9929688d04defc29`+"`"+`
-[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.16.1/kubernetes-client-windows-amd64.tar.gz) | `+"`"+`27864cc5219a951a7a6e52b8c8dddf6981d098da1658d96258c870b2c88dfbcb51841aea172a28bafa6a79731165584677066045c959ed0f9929688d04defc29`+"`"+`
-
-### Server Binaries
-
-filename | sha512 hash
--------- | -----------
-[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.16.1/kubernetes-server-linux-amd64.tar.gz) | `+"`"+`27864cc5219a951a7a6e52b8c8dddf6981d098da1658d96258c870b2c88dfbcb51841aea172a28bafa6a79731165584677066045c959ed0f9929688d04defc29`+"`"+`
-[kubernetes-server-linux-arm.tar.gz](https://dl.k8s.io/v1.16.1/kubernetes-server-linux-arm.tar.gz) | `+"`"+`27864cc5219a951a7a6e52b8c8dddf6981d098da1658d96258c870b2c88dfbcb51841aea172a28bafa6a79731165584677066045c959ed0f9929688d04defc29`+"`"+`
-[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.16.1/kubernetes-server-linux-arm64.tar.gz) | `+"`"+`27864cc5219a951a7a6e52b8c8dddf6981d098da1658d96258c870b2c88dfbcb51841aea172a28bafa6a79731165584677066045c959ed0f9929688d04defc29`+"`"+`
-[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.16.1/kubernetes-server-linux-ppc64le.tar.gz) | `+"`"+`27864cc5219a951a7a6e52b8c8dddf6981d098da1658d96258c870b2c88dfbcb51841aea172a28bafa6a79731165584677066045c959ed0f9929688d04defc29`+"`"+`
-[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.16.1/kubernetes-server-linux-s390x.tar.gz) | `+"`"+`27864cc5219a951a7a6e52b8c8dddf6981d098da1658d96258c870b2c88dfbcb51841aea172a28bafa6a79731165584677066045c959ed0f9929688d04defc29`+"`"+`
-
-### Node Binaries
-
-filename | sha512 hash
--------- | -----------
-[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.16.1/kubernetes-node-linux-amd64.tar.gz) | `+"`"+`27864cc5219a951a7a6e52b8c8dddf6981d098da1658d96258c870b2c88dfbcb51841aea172a28bafa6a79731165584677066045c959ed0f9929688d04defc29`+"`"+`
-[kubernetes-node-linux-arm.tar.gz](https://dl.k8s.io/v1.16.1/kubernetes-node-linux-arm.tar.gz) | `+"`"+`27864cc5219a951a7a6e52b8c8dddf6981d098da1658d96258c870b2c88dfbcb51841aea172a28bafa6a79731165584677066045c959ed0f9929688d04defc29`+"`"+`
-[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.16.1/kubernetes-node-linux-arm64.tar.gz) | `+"`"+`27864cc5219a951a7a6e52b8c8dddf6981d098da1658d96258c870b2c88dfbcb51841aea172a28bafa6a79731165584677066045c959ed0f9929688d04defc29`+"`"+`
-[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.16.1/kubernetes-node-linux-ppc64le.tar.gz) | `+"`"+`27864cc5219a951a7a6e52b8c8dddf6981d098da1658d96258c870b2c88dfbcb51841aea172a28bafa6a79731165584677066045c959ed0f9929688d04defc29`+"`"+`
-[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.16.1/kubernetes-node-linux-s390x.tar.gz) | `+"`"+`27864cc5219a951a7a6e52b8c8dddf6981d098da1658d96258c870b2c88dfbcb51841aea172a28bafa6a79731165584677066045c959ed0f9929688d04defc29`+"`"+`
-[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.16.1/kubernetes-node-windows-amd64.tar.gz) | `+"`"+`27864cc5219a951a7a6e52b8c8dddf6981d098da1658d96258c870b2c88dfbcb51841aea172a28bafa6a79731165584677066045c959ed0f9929688d04defc29`+"`"+`
-
-## Changelog since v1.16.0
-
-`)
-}
diff --git a/pkg/notes/notes_test.go b/pkg/notes/notes_test.go
index a0e77fa8..d63b8a01 100644
--- a/pkg/notes/notes_test.go
+++ b/pkg/notes/notes_test.go
@@ -88,7 +88,7 @@ func TestReleaseNoteParsing(t *testing.T) {
 		fmt.Println(sha)
 		commit, _, err := client.Repositories.GetCommit(ctx, "kubernetes", "kubernetes", sha)
 		require.NoError(t, err)
-		prs, err := PRsFromCommit(client, commit)
+		prs, err := PRsFromCommit(client, nil, commit)
 		require.NoError(t, err)
 		_, err = ReleaseNoteFromCommit(&Result{commit: commit, pullRequest: prs[0]}, client, "0.1")
 		require.NoError(t, err)
diff --git a/pkg/release/release_test.go b/pkg/release/release_test.go
deleted file mode 100644
index 62392d3c..00000000
--- a/pkg/release/release_test.go
+++ /dev/null
@@ -1,331 +0,0 @@
-/*
-Copyright 2019 The Kubernetes 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 release
-
-import (
-	"archive/tar"
-	"bytes"
-	"compress/gzip"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/require"
-)
-
-func TestBuiltWithBazel(t *testing.T) {
-	baseTmpDir, err := ioutil.TempDir("", "")
-	require.Nil(t, err)
-
-	bazelTmpDir, err := ioutil.TempDir("", "bazel")
-	require.Nil(t, err)
-	dockerTmpDir, err := ioutil.TempDir("", "docker")
-	require.Nil(t, err)
-
-	release := "kubernetes"
-
-	// Build directories.
-	require.Nil(t, os.MkdirAll(filepath.Join(baseTmpDir, bazelBuildPath), os.ModePerm))
-	require.Nil(t, os.MkdirAll(filepath.Join(baseTmpDir, dockerBuildPath), os.ModePerm))
-	require.Nil(t, os.MkdirAll(filepath.Join(bazelTmpDir, bazelBuildPath), os.ModePerm))
-	require.Nil(t, os.MkdirAll(filepath.Join(dockerTmpDir, dockerBuildPath), os.ModePerm))
-
-	// Create test files.
-	baseBazelFile := filepath.Join(baseTmpDir, "bazel-bin/build/release-tars/kubernetes.tar.gz")
-	require.Nil(t, ioutil.WriteFile(
-		baseBazelFile,
-		[]byte("test"),
-		0644,
-	))
-	bazelFile := filepath.Join(bazelTmpDir, "bazel-bin/build/release-tars/kubernetes.tar.gz")
-	require.Nil(t, ioutil.WriteFile(
-		bazelFile,
-		[]byte("test"),
-		0644,
-	))
-
-	time.Sleep(1 * time.Second)
-
-	baseDockerFile := filepath.Join(baseTmpDir, "_output/release-tars/kubernetes.tar.gz")
-	require.Nil(t, ioutil.WriteFile(
-		baseDockerFile,
-		[]byte("test"),
-		0644,
-	))
-	dockerFile := filepath.Join(dockerTmpDir, "_output/release-tars/1.1.1.tar.gz")
-	require.Nil(t, ioutil.WriteFile(
-		dockerFile,
-		[]byte("test"),
-		0644,
-	))
-
-	defer cleanupTmps(t, baseTmpDir, bazelTmpDir, dockerTmpDir)
-
-	type args struct {
-		path    string
-		release string
-	}
-	type want struct {
-		r   bool
-		err error
-	}
-	cases := map[string]struct {
-		args args
-		want want
-	}{
-		"DockerMoreRecent": {
-			args: args{
-				path:    baseTmpDir,
-				release: release,
-			},
-			want: want{
-				r:   false,
-				err: nil,
-			},
-		},
-		"DockerOnly": {
-			args: args{
-				path:    baseTmpDir,
-				release: release,
-			},
-			want: want{
-				r:   false,
-				err: nil,
-			},
-		},
-		"BazelOnly": {
-			args: args{
-				path:    bazelTmpDir,
-				release: release,
-			},
-			want: want{
-				r:   true,
-				err: nil,
-			},
-		},
-	}
-
-	for name, tc := range cases {
-		t.Run(name, func(t *testing.T) {
-			res, err := BuiltWithBazel(tc.args.path, tc.args.release)
-			require.Equal(t, tc.want.err, err)
-			require.Equal(t, tc.want.r, res)
-		})
-	}
-}
-
-func TestReadBazelVersion(t *testing.T) {
-	baseTmpDir, err := ioutil.TempDir("", "")
-	require.Nil(t, err)
-
-	version := "1.1.1"
-
-	// Build directories.
-	require.Nil(t, os.MkdirAll(filepath.Join(baseTmpDir, "bazel-genfiles"), os.ModePerm))
-
-	bazelVersionFile := filepath.Join(baseTmpDir, bazelVersionPath)
-	require.Nil(t, ioutil.WriteFile(
-		bazelVersionFile,
-		[]byte(version),
-		0644,
-	))
-
-	defer cleanupTmps(t, baseTmpDir)
-
-	type want struct {
-		r    string
-		rErr bool
-	}
-	cases := map[string]struct {
-		path string
-		want want
-	}{
-		"ReadVersion": {
-			path: baseTmpDir,
-			want: want{
-				r:    version,
-				rErr: false,
-			},
-		},
-		"ReadVersionError": {
-			path: "notadir",
-			want: want{
-				rErr: true,
-			},
-		},
-	}
-
-	for name, tc := range cases {
-		t.Run(name, func(t *testing.T) {
-			res, err := ReadBazelVersion(tc.path)
-			require.Equal(t, tc.want.rErr, err != nil)
-			require.Equal(t, tc.want.r, res)
-		})
-	}
-}
-
-func TestReadDockerVersion(t *testing.T) {
-	baseTmpDir, err := ioutil.TempDir("", "")
-	require.Nil(t, err)
-
-	release := "kubernetes"
-	version := "1.1.1"
-
-	// Build directories.
-	require.Nil(t, os.MkdirAll(filepath.Join(baseTmpDir, dockerBuildPath), os.ModePerm))
-
-	var b bytes.Buffer
-
-	// Create a zip archive.
-	gz := gzip.NewWriter(&b)
-	tw := tar.NewWriter(gz)
-	require.Nil(t, tw.WriteHeader(&tar.Header{
-		Name: filepath.Join(release, dockerVersionPath),
-		Size: int64(len(version)),
-	}))
-	_, err = tw.Write([]byte(version))
-	require.Nil(t, err)
-	require.Nil(t, gz.Close())
-	require.Nil(t, tw.Close())
-	require.Nil(t, ioutil.WriteFile(
-		filepath.Join(baseTmpDir, dockerBuildPath, "kubernetes.tar.gz"),
-		b.Bytes(),
-		0644,
-	))
-
-	defer cleanupTmps(t, baseTmpDir)
-
-	type args struct {
-		path        string
-		releaseKind string
-	}
-	type want struct {
-		r    string
-		rErr bool
-	}
-	cases := map[string]struct {
-		args args
-		want want
-	}{
-		"ReadVersion": {
-			args: args{
-				path:        baseTmpDir,
-				releaseKind: release,
-			},
-			want: want{
-				r:    version,
-				rErr: false,
-			},
-		},
-		"NoVersionFile": {
-			args: args{
-				path:        baseTmpDir,
-				releaseKind: "notarelease",
-			},
-			want: want{
-				rErr: true,
-			},
-		},
-	}
-
-	for name, tc := range cases {
-		t.Run(name, func(t *testing.T) {
-			res, err := ReadDockerizedVersion(tc.args.path, tc.args.releaseKind)
-			require.Equal(t, tc.want.rErr, err != nil)
-			require.Equal(t, tc.want.r, res)
-		})
-	}
-}
-
-func TestIsValidReleaseBuild(t *testing.T) {
-	type want struct {
-		r    bool
-		rErr bool
-	}
-	cases := map[string]struct {
-		build string
-		want  want
-	}{
-		"ValidRelease": {
-			build: "v1.17.6",
-			want: want{
-				r:    true,
-				rErr: false,
-			},
-		},
-		"ValidReleaseBuild": {
-			build: "v1.17.6.abcde",
-			want: want{
-				r:    true,
-				rErr: false,
-			},
-		},
-		"ValidReleaseDirty": {
-			build: "v1.17.6.abcde-dirty",
-			want: want{
-				r:    true,
-				rErr: false,
-			},
-		},
-		"NotValidRelease": {
-			build: "1.1.1",
-			want: want{
-				r:    false,
-				rErr: false,
-			},
-		},
-	}
-
-	for name, tc := range cases {
-		t.Run(name, func(t *testing.T) {
-			res, err := IsValidReleaseBuild(tc.build)
-			require.Equal(t, tc.want.rErr, err != nil)
-			require.Equal(t, tc.want.r, res)
-		})
-	}
-}
-
-func TestIsDirtyBuild(t *testing.T) {
-	cases := map[string]struct {
-		build string
-		want  bool
-	}{
-		"Dirty": {
-			build: "v1.17.6-dirty",
-			want:  true,
-		},
-		"NotDirty": {
-			build: "v1.17.6.abcde",
-			want:  false,
-		},
-	}
-
-	for name, tc := range cases {
-		t.Run(name, func(t *testing.T) {
-			res := IsDirtyBuild(tc.build)
-			require.Equal(t, tc.want, res)
-		})
-	}
-}
-
-func cleanupTmps(t *testing.T, dir ...string) {
-	for _, each := range dir {
-		require.Nil(t, os.RemoveAll(each))
-	}
-}
diff --git a/pkg/util/common_test.go b/pkg/util/common_test.go
deleted file mode 100644
index ee166756..00000000
--- a/pkg/util/common_test.go
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
-Copyright 2019 The Kubernetes 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 util
-
-import (
-	"archive/tar"
-	"bytes"
-	"compress/gzip"
-	"errors"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/require"
-)
-
-func TestMoreRecent(t *testing.T) {
-	baseTmpDir, err := ioutil.TempDir("", "")
-	require.Nil(t, err)
-
-	// Create test files.
-	testFileOne := filepath.Join(baseTmpDir, "testone.txt")
-	require.Nil(t, ioutil.WriteFile(
-		testFileOne,
-		[]byte("file-one-contents"),
-		0644,
-	))
-
-	time.Sleep(1 * time.Second)
-
-	testFileTwo := filepath.Join(baseTmpDir, "testtwo.txt")
-	require.Nil(t, ioutil.WriteFile(
-		testFileTwo,
-		[]byte("file-two-contents"),
-		0644,
-	))
-
-	notFile := filepath.Join(baseTmpDir, "noexist.txt")
-
-	defer cleanupTmp(t, baseTmpDir)
-
-	type args struct {
-		a string
-		b string
-	}
-	type want struct {
-		r   bool
-		err error
-	}
-	cases := map[string]struct {
-		args args
-		want want
-	}{
-		"AIsRecent": {
-			args: args{
-				a: testFileTwo,
-				b: testFileOne,
-			},
-			want: want{
-				r:   true,
-				err: nil,
-			},
-		},
-		"AIsNotRecent": {
-			args: args{
-				a: testFileOne,
-				b: testFileTwo,
-			},
-			want: want{
-				r:   false,
-				err: nil,
-			},
-		},
-		"ADoesNotExist": {
-			args: args{
-				a: notFile,
-				b: testFileTwo,
-			},
-			want: want{
-				r:   false,
-				err: nil,
-			},
-		},
-		"BDoesNotExist": {
-			args: args{
-				a: testFileOne,
-				b: notFile,
-			},
-			want: want{
-				r:   true,
-				err: nil,
-			},
-		},
-		"NeitherExists": {
-			args: args{
-				a: notFile,
-				b: notFile,
-			},
-			want: want{
-				r:   false,
-				err: errors.New("neither file exists"),
-			},
-		},
-	}
-
-	for name, tc := range cases {
-		t.Run(name, func(t *testing.T) {
-			more, err := MoreRecent(tc.args.a, tc.args.b)
-			require.Equal(t, tc.want.err, err)
-			require.Equal(t, tc.want.r, more)
-		})
-	}
-}
-
-func TestReadFileFromGzippedTar(t *testing.T) {
-	baseTmpDir, err := ioutil.TempDir("", "")
-	require.Nil(t, err)
-
-	testFilePath := "test.txt"
-	testFileContents := "test-file-contents"
-	testTarPath := filepath.Join(baseTmpDir, "test.tar.gz")
-
-	var b bytes.Buffer
-
-	// Create a zip archive.
-	gz := gzip.NewWriter(&b)
-	tw := tar.NewWriter(gz)
-	require.Nil(t, tw.WriteHeader(&tar.Header{
-		Name: testFilePath,
-		Size: int64(len(testFileContents)),
-	}))
-	_, err = tw.Write([]byte(testFileContents))
-	require.Nil(t, err)
-	require.Nil(t, gz.Close())
-	require.Nil(t, tw.Close())
-	require.Nil(t, ioutil.WriteFile(
-		testTarPath,
-		b.Bytes(),
-		0644,
-	))
-
-	defer cleanupTmp(t, baseTmpDir)
-
-	type args struct {
-		tarPath  string
-		filePath string
-	}
-	type want struct {
-		fileContents string
-		err          error
-	}
-	cases := map[string]struct {
-		args args
-		want want
-	}{
-		"FoundFileInTar": {
-			args: args{
-				tarPath:  testTarPath,
-				filePath: testFilePath,
-			},
-			want: want{
-				fileContents: testFileContents,
-				err:          nil,
-			},
-		},
-		"FileNotInTar": {
-			args: args{
-				tarPath:  testTarPath,
-				filePath: "badfile.txt",
-			},
-			want: want{
-				err: errors.New("unable to find file in tarball"),
-			},
-		},
-	}
-
-	for name, tc := range cases {
-		t.Run(name, func(t *testing.T) {
-			r, err := ReadFileFromGzippedTar(tc.args.tarPath, tc.args.filePath)
-			require.Equal(t, tc.want.err, err)
-			if tc.want.err == nil {
-				file, err := ioutil.ReadAll(r)
-				require.Nil(t, err)
-				require.Equal(t, tc.want.fileContents, string(file))
-			}
-		})
-	}
-}
-
-func cleanupTmp(t *testing.T, dir string) {
-	require.Nil(t, os.RemoveAll(dir))
-}