From 5209a8170d032cbdb2a560369bee6788e6b2c282 Mon Sep 17 00:00:00 2001 From: Ben Clayton Date: Fri, 30 Jul 2021 16:20:46 +0000 Subject: [PATCH] tools: Add roll-release roll-release is a tool to synchronize Dawn's release branches with Tint. roll-release will scan the release branches of both Dawn and Tint, and will: * Create new Gerrit changes to roll new release branch changes from Tint into Dawn. * Find and create missing Tint release branches, using the git hash of Tint in the DEPS file of the Dawn release branch. Change-Id: I009aedc826d604f7fda10769ea94fee931a56dcc Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/60341 Kokoro: Kokoro Commit-Queue: Ben Clayton Reviewed-by: David Neto --- tools/roll-release | 33 +++ tools/src/cmd/roll-release/main.go | 377 +++++++++++++++++++++++++++++ tools/src/gerrit/gerrit.go | 25 +- tools/src/go.mod | 1 + tools/src/go.sum | 84 ++++++- 5 files changed, 507 insertions(+), 13 deletions(-) create mode 100755 tools/roll-release create mode 100644 tools/src/cmd/roll-release/main.go diff --git a/tools/roll-release b/tools/roll-release new file mode 100755 index 0000000000..2f39f11bf3 --- /dev/null +++ b/tools/roll-release @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# Copyright 2021 The Tint 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. + +set -e # Fail on any error. + +if [ ! -x "$(which go)" ] ; then + echo "error: go needs to be on \$PATH to use $0" + exit 1 +fi + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )" +ROOT_DIR="$( cd "${SCRIPT_DIR}/.." >/dev/null 2>&1 && pwd )" +BINARY="${SCRIPT_DIR}/bin/roll-release" + +# Rebuild the binary. +# Note, go caches build artifacts, so this is quick for repeat calls +pushd "${SCRIPT_DIR}/src/cmd/roll-release" > /dev/null + go build -o "${BINARY}" main.go +popd > /dev/null + +"${BINARY}" "$@" diff --git a/tools/src/cmd/roll-release/main.go b/tools/src/cmd/roll-release/main.go new file mode 100644 index 0000000000..1169d3f496 --- /dev/null +++ b/tools/src/cmd/roll-release/main.go @@ -0,0 +1,377 @@ +// Copyright 2021 The Tint 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. + +// roll-release is a tool to roll changes in Tint release branches into Dawn, +// and create new Tint release branches. +// +// See showUsage() for more information +package main + +import ( + "encoding/hex" + "flag" + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + "os/exec" + "path/filepath" + "regexp" + "sort" + "strconv" + "strings" + + "dawn.googlesource.com/tint/tools/src/gerrit" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/config" + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/transport" + git_http "github.com/go-git/go-git/v5/plumbing/transport/http" + "github.com/go-git/go-git/v5/storage/memory" +) + +const ( + toolName = "roll-release" + gitCommitMsgHookURL = "https://gerrit-review.googlesource.com/tools/hooks/commit-msg" + tintURL = "https://dawn.googlesource.com/tint" + dawnURL = "https://dawn.googlesource.com/dawn" + tintSubdirInDawn = "third_party/tint" + branchPrefix = "chromium/" + branchLegacyCutoff = 4590 // Branch numbers < than this are ignored +) + +type branches = map[string]plumbing.Hash + +func main() { + if err := run(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func showUsage() { + fmt.Printf(` +%[1]v is a tool to synchronize Dawn's release branches with Tint. + +%[1]v will scan the release branches of both Dawn and Tint, and will: +* Create new Gerrit changes to roll new release branch changes from Tint into + Dawn. +* Find and create missing Tint release branches, using the git hash of Tint in + the DEPS file of the Dawn release branch. + +%[1]v does not depend on the current state of the Tint checkout, nor will it +make any changes to the local checkout. + +usage: + %[1]v +`, toolName) + flag.PrintDefaults() + fmt.Println(``) + os.Exit(1) +} + +func run() error { + dry := false + flag.BoolVar(&dry, "dry", false, "perform a dry run") + flag.Usage = showUsage + flag.Parse() + + // This tool uses a mix of 'go-git' and the command line git. + // go-git has the benefit of keeping the git information entirely in-memory, + // but has issues working with chromiums tools and gerrit. + // To create new release branches in Tint, we use 'go-git', so we need to + // dig out the username and password. + var auth transport.AuthMethod + if user, pass := gerrit.LoadCredentials(); user != "" { + auth = &git_http.BasicAuth{Username: user, Password: pass} + } else { + return fmt.Errorf("failed to fetch git credentials") + } + + // Using in-memory repos, find all the tint and dawn release branches + log.Println("Inspecting dawn and tint release branches...") + var tint, dawn *git.Repository + var tintBranches, dawnBranches branches + for _, r := range []struct { + name string + url string + repo **git.Repository + branches *branches + }{ + {"tint", tintURL, &tint, &tintBranches}, + {"dawn", dawnURL, &dawn, &dawnBranches}, + } { + repo, err := git.Init(memory.NewStorage(), nil) + if err != nil { + return fmt.Errorf("failed to create %v in-memory repo: %w", r.name, err) + } + remote, err := repo.CreateRemote(&config.RemoteConfig{ + Name: "origin", + URLs: []string{r.url}, + }) + if err != nil { + return fmt.Errorf("failed to add %v remote: %w", r.name, err) + } + refs, err := remote.List(&git.ListOptions{}) + if err != nil { + return fmt.Errorf("failed to fetch %v branches: %w", r.name, err) + } + branches := branches{} + for _, ref := range refs { + if !ref.Name().IsBranch() { + continue + } + name := ref.Name().Short() + if strings.HasPrefix(name, branchPrefix) { + branches[name] = ref.Hash() + } + } + *r.repo = repo + *r.branches = branches + } + + // Find the release branches found in dawn, which are missing in tint. + // Find the release branches in dawn that are behind HEAD of the + // corresponding branch in tint. + log.Println("Scanning dawn DEPS...") + type roll struct { + from, to plumbing.Hash + } + tintBranchesToCreate := branches{} // branch name -> tint hash + dawnBranchesToRoll := map[string]roll{} // branch name -> roll + for name := range dawnBranches { + if isBranchBefore(name, branchLegacyCutoff) { + continue // Branch is earlier than we're interested in + } + deps, err := getDEPS(dawn, name) + if err != nil { + return err + } + depsTintHash, err := parseTintFromDEPS(deps) + if err != nil { + return err + } + + if tintBranchHash, found := tintBranches[name]; found { + if tintBranchHash != depsTintHash { + dawnBranchesToRoll[name] = roll{from: depsTintHash, to: tintBranchHash} + } + } else { + tintBranchesToCreate[name] = depsTintHash + } + } + + if dry { + tasks := []string{} + for name, sha := range tintBranchesToCreate { + tasks = append(tasks, fmt.Sprintf("Create Tint release branch '%v' @ %v", name, sha)) + } + for name, roll := range dawnBranchesToRoll { + tasks = append(tasks, fmt.Sprintf("Roll Dawn release branch '%v' from %v to %v", name, roll.from, roll.to)) + } + sort.Strings(tasks) + fmt.Printf("%v was run with --dry. Run without --dry to:\n", toolName) + for _, task := range tasks { + fmt.Println(" >", task) + } + return nil + } + + didSomething := false + if n := len(tintBranchesToCreate); n > 0 { + log.Println("Creating", n, "release branches in tint...") + + // In order to create the branches, we need to know what the DEPS + // hashes are referring to. Perform an in-memory fetch of tint's main + // branch. + if _, err := fetch(tint, "main"); err != nil { + return err + } + + for name, sha := range tintBranchesToCreate { + log.Println("Creating branch", name, "@", sha, "...") + + // Pushing a branch by SHA does not work, so we need to create a + // local branch first. See https://github.com/go-git/go-git/issues/105 + src := plumbing.NewHashReference(plumbing.NewBranchReferenceName(name), sha) + if err := tint.Storer.SetReference(src); err != nil { + return fmt.Errorf("failed to create temporary branch: %w", err) + } + + dst := plumbing.NewBranchReferenceName(name) + refspec := config.RefSpec(src.Name() + ":" + dst) + err := tint.Push(&git.PushOptions{ + RefSpecs: []config.RefSpec{refspec}, + Progress: os.Stdout, + Auth: auth, + }) + if err != nil && err != git.NoErrAlreadyUpToDate { + return fmt.Errorf("failed to push branch: %w", err) + } + } + didSomething = true + } + + if n := len(dawnBranchesToRoll); n > 0 { + log.Println("Rolling", n, "release branches in dawn...") + + // Fetch the change-id hook script + commitMsgHookResp, err := http.Get(gitCommitMsgHookURL) + if err != nil { + return fmt.Errorf("failed to fetch the git commit message hook from '%v': %w", gitCommitMsgHookURL, err) + } + commitMsgHook, err := ioutil.ReadAll(commitMsgHookResp.Body) + if err != nil { + return fmt.Errorf("failed to fetch the git commit message hook from '%v': %w", gitCommitMsgHookURL, err) + } + + for name, roll := range dawnBranchesToRoll { + log.Println("Rolling branch", name, "from tint", roll.from, "to", roll.to, "...") + dir, err := ioutil.TempDir("", "dawn-roll") + if err != nil { + return err + } + defer os.RemoveAll(dir) + + // Clone dawn into dir + if err := call(dir, "git", "clone", "--depth", "1", "-b", name, dawnURL, "."); err != nil { + return fmt.Errorf("failed to clone dawn branch %v: %w", name, err) + } + + // Copy the Change-Id hook into the dawn directory + gitHooksDir := filepath.Join(dir, ".git", "hooks") + if err := os.MkdirAll(gitHooksDir, 0777); err != nil { + return fmt.Errorf("failed create commit hooks directory: %w", err) + } + if err := ioutil.WriteFile(filepath.Join(gitHooksDir, "commit-msg"), commitMsgHook, 0777); err != nil { + return fmt.Errorf("failed install commit message hook: %w", err) + } + + // Clone tint into third_party directory of dawn + tintDir := filepath.Join(dir, tintSubdirInDawn) + if err := os.MkdirAll(tintDir, 0777); err != nil { + return fmt.Errorf("failed to create directory %v: %w", tintDir, err) + } + if err := call(tintDir, "git", "clone", "-b", name, tintURL, "."); err != nil { + return fmt.Errorf("failed to clone tint hash %v: %w", roll.from, err) + } + + // Checkout tint at roll.from + if err := call(tintDir, "git", "checkout", roll.from); err != nil { + return fmt.Errorf("failed to checkout tint at %v: %w", roll.from, err) + } + + // Use roll-dep to roll tint to roll.to + if err := call(dir, "roll-dep", "--ignore-dirty-tree", fmt.Sprintf("--roll-to=%s", roll.to), tintSubdirInDawn); err != nil { + return err + } + + // Push the change to gerrit + if err := call(dir, "git", "push", "origin", "HEAD:refs/for/"+name); err != nil { + return fmt.Errorf("failed to push roll to gerrit: %w", err) + } + } + didSomething = true + } + + if !didSomething { + log.Println("Everything up to date") + } else { + log.Println("Done") + } + return nil +} + +// returns true if the branch name contains a branch number less than 'version' +func isBranchBefore(name string, version int) bool { + n, err := strconv.Atoi(strings.TrimPrefix(name, branchPrefix)) + if err != nil { + return false + } + return n < version +} + +// call invokes the executable 'exe' with the given arguments in the working +// directory 'dir'. +func call(dir, exe string, args ...interface{}) error { + s := make([]string, len(args)) + for i, a := range args { + s[i] = fmt.Sprint(a) + } + cmd := exec.Command(exe, s...) + cmd.Dir = dir + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return fmt.Errorf("%v returned %v", cmd, err) + } + return nil +} + +// getDEPS returns the content of the DEPS file for the given branch. +func getDEPS(r *git.Repository, branch string) (string, error) { + hash, err := fetch(r, branch) + if err != nil { + return "", err + } + commit, err := r.CommitObject(hash) + if err != nil { + return "", fmt.Errorf("failed to fetch commit: %w", err) + } + tree, err := commit.Tree() + if err != nil { + return "", fmt.Errorf("failed to fetch tree: %w", err) + } + deps, err := tree.File("DEPS") + if err != nil { + return "", fmt.Errorf("failed to find DEPS: %w", err) + } + return deps.Contents() +} + +// fetch performs a git-fetch of the given branch into 'r', returning the +// fetched branch's hash. +func fetch(r *git.Repository, branch string) (plumbing.Hash, error) { + src := plumbing.NewBranchReferenceName(branch) + dst := plumbing.NewRemoteReferenceName("origin", branch) + err := r.Fetch(&git.FetchOptions{ + RefSpecs: []config.RefSpec{config.RefSpec("+" + src + ":" + dst)}, + }) + if err != nil { + return plumbing.Hash{}, fmt.Errorf("failed to fetch branch %v: %w", branch, err) + } + ref, err := r.Reference(plumbing.ReferenceName(dst), true) + if err != nil { + return plumbing.Hash{}, fmt.Errorf("failed to resolve branch %v: %w", branch, err) + } + return ref.Hash(), nil +} + +var reDEPSTintVersion = regexp.MustCompile("tint@([0-9a-fA-F]*)") + +// parseTintFromDEPS returns the tint hash from the DEPS file content 'deps' +func parseTintFromDEPS(deps string) (plumbing.Hash, error) { + m := reDEPSTintVersion.FindStringSubmatch(deps) + if len(m) != 2 { + return plumbing.Hash{}, fmt.Errorf("failed to find tint hash in DEPS") + } + b, err := hex.DecodeString(m[1]) + if err != nil { + return plumbing.Hash{}, fmt.Errorf("failed to find parse tint hash in DEPS: %w", err) + } + var h plumbing.Hash + copy(h[:], b) + return h, nil +} diff --git a/tools/src/gerrit/gerrit.go b/tools/src/gerrit/gerrit.go index 0485bbd655..1c0e2a5de0 100644 --- a/tools/src/gerrit/gerrit.go +++ b/tools/src/gerrit/gerrit.go @@ -38,22 +38,27 @@ type Config struct { Password string } +func LoadCredentials() (user, pass string) { + cookiesFile := os.Getenv("HOME") + "/.gitcookies" + if cookies, err := ioutil.ReadFile(cookiesFile); err == nil { + re := regexp.MustCompile(`dawn-review.googlesource.com\s+(?:FALSE|TRUE)[\s/]+(?:FALSE|TRUE)\s+[0-9]+\s+.\s+(.*)=(.*)`) + match := re.FindStringSubmatch(string(cookies)) + if len(match) == 3 { + return match[1], match[2] + } + } + return "", "" +} + func New(cfg Config) (*G, error) { client, err := gerrit.NewClient(URL, nil) if err != nil { - return nil, fmt.Errorf("Couldn't create gerrit client: %w", err) + return nil, fmt.Errorf("couldn't create gerrit client: %w", err) } user, pass := cfg.Username, cfg.Password if user == "" { - cookiesFile := os.Getenv("HOME") + "/.gitcookies" - if cookies, err := ioutil.ReadFile(cookiesFile); err == nil { - re := regexp.MustCompile(`dawn-review.googlesource.com\s+(?:FALSE|TRUE)[\s/]+(?:FALSE|TRUE)\s+[0-9]+\s+.\s+(.*)=(.*)`) - match := re.FindStringSubmatch(string(cookies)) - if len(match) == 3 { - user, pass = match[1], match[2] - } - } + user, pass = LoadCredentials() } if user != "" { @@ -73,7 +78,7 @@ func (g *G) QueryChanges(queryParts ...string) (changes []gerrit.ChangeInfo, que }) if err != nil { if !g.authenticated { - err = fmt.Errorf(`Query failed, possibly because of authentication. + err = fmt.Errorf(`query failed, possibly because of authentication. See https://dawn-review.googlesource.com/new-password for obtaining a username and password which can be provided with --gerrit-user and --gerrit-pass. %w`, err) diff --git a/tools/src/go.mod b/tools/src/go.mod index 0183a32447..d3c85af2cc 100644 --- a/tools/src/go.mod +++ b/tools/src/go.mod @@ -5,6 +5,7 @@ go 1.16 require ( github.com/andygrunwald/go-gerrit v0.0.0-20210709065208-9d38b0be0268 github.com/fatih/color v1.10.0 + github.com/go-git/go-git/v5 v5.4.2 github.com/sergi/go-diff v1.2.0 golang.org/x/net v0.0.0-20210614182718-04defd469f4e ) diff --git a/tools/src/go.sum b/tools/src/go.sum index a7f59b9e3e..14f9171b67 100644 --- a/tools/src/go.sum +++ b/tools/src/go.sum @@ -1,41 +1,119 @@ +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= +github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= +github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= +github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/andygrunwald/go-gerrit v0.0.0-20210709065208-9d38b0be0268 h1:7gokoTWteZhP1t2f0OzrFFXlyL8o0+b0r4ZaRV9PXOs= github.com/andygrunwald/go-gerrit v0.0.0-20210709065208-9d38b0be0268/go.mod h1:aqcjwEnmLLSalFNYR0p2ttnEXOVVRctIzsUMHbEcruU= +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/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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +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-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.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= +github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8= +github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= +github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= +github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= +github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= +github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79 h1:RX8C8PRZc2hTIod4ds8ij+/4RQX3AqhYj3uOHmyaz4E= +golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=