From 4ec5b7fc6955a14189d39bc67f1b61b3602e04c8 Mon Sep 17 00:00:00 2001
From: stefanprodan <stefan.prodan@gmail.com>
Date: Fri, 5 Jun 2020 18:45:31 +0300
Subject: [PATCH] Implement GitHub repository bootstrap

---
 cmd/tk/bootstrap.go        |  20 ++
 cmd/tk/bootstrap_github.go | 364 +++++++++++++++++++++++++++++++++++++
 cmd/tk/utils.go            |  15 ++
 go.sum                     |  13 ++
 4 files changed, 412 insertions(+)
 create mode 100644 cmd/tk/bootstrap.go
 create mode 100644 cmd/tk/bootstrap_github.go

diff --git a/cmd/tk/bootstrap.go b/cmd/tk/bootstrap.go
new file mode 100644
index 00000000..052186c6
--- /dev/null
+++ b/cmd/tk/bootstrap.go
@@ -0,0 +1,20 @@
+package main
+
+import (
+	"github.com/spf13/cobra"
+)
+
+var bootstrapCmd = &cobra.Command{
+	Use:   "bootstrap",
+	Short: "Bootstrap commands",
+}
+
+var (
+	bootstrapVersion string
+)
+
+func init() {
+	bootstrapCmd.PersistentFlags().StringVar(&bootstrapVersion, "version", "master", "toolkit tag or branch")
+
+	rootCmd.AddCommand(bootstrapCmd)
+}
diff --git a/cmd/tk/bootstrap_github.go b/cmd/tk/bootstrap_github.go
new file mode 100644
index 00000000..ce91202b
--- /dev/null
+++ b/cmd/tk/bootstrap_github.go
@@ -0,0 +1,364 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path"
+	"path/filepath"
+	"sigs.k8s.io/yaml"
+	"strings"
+	"time"
+
+	"github.com/go-git/go-git/v5"
+	"github.com/go-git/go-git/v5/plumbing"
+	"github.com/go-git/go-git/v5/plumbing/object"
+	"github.com/go-git/go-git/v5/plumbing/transport/http"
+	"github.com/google/go-github/v32/github"
+	"github.com/spf13/cobra"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/wait"
+
+	kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1"
+	sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
+)
+
+var bootstrapGitHubCmd = &cobra.Command{
+	Use:   "github",
+	Short: "Bootstrap GitHub repository",
+	RunE:  bootstrapGitHubCmdRun,
+}
+
+var (
+	ghOwner      string
+	ghRepository string
+)
+
+const ghTokenName = "GITHUB_TOKEN"
+
+func init() {
+	bootstrapGitHubCmd.Flags().StringVar(&ghOwner, "owner", "", "GitHub user or organization name")
+	bootstrapGitHubCmd.Flags().StringVar(&ghRepository, "repository", "", "GitHub repository name")
+	bootstrapCmd.AddCommand(bootstrapGitHubCmd)
+}
+
+func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
+	ghToken := os.Getenv(ghTokenName)
+	ghURL := fmt.Sprintf("https://github.com/%s/%s", ghOwner, ghRepository)
+	if ghToken == "" {
+		return fmt.Errorf("%s environment variable not found", ghTokenName)
+	}
+
+	if ghOwner == "" || ghRepository == "" {
+		return fmt.Errorf("owner and repository are required")
+	}
+
+	tmpDir, err := ioutil.TempDir("", namespace)
+	if err != nil {
+		return err
+	}
+	defer os.RemoveAll(tmpDir)
+
+	ctx, cancel := context.WithTimeout(context.Background(), timeout)
+	defer cancel()
+
+	// create GitHub repository if doesn't exists
+	logAction("connecting to GitHub")
+	if err := initGitHubRepository(ctx, ghOwner, ghRepository, ghToken); err != nil {
+		return err
+	}
+
+	// clone repository and checkout the master branch
+	repo, err := checkoutGitHubRepository(ctx, tmpDir, ghURL, ghToken)
+	if err != nil {
+		return err
+	}
+	w, err := repo.Worktree()
+	if err != nil {
+		return err
+	}
+	logSuccess("repository cloned")
+
+	// generate install manifests
+	logGenerate("generating manifests")
+	kDir := path.Join(tmpDir, ".kustomization")
+	if err := os.MkdirAll(kDir, os.ModePerm); err != nil {
+		return fmt.Errorf("generating manifests failed: %w", err)
+	}
+	if err := genInstallManifests(bootstrapVersion, namespace, components, kDir); err != nil {
+		return fmt.Errorf("generating manifests failed: %w", err)
+	}
+
+	manifestsDir := path.Join(tmpDir, namespace)
+	if err := os.MkdirAll(manifestsDir, os.ModePerm); err != nil {
+		return fmt.Errorf("generating manifests failed: %w", err)
+	}
+
+	manifest := path.Join(manifestsDir, "toolkit.yaml")
+	if err := buildKustomization(kDir, manifest); err != nil {
+		return fmt.Errorf("build kustomization failed: %w", err)
+	}
+
+	os.RemoveAll(kDir)
+
+	// stage install manifests
+	_, err = w.Add(fmt.Sprintf("%s/toolkit.yaml", namespace))
+	if err != nil {
+		return err
+	}
+
+	status, err := w.Status()
+	if err != nil {
+		return err
+	}
+
+	isInstall := !status.IsClean()
+
+	if isInstall {
+		// commit install manifests
+		logGenerate("pushing manifests")
+		_, err = w.Commit("Add bootstrap manifests", &git.CommitOptions{
+			Author: &object.Signature{
+				Name:  "tk",
+				Email: "tk@@users.noreply.github.com",
+				When:  time.Now(),
+			},
+		})
+		if err != nil {
+			return err
+		}
+
+		// push install manifests
+		if err := pushGitHubRepository(ctx, repo, ghToken); err != nil {
+			return err
+		}
+		logSuccess("manifests pushed")
+
+		// apply install manifests
+		logAction("installing components in %s namespace", namespace)
+		command := fmt.Sprintf("cat %s | kubectl apply -f-", manifest)
+		if _, err := utils.execCommand(ctx, ModeOS, command); err != nil {
+			return fmt.Errorf("install failed")
+		}
+		logSuccess("install completed")
+
+		// check rollout status
+		logWaiting("verifying installation")
+		for _, deployment := range components {
+			command = fmt.Sprintf("kubectl -n %s rollout status deployment %s --timeout=%s",
+				namespace, deployment, timeout.String())
+			if _, err := utils.execCommand(ctx, ModeOS, command); err != nil {
+				return fmt.Errorf("install failed")
+			} else {
+				logSuccess("%s ready", deployment)
+			}
+		}
+	}
+
+	// create or update auth secret
+	if err := generateBasicAuth(ctx, namespace, namespace, "git", ghToken); err != nil {
+		return err
+	}
+	logSuccess("authentication configured")
+
+	// generate and push source kustomization
+	if isInstall {
+		logAction("generating kustomization manifests")
+		if err := generateGitHubKustomization(ghURL, namespace, namespace, tmpDir); err != nil {
+			return err
+		}
+
+		// stage manifests
+		_, err = w.Add(fmt.Sprintf("%s", namespace))
+		if err != nil {
+			return err
+		}
+
+		// commit manifests
+		logAction("pushing kustomization manifests")
+		_, err = w.Commit("Add kustomization manifests", &git.CommitOptions{
+			Author: &object.Signature{
+				Name:  "tk",
+				Email: "tk@@users.noreply.github.com",
+				When:  time.Now(),
+			},
+		})
+		if err != nil {
+			return err
+		}
+
+		// push install manifests
+		if err := pushGitHubRepository(ctx, repo, ghToken); err != nil {
+			return err
+		}
+		logSuccess("kustomization manifests pushed")
+
+		logAction("applying kustomization manifests")
+		if err := applyGitHubKustomization(ctx, namespace, namespace, tmpDir); err != nil {
+			return err
+		}
+	}
+
+	logSuccess("bootstrap finished")
+	return nil
+}
+
+func initGitHubRepository(ctx context.Context, owner, name, token string) error {
+	isPrivate := true
+	isAutoInit := true
+	auth := github.BasicAuthTransport{
+		Username: "git",
+		Password: token,
+	}
+
+	client := github.NewClient(auth.Client())
+	_, _, err := client.Repositories.Create(ctx, owner, &github.Repository{
+		AutoInit: &isAutoInit,
+		Name:     &name,
+		Private:  &isPrivate,
+	})
+	if err != nil {
+		if !strings.Contains(err.Error(), "name already exists on this account") {
+			return fmt.Errorf("github create repository error: %w", err)
+		}
+	} else {
+		logSuccess("repository created")
+	}
+	return nil
+}
+
+func checkoutGitHubRepository(ctx context.Context, path, url, token string) (*git.Repository, error) {
+	branch := "master"
+	auth := &http.BasicAuth{
+		Username: "git",
+		Password: token,
+	}
+	repo, err := git.PlainCloneContext(ctx, path, false, &git.CloneOptions{
+		URL:           url,
+		Auth:          auth,
+		RemoteName:    git.DefaultRemoteName,
+		ReferenceName: plumbing.NewBranchReferenceName(branch),
+		SingleBranch:  true,
+		NoCheckout:    false,
+		Progress:      nil,
+		Tags:          git.NoTags,
+	})
+	if err != nil {
+		return nil, fmt.Errorf("git clone error: %w", err)
+	}
+
+	_, err = repo.Head()
+	if err != nil {
+		return nil, fmt.Errorf("git resolve HEAD error: %w", err)
+	}
+
+	return repo, nil
+}
+
+func pushGitHubRepository(ctx context.Context, repo *git.Repository, token string) error {
+	auth := &http.BasicAuth{
+		Username: "git",
+		Password: token,
+	}
+	err := repo.PushContext(ctx, &git.PushOptions{
+		Auth:     auth,
+		Progress: nil,
+	})
+	if err != nil {
+		return fmt.Errorf("git push error: %w", err)
+	}
+	return nil
+}
+
+func generateGitHubKustomization(url, name, namespace, tmpDir string) error {
+	gvk := sourcev1.GroupVersion.WithKind("GitRepository")
+	gitRepository := sourcev1.GitRepository{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       gvk.Kind,
+			APIVersion: gvk.GroupVersion().String(),
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      name,
+			Namespace: namespace,
+		},
+		Spec: sourcev1.GitRepositorySpec{
+			URL: url,
+			Interval: metav1.Duration{
+				Duration: 1 * time.Minute,
+			},
+			Reference: &sourcev1.GitRepositoryRef{
+				Branch: "master",
+			},
+			SecretRef: &corev1.LocalObjectReference{
+				Name: name,
+			},
+		},
+	}
+
+	gitData, err := yaml.Marshal(gitRepository)
+	if err != nil {
+		return err
+	}
+
+	if err := utils.writeFile(string(gitData), filepath.Join(tmpDir, namespace, "toolkit-source.yaml")); err != nil {
+		return err
+	}
+
+	gvk = kustomizev1.GroupVersion.WithKind("Kustomization")
+	emptyAPIGroup := ""
+	kustomization := kustomizev1.Kustomization{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       gvk.Kind,
+			APIVersion: gvk.GroupVersion().String(),
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      name,
+			Namespace: namespace,
+		},
+		Spec: kustomizev1.KustomizationSpec{
+			Interval: metav1.Duration{
+				Duration: 10 * time.Minute,
+			},
+			Path:  "./",
+			Prune: true,
+			SourceRef: corev1.TypedLocalObjectReference{
+				APIGroup: &emptyAPIGroup,
+				Kind:     "GitRepository",
+				Name:     name,
+			},
+		},
+	}
+
+	ksData, err := yaml.Marshal(kustomization)
+	if err != nil {
+		return err
+	}
+
+	if err := utils.writeFile(string(ksData), filepath.Join(tmpDir, namespace, "toolkit-kustomization.yaml")); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func applyGitHubKustomization(ctx context.Context, name, namespace, tmpDir string) error {
+	kubeClient, err := utils.kubeClient(kubeconfig)
+	if err != nil {
+		return err
+	}
+
+	command := fmt.Sprintf("kubectl apply -f %s", filepath.Join(tmpDir, namespace))
+	if _, err := utils.execCommand(ctx, ModeStderrOS, command); err != nil {
+		return err
+	}
+
+	logWaiting("waiting for kustomization sync")
+	if err := wait.PollImmediate(pollInterval, timeout,
+		isKustomizationReady(ctx, kubeClient, name, namespace)); err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/cmd/tk/utils.go b/cmd/tk/utils.go
index 41ae679f..aec69352 100644
--- a/cmd/tk/utils.go
+++ b/cmd/tk/utils.go
@@ -112,3 +112,18 @@ func (*Utils) kubeClient(config string) (client.Client, error) {
 
 	return kubeClient, nil
 }
+
+func (*Utils) writeFile(content, filename string) error {
+	file, err := os.Create(filename)
+	if err != nil {
+		return err
+	}
+	defer file.Close()
+
+	_, err = io.WriteString(file, content)
+	if err != nil {
+		return err
+	}
+
+	return file.Sync()
+}
diff --git a/go.sum b/go.sum
index 5291ba85..7d9cdf05 100644
--- a/go.sum
+++ b/go.sum
@@ -48,13 +48,16 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdko
 github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
 github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
 github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
+github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
 github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
 github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
+github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
 github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
 github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
@@ -169,6 +172,7 @@ github.com/fluxcd/source-controller v0.0.1-alpha.6 h1:CpSH1mA9bRjifRwmcVS98ejY2M
 github.com/fluxcd/source-controller v0.0.1-alpha.6/go.mod h1:gvU+sR6yH9kbyWZAJ9NEBQJMsJ+S3yCyOdPIhM+8FUI=
 github.com/fluxcd/source-controller v0.0.1-beta.1 h1:1lNFrAwnrZG2qWbm9/6qgDsY4F6go+6HZ4XSCmmpAiY=
 github.com/fluxcd/source-controller v0.0.1-beta.1/go.mod h1:tmscNdCxEt7+Xt2g1+bI38hMPw2leYMFAaCn4UlMGuw=
+github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
 github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
 github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -176,6 +180,7 @@ github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYis
 github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
 github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
 github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
 github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
@@ -186,6 +191,7 @@ github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
 github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
 github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM=
 github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
+github.com/go-git/go-git-fixtures/v4 v4.0.1 h1:q+IFMfLx200Q3scvt2hN79JsEzy4AmBTp/pqnefH+Bc=
 github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
 github.com/go-git/go-git/v5 v5.0.0 h1:k5RWPm4iJwYtfWoxIJy4wJX9ON7ihPeZZYC1fLYDnpg=
 github.com/go-git/go-git/v5 v5.0.0/go.mod h1:oYD8y9kWsGINPFJoLdaScGCN6dlKg23blmClfZwtUVA=
@@ -313,6 +319,10 @@ github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-github/v32 v32.0.0 h1:q74KVb22spUq0U5HqZ9VCYqQz8YRuOtL/39ZnfwO+NM=
+github.com/google/go-github/v32 v32.0.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=
+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-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
@@ -657,6 +667,8 @@ golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947
 golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
 golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
+golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -793,6 +805,7 @@ google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRn
 google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-- 
GitLab