Merge remote-tracking branch 'origin/trunk' into relative-path
This commit is contained in:
commit
4996ba2ba4
355 changed files with 13818 additions and 3076 deletions
3
.github/CONTRIBUTING.md
vendored
3
.github/CONTRIBUTING.md
vendored
|
|
@ -23,8 +23,7 @@ Please avoid:
|
|||
## Building the project
|
||||
|
||||
Prerequisites:
|
||||
- Go 1.13+ for building the binary
|
||||
- Go 1.15+ for running the test suite
|
||||
- Go 1.16+
|
||||
|
||||
Build with:
|
||||
* Unix-like systems: `make`
|
||||
|
|
|
|||
15
.github/dependabot.yml
vendored
Normal file
15
.github/dependabot.yml
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: gomod
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
ignore:
|
||||
- dependency-name: "*"
|
||||
update-types:
|
||||
- version-update:semver-minor
|
||||
- version-update:semver-major
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
5
.github/workflows/codeql.yml
vendored
5
.github/workflows/codeql.yml
vendored
|
|
@ -2,7 +2,11 @@ name: Code Scanning
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: [trunk]
|
||||
pull_request:
|
||||
branches: [trunk]
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
schedule:
|
||||
- cron: "0 0 * * 0"
|
||||
|
||||
|
|
@ -18,6 +22,7 @@ jobs:
|
|||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: go
|
||||
queries: security-and-quality
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
|
|
|
|||
21
.github/workflows/go.yml
vendored
21
.github/workflows/go.yml
vendored
|
|
@ -9,10 +9,10 @@ jobs:
|
|||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Set up Go 1.15
|
||||
- name: Set up Go 1.16
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.15
|
||||
go-version: 1.16
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
|
|
@ -25,20 +25,3 @@ jobs:
|
|||
|
||||
- name: Build
|
||||
run: go build -v ./cmd/gh
|
||||
|
||||
build-minimum:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Set up Go 1.13
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.13
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
CGO_ENABLED: '0'
|
||||
run: go build -v ./cmd/gh
|
||||
|
|
|
|||
4
.github/workflows/lint.yml
vendored
4
.github/workflows/lint.yml
vendored
|
|
@ -16,10 +16,10 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Set up Go 1.15
|
||||
- name: Set up Go 1.16
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.15
|
||||
go-version: 1.16
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
|
|
|
|||
3
.github/workflows/prauto.yml
vendored
3
.github/workflows/prauto.yml
vendored
|
|
@ -15,6 +15,7 @@ jobs:
|
|||
PRNUM: ${{ github.event.pull_request.number }}
|
||||
PRHEAD: ${{ github.event.pull_request.head.label }}
|
||||
PRAUTHOR: ${{ github.event.pull_request.user.login }}
|
||||
PR_AUTHOR_TYPE: ${{ github.event.pull_request.user.type }}
|
||||
if: "!github.event.pull_request.draft"
|
||||
run: |
|
||||
commentPR () {
|
||||
|
|
@ -42,7 +43,7 @@ jobs:
|
|||
' -f colID="$(colID "Needs review")" -f prID="$PRID"
|
||||
}
|
||||
|
||||
if gh api orgs/cli/public_members/$PRAUTHOR --silent 2>/dev/null
|
||||
if [ "$PR_AUTHOR_TYPE" = "Bot" ] || gh api orgs/cli/public_members/$PRAUTHOR --silent 2>/dev/null
|
||||
then
|
||||
if ! errtext="$(addToBoard 2>&1)"
|
||||
then
|
||||
|
|
|
|||
8
.github/workflows/releases.yml
vendored
8
.github/workflows/releases.yml
vendored
|
|
@ -11,10 +11,10 @@ jobs:
|
|||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up Go 1.15
|
||||
- name: Set up Go 1.16
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.15
|
||||
go-version: 1.16
|
||||
- name: Generate changelog
|
||||
run: |
|
||||
echo "GORELEASER_CURRENT_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
|
|
@ -23,7 +23,7 @@ jobs:
|
|||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
with:
|
||||
version: v0.172.1 # pinning to prevent breaking on latest
|
||||
version: v0.174.1
|
||||
args: release --release-notes=CHANGELOG.md
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
|
|
@ -33,7 +33,7 @@ jobs:
|
|||
repository: github/cli.github.com
|
||||
path: site
|
||||
fetch-depth: 0
|
||||
token: ${{secrets.SITE_GITHUB_TOKEN}}
|
||||
ssh-key: ${{secrets.SITE_SSH_KEY}}
|
||||
- name: Update site man pages
|
||||
env:
|
||||
GIT_COMMITTER_NAME: cli automation
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ builds:
|
|||
binary: bin/gh
|
||||
main: ./cmd/gh
|
||||
ldflags:
|
||||
- -s -w -X github.com/cli/cli/internal/build.Version={{.Version}} -X github.com/cli/cli/internal/build.Date={{time "2006-01-02"}}
|
||||
- -s -w -X github.com/cli/cli/v2/internal/build.Version={{.Version}} -X github.com/cli/cli/v2/internal/build.Date={{time "2006-01-02"}}
|
||||
- -X main.updaterEnabled=cli/cli
|
||||
id: macos
|
||||
goos: [darwin]
|
||||
|
|
@ -57,7 +57,7 @@ nfpms:
|
|||
- license: MIT
|
||||
maintainer: GitHub
|
||||
homepage: https://github.com/cli/cli
|
||||
bindir: /usr
|
||||
bindir: /usr/bin
|
||||
dependencies:
|
||||
- git
|
||||
description: GitHub’s official command line tool.
|
||||
|
|
@ -65,5 +65,5 @@ nfpms:
|
|||
- deb
|
||||
- rpm
|
||||
contents:
|
||||
- src: "/share/man/man1/gh*.1"
|
||||
- src: "./share/man/man1/gh*.1"
|
||||
dst: "/usr/share/man/man1"
|
||||
|
|
|
|||
5
CODEOWNERS
Normal file
5
CODEOWNERS
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
* @cli/code-reviewers
|
||||
|
||||
pkg/cmd/codespace/* @cli/codespaces
|
||||
pkg/liveshare/* @cli/codespaces
|
||||
internal/codespaces/* @cli/codespaces
|
||||
19
README.md
19
README.md
|
|
@ -19,7 +19,7 @@ If anything feels off, or if you feel that some functionality is missing, please
|
|||
|
||||
### macOS
|
||||
|
||||
`gh` is available via [Homebrew][], [MacPorts][], [Conda][], and as a downloadable binary from the [releases page][].
|
||||
`gh` is available via [Homebrew][], [MacPorts][], [Conda][], [Spack][], and as a downloadable binary from the [releases page][].
|
||||
|
||||
#### Homebrew
|
||||
|
||||
|
|
@ -41,21 +41,27 @@ If anything feels off, or if you feel that some functionality is missing, please
|
|||
|
||||
Additional Conda installation options available on the [gh-feedstock page](https://github.com/conda-forge/gh-feedstock#installing-gh).
|
||||
|
||||
### Linux
|
||||
#### Spack
|
||||
|
||||
`gh` is available via [Homebrew](#homebrew), [Conda](#Conda), and as downloadable binaries from the [releases page][].
|
||||
| Install: | Upgrade: |
|
||||
| ------------------ | ---------------------------------------- |
|
||||
| `spack install gh` | `spack uninstall gh && spack install gh` |
|
||||
|
||||
For more information and distro-specific instructions, see the [Linux installation docs](./docs/install_linux.md).
|
||||
### Linux & BSD
|
||||
|
||||
`gh` is available via [Homebrew](#homebrew), [Conda](#conda), [Spack](#spack), and as downloadable binaries from the [releases page][].
|
||||
|
||||
For instructions on specific distributions and package managers, see [Linux & BSD installation](./docs/install_linux.md).
|
||||
|
||||
### Windows
|
||||
|
||||
`gh` is available via [WinGet][], [scoop][], [Chocolatey][], [Conda](#Conda), and as downloadable MSI.
|
||||
`gh` is available via [WinGet][], [scoop][], [Chocolatey][], [Conda](#conda), and as downloadable MSI.
|
||||
|
||||
#### WinGet
|
||||
|
||||
| Install: | Upgrade: |
|
||||
| ------------------- | --------------------|
|
||||
| `winget install gh` | `winget upgrade gh` |
|
||||
| `winget install --id GitHub.cli` | `winget upgrade --id GitHub.cli` |
|
||||
|
||||
#### scoop
|
||||
|
||||
|
|
@ -99,6 +105,7 @@ tool. Check out our [more detailed explanation][gh-vs-hub] to learn more.
|
|||
[scoop]: https://scoop.sh
|
||||
[Chocolatey]: https://chocolatey.org
|
||||
[Conda]: https://docs.conda.io/en/latest/
|
||||
[Spack]: https://spack.io
|
||||
[releases page]: https://github.com/cli/cli/releases/latest
|
||||
[hub]: https://github.com/github/hub
|
||||
[contributing]: ./.github/CONTRIBUTING.md
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import (
|
|||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/cli/cli/internal/ghinstance"
|
||||
"github.com/cli/cli/v2/internal/ghinstance"
|
||||
"github.com/henvic/httpretty"
|
||||
"github.com/shurcooL/graphql"
|
||||
)
|
||||
|
|
@ -123,8 +123,8 @@ type graphQLResponse struct {
|
|||
// GraphQLError is a single error returned in a GraphQL response
|
||||
type GraphQLError struct {
|
||||
Type string
|
||||
Path []string
|
||||
Message string
|
||||
// Path []interface // mixed strings and numbers
|
||||
}
|
||||
|
||||
// GraphQLErrorResponse contains errors returned in a GraphQL response
|
||||
|
|
@ -285,7 +285,10 @@ func HandleHTTPError(resp *http.Response) error {
|
|||
return httpError
|
||||
}
|
||||
|
||||
messages := []string{parsedBody.Message}
|
||||
var messages []string
|
||||
if parsedBody.Message != "" {
|
||||
messages = append(messages, parsedBody.Message)
|
||||
}
|
||||
for _, raw := range parsedBody.Errors {
|
||||
switch raw[0] {
|
||||
case '"':
|
||||
|
|
@ -297,7 +300,7 @@ func HandleHTTPError(resp *http.Response) error {
|
|||
var errInfo HTTPErrorItem
|
||||
_ = json.Unmarshal(raw, &errInfo)
|
||||
msg := errInfo.Message
|
||||
if errInfo.Code != "custom" {
|
||||
if errInfo.Code != "" && errInfo.Code != "custom" {
|
||||
msg = fmt.Sprintf("%s.%s %s", errInfo.Resource, errInfo.Field, errorCodeToMessage(errInfo.Code))
|
||||
}
|
||||
if msg != "" {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/pkg/httpmock"
|
||||
"github.com/cli/cli/v2/pkg/httpmock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
|
@ -129,3 +129,20 @@ func TestRESTError(t *testing.T) {
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleHTTPError_GraphQL502(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "https://api.github.com/user", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp := &http.Response{
|
||||
Request: req,
|
||||
StatusCode: 502,
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString(`{ "data": null, "errors": [{ "message": "Something went wrong" }] }`)),
|
||||
Header: map[string][]string{"Content-Type": {"application/json"}},
|
||||
}
|
||||
err = HandleHTTPError(resp)
|
||||
if err == nil || err.Error() != "HTTP 502: Something went wrong (https://api.github.com/user)" {
|
||||
t.Errorf("got error: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/shurcooL/githubv4"
|
||||
)
|
||||
|
||||
|
|
@ -16,8 +16,9 @@ type IssuesPayload struct {
|
|||
}
|
||||
|
||||
type IssuesAndTotalCount struct {
|
||||
Issues []Issue
|
||||
TotalCount int
|
||||
Issues []Issue
|
||||
TotalCount int
|
||||
SearchCapped bool
|
||||
}
|
||||
|
||||
type Issue struct {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package api
|
|||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/shurcooL/githubv4"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,16 +2,14 @@ package api
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cli/cli/internal/ghinstance"
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/cli/cli/pkg/set"
|
||||
"github.com/cli/cli/v2/internal/ghinstance"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/set"
|
||||
"github.com/shurcooL/githubv4"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
|
@ -26,6 +24,7 @@ type PullRequestsPayload struct {
|
|||
type PullRequestAndTotalCount struct {
|
||||
TotalCount int
|
||||
PullRequests []PullRequest
|
||||
SearchCapped bool
|
||||
}
|
||||
|
||||
type PullRequest struct {
|
||||
|
|
@ -266,30 +265,6 @@ func (pr *PullRequest) DisplayableReviews() PullRequestReviews {
|
|||
return PullRequestReviews{Nodes: published, TotalCount: len(published)}
|
||||
}
|
||||
|
||||
func (c Client) PullRequestDiff(baseRepo ghrepo.Interface, prNumber int) (io.ReadCloser, error) {
|
||||
url := fmt.Sprintf("%srepos/%s/pulls/%d",
|
||||
ghinstance.RESTPrefix(baseRepo.RepoHost()), ghrepo.FullName(baseRepo), prNumber)
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Accept", "application/vnd.github.v3.diff; charset=utf-8")
|
||||
|
||||
resp, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode == 404 {
|
||||
return nil, errors.New("pull request not found")
|
||||
} else if resp.StatusCode != 200 {
|
||||
return nil, HandleHTTPError(resp)
|
||||
}
|
||||
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
type pullRequestFeature struct {
|
||||
HasReviewDecision bool
|
||||
HasStatusCheckRollup bool
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import (
|
|||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/shurcooL/githubv4"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/cli/cli/pkg/httpmock"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/httpmock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/shurcooL/githubv4"
|
||||
)
|
||||
|
||||
|
|
@ -230,6 +230,25 @@ func (r Repository) ViewerCanTriage() bool {
|
|||
}
|
||||
}
|
||||
|
||||
func FetchRepository(client *Client, repo ghrepo.Interface, fields []string) (*Repository, error) {
|
||||
query := fmt.Sprintf(`query RepositoryInfo($owner: String!, $name: String!) {
|
||||
repository(owner: $owner, name: $name) {%s}
|
||||
}`, RepositoryGraphQL(fields))
|
||||
|
||||
variables := map[string]interface{}{
|
||||
"owner": repo.RepoOwner(),
|
||||
"name": repo.RepoName(),
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Repository Repository
|
||||
}
|
||||
if err := client.GraphQL(repo.RepoHost(), query, variables, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return InitRepoHostname(&result.Repository, repo.RepoHost()), nil
|
||||
}
|
||||
|
||||
func GitHubRepo(client *Client, repo ghrepo.Interface) (*Repository, error) {
|
||||
query := `
|
||||
fragment repo on Repository {
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/cli/cli/pkg/httpmock"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/httpmock"
|
||||
)
|
||||
|
||||
func Test_RepoMetadata(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ import (
|
|||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/cli/cli/internal/docs"
|
||||
"github.com/cli/cli/pkg/cmd/root"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/internal/docs"
|
||||
"github.com/cli/cli/v2/pkg/cmd/root"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -13,18 +13,18 @@ import (
|
|||
|
||||
surveyCore "github.com/AlecAivazis/survey/v2/core"
|
||||
"github.com/AlecAivazis/survey/v2/terminal"
|
||||
"github.com/cli/cli/api"
|
||||
"github.com/cli/cli/internal/build"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/internal/ghinstance"
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/cli/cli/internal/run"
|
||||
"github.com/cli/cli/internal/update"
|
||||
"github.com/cli/cli/pkg/cmd/alias/expand"
|
||||
"github.com/cli/cli/pkg/cmd/factory"
|
||||
"github.com/cli/cli/pkg/cmd/root"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/utils"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/build"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghinstance"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/run"
|
||||
"github.com/cli/cli/v2/internal/update"
|
||||
"github.com/cli/cli/v2/pkg/cmd/alias/expand"
|
||||
"github.com/cli/cli/v2/pkg/cmd/factory"
|
||||
"github.com/cli/cli/v2/pkg/cmd/root"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/cli/safeexec"
|
||||
"github.com/mattn/go-colorable"
|
||||
"github.com/mgutz/ansi"
|
||||
|
|
@ -61,6 +61,10 @@ func mainRun() exitCode {
|
|||
|
||||
cmdFactory := factory.New(buildVersion)
|
||||
stderr := cmdFactory.IOStreams.ErrOut
|
||||
|
||||
if spec := os.Getenv("GH_FORCE_TTY"); spec != "" {
|
||||
cmdFactory.IOStreams.ForceTerminal(spec)
|
||||
}
|
||||
if !cmdFactory.IOStreams.ColorEnabled() {
|
||||
surveyCore.DisableColor = true
|
||||
} else {
|
||||
|
|
@ -102,12 +106,17 @@ func mainRun() exitCode {
|
|||
expandedArgs = os.Args[1:]
|
||||
}
|
||||
|
||||
cmd, _, err := rootCmd.Traverse(expandedArgs)
|
||||
if err != nil || cmd == rootCmd {
|
||||
// translate `gh help <command>` to `gh <command> --help` for extensions
|
||||
if len(expandedArgs) == 2 && expandedArgs[0] == "help" && !hasCommand(rootCmd, expandedArgs[1:]) {
|
||||
expandedArgs = []string{expandedArgs[1], "--help"}
|
||||
}
|
||||
|
||||
if !hasCommand(rootCmd, expandedArgs) {
|
||||
originalArgs := expandedArgs
|
||||
isShell := false
|
||||
|
||||
expandedArgs, isShell, err = expand.ExpandAlias(cfg, os.Args, nil)
|
||||
argsForExpansion := append([]string{"gh"}, expandedArgs...)
|
||||
expandedArgs, isShell, err = expand.ExpandAlias(cfg, argsForExpansion, nil)
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "failed to process aliases: %s\n", err)
|
||||
return exitError
|
||||
|
|
@ -141,7 +150,7 @@ func mainRun() exitCode {
|
|||
}
|
||||
|
||||
return exitOK
|
||||
} else if c, _, err := rootCmd.Traverse(expandedArgs); err == nil && c == rootCmd && len(expandedArgs) > 0 {
|
||||
} else if len(expandedArgs) > 0 && !hasCommand(rootCmd, expandedArgs) {
|
||||
extensionManager := cmdFactory.ExtensionManager
|
||||
if found, err := extensionManager.Dispatch(expandedArgs, os.Stdin, os.Stdout, os.Stderr); err != nil {
|
||||
var execError *exec.ExitError
|
||||
|
|
@ -166,7 +175,7 @@ func mainRun() exitCode {
|
|||
}
|
||||
}
|
||||
}
|
||||
for _, ext := range cmdFactory.ExtensionManager.List() {
|
||||
for _, ext := range cmdFactory.ExtensionManager.List(false) {
|
||||
if strings.HasPrefix(ext.Name(), toComplete) {
|
||||
results = append(results, ext.Name())
|
||||
}
|
||||
|
|
@ -208,13 +217,15 @@ func mainRun() exitCode {
|
|||
|
||||
if strings.Contains(err.Error(), "Incorrect function") {
|
||||
fmt.Fprintln(stderr, "You appear to be running in MinTTY without pseudo terminal support.")
|
||||
fmt.Fprintln(stderr, "To learn about workarounds for this error, run: gh help mintty")
|
||||
fmt.Fprintln(stderr, "To learn about workarounds for this error, run: gh help mintty")
|
||||
return exitError
|
||||
}
|
||||
|
||||
var httpErr api.HTTPError
|
||||
if errors.As(err, &httpErr) && httpErr.StatusCode == 401 {
|
||||
fmt.Fprintln(stderr, "hint: try authenticating with `gh auth login`")
|
||||
fmt.Fprintln(stderr, "Try authenticating with: gh auth login")
|
||||
} else if strings.Contains(err.Error(), "Resource protected by organization SAML enforcement") {
|
||||
fmt.Fprintln(stderr, "Try re-authenticating with: gh auth refresh")
|
||||
}
|
||||
|
||||
return exitError
|
||||
|
|
@ -225,7 +236,7 @@ func mainRun() exitCode {
|
|||
|
||||
newRelease := <-updateMessageChan
|
||||
if newRelease != nil {
|
||||
isHomebrew := isUnderHomebrew(cmdFactory.Executable)
|
||||
isHomebrew := isUnderHomebrew(cmdFactory.Executable())
|
||||
if isHomebrew && isRecentRelease(newRelease.PublishedAt) {
|
||||
// do not notify Homebrew users before the version bump had a chance to get merged into homebrew-core
|
||||
return exitOK
|
||||
|
|
@ -244,6 +255,12 @@ func mainRun() exitCode {
|
|||
return exitOK
|
||||
}
|
||||
|
||||
// hasCommand returns true if args resolve to a built-in command
|
||||
func hasCommand(rootCmd *cobra.Command, args []string) bool {
|
||||
c, _, err := rootCmd.Traverse(args)
|
||||
return err == nil && c != rootCmd
|
||||
}
|
||||
|
||||
func printError(out io.Writer, err error, cmd *cobra.Command, debug bool) {
|
||||
var dnsError *net.DNSError
|
||||
if errors.As(err, &dnsError) {
|
||||
|
|
@ -251,7 +268,7 @@ func printError(out io.Writer, err error, cmd *cobra.Command, debug bool) {
|
|||
if debug {
|
||||
fmt.Fprintln(out, dnsError)
|
||||
}
|
||||
fmt.Fprintln(out, "check your internet connection or githubstatus.com")
|
||||
fmt.Fprintln(out, "check your internet connection or https://githubstatus.com")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ func Test_printError(t *testing.T) {
|
|||
debug: false,
|
||||
},
|
||||
wantOut: `error connecting to api.github.com
|
||||
check your internet connection or githubstatus.com
|
||||
check your internet connection or https://githubstatus.com
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ import (
|
|||
"sort"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/cli/cli/api"
|
||||
"github.com/cli/cli/git"
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/pkg/prompt"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/git"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/prompt"
|
||||
)
|
||||
|
||||
// cap the number of git remotes looked up, since the user might have an
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import (
|
|||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/cli/cli/git"
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/git"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
)
|
||||
|
||||
// Remotes represents a set of git remotes
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import (
|
|||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/git"
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/git"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Installing gh on Linux and FreeBSD
|
||||
# Installing gh on Linux and BSD
|
||||
|
||||
Packages downloaded from https://cli.github.com or from https://github.com/cli/cli/releases
|
||||
are considered official binaries. We focus on popular Linux distros and
|
||||
|
|
@ -7,15 +7,9 @@ the following CPU architectures: `i386`, `amd64`, `arm64`, `armhf`.
|
|||
Other sources for installation are community-maintained and thus might lag behind
|
||||
our release schedule.
|
||||
|
||||
If none of our official binaries, packages, repositories, nor community sources work for you, we recommend using our `Makefile` to build `gh` from source. It's quick and easy.
|
||||
|
||||
## Official sources
|
||||
|
||||
### Debian, Ubuntu Linux (apt)
|
||||
|
||||
:warning: This will only work for the [architectures we officially support](/.goreleaser.yml#L27).
|
||||
|
||||
The below should work for any debian-based distribution. You can change `stable` to a specific codename [we support](/.github/workflows/releases.yml#L83) if that is your preference.
|
||||
### Debian, Ubuntu Linux, Raspberry Pi OS (apt)
|
||||
|
||||
Install:
|
||||
|
||||
|
|
@ -26,7 +20,7 @@ sudo apt update
|
|||
sudo apt install gh
|
||||
```
|
||||
|
||||
**Note**: If you get _"gpg: failed to start the dirmngr '/usr/bin/dirmngr': No such file or directory"_ error, try installing the `dirmngr` package. Run `sudo apt-get install dirmngr` and repeat the steps above.
|
||||
**Note**: If you get the error _"gpg: failed to start the dirmngr '/usr/bin/dirmngr': No such file or directory"_, try installing the `dirmngr` package: `sudo apt install dirmngr`.
|
||||
|
||||
Upgrade:
|
||||
|
||||
|
|
@ -72,16 +66,13 @@ sudo zypper update gh
|
|||
* [Download release binaries][releases page] that match your platform; or
|
||||
* [Build from source](./source.md).
|
||||
|
||||
### openSUSE/SUSE Linux (zypper)
|
||||
|
||||
Install and upgrade:
|
||||
## Unofficial, community-supported methods
|
||||
|
||||
1. Download the `.rpm` file from the [releases page][];
|
||||
2. Install the downloaded file: `sudo zypper in gh_*_linux_amd64.rpm`
|
||||
The GitHub CLI team does not maintain the following packages or repositories and thus we are unable to provide support for those installation methods.
|
||||
|
||||
## Unofficial, Community-supported methods
|
||||
### Snap (do not use)
|
||||
|
||||
The core GitHub CLI team does not maintain the following packages or repositories. They are unofficial and we are unable to provide support or guarantees for them. They are linked here as a convenience and their presence does not imply continued oversight from the CLI core team. Users who choose to use them do so at their own risk.
|
||||
There are [so many issues with Snap](https://github.com/casperdcl/cli/issues/7) as a runtime mechanism for apps like GitHub CLI that our team suggests _never installing gh as a snap_.
|
||||
|
||||
### Arch Linux
|
||||
|
||||
|
|
@ -101,13 +92,6 @@ Android 7+ users can install via [Termux](https://wiki.termux.com/wiki/Main_Page
|
|||
pkg install gh
|
||||
```
|
||||
|
||||
### Homebrew (Linuxbrew)
|
||||
|
||||
Linuxbrew users can install it as a [brew package](https://formulae.brew.sh/formula/gh#default):
|
||||
```bash
|
||||
brew install gh
|
||||
```
|
||||
|
||||
### FreeBSD
|
||||
|
||||
FreeBSD users can install from the [ports collection](https://www.freshports.org/devel/gh/):
|
||||
|
|
@ -122,6 +106,14 @@ Or via [pkg(8)](https://www.freebsd.org/cgi/man.cgi?pkg(8)):
|
|||
pkg install gh
|
||||
```
|
||||
|
||||
### OpenBSD
|
||||
|
||||
In -current, or in releases starting from 7.0, OpenBSD users can install from packages:
|
||||
|
||||
```
|
||||
pkg_add github-cli
|
||||
```
|
||||
|
||||
### Funtoo
|
||||
|
||||
Funtoo Linux has an autogenerated github-cli package, located in [dev-kit](https://github.com/funtoo/dev-kit/tree/1.4-release/dev-util/github-cli), which can be installed in the following way:
|
||||
|
|
@ -170,23 +162,11 @@ nix-env -iA nixos.gitAndTools.gh
|
|||
|
||||
### openSUSE Tumbleweed
|
||||
|
||||
openSUSE Tumbleweed users can install from the [offical distribution repo](https://software.opensuse.org/package/gh):
|
||||
openSUSE Tumbleweed users can install from the [official distribution repo](https://software.opensuse.org/package/gh):
|
||||
```bash
|
||||
sudo zypper in gh
|
||||
```
|
||||
|
||||
### Snaps
|
||||
|
||||
Many Linux distro users can install using Snapd from the [Snap Store](https://snapcraft.io/gh) or the associated [repo](https://github.com/casperdcl/cli/tree/snap)
|
||||
|
||||
```bash
|
||||
sudo snap install --edge gh && snap connect gh:ssh-keys
|
||||
```
|
||||
> Snaps are auto-updated every 6 hours. `Snapd` is required and is available on a wide range of Linux distros.
|
||||
> Find out which distros have Snapd pre-installed and how to install it in the [Snapcraft Installation Docs](https://snapcraft.io/docs/installing-snapd)
|
||||
>
|
||||
> **Note:** `snap connect gh:ssh-keys` is needed for all authentication and SSH needs.
|
||||
|
||||
[releases page]: https://github.com/cli/cli/releases/latest
|
||||
[arch linux repo]: https://www.archlinux.org/packages/community/x86_64/github-cli
|
||||
[arch linux aur]: https://aur.archlinux.org/packages/github-cli-git
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ commands is:
|
|||
pkg/cmd/<command>/<subcommand>/<subcommand>.go
|
||||
```
|
||||
Following the above example, the main implementation for the `gh issue list` command, including its help
|
||||
text, is in [pkg/cmd/issue/view/view.go](../pkg/cmd/issue/view/view.go)
|
||||
text, is in [pkg/cmd/issue/list/list.go](../pkg/cmd/issue/list/list.go)
|
||||
|
||||
Other help topics not specific to any command, for example `gh help environment`, are found in
|
||||
[pkg/cmd/root/help_topic.go](../pkg/cmd/root/help_topic.go).
|
||||
|
|
@ -37,7 +37,7 @@ manual pages and published under https://cli.github.com/manual/.
|
|||
To illustrate how GitHub CLI works in its typical mode of operation, let's build the project, run a command,
|
||||
and talk through which code gets run in order.
|
||||
|
||||
1. `go run script/build.go` - Makes sure all external Go depedencies are fetched, then compiles the
|
||||
1. `go run script/build.go` - Makes sure all external Go dependencies are fetched, then compiles the
|
||||
`cmd/gh/main.go` file into a `bin/gh` binary.
|
||||
2. `bin/gh issue list --limit 5` - Runs the newly built `bin/gh` binary (note: on Windows you must use
|
||||
backslashes like `bin\gh`) and passes the following arguments to the process: `["issue", "list", "--limit", "5"]`.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Installation from source
|
||||
|
||||
0. Verify that you have Go 1.13+ installed
|
||||
0. Verify that you have Go 1.16+ installed
|
||||
|
||||
```sh
|
||||
$ go version
|
||||
|
|
|
|||
27
git/git.go
27
git/git.go
|
|
@ -13,7 +13,7 @@ import (
|
|||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/cli/cli/internal/run"
|
||||
"github.com/cli/cli/v2/internal/run"
|
||||
"github.com/cli/safeexec"
|
||||
)
|
||||
|
||||
|
|
@ -107,14 +107,29 @@ func Config(name string) (string, error) {
|
|||
|
||||
}
|
||||
|
||||
var GitCommand = func(args ...string) (*exec.Cmd, error) {
|
||||
type NotInstalled struct {
|
||||
message string
|
||||
error
|
||||
}
|
||||
|
||||
func (e *NotInstalled) Error() string {
|
||||
return e.message
|
||||
}
|
||||
|
||||
func GitCommand(args ...string) (*exec.Cmd, error) {
|
||||
gitExe, err := safeexec.LookPath("git")
|
||||
if err != nil {
|
||||
programName := "git"
|
||||
if runtime.GOOS == "windows" {
|
||||
programName = "Git for Windows"
|
||||
if errors.Is(err, exec.ErrNotFound) {
|
||||
programName := "git"
|
||||
if runtime.GOOS == "windows" {
|
||||
programName = "Git for Windows"
|
||||
}
|
||||
return nil, &NotInstalled{
|
||||
message: fmt.Sprintf("unable to find git executable in PATH; please install %s before retrying", programName),
|
||||
error: err,
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("unable to find git executable in PATH; please install %s before retrying", programName)
|
||||
return nil, err
|
||||
}
|
||||
return exec.Command(gitExe, args...), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import (
|
|||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/internal/run"
|
||||
"github.com/cli/cli/v2/internal/run"
|
||||
)
|
||||
|
||||
func setGitDir(t *testing.T, dir string) {
|
||||
|
|
@ -100,7 +100,7 @@ func Test_CurrentBranch(t *testing.T) {
|
|||
|
||||
result, err := CurrentBranch()
|
||||
if err != nil {
|
||||
t.Errorf("got unexpected error: %w", err)
|
||||
t.Errorf("got unexpected error: %v", err)
|
||||
}
|
||||
if result != v.Expected {
|
||||
t.Errorf("unexpected branch name: %s instead of %s", result, v.Expected)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/cli/cli/internal/run"
|
||||
"github.com/cli/cli/v2/internal/run"
|
||||
)
|
||||
|
||||
var remoteRE = regexp.MustCompile(`(.+)\s+(.+)\s+\((push|fetch)\)`)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import (
|
|||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ func isSupportedProtocol(u string) bool {
|
|||
strings.HasPrefix(u, "git+ssh:") ||
|
||||
strings.HasPrefix(u, "git:") ||
|
||||
strings.HasPrefix(u, "http:") ||
|
||||
strings.HasPrefix(u, "git+https:") ||
|
||||
strings.HasPrefix(u, "https:")
|
||||
}
|
||||
|
||||
|
|
@ -43,6 +44,10 @@ func ParseURL(rawURL string) (u *url.URL, err error) {
|
|||
u.Scheme = "ssh"
|
||||
}
|
||||
|
||||
if u.Scheme == "git+https" {
|
||||
u.Scheme = "https"
|
||||
}
|
||||
|
||||
if u.Scheme != "ssh" {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,11 +28,26 @@ func TestIsURL(t *testing.T) {
|
|||
url: "git://example.com/owner/repo",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "git with extension",
|
||||
url: "git://example.com/owner/repo.git",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "git+ssh",
|
||||
url: "git+ssh://git@example.com/owner/repo.git",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "https",
|
||||
url: "https://example.com/owner/repo.git",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "git+https",
|
||||
url: "git+https://example.com/owner/repo.git",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "no protocol",
|
||||
url: "example.com/owner/repo",
|
||||
|
|
@ -121,6 +136,16 @@ func TestParseURL(t *testing.T) {
|
|||
Path: "/owner/repo.git",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "git+https",
|
||||
url: "git+https://example.com/owner/repo.git",
|
||||
want: url{
|
||||
Scheme: "https",
|
||||
User: "",
|
||||
Host: "example.com",
|
||||
Path: "/owner/repo.git",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "scp-like",
|
||||
url: "git@example.com:owner/repo.git",
|
||||
|
|
|
|||
53
go.mod
53
go.mod
|
|
@ -1,42 +1,49 @@
|
|||
module github.com/cli/cli
|
||||
module github.com/cli/cli/v2
|
||||
|
||||
go 1.13
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.2.9
|
||||
github.com/AlecAivazis/survey/v2 v2.3.2
|
||||
github.com/MakeNowJust/heredoc v1.0.0
|
||||
github.com/briandowns/spinner v1.11.1
|
||||
github.com/briandowns/spinner v1.13.0
|
||||
github.com/charmbracelet/glamour v0.3.0
|
||||
github.com/cli/browser v1.1.0
|
||||
github.com/cli/oauth v0.8.0
|
||||
github.com/cli/safeexec v1.0.0
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0
|
||||
github.com/creack/pty v1.1.13
|
||||
github.com/gabriel-vasile/mimetype v1.1.2
|
||||
github.com/google/go-cmp v0.5.2
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1
|
||||
github.com/creack/pty v1.1.16
|
||||
github.com/fatih/camelcase v1.0.0
|
||||
github.com/gabriel-vasile/mimetype v1.4.0
|
||||
github.com/google/go-cmp v0.5.6
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/hashicorp/go-version v1.2.1
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/hashicorp/go-version v1.3.0
|
||||
github.com/henvic/httpretty v0.0.6
|
||||
github.com/itchyny/gojq v0.12.1
|
||||
github.com/itchyny/gojq v0.12.5
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/mattn/go-colorable v0.1.8
|
||||
github.com/mattn/go-isatty v0.0.12
|
||||
github.com/mattn/go-runewidth v0.0.10
|
||||
github.com/mattn/go-colorable v0.1.11
|
||||
github.com/mattn/go-isatty v0.0.14
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/muesli/termenv v0.8.1
|
||||
github.com/rivo/uniseg v0.2.0
|
||||
github.com/muesli/reflow v0.2.1-0.20210502190812-c80126ec2ad5
|
||||
github.com/muesli/termenv v0.9.0
|
||||
github.com/muhammadmuzzammil1998/jsonc v0.0.0-20201229145248-615b0916ca38
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/opentracing/opentracing-go v1.1.0
|
||||
github.com/shurcooL/githubv4 v0.0.0-20200928013246-d292edc3691b
|
||||
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f
|
||||
github.com/spf13/cobra v1.1.3
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||
github.com/sourcegraph/jsonrpc2 v0.1.0
|
||||
github.com/spf13/cobra v1.2.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.6.1
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44
|
||||
golang.org/x/term v0.0.0-20210422114643-f5beecf764ed
|
||||
golang.org/x/text v0.3.4 // indirect
|
||||
github.com/stretchr/objx v0.1.1 // indirect
|
||||
github.com/stretchr/testify v1.7.0
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6
|
||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
)
|
||||
|
||||
replace github.com/shurcooL/graphql => github.com/cli/shurcooL-graphql v0.0.0-20200707151639-0f7232a2bf7e
|
||||
|
||||
replace golang.org/x/crypto => github.com/cli/crypto v0.0.0-20210929142629-6be313f59b03
|
||||
|
|
|
|||
532
go.sum
532
go.sum
|
|
@ -5,21 +5,46 @@ cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6A
|
|||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
|
||||
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
|
||||
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/AlecAivazis/survey/v2 v2.2.9 h1:LWvJtUswz/W9/zVVXELrmlvdwWcKE60ZAw0FWV9vssk=
|
||||
github.com/AlecAivazis/survey/v2 v2.2.9/go.mod h1:9DYvHgXtiXm6nCn+jXnOXLKbH+Yo9u8fAS/SduGdoPk=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.2 h1:TqTB+aDDCLYhf9/bD2TwSO8u8jDSmMUd2SUVO4gCnU8=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.2/go.mod h1:TH2kPCDU3Kqq7pLbnCWwZXDBjnhZtmsCle5EiYDJ2fg=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
|
||||
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
|
||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
|
||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
|
||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
||||
github.com/alecthomas/chroma v0.8.2 h1:x3zkuE2lUk/RIekyAJ3XRqSCP4zwWDfcw/YJCuCAACg=
|
||||
|
|
@ -29,25 +54,27 @@ github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBo
|
|||
github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
|
||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY=
|
||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
||||
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/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||
github.com/briandowns/spinner v1.11.1 h1:OixPqDEcX3juo5AjQZAnFPbeUA0jvkp2qzB5gOZJ/L0=
|
||||
github.com/briandowns/spinner v1.11.1/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
|
||||
github.com/briandowns/spinner v1.13.0 h1:q/Y9LtpwtvL0CRzXrAMj0keVXqNhBYUFg6tBOUiY8ek=
|
||||
github.com/briandowns/spinner v1.13.0/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/charmbracelet/glamour v0.3.0 h1:3H+ZrKlSg8s+WU6V7eF2eRVYt8lCueffbi7r2+ffGkc=
|
||||
github.com/charmbracelet/glamour v0.3.0/go.mod h1:TzF0koPZhqq0YVBNL100cPHznAAjVj7fksX2RInwjGw=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cli/browser v1.0.0/go.mod h1:IEWkHYbLjkhtjwwWlwTHW2lGxeS5gezEQBMLTwDHf5Q=
|
||||
github.com/cli/browser v1.1.0 h1:xOZBfkfY9L9vMBgqb1YwRirGu6QFaQ5dP/vXt5ENSOY=
|
||||
github.com/cli/browser v1.1.0/go.mod h1:HKMQAt9t12kov91Mn7RfZxyJQQgWgyS/3SZswlZ5iTI=
|
||||
github.com/cli/crypto v0.0.0-20210929142629-6be313f59b03 h1:3f4uHLfWx4/WlnMPXGai03eoWAI+oGHJwr+5OXfxCr8=
|
||||
github.com/cli/crypto v0.0.0-20210929142629-6be313f59b03/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
github.com/cli/oauth v0.8.0 h1:YTFgPXSTvvDUFti3tR4o6q7Oll2SnQ9ztLwCAn4/IOA=
|
||||
github.com/cli/oauth v0.8.0/go.mod h1:qd/FX8ZBD6n1sVNQO3aIdRxeu5LGw9WhKnYhIIoC2A4=
|
||||
github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI=
|
||||
|
|
@ -55,68 +82,117 @@ github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5
|
|||
github.com/cli/shurcooL-graphql v0.0.0-20200707151639-0f7232a2bf7e h1:aq/1jlmtZoS6nlSp3yLOTZQ50G+dzHdeRNENgE/iBew=
|
||||
github.com/cli/shurcooL-graphql v0.0.0-20200707151639-0f7232a2bf7e/go.mod h1:it23pLwxmz6OyM6I5O0ATIXQS1S190Nas26L5Kahp4U=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.13 h1:rTPnd/xocYRjutMfqide2zle1u96upp1gm6eUHKi7us=
|
||||
github.com/creack/pty v1.1.13/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.16 h1:vfetlOf3A+9YKggibynnX9mnFjuSVvkRj+IWpcTSLEQ=
|
||||
github.com/creack/pty v1.1.16/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
|
||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
|
||||
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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
|
||||
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
|
||||
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
|
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gabriel-vasile/mimetype v1.1.2 h1:gaPnPcNor5aZSVCJVSGipcpbgMWiAAj9z182ocSGbHU=
|
||||
github.com/gabriel-vasile/mimetype v1.1.2/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/gabriel-vasile/mimetype v1.4.0 h1:Cn9dkdYsMIu56tGho+fqzh7XmvY2YyGU0FnbhiOsEro=
|
||||
github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
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.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/graph-gophers/graphql-go v0.0.0-20200622220639-c1d9693c95a6/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc=
|
||||
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.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
|
|
@ -129,8 +205,8 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX
|
|||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
|
||||
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw=
|
||||
github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
|
|
@ -143,26 +219,24 @@ github.com/henvic/httpretty v0.0.6 h1:JdzGzKZBajBfnvlMALXXMVQWxWMF/ofTy8C3/OSUTx
|
|||
github.com/henvic/httpretty v0.0.6/go.mod h1:X38wLjWXHkXT7r2+uK8LjCMne9rsuNaBLJ+5cU2/Pmo=
|
||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
|
||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/itchyny/astgen-go v0.0.0-20210113000433-0da0671862a3 h1:l7vogWrq+zj8v5t/G69/eT13nAGs2H7cq+CI2nlnKdk=
|
||||
github.com/itchyny/astgen-go v0.0.0-20210113000433-0da0671862a3/go.mod h1:296z3W7Xsrp2mlIY88ruDKscuvrkL6zXCNRtaYVshzw=
|
||||
github.com/itchyny/go-flags v1.5.0/go.mod h1:lenkYuCobuxLBAd/HGFE4LRoW8D3B6iXRQfWYJ+MNbA=
|
||||
github.com/itchyny/gojq v0.12.1 h1:pQJrG8LXgEbZe9hvpfjKg7UlBfieQQydIw3YQq+7WIA=
|
||||
github.com/itchyny/gojq v0.12.1/go.mod h1:Y5Lz0qoT54ii+ucY/K3yNDy19qzxZvWNBMBpKUDQR/4=
|
||||
github.com/itchyny/timefmt-go v0.1.1 h1:rLpnm9xxb39PEEVzO0n4IRp0q6/RmBc7Dy/rE4HrA0U=
|
||||
github.com/itchyny/timefmt-go v0.1.1/go.mod h1:0osSSCQSASBJMsIZnhAaF1C2fCBTJZXrnj37mG8/c+A=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/itchyny/gojq v0.12.5 h1:6SJ1BQ1VAwJAlIvLSIZmqHP/RUEq3qfVWvsRxrqhsD0=
|
||||
github.com/itchyny/gojq v0.12.5/go.mod h1:3e1hZXv+Kwvdp6V9HXpVrvddiHVApi5EDZwS+zLFeiE=
|
||||
github.com/itchyny/timefmt-go v0.1.3 h1:7M3LGVDsqcd0VZH2U+x393obrzZisp7C0uEe921iRkU=
|
||||
github.com/itchyny/timefmt-go v0.1.3/go.mod h1:0osSSCQSASBJMsIZnhAaF1C2fCBTJZXrnj37mG8/c+A=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
|
|
@ -172,20 +246,22 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
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-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
|
||||
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
|
||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
|
|
@ -194,106 +270,109 @@ github.com/microcosm-cc/bluemonday v1.0.6/go.mod h1:HOT/6NaBlR0f9XlxD3zolN6Z3N8L
|
|||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
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/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/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/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/muesli/reflow v0.2.0 h1:2o0UBJPHHH4fa2GCXU4Rg4DwOtWPMekCeyc5EWbAQp0=
|
||||
github.com/muesli/reflow v0.2.0/go.mod h1:qT22vjVmM9MIUeLgsVYe/Ye7eZlbv9dZjL3dVhUqLX8=
|
||||
github.com/muesli/termenv v0.8.1 h1:9q230czSP3DHVpkaPDXGp0TOfAwyjyYwXlUCQxQSaBk=
|
||||
github.com/muesli/reflow v0.2.1-0.20210502190812-c80126ec2ad5 h1:T+Fc6qGlSfM+z0JPlp+n5rijvlg6C6JYFSNaqnCifDU=
|
||||
github.com/muesli/reflow v0.2.1-0.20210502190812-c80126ec2ad5/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
|
||||
github.com/muesli/termenv v0.8.1/go.mod h1:kzt/D/4a88RoheZmwfqorY3A+tnsSMA9HJC/fQSFKo0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/muesli/termenv v0.9.0 h1:wnbOaGz+LUR3jNT0zOzinPnyDaCZUQRZj9GxK8eRVl8=
|
||||
github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw=
|
||||
github.com/muhammadmuzzammil1998/jsonc v0.0.0-20201229145248-615b0916ca38 h1:0FrBxrkJ0hVembTb/e4EU5Ml6vLcOusAqymmYISg5Uo=
|
||||
github.com/muhammadmuzzammil1998/jsonc v0.0.0-20201229145248-615b0916ca38/go.mod h1:saF2fIVw4banK0H4+/EuqfFLpRnoy5S+ECwTOCcRcSU=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/githubv4 v0.0.0-20200928013246-d292edc3691b h1:0/ecDXh/HTHRtSDSFnD2/Ta1yQ5J76ZspVY4u0/jGFk=
|
||||
github.com/shurcooL/githubv4 v0.0.0-20200928013246-d292edc3691b/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
|
||||
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/sourcegraph/jsonrpc2 v0.1.0 h1:ohJHjZ+PcaLxDUjqk2NC3tIGsVa5bXThe1ZheSXOjuk=
|
||||
github.com/sourcegraph/jsonrpc2 v0.1.0/go.mod h1:ZafdZgk/axhT1cvZAPOhw+95nz2I/Ra5qMlU4gTRwIo=
|
||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=
|
||||
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
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/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.3 h1:37BdQwPx8VOSic8eDSWee6QL9mRpZRm9VJp/QugNrW0=
|
||||
github.com/yuin/goldmark v1.3.3/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.3.5 h1:dPmz1Snjq0kmkz159iL7S6WzdahUTHnHB5M56WFVifs=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os=
|
||||
github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
|
@ -303,74 +382,151 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
|
|||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
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-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c h1:KHUzaHIpjWVlVVNh65G3hhuj3KB1HnjY6Cq5cTvRQT8=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125 h1:Ugb8sMTWuWRC3+sz5WeN/4kejDx9BvIwnPUiJBjJE+8=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 h1:0Ja1LBD+yisY6RWM/BH7TJVXWsSjs2VwBSmvSX4HdBc=
|
||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/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-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210422114643-f5beecf764ed h1:Ei4bQjjpYUsS4efOUz+5Nz++IVkHk87n2zBA0NxBWc0=
|
||||
golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w=
|
||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/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/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
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/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
|
|
@ -380,6 +536,7 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3
|
|||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/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-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
|
|
@ -387,19 +544,75 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn
|
|||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
|
||||
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
|
||||
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.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.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
|
|
@ -409,19 +622,79 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98
|
|||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
|
|
@ -429,5 +702,10 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C
|
|||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/cli/cli/api"
|
||||
"github.com/cli/cli/internal/ghinstance"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/ghinstance"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/oauth"
|
||||
)
|
||||
|
||||
|
|
|
|||
659
internal/codespaces/api/api.go
Normal file
659
internal/codespaces/api/api.go
Normal file
|
|
@ -0,0 +1,659 @@
|
|||
package api
|
||||
|
||||
// For descriptions of service interfaces, see:
|
||||
// - https://online.visualstudio.com/api/swagger (for visualstudio.com)
|
||||
// - https://docs.github.com/en/rest/reference/repos (for api.github.com)
|
||||
// - https://github.com/github/github/blob/master/app/api/codespaces.rb (for vscs_internal)
|
||||
// TODO(adonovan): replace the last link with a public doc URL when available.
|
||||
|
||||
// TODO(adonovan): a possible reorganization would be to split this
|
||||
// file into three internal packages, one per backend service, and to
|
||||
// rename api.API to github.Client:
|
||||
//
|
||||
// - github.GetUser(github.Client)
|
||||
// - github.GetRepository(Client)
|
||||
// - github.ReadFile(Client, nwo, branch, path) // was GetCodespaceRepositoryContents
|
||||
// - github.AuthorizedKeys(Client, user)
|
||||
// - codespaces.Create(Client, user, repo, sku, branch, location)
|
||||
// - codespaces.Delete(Client, user, token, name)
|
||||
// - codespaces.Get(Client, token, owner, name)
|
||||
// - codespaces.GetMachineTypes(Client, user, repo, branch, location)
|
||||
// - codespaces.GetToken(Client, login, name)
|
||||
// - codespaces.List(Client, user)
|
||||
// - codespaces.Start(Client, token, codespace)
|
||||
// - visualstudio.GetRegionLocation(http.Client) // no dependency on github
|
||||
//
|
||||
// This would make the meaning of each operation clearer.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/opentracing/opentracing-go"
|
||||
)
|
||||
|
||||
const githubAPI = "https://api.github.com"
|
||||
|
||||
// API is the interface to the codespace service.
|
||||
type API struct {
|
||||
token string
|
||||
client httpClient
|
||||
githubAPI string
|
||||
}
|
||||
|
||||
type httpClient interface {
|
||||
Do(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
// New creates a new API client with the given token and HTTP client.
|
||||
func New(token string, httpClient httpClient) *API {
|
||||
return &API{
|
||||
token: token,
|
||||
client: httpClient,
|
||||
githubAPI: githubAPI,
|
||||
}
|
||||
}
|
||||
|
||||
// User represents a GitHub user.
|
||||
type User struct {
|
||||
Login string `json:"login"`
|
||||
}
|
||||
|
||||
// GetUser returns the user associated with the given token.
|
||||
func (a *API) GetUser(ctx context.Context) (*User, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, a.githubAPI+"/user", nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating request: %w", err)
|
||||
}
|
||||
|
||||
a.setHeaders(req)
|
||||
resp, err := a.do(ctx, req, "/user")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error making request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading response body: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, jsonErrorResponse(b)
|
||||
}
|
||||
|
||||
var response User
|
||||
if err := json.Unmarshal(b, &response); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling response: %w", err)
|
||||
}
|
||||
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// jsonErrorResponse returns the error message from a JSON response.
|
||||
func jsonErrorResponse(b []byte) error {
|
||||
var response struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &response); err != nil {
|
||||
return fmt.Errorf("error unmarshaling error response: %w", err)
|
||||
}
|
||||
|
||||
return errors.New(response.Message)
|
||||
}
|
||||
|
||||
// Repository represents a GitHub repository.
|
||||
type Repository struct {
|
||||
ID int `json:"id"`
|
||||
}
|
||||
|
||||
// GetRepository returns the repository associated with the given owner and name.
|
||||
func (a *API) GetRepository(ctx context.Context, nwo string) (*Repository, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, a.githubAPI+"/repos/"+strings.ToLower(nwo), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating request: %w", err)
|
||||
}
|
||||
|
||||
a.setHeaders(req)
|
||||
resp, err := a.do(ctx, req, "/repos/*")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error making request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading response body: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, jsonErrorResponse(b)
|
||||
}
|
||||
|
||||
var response Repository
|
||||
if err := json.Unmarshal(b, &response); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling response: %w", err)
|
||||
}
|
||||
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// Codespace represents a codespace.
|
||||
type Codespace struct {
|
||||
Name string `json:"name"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
LastUsedAt string `json:"last_used_at"`
|
||||
State string `json:"state"`
|
||||
Branch string `json:"branch"`
|
||||
RepositoryName string `json:"repository_name"`
|
||||
RepositoryNWO string `json:"repository_nwo"`
|
||||
OwnerLogin string `json:"owner_login"`
|
||||
Environment CodespaceEnvironment `json:"environment"`
|
||||
Connection CodespaceConnection `json:"connection"`
|
||||
}
|
||||
|
||||
const CodespaceStateProvisioned = "provisioned"
|
||||
|
||||
type CodespaceEnvironment struct {
|
||||
State string `json:"state"`
|
||||
GitStatus CodespaceEnvironmentGitStatus `json:"gitStatus"`
|
||||
}
|
||||
|
||||
type CodespaceEnvironmentGitStatus struct {
|
||||
Ahead int `json:"ahead"`
|
||||
Behind int `json:"behind"`
|
||||
Branch string `json:"branch"`
|
||||
Commit string `json:"commit"`
|
||||
HasUnpushedChanges bool `json:"hasUnpushedChanges"`
|
||||
HasUncommitedChanges bool `json:"hasUncommitedChanges"`
|
||||
}
|
||||
|
||||
const (
|
||||
// CodespaceEnvironmentStateAvailable is the state for a running codespace environment.
|
||||
CodespaceEnvironmentStateAvailable = "Available"
|
||||
)
|
||||
|
||||
type CodespaceConnection struct {
|
||||
SessionID string `json:"sessionId"`
|
||||
SessionToken string `json:"sessionToken"`
|
||||
RelayEndpoint string `json:"relayEndpoint"`
|
||||
RelaySAS string `json:"relaySas"`
|
||||
HostPublicKeys []string `json:"hostPublicKeys"`
|
||||
}
|
||||
|
||||
// ListCodespaces returns a list of codespaces for the user. Pass a negative limit to request all pages from
|
||||
// the API until all codespaces have been fetched.
|
||||
func (a *API) ListCodespaces(ctx context.Context, limit int) (codespaces []*Codespace, err error) {
|
||||
perPage := 100
|
||||
if limit > 0 && limit < 100 {
|
||||
perPage = limit
|
||||
}
|
||||
|
||||
listURL := fmt.Sprintf("%s/user/codespaces?per_page=%d", a.githubAPI, perPage)
|
||||
for {
|
||||
req, err := http.NewRequest(http.MethodGet, listURL, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating request: %w", err)
|
||||
}
|
||||
a.setHeaders(req)
|
||||
|
||||
resp, err := a.do(ctx, req, "/user/codespaces")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error making request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, api.HandleHTTPError(resp)
|
||||
}
|
||||
|
||||
var response struct {
|
||||
Codespaces []*Codespace `json:"codespaces"`
|
||||
}
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
if err := dec.Decode(&response); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling response: %w", err)
|
||||
}
|
||||
|
||||
nextURL := findNextPage(resp.Header.Get("Link"))
|
||||
codespaces = append(codespaces, response.Codespaces...)
|
||||
|
||||
if nextURL == "" || (limit > 0 && len(codespaces) >= limit) {
|
||||
break
|
||||
}
|
||||
|
||||
if newPerPage := limit - len(codespaces); limit > 0 && newPerPage < 100 {
|
||||
u, _ := url.Parse(nextURL)
|
||||
q := u.Query()
|
||||
q.Set("per_page", strconv.Itoa(newPerPage))
|
||||
u.RawQuery = q.Encode()
|
||||
listURL = u.String()
|
||||
} else {
|
||||
listURL = nextURL
|
||||
}
|
||||
}
|
||||
|
||||
return codespaces, nil
|
||||
}
|
||||
|
||||
var linkRE = regexp.MustCompile(`<([^>]+)>;\s*rel="([^"]+)"`)
|
||||
|
||||
func findNextPage(linkValue string) string {
|
||||
for _, m := range linkRE.FindAllStringSubmatch(linkValue, -1) {
|
||||
if len(m) > 2 && m[2] == "next" {
|
||||
return m[1]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetCodespace returns the user codespace based on the provided name.
|
||||
// If the codespace is not found, an error is returned.
|
||||
// If includeConnection is true, it will return the connection information for the codespace.
|
||||
func (a *API) GetCodespace(ctx context.Context, codespaceName string, includeConnection bool) (*Codespace, error) {
|
||||
req, err := http.NewRequest(
|
||||
http.MethodGet,
|
||||
a.githubAPI+"/user/codespaces/"+codespaceName,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating request: %w", err)
|
||||
}
|
||||
|
||||
if includeConnection {
|
||||
q := req.URL.Query()
|
||||
q.Add("internal", "true")
|
||||
q.Add("refresh", "true")
|
||||
req.URL.RawQuery = q.Encode()
|
||||
}
|
||||
|
||||
a.setHeaders(req)
|
||||
resp, err := a.do(ctx, req, "/user/codespaces/*")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error making request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading response body: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, jsonErrorResponse(b)
|
||||
}
|
||||
|
||||
var response Codespace
|
||||
if err := json.Unmarshal(b, &response); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling response: %w", err)
|
||||
}
|
||||
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// StartCodespace starts a codespace for the user.
|
||||
// If the codespace is already running, the returned error from the API is ignored.
|
||||
func (a *API) StartCodespace(ctx context.Context, codespaceName string) error {
|
||||
req, err := http.NewRequest(
|
||||
http.MethodPost,
|
||||
a.githubAPI+"/user/codespaces/"+codespaceName+"/start",
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating request: %w", err)
|
||||
}
|
||||
|
||||
a.setHeaders(req)
|
||||
resp, err := a.do(ctx, req, "/user/codespaces/*/start")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error making request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading response body: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
if resp.StatusCode == http.StatusConflict {
|
||||
// 409 means the codespace is already running which we can safely ignore
|
||||
return nil
|
||||
}
|
||||
|
||||
// Error response may be a numeric code or a JSON {"message": "..."}.
|
||||
if bytes.HasPrefix(b, []byte("{")) {
|
||||
return jsonErrorResponse(b) // probably JSON
|
||||
}
|
||||
|
||||
if len(b) > 100 {
|
||||
b = append(b[:97], "..."...)
|
||||
}
|
||||
return fmt.Errorf("failed to start codespace: %s (%s)", b, resp.Status)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *API) StopCodespace(ctx context.Context, codespaceName string) error {
|
||||
req, err := http.NewRequest(
|
||||
http.MethodPost,
|
||||
a.githubAPI+"/user/codespaces/"+codespaceName+"/stop",
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating request: %w", err)
|
||||
}
|
||||
|
||||
a.setHeaders(req)
|
||||
resp, err := a.do(ctx, req, "/user/codespaces/*/stop")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error making request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return api.HandleHTTPError(resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type getCodespaceRegionLocationResponse struct {
|
||||
Current string `json:"current"`
|
||||
}
|
||||
|
||||
// GetCodespaceRegionLocation returns the closest codespace location for the user.
|
||||
func (a *API) GetCodespaceRegionLocation(ctx context.Context) (string, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, "https://online.visualstudio.com/api/v1/locations", nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error creating request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := a.do(ctx, req, req.URL.String())
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error making request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error reading response body: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", jsonErrorResponse(b)
|
||||
}
|
||||
|
||||
var response getCodespaceRegionLocationResponse
|
||||
if err := json.Unmarshal(b, &response); err != nil {
|
||||
return "", fmt.Errorf("error unmarshaling response: %w", err)
|
||||
}
|
||||
|
||||
return response.Current, nil
|
||||
}
|
||||
|
||||
type Machine struct {
|
||||
Name string `json:"name"`
|
||||
DisplayName string `json:"display_name"`
|
||||
}
|
||||
|
||||
// GetCodespacesMachines returns the codespaces machines for the given repo, branch and location.
|
||||
func (a *API) GetCodespacesMachines(ctx context.Context, repoID int, branch, location string) ([]*Machine, error) {
|
||||
reqURL := fmt.Sprintf("%s/repositories/%d/codespaces/machines", a.githubAPI, repoID)
|
||||
req, err := http.NewRequest(http.MethodGet, reqURL, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating request: %w", err)
|
||||
}
|
||||
|
||||
q := req.URL.Query()
|
||||
q.Add("location", location)
|
||||
q.Add("ref", branch)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
a.setHeaders(req)
|
||||
resp, err := a.do(ctx, req, "/repositories/*/codespaces/machines")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error making request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading response body: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, jsonErrorResponse(b)
|
||||
}
|
||||
|
||||
var response struct {
|
||||
Machines []*Machine `json:"machines"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &response); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling response: %w", err)
|
||||
}
|
||||
|
||||
return response.Machines, nil
|
||||
}
|
||||
|
||||
// CreateCodespaceParams are the required parameters for provisioning a Codespace.
|
||||
type CreateCodespaceParams struct {
|
||||
RepositoryID int
|
||||
Branch, Machine, Location string
|
||||
}
|
||||
|
||||
// CreateCodespace creates a codespace with the given parameters and returns a non-nil error if it
|
||||
// fails to create.
|
||||
func (a *API) CreateCodespace(ctx context.Context, params *CreateCodespaceParams) (*Codespace, error) {
|
||||
codespace, err := a.startCreate(ctx, params.RepositoryID, params.Machine, params.Branch, params.Location)
|
||||
if err != errProvisioningInProgress {
|
||||
return codespace, err
|
||||
}
|
||||
|
||||
// errProvisioningInProgress indicates that codespace creation did not complete
|
||||
// within the GitHub API RPC time limit (10s), so it continues asynchronously.
|
||||
// We must poll the server to discover the outcome.
|
||||
ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-ticker.C:
|
||||
codespace, err = a.GetCodespace(ctx, codespace.Name, false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get codespace: %w", err)
|
||||
}
|
||||
|
||||
// we continue to poll until the codespace shows as provisioned
|
||||
if codespace.State != CodespaceStateProvisioned {
|
||||
continue
|
||||
}
|
||||
|
||||
return codespace, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type startCreateRequest struct {
|
||||
RepositoryID int `json:"repository_id"`
|
||||
Ref string `json:"ref"`
|
||||
Location string `json:"location"`
|
||||
Machine string `json:"machine"`
|
||||
}
|
||||
|
||||
var errProvisioningInProgress = errors.New("provisioning in progress")
|
||||
|
||||
// startCreate starts the creation of a codespace.
|
||||
// It may return success or an error, or errProvisioningInProgress indicating that the operation
|
||||
// did not complete before the GitHub API's time limit for RPCs (10s), in which case the caller
|
||||
// must poll the server to learn the outcome.
|
||||
func (a *API) startCreate(ctx context.Context, repoID int, machine, branch, location string) (*Codespace, error) {
|
||||
requestBody, err := json.Marshal(startCreateRequest{repoID, branch, location, machine})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error marshaling request: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, a.githubAPI+"/user/codespaces", bytes.NewBuffer(requestBody))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating request: %w", err)
|
||||
}
|
||||
|
||||
a.setHeaders(req)
|
||||
resp, err := a.do(ctx, req, "/user/codespaces")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error making request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading response body: %w", err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case resp.StatusCode > http.StatusAccepted:
|
||||
return nil, jsonErrorResponse(b)
|
||||
case resp.StatusCode == http.StatusAccepted:
|
||||
return nil, errProvisioningInProgress // RPC finished before result of creation known
|
||||
}
|
||||
|
||||
var response Codespace
|
||||
if err := json.Unmarshal(b, &response); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling response: %w", err)
|
||||
}
|
||||
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// DeleteCodespace deletes the given codespace.
|
||||
func (a *API) DeleteCodespace(ctx context.Context, codespaceName string) error {
|
||||
req, err := http.NewRequest(http.MethodDelete, a.githubAPI+"/user/codespaces/"+codespaceName, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating request: %w", err)
|
||||
}
|
||||
|
||||
a.setHeaders(req)
|
||||
resp, err := a.do(ctx, req, "/user/codespaces/*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error making request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode > http.StatusAccepted {
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading response body: %w", err)
|
||||
}
|
||||
return jsonErrorResponse(b)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type getCodespaceRepositoryContentsResponse struct {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
func (a *API) GetCodespaceRepositoryContents(ctx context.Context, codespace *Codespace, path string) ([]byte, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, a.githubAPI+"/repos/"+codespace.RepositoryNWO+"/contents/"+path, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating request: %w", err)
|
||||
}
|
||||
|
||||
q := req.URL.Query()
|
||||
q.Add("ref", codespace.Branch)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
a.setHeaders(req)
|
||||
resp, err := a.do(ctx, req, "/repos/*/contents/*")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error making request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading response body: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, jsonErrorResponse(b)
|
||||
}
|
||||
|
||||
var response getCodespaceRepositoryContentsResponse
|
||||
if err := json.Unmarshal(b, &response); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling response: %w", err)
|
||||
}
|
||||
|
||||
decoded, err := base64.StdEncoding.DecodeString(response.Content)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decoding content: %w", err)
|
||||
}
|
||||
|
||||
return decoded, nil
|
||||
}
|
||||
|
||||
// AuthorizedKeys returns the public keys (in ~/.ssh/authorized_keys
|
||||
// format) registered by the specified GitHub user.
|
||||
func (a *API) AuthorizedKeys(ctx context.Context, user string) ([]byte, error) {
|
||||
url := fmt.Sprintf("https://github.com/%s.keys", user)
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := a.do(ctx, req, "/user.keys")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading response body: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("server returned %s", resp.Status)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// do executes the given request and returns the response. It creates an
|
||||
// opentracing span to track the length of the request.
|
||||
func (a *API) do(ctx context.Context, req *http.Request, spanName string) (*http.Response, error) {
|
||||
// TODO(adonovan): use NewRequestWithContext(ctx) and drop ctx parameter.
|
||||
span, ctx := opentracing.StartSpanFromContext(ctx, spanName)
|
||||
defer span.Finish()
|
||||
req = req.WithContext(ctx)
|
||||
return a.client.Do(req)
|
||||
}
|
||||
|
||||
// setHeaders sets the required headers for the API.
|
||||
func (a *API) setHeaders(req *http.Request) {
|
||||
if a.token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+a.token)
|
||||
}
|
||||
req.Header.Set("Accept", "application/vnd.github.v3+json")
|
||||
}
|
||||
118
internal/codespaces/api/api_test.go
Normal file
118
internal/codespaces/api/api_test.go
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func generateCodespaceList(start int, end int) []*Codespace {
|
||||
codespacesList := []*Codespace{}
|
||||
for i := start; i < end; i++ {
|
||||
codespacesList = append(codespacesList, &Codespace{
|
||||
Name: fmt.Sprintf("codespace-%d", i),
|
||||
})
|
||||
}
|
||||
return codespacesList
|
||||
}
|
||||
|
||||
func createFakeListEndpointServer(t *testing.T, initalTotal int, finalTotal int) *httptest.Server {
|
||||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/user/codespaces" {
|
||||
t.Fatal("Incorrect path")
|
||||
}
|
||||
|
||||
page := 1
|
||||
if r.URL.Query().Get("page") != "" {
|
||||
page, _ = strconv.Atoi(r.URL.Query().Get("page"))
|
||||
}
|
||||
|
||||
per_page := 0
|
||||
if r.URL.Query().Get("per_page") != "" {
|
||||
per_page, _ = strconv.Atoi(r.URL.Query().Get("per_page"))
|
||||
}
|
||||
|
||||
response := struct {
|
||||
Codespaces []*Codespace `json:"codespaces"`
|
||||
TotalCount int `json:"total_count"`
|
||||
}{
|
||||
Codespaces: []*Codespace{},
|
||||
TotalCount: finalTotal,
|
||||
}
|
||||
|
||||
switch page {
|
||||
case 1:
|
||||
response.Codespaces = generateCodespaceList(0, per_page)
|
||||
response.TotalCount = initalTotal
|
||||
w.Header().Set("Link", fmt.Sprintf(`<http://%[1]s/user/codespaces?page=3&per_page=%[2]d>; rel="last", <http://%[1]s/user/codespaces?page=2&per_page=%[2]d>; rel="next"`, r.Host, per_page))
|
||||
case 2:
|
||||
response.Codespaces = generateCodespaceList(per_page, per_page*2)
|
||||
response.TotalCount = finalTotal
|
||||
w.Header().Set("Link", fmt.Sprintf(`<http://%s/user/codespaces?page=3&per_page=%d>; rel="next"`, r.Host, per_page))
|
||||
case 3:
|
||||
response.Codespaces = generateCodespaceList(per_page*2, per_page*3-per_page/2)
|
||||
response.TotalCount = finalTotal
|
||||
default:
|
||||
t.Fatal("Should not check extra page")
|
||||
}
|
||||
|
||||
data, _ := json.Marshal(response)
|
||||
fmt.Fprint(w, string(data))
|
||||
}))
|
||||
}
|
||||
|
||||
func TestListCodespaces_limited(t *testing.T) {
|
||||
svr := createFakeListEndpointServer(t, 200, 200)
|
||||
defer svr.Close()
|
||||
|
||||
api := API{
|
||||
githubAPI: svr.URL,
|
||||
client: &http.Client{},
|
||||
token: "faketoken",
|
||||
}
|
||||
ctx := context.TODO()
|
||||
codespaces, err := api.ListCodespaces(ctx, 200)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(codespaces) != 200 {
|
||||
t.Fatalf("expected 200 codespace, got %d", len(codespaces))
|
||||
}
|
||||
if codespaces[0].Name != "codespace-0" {
|
||||
t.Fatalf("expected codespace-0, got %s", codespaces[0].Name)
|
||||
}
|
||||
if codespaces[199].Name != "codespace-199" {
|
||||
t.Fatalf("expected codespace-199, got %s", codespaces[0].Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListCodespaces_unlimited(t *testing.T) {
|
||||
svr := createFakeListEndpointServer(t, 200, 200)
|
||||
defer svr.Close()
|
||||
|
||||
api := API{
|
||||
githubAPI: svr.URL,
|
||||
client: &http.Client{},
|
||||
token: "faketoken",
|
||||
}
|
||||
ctx := context.TODO()
|
||||
codespaces, err := api.ListCodespaces(ctx, -1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(codespaces) != 250 {
|
||||
t.Fatalf("expected 250 codespace, got %d", len(codespaces))
|
||||
}
|
||||
if codespaces[0].Name != "codespace-0" {
|
||||
t.Fatalf("expected codespace-0, got %s", codespaces[0].Name)
|
||||
}
|
||||
if codespaces[249].Name != "codespace-249" {
|
||||
t.Fatalf("expected codespace-249, got %s", codespaces[0].Name)
|
||||
}
|
||||
}
|
||||
85
internal/codespaces/codespaces.go
Normal file
85
internal/codespaces/codespaces.go
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
package codespaces
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/cli/cli/v2/internal/codespaces/api"
|
||||
"github.com/cli/cli/v2/pkg/liveshare"
|
||||
)
|
||||
|
||||
type logger interface {
|
||||
Print(v ...interface{}) (int, error)
|
||||
Println(v ...interface{}) (int, error)
|
||||
}
|
||||
|
||||
// TODO(josebalius): clean this up once we standardrize
|
||||
// logging for codespaces
|
||||
type liveshareLogger interface {
|
||||
Println(v ...interface{})
|
||||
Printf(f string, v ...interface{})
|
||||
}
|
||||
|
||||
func connectionReady(codespace *api.Codespace) bool {
|
||||
return codespace.Connection.SessionID != "" &&
|
||||
codespace.Connection.SessionToken != "" &&
|
||||
codespace.Connection.RelayEndpoint != "" &&
|
||||
codespace.Connection.RelaySAS != "" &&
|
||||
codespace.Environment.State == api.CodespaceEnvironmentStateAvailable
|
||||
}
|
||||
|
||||
type apiClient interface {
|
||||
GetCodespace(ctx context.Context, name string, includeConnection bool) (*api.Codespace, error)
|
||||
StartCodespace(ctx context.Context, name string) error
|
||||
}
|
||||
|
||||
// ConnectToLiveshare waits for a Codespace to become running,
|
||||
// and connects to it using a Live Share session.
|
||||
func ConnectToLiveshare(ctx context.Context, log logger, sessionLogger liveshareLogger, apiClient apiClient, codespace *api.Codespace) (*liveshare.Session, error) {
|
||||
var startedCodespace bool
|
||||
if codespace.Environment.State != api.CodespaceEnvironmentStateAvailable {
|
||||
startedCodespace = true
|
||||
log.Print("Starting your codespace...")
|
||||
if err := apiClient.StartCodespace(ctx, codespace.Name); err != nil {
|
||||
return nil, fmt.Errorf("error starting codespace: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
for retries := 0; !connectionReady(codespace); retries++ {
|
||||
if retries > 1 {
|
||||
if retries%2 == 0 {
|
||||
log.Print(".")
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
if retries == 30 {
|
||||
return nil, errors.New("timed out while waiting for the codespace to start")
|
||||
}
|
||||
|
||||
var err error
|
||||
codespace, err = apiClient.GetCodespace(ctx, codespace.Name, true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting codespace: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if startedCodespace {
|
||||
fmt.Print("\n")
|
||||
}
|
||||
|
||||
log.Println("Connecting to your codespace...")
|
||||
|
||||
return liveshare.Connect(ctx, liveshare.Options{
|
||||
ClientName: "gh",
|
||||
SessionID: codespace.Connection.SessionID,
|
||||
SessionToken: codespace.Connection.SessionToken,
|
||||
RelaySAS: codespace.Connection.RelaySAS,
|
||||
RelayEndpoint: codespace.Connection.RelayEndpoint,
|
||||
HostPublicKeys: codespace.Connection.HostPublicKeys,
|
||||
Logger: sessionLogger,
|
||||
})
|
||||
}
|
||||
90
internal/codespaces/ssh.go
Normal file
90
internal/codespaces/ssh.go
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
package codespaces
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Shell runs an interactive secure shell over an existing
|
||||
// port-forwarding session. It runs until the shell is terminated
|
||||
// (including by cancellation of the context).
|
||||
func Shell(ctx context.Context, log logger, sshArgs []string, port int, destination string, usingCustomPort bool) error {
|
||||
cmd, connArgs, err := newSSHCommand(ctx, port, destination, sshArgs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create ssh command: %w", err)
|
||||
}
|
||||
|
||||
if usingCustomPort {
|
||||
log.Println("Connection Details: ssh " + destination + " " + strings.Join(connArgs, " "))
|
||||
}
|
||||
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// NewRemoteCommand returns an exec.Cmd that will securely run a shell
|
||||
// command on the remote machine.
|
||||
func NewRemoteCommand(ctx context.Context, tunnelPort int, destination string, sshArgs ...string) (*exec.Cmd, error) {
|
||||
cmd, _, err := newSSHCommand(ctx, tunnelPort, destination, sshArgs)
|
||||
return cmd, err
|
||||
}
|
||||
|
||||
// newSSHCommand populates an exec.Cmd to run a command (or if blank,
|
||||
// an interactive shell) over ssh.
|
||||
func newSSHCommand(ctx context.Context, port int, dst string, cmdArgs []string) (*exec.Cmd, []string, error) {
|
||||
connArgs := []string{"-p", strconv.Itoa(port), "-o", "NoHostAuthenticationForLocalhost=yes"}
|
||||
|
||||
// The ssh command syntax is: ssh [flags] user@host command [args...]
|
||||
// There is no way to specify the user@host destination as a flag.
|
||||
// Unfortunately, that means we need to know which user-provided words are
|
||||
// SSH flags and which are command arguments so that we can place
|
||||
// them before or after the destination, and that means we need to know all
|
||||
// the flags and their arities.
|
||||
cmdArgs, command, err := parseSSHArgs(cmdArgs)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
cmdArgs = append(cmdArgs, connArgs...)
|
||||
cmdArgs = append(cmdArgs, "-C") // Compression
|
||||
cmdArgs = append(cmdArgs, dst) // user@host
|
||||
|
||||
if command != nil {
|
||||
cmdArgs = append(cmdArgs, command...)
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx, "ssh", cmdArgs...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
return cmd, connArgs, nil
|
||||
}
|
||||
|
||||
// parseSSHArgs parses SSH arguments into two distinct slices of flags and command.
|
||||
// It returns an error if a unary flag is provided without an argument.
|
||||
func parseSSHArgs(args []string) (cmdArgs, command []string, err error) {
|
||||
for i := 0; i < len(args); i++ {
|
||||
arg := args[i]
|
||||
|
||||
// if we've started parsing the command, set it to the rest of the args
|
||||
if !strings.HasPrefix(arg, "-") {
|
||||
command = args[i:]
|
||||
break
|
||||
}
|
||||
|
||||
cmdArgs = append(cmdArgs, arg)
|
||||
if len(arg) == 2 && strings.Contains("bcDeFIiLlmOopRSWw", arg[1:2]) {
|
||||
if i++; i == len(args) {
|
||||
return nil, nil, fmt.Errorf("ssh flag: %s requires an argument", arg)
|
||||
}
|
||||
|
||||
cmdArgs = append(cmdArgs, args[i])
|
||||
}
|
||||
}
|
||||
|
||||
return cmdArgs, command, nil
|
||||
}
|
||||
105
internal/codespaces/ssh_test.go
Normal file
105
internal/codespaces/ssh_test.go
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
package codespaces
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseSSHArgs(t *testing.T) {
|
||||
type testCase struct {
|
||||
Args []string
|
||||
ParsedArgs []string
|
||||
Command []string
|
||||
Error string
|
||||
}
|
||||
|
||||
testCases := []testCase{
|
||||
{}, // empty test case
|
||||
{
|
||||
Args: []string{"-X", "-Y"},
|
||||
ParsedArgs: []string{"-X", "-Y"},
|
||||
Command: nil,
|
||||
},
|
||||
{
|
||||
Args: []string{"-X", "-Y", "-o", "someoption=test"},
|
||||
ParsedArgs: []string{"-X", "-Y", "-o", "someoption=test"},
|
||||
Command: nil,
|
||||
},
|
||||
{
|
||||
Args: []string{"-X", "-Y", "-o", "someoption=test", "somecommand"},
|
||||
ParsedArgs: []string{"-X", "-Y", "-o", "someoption=test"},
|
||||
Command: []string{"somecommand"},
|
||||
},
|
||||
{
|
||||
Args: []string{"-X", "-Y", "-o", "someoption=test", "echo", "test"},
|
||||
ParsedArgs: []string{"-X", "-Y", "-o", "someoption=test"},
|
||||
Command: []string{"echo", "test"},
|
||||
},
|
||||
{
|
||||
Args: []string{"somecommand"},
|
||||
ParsedArgs: []string{},
|
||||
Command: []string{"somecommand"},
|
||||
},
|
||||
{
|
||||
Args: []string{"echo", "test"},
|
||||
ParsedArgs: []string{},
|
||||
Command: []string{"echo", "test"},
|
||||
},
|
||||
{
|
||||
Args: []string{"-v", "echo", "hello", "world"},
|
||||
ParsedArgs: []string{"-v"},
|
||||
Command: []string{"echo", "hello", "world"},
|
||||
},
|
||||
{
|
||||
Args: []string{"-L", "-l"},
|
||||
ParsedArgs: []string{"-L", "-l"},
|
||||
Command: nil,
|
||||
},
|
||||
{
|
||||
Args: []string{"-v", "echo", "-n", "test"},
|
||||
ParsedArgs: []string{"-v"},
|
||||
Command: []string{"echo", "-n", "test"},
|
||||
},
|
||||
{
|
||||
Args: []string{"-v", "echo", "-b", "test"},
|
||||
ParsedArgs: []string{"-v"},
|
||||
Command: []string{"echo", "-b", "test"},
|
||||
},
|
||||
{
|
||||
Args: []string{"-b"},
|
||||
ParsedArgs: nil,
|
||||
Command: nil,
|
||||
Error: "ssh flag: -b requires an argument",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tcase := range testCases {
|
||||
args, command, err := parseSSHArgs(tcase.Args)
|
||||
if tcase.Error != "" {
|
||||
if err == nil {
|
||||
t.Errorf("expected error and got nil: %#v", tcase)
|
||||
}
|
||||
|
||||
if err.Error() != tcase.Error {
|
||||
t.Errorf("error does not match expected error, got: '%s', expected: '%s'", err.Error(), tcase.Error)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v on test case: %#v", err, tcase)
|
||||
continue
|
||||
}
|
||||
|
||||
argsStr, parsedArgsStr := fmt.Sprintf("%s", args), fmt.Sprintf("%s", tcase.ParsedArgs)
|
||||
if argsStr != parsedArgsStr {
|
||||
t.Errorf("args do not match parsed args. got: '%s', expected: '%s'", argsStr, parsedArgsStr)
|
||||
}
|
||||
|
||||
commandStr, parsedCommandStr := fmt.Sprintf("%s", command), fmt.Sprintf("%s", tcase.Command)
|
||||
if commandStr != parsedCommandStr {
|
||||
t.Errorf("command does not match parsed command. got: '%s', expected: '%s'", commandStr, parsedCommandStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
117
internal/codespaces/states.go
Normal file
117
internal/codespaces/states.go
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
package codespaces
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cli/cli/v2/internal/codespaces/api"
|
||||
"github.com/cli/cli/v2/pkg/liveshare"
|
||||
)
|
||||
|
||||
// PostCreateStateStatus is a string value representing the different statuses a state can have.
|
||||
type PostCreateStateStatus string
|
||||
|
||||
func (p PostCreateStateStatus) String() string {
|
||||
return strings.Title(string(p))
|
||||
}
|
||||
|
||||
const (
|
||||
PostCreateStateRunning PostCreateStateStatus = "running"
|
||||
PostCreateStateSuccess PostCreateStateStatus = "succeeded"
|
||||
PostCreateStateFailed PostCreateStateStatus = "failed"
|
||||
)
|
||||
|
||||
// PostCreateState is a combination of a state and status value that is captured
|
||||
// during codespace creation.
|
||||
type PostCreateState struct {
|
||||
Name string `json:"name"`
|
||||
Status PostCreateStateStatus `json:"status"`
|
||||
}
|
||||
|
||||
// PollPostCreateStates watches for state changes in a codespace,
|
||||
// and calls the supplied poller for each batch of state changes.
|
||||
// It runs until it encounters an error, including cancellation of the context.
|
||||
func PollPostCreateStates(ctx context.Context, logger logger, apiClient apiClient, codespace *api.Codespace, poller func([]PostCreateState)) (err error) {
|
||||
noopLogger := log.New(ioutil.Discard, "", 0)
|
||||
|
||||
session, err := ConnectToLiveshare(ctx, logger, noopLogger, apiClient, codespace)
|
||||
if err != nil {
|
||||
return fmt.Errorf("connect to Live Share: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if closeErr := session.Close(); err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
}()
|
||||
|
||||
// Ensure local port is listening before client (getPostCreateOutput) connects.
|
||||
listen, err := net.Listen("tcp", ":0") // arbitrary port
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
localPort := listen.Addr().(*net.TCPAddr).Port
|
||||
|
||||
logger.Println("Fetching SSH Details...")
|
||||
remoteSSHServerPort, sshUser, err := session.StartSSHServer(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting ssh server details: %w", err)
|
||||
}
|
||||
|
||||
tunnelClosed := make(chan error, 1) // buffered to avoid sender stuckness
|
||||
go func() {
|
||||
fwd := liveshare.NewPortForwarder(session, "sshd", remoteSSHServerPort, false)
|
||||
tunnelClosed <- fwd.ForwardToListener(ctx, listen) // error is non-nil
|
||||
}()
|
||||
|
||||
t := time.NewTicker(1 * time.Second)
|
||||
defer t.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
|
||||
case err := <-tunnelClosed:
|
||||
return fmt.Errorf("connection failed: %w", err)
|
||||
|
||||
case <-t.C:
|
||||
states, err := getPostCreateOutput(ctx, localPort, sshUser)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get post create output: %w", err)
|
||||
}
|
||||
|
||||
poller(states)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getPostCreateOutput(ctx context.Context, tunnelPort int, user string) ([]PostCreateState, error) {
|
||||
cmd, err := NewRemoteCommand(
|
||||
ctx, tunnelPort, fmt.Sprintf("%s@localhost", user),
|
||||
"cat /workspaces/.codespaces/shared/postCreateOutput.json",
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("remote command: %w", err)
|
||||
}
|
||||
|
||||
stdout := new(bytes.Buffer)
|
||||
cmd.Stdout = stdout
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, fmt.Errorf("run command: %w", err)
|
||||
}
|
||||
var output struct {
|
||||
Steps []PostCreateState `json:"steps"`
|
||||
}
|
||||
if err := json.Unmarshal(stdout.Bytes(), &output); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal output: %w", err)
|
||||
}
|
||||
|
||||
return output.Steps, nil
|
||||
}
|
||||
|
|
@ -9,7 +9,6 @@ import (
|
|||
"runtime"
|
||||
"syscall"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
|
|
@ -40,8 +39,9 @@ func ConfigDir() string {
|
|||
path = filepath.Join(d, ".config", "gh")
|
||||
}
|
||||
|
||||
// If the path does not exist try migrating config from default paths
|
||||
if !dirExists(path) {
|
||||
// If the path does not exist and the GH_CONFIG_DIR flag is not set try
|
||||
// migrating config from default paths.
|
||||
if !dirExists(path) && os.Getenv(GH_CONFIG_DIR) == "" {
|
||||
_ = autoMigrateConfigDir(path)
|
||||
}
|
||||
|
||||
|
|
@ -92,37 +92,25 @@ func DataDir() string {
|
|||
var errSamePath = errors.New("same path")
|
||||
var errNotExist = errors.New("not exist")
|
||||
|
||||
// Check default paths (os.UserHomeDir, and homedir.Dir) for existing configs
|
||||
// Check default path, os.UserHomeDir, for existing configs
|
||||
// If configs exist then move them to newPath
|
||||
// TODO: Remove support for homedir.Dir location in v2
|
||||
func autoMigrateConfigDir(newPath string) error {
|
||||
path, err := os.UserHomeDir()
|
||||
if oldPath := filepath.Join(path, ".config", "gh"); err == nil && dirExists(oldPath) {
|
||||
return migrateDir(oldPath, newPath)
|
||||
}
|
||||
|
||||
path, err = homedir.Dir()
|
||||
if oldPath := filepath.Join(path, ".config", "gh"); err == nil && dirExists(oldPath) {
|
||||
return migrateDir(oldPath, newPath)
|
||||
}
|
||||
|
||||
return errNotExist
|
||||
}
|
||||
|
||||
// Check default paths (os.UserHomeDir, and homedir.Dir) for existing state file (state.yml)
|
||||
// Check default path, os.UserHomeDir, for existing state file (state.yml)
|
||||
// If state file exist then move it to newPath
|
||||
// TODO: Remove support for homedir.Dir location in v2
|
||||
func autoMigrateStateDir(newPath string) error {
|
||||
path, err := os.UserHomeDir()
|
||||
if oldPath := filepath.Join(path, ".config", "gh"); err == nil && dirExists(oldPath) {
|
||||
return migrateFile(oldPath, newPath, "state.yml")
|
||||
}
|
||||
|
||||
path, err = homedir.Dir()
|
||||
if oldPath := filepath.Join(path, ".config", "gh"); err == nil && dirExists(oldPath) {
|
||||
return migrateFile(oldPath, newPath, "state.yml")
|
||||
}
|
||||
|
||||
return errNotExist
|
||||
}
|
||||
|
||||
|
|
@ -180,26 +168,10 @@ func ParseDefaultConfig() (Config, error) {
|
|||
func HomeDirPath(subdir string) (string, error) {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
// TODO: remove go-homedir fallback in GitHub CLI v2
|
||||
if legacyDir, err := homedir.Dir(); err == nil {
|
||||
return filepath.Join(legacyDir, subdir), nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
newPath := filepath.Join(homeDir, subdir)
|
||||
if s, err := os.Stat(newPath); err == nil && s.IsDir() {
|
||||
return newPath, nil
|
||||
}
|
||||
|
||||
// TODO: remove go-homedir fallback in GitHub CLI v2
|
||||
if legacyDir, err := homedir.Dir(); err == nil {
|
||||
legacyPath := filepath.Join(legacyDir, subdir)
|
||||
if s, err := os.Stat(legacyPath); err == nil && s.IsDir() {
|
||||
return legacyPath, nil
|
||||
}
|
||||
}
|
||||
|
||||
return newPath, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,6 +55,11 @@ var configOptions = []ConfigOption{
|
|||
Description: "the path to a unix socket through which to make HTTP connection",
|
||||
DefaultValue: "",
|
||||
},
|
||||
{
|
||||
Key: "browser",
|
||||
Description: "the web browser to use for opening URLs",
|
||||
DefaultValue: "",
|
||||
},
|
||||
}
|
||||
|
||||
func ConfigOptions() []ConfigOption {
|
||||
|
|
@ -193,6 +198,15 @@ func NewBlankRoot() *yaml.Node {
|
|||
Kind: yaml.ScalarNode,
|
||||
Value: "",
|
||||
},
|
||||
{
|
||||
HeadComment: "What web browser gh should use when opening URLs. If blank, will refer to environment.",
|
||||
Kind: yaml.ScalarNode,
|
||||
Value: "browser",
|
||||
},
|
||||
{
|
||||
Kind: yaml.ScalarNode,
|
||||
Value: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@ func Test_defaultConfig(t *testing.T) {
|
|||
co: pr checkout
|
||||
# The path to a unix socket through which send HTTP connections. If blank, HTTP traffic will be handled by net/http.DefaultTransport.
|
||||
http_unix_socket:
|
||||
# What web browser gh should use when opening URLs. If blank, will refer to environment.
|
||||
browser:
|
||||
`)
|
||||
assert.Equal(t, expected, mainBuf.String())
|
||||
assert.Equal(t, "", hostsBuf.String())
|
||||
|
|
@ -69,6 +71,10 @@ func Test_defaultConfig(t *testing.T) {
|
|||
assert.Equal(t, len(aliases.All()), 1)
|
||||
expansion, _ := aliases.Get("co")
|
||||
assert.Equal(t, expansion, "pr checkout")
|
||||
|
||||
browser, err := cfg.Get("", "browser")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "", browser)
|
||||
}
|
||||
|
||||
func Test_ValidateValue(t *testing.T) {
|
||||
|
|
@ -106,4 +112,7 @@ func Test_ValidateKey(t *testing.T) {
|
|||
|
||||
err = ValidateKey("http_unix_socket")
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = ValidateKey("browser")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/cli/cli/internal/ghinstance"
|
||||
"github.com/cli/cli/v2/internal/ghinstance"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -106,3 +106,11 @@ func AuthTokenProvidedFromEnv() bool {
|
|||
os.Getenv(GH_TOKEN) != "" ||
|
||||
os.Getenv(GITHUB_TOKEN) != ""
|
||||
}
|
||||
|
||||
func IsHostEnv(src string) bool {
|
||||
return src == GH_HOST
|
||||
}
|
||||
|
||||
func IsEnterpriseEnv(src string) bool {
|
||||
return src == GH_ENTERPRISE_TOKEN || src == GITHUB_ENTERPRISE_TOKEN
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ func TestInheritEnv(t *testing.T) {
|
|||
baseConfig: ``,
|
||||
hostname: "github.com",
|
||||
wants: wants{
|
||||
hosts: []string(nil),
|
||||
hosts: []string{},
|
||||
token: "",
|
||||
source: ".config.gh.config.yml",
|
||||
writeable: true,
|
||||
|
|
@ -104,7 +104,7 @@ func TestInheritEnv(t *testing.T) {
|
|||
GITHUB_ENTERPRISE_TOKEN: "ENTOKEN",
|
||||
hostname: "example.org",
|
||||
wants: wants{
|
||||
hosts: []string(nil),
|
||||
hosts: []string{},
|
||||
token: "ENTOKEN",
|
||||
source: "GITHUB_ENTERPRISE_TOKEN",
|
||||
writeable: false,
|
||||
|
|
@ -116,7 +116,7 @@ func TestInheritEnv(t *testing.T) {
|
|||
GH_ENTERPRISE_TOKEN: "ENTOKEN",
|
||||
hostname: "example.org",
|
||||
wants: wants{
|
||||
hosts: []string(nil),
|
||||
hosts: []string{},
|
||||
token: "ENTOKEN",
|
||||
source: "GH_ENTERPRISE_TOKEN",
|
||||
writeable: false,
|
||||
|
|
@ -221,7 +221,7 @@ func TestInheritEnv(t *testing.T) {
|
|||
GITHUB_ENTERPRISE_TOKEN: "GITHUBTOKEN",
|
||||
hostname: "example.org",
|
||||
wants: wants{
|
||||
hosts: []string(nil),
|
||||
hosts: []string{},
|
||||
token: "GHTOKEN",
|
||||
source: "GH_ENTERPRISE_TOKEN",
|
||||
writeable: false,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/cli/cli/internal/ghinstance"
|
||||
"github.com/cli/cli/v2/internal/ghinstance"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
|
|
@ -104,7 +104,7 @@ func (c *fileConfig) UnsetHost(hostname string) {
|
|||
func (c *fileConfig) configForHost(hostname string) (*HostConfig, error) {
|
||||
hosts, err := c.hostEntries()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse hosts config: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, hc := range hosts {
|
||||
|
|
@ -209,7 +209,7 @@ func (c *fileConfig) Aliases() (*AliasConfig, error) {
|
|||
func (c *fileConfig) hostEntries() ([]*HostConfig, error) {
|
||||
entry, err := c.FindEntry("hosts")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not find hosts config: %w", err)
|
||||
return []*HostConfig{}, nil
|
||||
}
|
||||
|
||||
hostConfigs, err := c.parseHosts(entry.ValueNode)
|
||||
|
|
|
|||
15
internal/config/from_file_test.go
Normal file
15
internal/config/from_file_test.go
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_fileConfig_Hosts(t *testing.T) {
|
||||
c := NewBlankConfig()
|
||||
hosts, err := c.Hosts()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{}, hosts)
|
||||
}
|
||||
|
|
@ -101,7 +101,7 @@ func TestGenManSeeAlso(t *testing.T) {
|
|||
if err := assertNextLineEquals(scanner, ".PP"); err != nil {
|
||||
t.Fatalf("First line after SEE ALSO wasn't break-indent: %v", err)
|
||||
}
|
||||
if err := assertNextLineEquals(scanner, `\fBroot\-bbb(1)\fP, \fBroot\-ccc(1)\fP`); err != nil {
|
||||
if err := assertNextLineEquals(scanner, `\fBroot-bbb(1)\fP, \fBroot-ccc(1)\fP`); err != nil {
|
||||
t.Fatalf("Second line after SEE ALSO wasn't correct: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import (
|
|||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/cli/cli/git"
|
||||
"github.com/cli/cli/internal/ghinstance"
|
||||
"github.com/cli/cli/v2/git"
|
||||
"github.com/cli/cli/v2/internal/ghinstance"
|
||||
)
|
||||
|
||||
// Interface describes an object that represents a GitHub repository
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cli/cli/api"
|
||||
"github.com/cli/cli/internal/ghinstance"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/ghinstance"
|
||||
"github.com/hashicorp/go-version"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import (
|
|||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/api"
|
||||
"github.com/cli/cli/pkg/httpmock"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/pkg/httpmock"
|
||||
)
|
||||
|
||||
func TestCheckForUpdate(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -56,8 +56,5 @@ func actionsExplainer(cs *iostreams.ColorScheme) string {
|
|||
gh workflow run: Trigger a workflow_dispatch run for a workflow file
|
||||
|
||||
To see more help, run 'gh help workflow <subcommand>'
|
||||
|
||||
For more in depth help including examples, see online documentation at:
|
||||
<https://docs.github.com/en/actions/guides/managing-github-actions-with-github-cli>
|
||||
`, header, runHeader, workflowHeader)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ package alias
|
|||
|
||||
import (
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
deleteCmd "github.com/cli/cli/pkg/cmd/alias/delete"
|
||||
listCmd "github.com/cli/cli/pkg/cmd/alias/list"
|
||||
setCmd "github.com/cli/cli/pkg/cmd/alias/set"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
deleteCmd "github.com/cli/cli/v2/pkg/cmd/alias/delete"
|
||||
listCmd "github.com/cli/cli/v2/pkg/cmd/alias/list"
|
||||
setCmd "github.com/cli/cli/v2/pkg/cmd/alias/set"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ package delete
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/google/shlex"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import (
|
|||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/pkg/findsh"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/pkg/findsh"
|
||||
"github.com/google/shlex"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
)
|
||||
|
||||
func TestExpandAlias(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ import (
|
|||
"sort"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/utils"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/google/shlex"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
|
@ -34,48 +34,36 @@ func NewCmdSet(f *cmdutil.Factory, runF func(*SetOptions) error) *cobra.Command
|
|||
Use: "set <alias> <expansion>",
|
||||
Short: "Create a shortcut for a gh command",
|
||||
Long: heredoc.Doc(`
|
||||
Declare a word as a command alias that will expand to the specified command(s).
|
||||
Define a word that will expand to a full gh command when invoked.
|
||||
|
||||
The expansion may specify additional arguments and flags. If the expansion
|
||||
includes positional placeholders such as '$1', '$2', etc., any extra arguments
|
||||
that follow the invocation of an alias will be inserted appropriately.
|
||||
Reads from STDIN if '-' is specified as the expansion parameter. This can be useful
|
||||
for commands with mixed quotes or multiple lines.
|
||||
The expansion may specify additional arguments and flags. If the expansion includes
|
||||
positional placeholders such as "$1", extra arguments that follow the alias will be
|
||||
inserted appropriately. Otherwise, extra arguments will be appended to the expanded
|
||||
command.
|
||||
|
||||
If '--shell' is specified, the alias will be run through a shell interpreter (sh). This allows you
|
||||
to compose commands with "|" or redirect with ">". Note that extra arguments following the alias
|
||||
will not be automatically passed to the expanded expression. To have a shell alias receive
|
||||
arguments, you must explicitly accept them using "$1", "$2", etc., or "$@" to accept all of them.
|
||||
Use "-" as expansion argument to read the expansion string from standard input. This
|
||||
is useful to avoid quoting issues when defining expansions.
|
||||
|
||||
Platform note: on Windows, shell aliases are executed via "sh" as installed by Git For Windows. If
|
||||
you have installed git on Windows in some other way, shell aliases may not work for you.
|
||||
|
||||
Quotes must always be used when defining a command as in the examples unless you pass '-'
|
||||
as the expansion parameter and pipe your command to 'gh alias set'.
|
||||
If the expansion starts with "!" or if "--shell" was given, the expansion is a shell
|
||||
expression that will be evaluated through the "sh" interpreter when the alias is
|
||||
invoked. This allows for chaining multiple commands via piping and redirection.
|
||||
`),
|
||||
Example: heredoc.Doc(`
|
||||
# note: Command Prompt on Windows requires using double quotes for arguments
|
||||
$ gh alias set pv 'pr view'
|
||||
$ gh pv -w 123
|
||||
#=> gh pr view -w 123
|
||||
|
||||
$ gh alias set bugs 'issue list --label="bugs"'
|
||||
$ gh pv -w 123 #=> gh pr view -w 123
|
||||
|
||||
$ gh alias set bugs 'issue list --label=bugs'
|
||||
$ gh bugs
|
||||
|
||||
$ gh alias set homework 'issue list --assignee @me'
|
||||
$ gh homework
|
||||
|
||||
$ gh alias set epicsBy 'issue list --author="$1" --label="epic"'
|
||||
$ gh epicsBy vilmibm
|
||||
#=> gh issue list --author="vilmibm" --label="epic"
|
||||
$ gh epicsBy vilmibm #=> gh issue list --author="vilmibm" --label="epic"
|
||||
|
||||
$ gh alias set --shell igrep 'gh issue list --label="$1" | grep $2'
|
||||
$ gh igrep epic foo
|
||||
#=> gh issue list --label="epic" | grep "foo"
|
||||
|
||||
# users.txt contains multiline 'api graphql -F name="$1" ...' with mixed quotes
|
||||
$ gh alias set users - < users.txt
|
||||
$ gh users octocat
|
||||
#=> gh api graphql -F name="octocat" ...
|
||||
$ gh alias set --shell igrep 'gh issue list --label="$1" | grep "$2"'
|
||||
$ gh igrep epic foo #=> gh issue list --label="epic" | grep "foo"
|
||||
`),
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
|
@ -94,7 +82,7 @@ func NewCmdSet(f *cmdutil.Factory, runF func(*SetOptions) error) *cobra.Command
|
|||
return true
|
||||
}
|
||||
|
||||
for _, ext := range f.ExtensionManager.List() {
|
||||
for _, ext := range f.ExtensionManager.List(false) {
|
||||
if ext.Name() == split[0] {
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/extensions"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/test"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/extensions"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/test"
|
||||
"github.com/google/shlex"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -30,7 +30,7 @@ func runCommand(cfg config.Config, isTTY bool, cli string, in string) (*test.Cmd
|
|||
return cfg, nil
|
||||
},
|
||||
ExtensionManager: &extensions.ExtensionManagerMock{
|
||||
ListFunc: func() []extensions.Extension {
|
||||
ListFunc: func(bool) []extensions.Extension {
|
||||
return []extensions.Extension{}
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -17,14 +17,14 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/api"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/internal/ghinstance"
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/export"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/pkg/jsoncolor"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghinstance"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/export"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/jsoncolor"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -213,7 +213,7 @@ func NewCmdApi(f *cmdutil.Factory, runF func(*ApiOptions) error) *cobra.Command
|
|||
cmd.Flags().StringSliceVarP(&opts.Previews, "preview", "p", nil, "Opt into GitHub API previews")
|
||||
cmd.Flags().BoolVarP(&opts.ShowResponseHeaders, "include", "i", false, "Include HTTP response headers in the output")
|
||||
cmd.Flags().BoolVar(&opts.Paginate, "paginate", false, "Make additional HTTP requests to fetch all pages of results")
|
||||
cmd.Flags().StringVar(&opts.RequestInputFile, "input", "", "The `file` to use as body for the HTTP request")
|
||||
cmd.Flags().StringVar(&opts.RequestInputFile, "input", "", "The `file` to use as body for the HTTP request (use \"-\" to read from standard input)")
|
||||
cmd.Flags().BoolVar(&opts.Silent, "silent", false, "Do not print the response body")
|
||||
cmd.Flags().StringVarP(&opts.Template, "template", "t", "", "Format the response using a Go template")
|
||||
cmd.Flags().StringVarP(&opts.FilterOutput, "jq", "q", "", "Query to select values from the response using jq syntax")
|
||||
|
|
@ -294,6 +294,8 @@ func apiRun(opts *ApiOptions) error {
|
|||
host = opts.Hostname
|
||||
}
|
||||
|
||||
template := export.NewTemplate(opts.IO, opts.Template)
|
||||
|
||||
hasNextPage := true
|
||||
for hasNextPage {
|
||||
resp, err := httpRequest(httpClient, host, method, requestPath, requestBody, requestHeaders)
|
||||
|
|
@ -301,7 +303,7 @@ func apiRun(opts *ApiOptions) error {
|
|||
return err
|
||||
}
|
||||
|
||||
endCursor, err := processResponse(resp, opts, headersOutputStream)
|
||||
endCursor, err := processResponse(resp, opts, headersOutputStream, &template)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -324,10 +326,10 @@ func apiRun(opts *ApiOptions) error {
|
|||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return template.End()
|
||||
}
|
||||
|
||||
func processResponse(resp *http.Response, opts *ApiOptions, headersOutputStream io.Writer) (endCursor string, err error) {
|
||||
func processResponse(resp *http.Response, opts *ApiOptions, headersOutputStream io.Writer, template *export.Template) (endCursor string, err error) {
|
||||
if opts.ShowResponseHeaders {
|
||||
fmt.Fprintln(headersOutputStream, resp.Proto, resp.Status)
|
||||
printHeaders(headersOutputStream, resp.Header, opts.IO.ColorEnabled())
|
||||
|
|
@ -365,7 +367,7 @@ func processResponse(resp *http.Response, opts *ApiOptions, headersOutputStream
|
|||
}
|
||||
} else if opts.Template != "" {
|
||||
// TODO: reuse parsed template across pagination invocations
|
||||
err = export.ExecuteTemplate(opts.IO.Out, responseBody, opts.Template, opts.IO.ColorEnabled())
|
||||
err = template.Execute(responseBody)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,11 +13,12 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/git"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/git"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/export"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/google/shlex"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
|
@ -671,6 +672,101 @@ func Test_apiRun_paginationGraphQL(t *testing.T) {
|
|||
assert.Equal(t, "PAGE1_END", endCursor)
|
||||
}
|
||||
|
||||
func Test_apiRun_paginated_template(t *testing.T) {
|
||||
io, _, stdout, stderr := iostreams.Test()
|
||||
io.SetStdoutTTY(true)
|
||||
|
||||
requestCount := 0
|
||||
responses := []*http.Response{
|
||||
{
|
||||
StatusCode: 200,
|
||||
Header: http.Header{"Content-Type": []string{`application/json`}},
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString(`{
|
||||
"data": {
|
||||
"nodes": [
|
||||
{
|
||||
"page": 1,
|
||||
"caption": "page one"
|
||||
}
|
||||
],
|
||||
"pageInfo": {
|
||||
"endCursor": "PAGE1_END",
|
||||
"hasNextPage": true
|
||||
}
|
||||
}
|
||||
}`)),
|
||||
},
|
||||
{
|
||||
StatusCode: 200,
|
||||
Header: http.Header{"Content-Type": []string{`application/json`}},
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString(`{
|
||||
"data": {
|
||||
"nodes": [
|
||||
{
|
||||
"page": 20,
|
||||
"caption": "page twenty"
|
||||
}
|
||||
],
|
||||
"pageInfo": {
|
||||
"endCursor": "PAGE20_END",
|
||||
"hasNextPage": false
|
||||
}
|
||||
}
|
||||
}`)),
|
||||
},
|
||||
}
|
||||
|
||||
options := ApiOptions{
|
||||
IO: io,
|
||||
HttpClient: func() (*http.Client, error) {
|
||||
var tr roundTripper = func(req *http.Request) (*http.Response, error) {
|
||||
resp := responses[requestCount]
|
||||
resp.Request = req
|
||||
requestCount++
|
||||
return resp, nil
|
||||
}
|
||||
return &http.Client{Transport: tr}, nil
|
||||
},
|
||||
Config: func() (config.Config, error) {
|
||||
return config.NewBlankConfig(), nil
|
||||
},
|
||||
|
||||
RequestMethod: "POST",
|
||||
RequestPath: "graphql",
|
||||
Paginate: true,
|
||||
// test that templates executed per page properly render a table.
|
||||
Template: `{{range .data.nodes}}{{tablerow .page .caption}}{{end}}`,
|
||||
}
|
||||
|
||||
err := apiRun(&options)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, heredoc.Doc(`
|
||||
1 page one
|
||||
20 page twenty
|
||||
`), stdout.String(), "stdout")
|
||||
assert.Equal(t, "", stderr.String(), "stderr")
|
||||
|
||||
var requestData struct {
|
||||
Variables map[string]interface{}
|
||||
}
|
||||
|
||||
bb, err := ioutil.ReadAll(responses[0].Request.Body)
|
||||
require.NoError(t, err)
|
||||
err = json.Unmarshal(bb, &requestData)
|
||||
require.NoError(t, err)
|
||||
_, hasCursor := requestData.Variables["endCursor"].(string)
|
||||
assert.Equal(t, false, hasCursor)
|
||||
|
||||
bb, err = ioutil.ReadAll(responses[1].Request.Body)
|
||||
require.NoError(t, err)
|
||||
err = json.Unmarshal(bb, &requestData)
|
||||
require.NoError(t, err)
|
||||
endCursor, hasCursor := requestData.Variables["endCursor"].(string)
|
||||
assert.Equal(t, true, hasCursor)
|
||||
assert.Equal(t, "PAGE1_END", endCursor)
|
||||
}
|
||||
|
||||
func Test_apiRun_inputFile(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
@ -1167,10 +1263,15 @@ func Test_processResponse_template(t *testing.T) {
|
|||
]`)),
|
||||
}
|
||||
|
||||
_, err := processResponse(&resp, &ApiOptions{
|
||||
opts := ApiOptions{
|
||||
IO: io,
|
||||
Template: `{{range .}}{{.title}} ({{.labels | pluck "name" | join ", " }}){{"\n"}}{{end}}`,
|
||||
}, ioutil.Discard)
|
||||
}
|
||||
template := export.NewTemplate(io, opts.Template)
|
||||
_, err := processResponse(&resp, &opts, ioutil.Discard, &template)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = template.End()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, heredoc.Doc(`
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cli/cli/internal/ghinstance"
|
||||
"github.com/cli/cli/v2/internal/ghinstance"
|
||||
)
|
||||
|
||||
func httpRequest(client *http.Client, hostname string, method string, p string, params interface{}, headers []string) (*http.Response, error) {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ var linkRE = regexp.MustCompile(`<([^>]+)>;\s*rel="([^"]+)"`)
|
|||
|
||||
func findNextPage(resp *http.Response) (string, bool) {
|
||||
for _, m := range linkRE.FindAllStringSubmatch(resp.Header.Get("Link"), -1) {
|
||||
if len(m) >= 2 && m[2] == "next" {
|
||||
if len(m) > 2 && m[2] == "next" {
|
||||
return m[1], true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
gitCredentialCmd "github.com/cli/cli/pkg/cmd/auth/gitcredential"
|
||||
authLoginCmd "github.com/cli/cli/pkg/cmd/auth/login"
|
||||
authLogoutCmd "github.com/cli/cli/pkg/cmd/auth/logout"
|
||||
authRefreshCmd "github.com/cli/cli/pkg/cmd/auth/refresh"
|
||||
authStatusCmd "github.com/cli/cli/pkg/cmd/auth/status"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
gitCredentialCmd "github.com/cli/cli/v2/pkg/cmd/auth/gitcredential"
|
||||
authLoginCmd "github.com/cli/cli/v2/pkg/cmd/auth/login"
|
||||
authLogoutCmd "github.com/cli/cli/v2/pkg/cmd/auth/logout"
|
||||
authRefreshCmd "github.com/cli/cli/v2/pkg/cmd/auth/refresh"
|
||||
authStatusCmd "github.com/cli/cli/v2/pkg/cmd/auth/status"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import (
|
|||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
)
|
||||
|
||||
type tinyConfig map[string]string
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ import (
|
|||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/internal/ghinstance"
|
||||
"github.com/cli/cli/pkg/cmd/auth/shared"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/pkg/prompt"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghinstance"
|
||||
"github.com/cli/cli/v2/pkg/cmd/auth/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/prompt"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -38,8 +38,6 @@ func NewCmdLogin(f *cmdutil.Factory, runF func(*LoginOptions) error) *cobra.Comm
|
|||
IO: f.IOStreams,
|
||||
Config: f.Config,
|
||||
HttpClient: f.HttpClient,
|
||||
|
||||
MainExecutable: f.Executable,
|
||||
}
|
||||
|
||||
var tokenStdin bool
|
||||
|
|
@ -103,6 +101,7 @@ func NewCmdLogin(f *cmdutil.Factory, runF func(*LoginOptions) error) *cobra.Comm
|
|||
}
|
||||
}
|
||||
|
||||
opts.MainExecutable = f.Executable()
|
||||
if runF != nil {
|
||||
return runF(opts)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/internal/run"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/httpmock"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/pkg/prompt"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/run"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/httpmock"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/prompt"
|
||||
"github.com/google/shlex"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
|
@ -147,7 +147,8 @@ func Test_NewCmdLogin(t *testing.T) {
|
|||
t.Run(tt.name, func(t *testing.T) {
|
||||
io, stdin, _, _ := iostreams.Test()
|
||||
f := &cmdutil.Factory{
|
||||
IOStreams: io,
|
||||
IOStreams: io,
|
||||
Executable: func() string { return "/path/to/gh" },
|
||||
}
|
||||
|
||||
io.SetStdoutTTY(true)
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@ import (
|
|||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/api"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/pkg/prompt"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/prompt"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -74,6 +74,9 @@ func logoutRun(opts *LogoutOptions) error {
|
|||
|
||||
candidates, err := cfg.Hosts()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(candidates) == 0 {
|
||||
return fmt.Errorf("not logged in to any hosts")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ import (
|
|||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/httpmock"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/pkg/prompt"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/httpmock"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/prompt"
|
||||
"github.com/google/shlex"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ import (
|
|||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/internal/authflow"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/pkg/cmd/auth/shared"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/pkg/prompt"
|
||||
"github.com/cli/cli/v2/internal/authflow"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/pkg/cmd/auth/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/prompt"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -36,7 +36,6 @@ func NewCmdRefresh(f *cmdutil.Factory, runF func(*RefreshOptions) error) *cobra.
|
|||
_, err := authflow.AuthFlowWithConfig(cfg, io, hostname, "", scopes)
|
||||
return err
|
||||
},
|
||||
MainExecutable: f.Executable,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
|
@ -62,6 +61,7 @@ func NewCmdRefresh(f *cmdutil.Factory, runF func(*RefreshOptions) error) *cobra.
|
|||
return &cmdutil.FlagError{Err: errors.New("--hostname required when not running interactively")}
|
||||
}
|
||||
|
||||
opts.MainExecutable = f.Executable()
|
||||
if runF != nil {
|
||||
return runF(opts)
|
||||
}
|
||||
|
|
@ -83,6 +83,9 @@ func refreshRun(opts *RefreshOptions) error {
|
|||
|
||||
candidates, err := cfg.Hosts()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(candidates) == 0 {
|
||||
return fmt.Errorf("not logged in to any hosts. Use 'gh auth login' to authenticate with a host")
|
||||
}
|
||||
|
||||
|
|
@ -126,7 +129,9 @@ func refreshRun(opts *RefreshOptions) error {
|
|||
|
||||
var additionalScopes []string
|
||||
|
||||
credentialFlow := &shared.GitCredentialFlow{}
|
||||
credentialFlow := &shared.GitCredentialFlow{
|
||||
Executable: opts.MainExecutable,
|
||||
}
|
||||
gitProtocol, _ := cfg.Get(hostname, "git_protocol")
|
||||
if opts.Interactive && gitProtocol == "https" {
|
||||
if err := credentialFlow.Prompt(hostname); err != nil {
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ import (
|
|||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/httpmock"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/pkg/prompt"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/httpmock"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/prompt"
|
||||
"github.com/google/shlex"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
|
@ -88,7 +88,8 @@ func Test_NewCmdRefresh(t *testing.T) {
|
|||
t.Run(tt.name, func(t *testing.T) {
|
||||
io, _, _, _ := iostreams.Test()
|
||||
f := &cmdutil.Factory{
|
||||
IOStreams: io,
|
||||
IOStreams: io,
|
||||
Executable: func() string { return "/path/to/gh" },
|
||||
}
|
||||
io.SetStdinTTY(tt.tty)
|
||||
io.SetStdoutTTY(tt.tty)
|
||||
|
|
|
|||
|
|
@ -2,15 +2,16 @@ package shared
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/git"
|
||||
"github.com/cli/cli/internal/run"
|
||||
"github.com/cli/cli/pkg/prompt"
|
||||
"github.com/cli/cli/v2/git"
|
||||
"github.com/cli/cli/v2/internal/run"
|
||||
"github.com/cli/cli/v2/pkg/prompt"
|
||||
"github.com/google/shlex"
|
||||
)
|
||||
|
||||
|
|
@ -23,7 +24,8 @@ type GitCredentialFlow struct {
|
|||
}
|
||||
|
||||
func (flow *GitCredentialFlow) Prompt(hostname string) error {
|
||||
flow.helper, _ = gitCredentialHelper(hostname)
|
||||
var gitErr error
|
||||
flow.helper, gitErr = gitCredentialHelper(hostname)
|
||||
if isOurCredentialHelper(flow.helper) {
|
||||
flow.scopes = append(flow.scopes, "workflow")
|
||||
return nil
|
||||
|
|
@ -37,6 +39,9 @@ func (flow *GitCredentialFlow) Prompt(hostname string) error {
|
|||
return fmt.Errorf("could not prompt: %w", err)
|
||||
}
|
||||
if flow.shouldSetup {
|
||||
if isGitMissing(gitErr) {
|
||||
return gitErr
|
||||
}
|
||||
flow.scopes = append(flow.scopes, "workflow")
|
||||
}
|
||||
|
||||
|
|
@ -140,6 +145,14 @@ func isOurCredentialHelper(cmd string) bool {
|
|||
return strings.TrimSuffix(filepath.Base(args[0]), ".exe") == "gh"
|
||||
}
|
||||
|
||||
func isGitMissing(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
var errNotInstalled *git.NotInstalled
|
||||
return errors.As(err, &errNotInstalled)
|
||||
}
|
||||
|
||||
func shellQuote(s string) string {
|
||||
if strings.ContainsAny(s, " $") {
|
||||
return "'" + s + "'"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package shared
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/internal/run"
|
||||
"github.com/cli/cli/v2/internal/run"
|
||||
)
|
||||
|
||||
func TestGitCredentialSetup_configureExisting(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@ import (
|
|||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/api"
|
||||
"github.com/cli/cli/internal/authflow"
|
||||
"github.com/cli/cli/internal/ghinstance"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/pkg/prompt"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/authflow"
|
||||
"github.com/cli/cli/v2/internal/ghinstance"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/prompt"
|
||||
)
|
||||
|
||||
type iconfig interface {
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/internal/run"
|
||||
"github.com/cli/cli/pkg/httpmock"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/pkg/prompt"
|
||||
"github.com/cli/cli/v2/internal/run"
|
||||
"github.com/cli/cli/v2/pkg/httpmock"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/prompt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/cli/cli/api"
|
||||
"github.com/cli/cli/internal/ghinstance"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/ghinstance"
|
||||
)
|
||||
|
||||
type MissingScopesError struct {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/pkg/httpmock"
|
||||
"github.com/cli/cli/v2/pkg/httpmock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,10 +9,10 @@ import (
|
|||
"runtime"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/internal/run"
|
||||
"github.com/cli/cli/pkg/cmd/ssh-key/add"
|
||||
"github.com/cli/cli/pkg/prompt"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/run"
|
||||
"github.com/cli/cli/v2/pkg/cmd/ssh-key/add"
|
||||
"github.com/cli/cli/v2/pkg/prompt"
|
||||
"github.com/cli/safeexec"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/api"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/pkg/cmd/auth/shared"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/pkg/cmd/auth/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -69,7 +69,10 @@ func statusRun(opts *StatusOptions) error {
|
|||
statusInfo := map[string][]string{}
|
||||
|
||||
hostnames, err := cfg.Hosts()
|
||||
if len(hostnames) == 0 || err != nil {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(hostnames) == 0 {
|
||||
fmt.Fprintf(stderr,
|
||||
"You are not logged into any GitHub hosts. Run %s to authenticate.\n", cs.Bold("gh auth login"))
|
||||
return cmdutil.SilentError
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ import (
|
|||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/httpmock"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/httpmock"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/google/shlex"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,18 +3,18 @@ package browse
|
|||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/api"
|
||||
"github.com/cli/cli/git"
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/git"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -31,6 +31,7 @@ type BrowseOptions struct {
|
|||
SelectorArg string
|
||||
|
||||
Branch string
|
||||
CommitFlag bool
|
||||
ProjectsFlag bool
|
||||
SettingsFlag bool
|
||||
WikiFlag bool
|
||||
|
|
@ -84,8 +85,9 @@ func NewCmdBrowse(f *cmdutil.Factory, runF func(*BrowseOptions) error) *cobra.Co
|
|||
}
|
||||
|
||||
if err := cmdutil.MutuallyExclusive(
|
||||
"specify only one of `--branch`, `--projects`, `--wiki`, or `--settings`",
|
||||
"specify only one of `--branch`, `--commit`, `--projects`, `--wiki`, or `--settings`",
|
||||
opts.Branch != "",
|
||||
opts.CommitFlag,
|
||||
opts.WikiFlag,
|
||||
opts.SettingsFlag,
|
||||
opts.ProjectsFlag,
|
||||
|
|
@ -105,6 +107,7 @@ func NewCmdBrowse(f *cmdutil.Factory, runF func(*BrowseOptions) error) *cobra.Co
|
|||
cmd.Flags().BoolVarP(&opts.WikiFlag, "wiki", "w", false, "Open repository wiki")
|
||||
cmd.Flags().BoolVarP(&opts.SettingsFlag, "settings", "s", false, "Open repository settings")
|
||||
cmd.Flags().BoolVarP(&opts.NoBrowserFlag, "no-browser", "n", false, "Print destination URL instead of opening the browser")
|
||||
cmd.Flags().BoolVarP(&opts.CommitFlag, "commit", "c", false, "Open the last commit")
|
||||
cmd.Flags().StringVarP(&opts.Branch, "branch", "b", "", "Select another branch by passing in the branch name")
|
||||
|
||||
return cmd
|
||||
|
|
@ -116,57 +119,108 @@ func runBrowse(opts *BrowseOptions) error {
|
|||
return fmt.Errorf("unable to determine base repository: %w", err)
|
||||
}
|
||||
|
||||
httpClient, err := opts.HttpClient()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create an http client: %w", err)
|
||||
if opts.CommitFlag {
|
||||
commit, err := git.LastCommit()
|
||||
if err == nil {
|
||||
opts.Branch = commit.Sha
|
||||
}
|
||||
}
|
||||
url := ghrepo.GenerateRepoURL(baseRepo, "")
|
||||
|
||||
if opts.SelectorArg == "" {
|
||||
if opts.ProjectsFlag {
|
||||
url += "/projects"
|
||||
}
|
||||
if opts.SettingsFlag {
|
||||
url += "/settings"
|
||||
}
|
||||
if opts.WikiFlag {
|
||||
url += "/wiki"
|
||||
}
|
||||
if opts.Branch != "" {
|
||||
url += "/tree/" + opts.Branch + "/"
|
||||
}
|
||||
} else {
|
||||
if isNumber(opts.SelectorArg) {
|
||||
url += "/issues/" + opts.SelectorArg
|
||||
} else {
|
||||
if opts.Branch != "" {
|
||||
url += "/tree/" + opts.Branch + "/"
|
||||
} else {
|
||||
apiClient := api.NewClientFromHTTP(httpClient)
|
||||
branchName, err := api.RepoDefaultBranch(apiClient, baseRepo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
url += "/tree/" + branchName + "/"
|
||||
}
|
||||
fileArg, err := parseFileArg(opts.SelectorArg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path := parsePathFromFileArg(fileArg)
|
||||
url += path
|
||||
}
|
||||
section, err := parseSection(baseRepo, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
url := ghrepo.GenerateRepoURL(baseRepo, section)
|
||||
|
||||
if opts.NoBrowserFlag {
|
||||
fmt.Fprintf(opts.IO.Out, "%s\n", url)
|
||||
return nil
|
||||
} else {
|
||||
if opts.IO.IsStdoutTTY() {
|
||||
fmt.Fprintf(opts.IO.Out, "now opening %s in browser\n", url)
|
||||
}
|
||||
return opts.Browser.Browse(url)
|
||||
_, err := fmt.Fprintln(opts.IO.Out, url)
|
||||
return err
|
||||
}
|
||||
|
||||
if opts.IO.IsStdoutTTY() {
|
||||
fmt.Fprintf(opts.IO.Out, "Opening %s in your browser.\n", utils.DisplayURL(url))
|
||||
}
|
||||
return opts.Browser.Browse(url)
|
||||
}
|
||||
|
||||
func parseSection(baseRepo ghrepo.Interface, opts *BrowseOptions) (string, error) {
|
||||
if opts.SelectorArg == "" {
|
||||
if opts.ProjectsFlag {
|
||||
return "projects", nil
|
||||
} else if opts.SettingsFlag {
|
||||
return "settings", nil
|
||||
} else if opts.WikiFlag {
|
||||
return "wiki", nil
|
||||
} else if opts.Branch == "" {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
if isNumber(opts.SelectorArg) {
|
||||
return fmt.Sprintf("issues/%s", opts.SelectorArg), nil
|
||||
}
|
||||
|
||||
filePath, rangeStart, rangeEnd, err := parseFile(opts.SelectorArg)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
branchName := opts.Branch
|
||||
if branchName == "" {
|
||||
httpClient, err := opts.HttpClient()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
apiClient := api.NewClientFromHTTP(httpClient)
|
||||
branchName, err = api.RepoDefaultBranch(apiClient, baseRepo)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error determining the default branch: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if rangeStart > 0 {
|
||||
var rangeFragment string
|
||||
if rangeEnd > 0 && rangeStart != rangeEnd {
|
||||
rangeFragment = fmt.Sprintf("L%d-L%d", rangeStart, rangeEnd)
|
||||
} else {
|
||||
rangeFragment = fmt.Sprintf("L%d", rangeStart)
|
||||
}
|
||||
return fmt.Sprintf("blob/%s/%s?plain=1#%s", branchName, filePath, rangeFragment), nil
|
||||
}
|
||||
return fmt.Sprintf("tree/%s/%s", branchName, filePath), nil
|
||||
}
|
||||
|
||||
func parseFile(f string) (p string, start int, end int, err error) {
|
||||
parts := strings.SplitN(f, ":", 3)
|
||||
if len(parts) > 2 {
|
||||
err = fmt.Errorf("invalid file argument: %q", f)
|
||||
return
|
||||
}
|
||||
|
||||
p = parts[0]
|
||||
if len(parts) < 2 {
|
||||
return
|
||||
}
|
||||
|
||||
if idx := strings.IndexRune(parts[1], '-'); idx >= 0 {
|
||||
start, err = strconv.Atoi(parts[1][:idx])
|
||||
if err != nil {
|
||||
err = fmt.Errorf("invalid file argument: %q", f)
|
||||
return
|
||||
}
|
||||
end, err = strconv.Atoi(parts[1][idx+1:])
|
||||
if err != nil {
|
||||
err = fmt.Errorf("invalid file argument: %q", f)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
start, err = strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
err = fmt.Errorf("invalid file argument: %q", f)
|
||||
}
|
||||
end = start
|
||||
return
|
||||
}
|
||||
|
||||
func parseFileArg(fileArg string) (string, error) {
|
||||
|
|
@ -189,7 +243,7 @@ func isNumber(arg string) bool {
|
|||
}
|
||||
|
||||
func parsePathFromFileArg(fileArg string) string {
|
||||
if !hasRelativePrefix(fileArg) {
|
||||
if filepath.IsAbs(fileArg) {
|
||||
return fileArg
|
||||
}
|
||||
path := filepath.Clean(filepath.Join(git.PathFromRepoRoot(), fileArg))
|
||||
|
|
@ -200,9 +254,3 @@ func parsePathFromFileArg(fileArg string) string {
|
|||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func hasRelativePrefix(fileArg string) bool {
|
||||
return strings.HasPrefix(fileArg, ".."+string(os.PathSeparator)) ||
|
||||
strings.HasPrefix(fileArg, "."+string(os.PathSeparator)) ||
|
||||
fileArg == "."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ import (
|
|||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/httpmock"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/httpmock"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/google/shlex"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
|
@ -103,6 +103,14 @@ func TestNewCmdBrowse(t *testing.T) {
|
|||
cli: "main.go main.go",
|
||||
wantsErr: true,
|
||||
},
|
||||
{
|
||||
name: "last commit flag",
|
||||
cli: "-c",
|
||||
wants: BrowseOptions{
|
||||
CommitFlag: true,
|
||||
},
|
||||
wantsErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
@ -134,8 +142,18 @@ func TestNewCmdBrowse(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func setGitDir(t *testing.T, dir string) {
|
||||
// taken from git_test.go
|
||||
old_GIT_DIR := os.Getenv("GIT_DIR")
|
||||
os.Setenv("GIT_DIR", dir)
|
||||
t.Cleanup(func() {
|
||||
os.Setenv("GIT_DIR", old_GIT_DIR)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_runBrowse(t *testing.T) {
|
||||
s := string(os.PathSeparator)
|
||||
setGitDir(t, "../../../git/fixtures/simple.git")
|
||||
tests := []struct {
|
||||
name string
|
||||
opts BrowseOptions
|
||||
|
|
@ -215,12 +233,54 @@ func Test_runBrowse(t *testing.T) {
|
|||
},
|
||||
baseRepo: ghrepo.New("ravocean", "angur"),
|
||||
defaultBranch: "trunk",
|
||||
expectedURL: "https://github.com/ravocean/angur/tree/trunk/path/to/file.txt#L32",
|
||||
expectedURL: "https://github.com/ravocean/angur/blob/trunk/path/to/file.txt?plain=1#L32",
|
||||
},
|
||||
{
|
||||
name: "file with line range",
|
||||
opts: BrowseOptions{
|
||||
SelectorArg: "path/to/file.txt:32-40",
|
||||
},
|
||||
baseRepo: ghrepo.New("ravocean", "angur"),
|
||||
defaultBranch: "trunk",
|
||||
expectedURL: "https://github.com/ravocean/angur/blob/trunk/path/to/file.txt?plain=1#L32-L40",
|
||||
},
|
||||
{
|
||||
name: "invalid default branch",
|
||||
opts: BrowseOptions{
|
||||
SelectorArg: "chocolate-pecan-pie.txt",
|
||||
},
|
||||
baseRepo: ghrepo.New("andrewhsu", "recipies"),
|
||||
defaultBranch: "",
|
||||
wantsErr: true,
|
||||
},
|
||||
{
|
||||
name: "file with invalid line number after colon",
|
||||
opts: BrowseOptions{
|
||||
SelectorArg: "laptime-notes.txt:w-9",
|
||||
},
|
||||
baseRepo: ghrepo.New("andrewhsu", "sonoma-raceway"),
|
||||
wantsErr: true,
|
||||
},
|
||||
{
|
||||
name: "file with invalid file format",
|
||||
opts: BrowseOptions{
|
||||
SelectorArg: "path/to/file.txt:32:32",
|
||||
},
|
||||
baseRepo: ghrepo.New("ttran112", "ttrain211"),
|
||||
wantsErr: true,
|
||||
},
|
||||
{
|
||||
name: "file with invalid line number",
|
||||
opts: BrowseOptions{
|
||||
SelectorArg: "path/to/file.txt:32:32",
|
||||
SelectorArg: "path/to/file.txt:32a",
|
||||
},
|
||||
baseRepo: ghrepo.New("ttran112", "ttrain211"),
|
||||
wantsErr: true,
|
||||
},
|
||||
{
|
||||
name: "file with invalid line range",
|
||||
opts: BrowseOptions{
|
||||
SelectorArg: "path/to/file.txt:32-abc",
|
||||
},
|
||||
baseRepo: ghrepo.New("ttran112", "ttrain211"),
|
||||
wantsErr: true,
|
||||
|
|
@ -243,7 +303,7 @@ func Test_runBrowse(t *testing.T) {
|
|||
},
|
||||
baseRepo: ghrepo.New("github", "ThankYouGitHub"),
|
||||
wantsErr: false,
|
||||
expectedURL: "https://github.com/github/ThankYouGitHub/tree/first-browse-pull/browse.go#L32",
|
||||
expectedURL: "https://github.com/github/ThankYouGitHub/blob/first-browse-pull/browse.go?plain=1#L32",
|
||||
},
|
||||
{
|
||||
name: "no browser with branch file and line number",
|
||||
|
|
@ -254,7 +314,26 @@ func Test_runBrowse(t *testing.T) {
|
|||
},
|
||||
baseRepo: ghrepo.New("mislav", "will_paginate"),
|
||||
wantsErr: false,
|
||||
expectedURL: "https://github.com/mislav/will_paginate/tree/3-0-stable/init.rb#L6",
|
||||
expectedURL: "https://github.com/mislav/will_paginate/blob/3-0-stable/init.rb?plain=1#L6",
|
||||
},
|
||||
{
|
||||
name: "open last commit",
|
||||
opts: BrowseOptions{
|
||||
CommitFlag: true,
|
||||
},
|
||||
baseRepo: ghrepo.New("vilmibm", "gh-user-status"),
|
||||
wantsErr: false,
|
||||
expectedURL: "https://github.com/vilmibm/gh-user-status/tree/6f1a2405cace1633d89a79c74c65f22fe78f9659/",
|
||||
},
|
||||
{
|
||||
name: "open last commit with a file",
|
||||
opts: BrowseOptions{
|
||||
CommitFlag: true,
|
||||
SelectorArg: "main.go",
|
||||
},
|
||||
baseRepo: ghrepo.New("vilmibm", "gh-user-status"),
|
||||
wantsErr: false,
|
||||
expectedURL: "https://github.com/vilmibm/gh-user-status/tree/6f1a2405cace1633d89a79c74c65f22fe78f9659/main.go",
|
||||
},
|
||||
{
|
||||
name: "relative path from browse_test.go",
|
||||
|
|
@ -358,7 +437,6 @@ func Test_parseFileArg(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_parsePathFromFileArg(t *testing.T) {
|
||||
|
||||
s := string(os.PathSeparator)
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
|
|||
60
pkg/cmd/codespace/code.go
Normal file
60
pkg/cmd/codespace/code.go
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
package codespace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/skratchdot/open-golang/open"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newCodeCmd(app *App) *cobra.Command {
|
||||
var (
|
||||
codespace string
|
||||
useInsiders bool
|
||||
)
|
||||
|
||||
codeCmd := &cobra.Command{
|
||||
Use: "code",
|
||||
Short: "Open a codespace in VS Code",
|
||||
Args: noArgsConstraint,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return app.VSCode(cmd.Context(), codespace, useInsiders)
|
||||
},
|
||||
}
|
||||
|
||||
codeCmd.Flags().StringVarP(&codespace, "codespace", "c", "", "Name of the codespace")
|
||||
codeCmd.Flags().BoolVar(&useInsiders, "insiders", false, "Use the insiders version of VS Code")
|
||||
|
||||
return codeCmd
|
||||
}
|
||||
|
||||
// VSCode opens a codespace in the local VS VSCode application.
|
||||
func (a *App) VSCode(ctx context.Context, codespaceName string, useInsiders bool) error {
|
||||
if codespaceName == "" {
|
||||
codespace, err := chooseCodespace(ctx, a.apiClient)
|
||||
if err != nil {
|
||||
if err == errNoCodespaces {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("error choosing codespace: %w", err)
|
||||
}
|
||||
codespaceName = codespace.Name
|
||||
}
|
||||
|
||||
url := vscodeProtocolURL(codespaceName, useInsiders)
|
||||
if err := open.Run(url); err != nil {
|
||||
return fmt.Errorf("error opening vscode URL %s: %s. (Is VS Code installed?)", url, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func vscodeProtocolURL(codespaceName string, useInsiders bool) string {
|
||||
application := "vscode"
|
||||
if useInsiders {
|
||||
application = "vscode-insiders"
|
||||
}
|
||||
return fmt.Sprintf("%s://github.codespaces/connect?name=%s", application, url.QueryEscape(codespaceName))
|
||||
}
|
||||
265
pkg/cmd/codespace/common.go
Normal file
265
pkg/cmd/codespace/common.go
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
package codespace
|
||||
|
||||
// This file defines functions common to the entire codespace command set.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/AlecAivazis/survey/v2/terminal"
|
||||
"github.com/cli/cli/v2/internal/codespaces/api"
|
||||
"github.com/cli/cli/v2/pkg/cmd/codespace/output"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
apiClient apiClient
|
||||
logger *output.Logger
|
||||
}
|
||||
|
||||
func NewApp(logger *output.Logger, apiClient apiClient) *App {
|
||||
return &App{
|
||||
apiClient: apiClient,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
//go:generate moq -fmt goimports -rm -skip-ensure -out mock_api.go . apiClient
|
||||
type apiClient interface {
|
||||
GetUser(ctx context.Context) (*api.User, error)
|
||||
GetCodespace(ctx context.Context, name string, includeConnection bool) (*api.Codespace, error)
|
||||
ListCodespaces(ctx context.Context, limit int) ([]*api.Codespace, error)
|
||||
DeleteCodespace(ctx context.Context, name string) error
|
||||
StartCodespace(ctx context.Context, name string) error
|
||||
StopCodespace(ctx context.Context, name string) error
|
||||
CreateCodespace(ctx context.Context, params *api.CreateCodespaceParams) (*api.Codespace, error)
|
||||
GetRepository(ctx context.Context, nwo string) (*api.Repository, error)
|
||||
AuthorizedKeys(ctx context.Context, user string) ([]byte, error)
|
||||
GetCodespaceRegionLocation(ctx context.Context) (string, error)
|
||||
GetCodespacesMachines(ctx context.Context, repoID int, branch, location string) ([]*api.Machine, error)
|
||||
GetCodespaceRepositoryContents(ctx context.Context, codespace *api.Codespace, path string) ([]byte, error)
|
||||
}
|
||||
|
||||
var errNoCodespaces = errors.New("you have no codespaces")
|
||||
|
||||
func chooseCodespace(ctx context.Context, apiClient apiClient) (*api.Codespace, error) {
|
||||
codespaces, err := apiClient.ListCodespaces(ctx, -1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting codespaces: %w", err)
|
||||
}
|
||||
return chooseCodespaceFromList(ctx, codespaces)
|
||||
}
|
||||
|
||||
// chooseCodespaceFromList returns the selected codespace from the list,
|
||||
// or an error if there are no codespaces.
|
||||
func chooseCodespaceFromList(ctx context.Context, codespaces []*api.Codespace) (*api.Codespace, error) {
|
||||
if len(codespaces) == 0 {
|
||||
return nil, errNoCodespaces
|
||||
}
|
||||
|
||||
sort.Slice(codespaces, func(i, j int) bool {
|
||||
return codespaces[i].CreatedAt > codespaces[j].CreatedAt
|
||||
})
|
||||
|
||||
type codespaceWithIndex struct {
|
||||
cs codespace
|
||||
idx int
|
||||
}
|
||||
|
||||
namesWithConflict := make(map[string]bool)
|
||||
codespacesByName := make(map[string]codespaceWithIndex)
|
||||
codespacesNames := make([]string, 0, len(codespaces))
|
||||
for _, apiCodespace := range codespaces {
|
||||
cs := codespace{apiCodespace}
|
||||
csName := cs.displayName(false, false)
|
||||
displayNameWithGitStatus := cs.displayName(false, true)
|
||||
|
||||
_, hasExistingConflict := namesWithConflict[csName]
|
||||
if seenCodespace, ok := codespacesByName[csName]; ok || hasExistingConflict {
|
||||
// There is an existing codespace on the repo and branch.
|
||||
// We need to disambiguate by adding the codespace name
|
||||
// to the existing entry and the one we are processing now.
|
||||
if !hasExistingConflict {
|
||||
fullDisplayName := seenCodespace.cs.displayName(true, false)
|
||||
fullDisplayNameWithGitStatus := seenCodespace.cs.displayName(true, true)
|
||||
|
||||
codespacesByName[fullDisplayName] = codespaceWithIndex{seenCodespace.cs, seenCodespace.idx}
|
||||
codespacesNames[seenCodespace.idx] = fullDisplayNameWithGitStatus
|
||||
delete(codespacesByName, csName) // delete the existing map entry with old name
|
||||
|
||||
// All other codespaces with the same name should update
|
||||
// to their specific name, this tracks conflicting names going forward
|
||||
namesWithConflict[csName] = true
|
||||
}
|
||||
|
||||
// update this codespace names to include the name to disambiguate
|
||||
csName = cs.displayName(true, false)
|
||||
displayNameWithGitStatus = cs.displayName(true, true)
|
||||
}
|
||||
|
||||
codespacesByName[csName] = codespaceWithIndex{cs, len(codespacesNames)}
|
||||
codespacesNames = append(codespacesNames, displayNameWithGitStatus)
|
||||
}
|
||||
|
||||
csSurvey := []*survey.Question{
|
||||
{
|
||||
Name: "codespace",
|
||||
Prompt: &survey.Select{
|
||||
Message: "Choose codespace:",
|
||||
Options: codespacesNames,
|
||||
Default: codespacesNames[0],
|
||||
},
|
||||
Validate: survey.Required,
|
||||
},
|
||||
}
|
||||
|
||||
var answers struct {
|
||||
Codespace string
|
||||
}
|
||||
if err := ask(csSurvey, &answers); err != nil {
|
||||
return nil, fmt.Errorf("error getting answers: %w", err)
|
||||
}
|
||||
|
||||
// Codespaces are indexed without the git status included as compared
|
||||
// to how it is displayed in the prompt, so the git status symbol needs
|
||||
// cleaning up in case it is included.
|
||||
selectedCodespace := strings.Replace(answers.Codespace, gitStatusDirty, "", -1)
|
||||
return codespacesByName[selectedCodespace].cs.Codespace, nil
|
||||
}
|
||||
|
||||
// getOrChooseCodespace prompts the user to choose a codespace if the codespaceName is empty.
|
||||
// It then fetches the codespace record with full connection details.
|
||||
func getOrChooseCodespace(ctx context.Context, apiClient apiClient, codespaceName string) (codespace *api.Codespace, err error) {
|
||||
if codespaceName == "" {
|
||||
codespace, err = chooseCodespace(ctx, apiClient)
|
||||
if err != nil {
|
||||
if err == errNoCodespaces {
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf("choosing codespace: %w", err)
|
||||
}
|
||||
} else {
|
||||
codespace, err = apiClient.GetCodespace(ctx, codespaceName, true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting full codespace details: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return codespace, nil
|
||||
}
|
||||
|
||||
func safeClose(closer io.Closer, err *error) {
|
||||
if closeErr := closer.Close(); *err == nil {
|
||||
*err = closeErr
|
||||
}
|
||||
}
|
||||
|
||||
// hasTTY indicates whether the process connected to a terminal.
|
||||
// It is not portable to assume stdin/stdout are fds 0 and 1.
|
||||
var hasTTY = term.IsTerminal(int(os.Stdin.Fd())) && term.IsTerminal(int(os.Stdout.Fd()))
|
||||
|
||||
// ask asks survey questions on the terminal, using standard options.
|
||||
// It fails unless hasTTY, but ideally callers should avoid calling it in that case.
|
||||
func ask(qs []*survey.Question, response interface{}) error {
|
||||
if !hasTTY {
|
||||
return fmt.Errorf("no terminal")
|
||||
}
|
||||
err := survey.Ask(qs, response, survey.WithShowCursor(true))
|
||||
// The survey package temporarily clears the terminal's ISIG mode bit
|
||||
// (see tcsetattr(3)) so the QUIT button (Ctrl-C) is reported as
|
||||
// ASCII \x03 (ETX) instead of delivering SIGINT to the application.
|
||||
// So we have to serve ourselves the SIGINT.
|
||||
//
|
||||
// https://github.com/AlecAivazis/survey/#why-isnt-ctrl-c-working
|
||||
if err == terminal.InterruptErr {
|
||||
self, _ := os.FindProcess(os.Getpid())
|
||||
_ = self.Signal(os.Interrupt) // assumes POSIX
|
||||
|
||||
// Suspend the goroutine, to avoid a race between
|
||||
// return from main and async delivery of INT signal.
|
||||
select {}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// checkAuthorizedKeys reports an error if the user has not registered any SSH keys;
|
||||
// see https://github.com/cli/cli/v2/issues/166#issuecomment-921769703.
|
||||
// The check is not required for security but it improves the error message.
|
||||
func checkAuthorizedKeys(ctx context.Context, client apiClient, user string) error {
|
||||
keys, err := client.AuthorizedKeys(ctx, user)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read GitHub-authorized SSH keys for %s: %w", user, err)
|
||||
}
|
||||
if len(keys) == 0 {
|
||||
return fmt.Errorf("user %s has no GitHub-authorized SSH keys", user)
|
||||
}
|
||||
return nil // success
|
||||
}
|
||||
|
||||
var ErrTooManyArgs = errors.New("the command accepts no arguments")
|
||||
|
||||
func noArgsConstraint(cmd *cobra.Command, args []string) error {
|
||||
if len(args) > 0 {
|
||||
return ErrTooManyArgs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func noopLogger() *log.Logger {
|
||||
return log.New(ioutil.Discard, "", 0)
|
||||
}
|
||||
|
||||
type codespace struct {
|
||||
*api.Codespace
|
||||
}
|
||||
|
||||
// displayName returns the repository nwo and branch.
|
||||
// If includeName is true, the name of the codespace is included.
|
||||
// If includeGitStatus is true, the branch will include a star if
|
||||
// the codespace has unsaved changes.
|
||||
func (c codespace) displayName(includeName, includeGitStatus bool) string {
|
||||
branch := c.Branch
|
||||
if includeGitStatus {
|
||||
branch = c.branchWithGitStatus()
|
||||
}
|
||||
|
||||
if includeName {
|
||||
return fmt.Sprintf(
|
||||
"%s: %s [%s]", c.RepositoryNWO, branch, c.Name,
|
||||
)
|
||||
}
|
||||
return c.RepositoryNWO + ": " + branch
|
||||
}
|
||||
|
||||
// gitStatusDirty represents an unsaved changes status.
|
||||
const gitStatusDirty = "*"
|
||||
|
||||
// branchWithGitStatus returns the branch with a star
|
||||
// if the branch is currently being worked on.
|
||||
func (c codespace) branchWithGitStatus() string {
|
||||
if c.hasUnsavedChanges() {
|
||||
return c.Branch + gitStatusDirty
|
||||
}
|
||||
|
||||
return c.Branch
|
||||
}
|
||||
|
||||
// hasUnsavedChanges returns whether the environment has
|
||||
// unsaved changes.
|
||||
func (c codespace) hasUnsavedChanges() bool {
|
||||
return c.Environment.GitStatus.HasUncommitedChanges || c.Environment.GitStatus.HasUnpushedChanges
|
||||
}
|
||||
|
||||
// running returns whether the codespace environment is running.
|
||||
func (c codespace) running() bool {
|
||||
return c.Environment.State == api.CodespaceEnvironmentStateAvailable
|
||||
}
|
||||
296
pkg/cmd/codespace/create.go
Normal file
296
pkg/cmd/codespace/create.go
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
package codespace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/cli/cli/v2/internal/codespaces"
|
||||
"github.com/cli/cli/v2/internal/codespaces/api"
|
||||
"github.com/cli/cli/v2/pkg/cmd/codespace/output"
|
||||
"github.com/fatih/camelcase"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type createOptions struct {
|
||||
repo string
|
||||
branch string
|
||||
machine string
|
||||
showStatus bool
|
||||
}
|
||||
|
||||
func newCreateCmd(app *App) *cobra.Command {
|
||||
opts := createOptions{}
|
||||
|
||||
createCmd := &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create a codespace",
|
||||
Args: noArgsConstraint,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return app.Create(cmd.Context(), opts)
|
||||
},
|
||||
}
|
||||
|
||||
createCmd.Flags().StringVarP(&opts.repo, "repo", "r", "", "repository name with owner: user/repo")
|
||||
createCmd.Flags().StringVarP(&opts.branch, "branch", "b", "", "repository branch")
|
||||
createCmd.Flags().StringVarP(&opts.machine, "machine", "m", "", "hardware specifications for the VM")
|
||||
createCmd.Flags().BoolVarP(&opts.showStatus, "status", "s", false, "show status of post-create command and dotfiles")
|
||||
|
||||
return createCmd
|
||||
}
|
||||
|
||||
// Create creates a new Codespace
|
||||
func (a *App) Create(ctx context.Context, opts createOptions) error {
|
||||
locationCh := getLocation(ctx, a.apiClient)
|
||||
userCh := getUser(ctx, a.apiClient)
|
||||
|
||||
repo, err := getRepoName(opts.repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting repository name: %w", err)
|
||||
}
|
||||
branch, err := getBranchName(opts.branch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting branch name: %w", err)
|
||||
}
|
||||
|
||||
repository, err := a.apiClient.GetRepository(ctx, repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting repository: %w", err)
|
||||
}
|
||||
|
||||
locationResult := <-locationCh
|
||||
if locationResult.Err != nil {
|
||||
return fmt.Errorf("error getting codespace region location: %w", locationResult.Err)
|
||||
}
|
||||
|
||||
userResult := <-userCh
|
||||
if userResult.Err != nil {
|
||||
return fmt.Errorf("error getting codespace user: %w", userResult.Err)
|
||||
}
|
||||
|
||||
machine, err := getMachineName(ctx, a.apiClient, repository.ID, opts.machine, branch, locationResult.Location)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting machine type: %w", err)
|
||||
}
|
||||
if machine == "" {
|
||||
return errors.New("there are no available machine types for this repository")
|
||||
}
|
||||
|
||||
a.logger.Print("Creating your codespace...")
|
||||
codespace, err := a.apiClient.CreateCodespace(ctx, &api.CreateCodespaceParams{
|
||||
RepositoryID: repository.ID,
|
||||
Branch: branch,
|
||||
Machine: machine,
|
||||
Location: locationResult.Location,
|
||||
})
|
||||
a.logger.Print("\n")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating codespace: %w", err)
|
||||
}
|
||||
|
||||
if opts.showStatus {
|
||||
if err := showStatus(ctx, a.logger, a.apiClient, userResult.User, codespace); err != nil {
|
||||
return fmt.Errorf("show status: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
a.logger.Printf("Codespace created: ")
|
||||
|
||||
fmt.Fprintln(os.Stdout, codespace.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// showStatus polls the codespace for a list of post create states and their status. It will keep polling
|
||||
// until all states have finished. Once all states have finished, we poll once more to check if any new
|
||||
// states have been introduced and stop polling otherwise.
|
||||
func showStatus(ctx context.Context, log *output.Logger, apiClient apiClient, user *api.User, codespace *api.Codespace) error {
|
||||
var lastState codespaces.PostCreateState
|
||||
var breakNextState bool
|
||||
|
||||
finishedStates := make(map[string]bool)
|
||||
ctx, stopPolling := context.WithCancel(ctx)
|
||||
defer stopPolling()
|
||||
|
||||
poller := func(states []codespaces.PostCreateState) {
|
||||
var inProgress bool
|
||||
for _, state := range states {
|
||||
if _, found := finishedStates[state.Name]; found {
|
||||
continue // skip this state as we've processed it already
|
||||
}
|
||||
|
||||
if state.Name != lastState.Name {
|
||||
log.Print(state.Name)
|
||||
|
||||
if state.Status == codespaces.PostCreateStateRunning {
|
||||
inProgress = true
|
||||
lastState = state
|
||||
log.Print("...")
|
||||
break
|
||||
}
|
||||
|
||||
finishedStates[state.Name] = true
|
||||
log.Println("..." + state.Status)
|
||||
} else {
|
||||
if state.Status == codespaces.PostCreateStateRunning {
|
||||
inProgress = true
|
||||
log.Print(".")
|
||||
break
|
||||
}
|
||||
|
||||
finishedStates[state.Name] = true
|
||||
log.Println(state.Status)
|
||||
lastState = codespaces.PostCreateState{} // reset the value
|
||||
}
|
||||
}
|
||||
|
||||
if !inProgress {
|
||||
if breakNextState {
|
||||
stopPolling()
|
||||
return
|
||||
}
|
||||
breakNextState = true
|
||||
}
|
||||
}
|
||||
|
||||
err := codespaces.PollPostCreateStates(ctx, log, apiClient, codespace, poller)
|
||||
if err != nil {
|
||||
if errors.Is(err, context.Canceled) && breakNextState {
|
||||
return nil // we cancelled the context to stop polling, we can ignore the error
|
||||
}
|
||||
|
||||
return fmt.Errorf("failed to poll state changes from codespace: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type getUserResult struct {
|
||||
User *api.User
|
||||
Err error
|
||||
}
|
||||
|
||||
// getUser fetches the user record associated with the GITHUB_TOKEN
|
||||
func getUser(ctx context.Context, apiClient apiClient) <-chan getUserResult {
|
||||
ch := make(chan getUserResult, 1)
|
||||
go func() {
|
||||
user, err := apiClient.GetUser(ctx)
|
||||
ch <- getUserResult{user, err}
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
type locationResult struct {
|
||||
Location string
|
||||
Err error
|
||||
}
|
||||
|
||||
// getLocation fetches the closest Codespace datacenter region/location to the user.
|
||||
func getLocation(ctx context.Context, apiClient apiClient) <-chan locationResult {
|
||||
ch := make(chan locationResult, 1)
|
||||
go func() {
|
||||
location, err := apiClient.GetCodespaceRegionLocation(ctx)
|
||||
ch <- locationResult{location, err}
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
// getRepoName prompts the user for the name of the repository, or returns the repository if non-empty.
|
||||
func getRepoName(repo string) (string, error) {
|
||||
if repo != "" {
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
repoSurvey := []*survey.Question{
|
||||
{
|
||||
Name: "repository",
|
||||
Prompt: &survey.Input{Message: "Repository:"},
|
||||
Validate: survey.Required,
|
||||
},
|
||||
}
|
||||
err := ask(repoSurvey, &repo)
|
||||
return repo, err
|
||||
}
|
||||
|
||||
// getBranchName prompts the user for the name of the branch, or returns the branch if non-empty.
|
||||
func getBranchName(branch string) (string, error) {
|
||||
if branch != "" {
|
||||
return branch, nil
|
||||
}
|
||||
|
||||
branchSurvey := []*survey.Question{
|
||||
{
|
||||
Name: "branch",
|
||||
Prompt: &survey.Input{Message: "Branch:"},
|
||||
Validate: survey.Required,
|
||||
},
|
||||
}
|
||||
err := ask(branchSurvey, &branch)
|
||||
return branch, err
|
||||
}
|
||||
|
||||
// getMachineName prompts the user to select the machine type, or validates the machine if non-empty.
|
||||
func getMachineName(ctx context.Context, apiClient apiClient, repoID int, machine, branch, location string) (string, error) {
|
||||
machines, err := apiClient.GetCodespacesMachines(ctx, repoID, branch, location)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error requesting machine instance types: %w", err)
|
||||
}
|
||||
|
||||
// if user supplied a machine type, it must be valid
|
||||
// if no machine type was supplied, we don't error if there are no machine types for the current repo
|
||||
if machine != "" {
|
||||
for _, m := range machines {
|
||||
if machine == m.Name {
|
||||
return machine, nil
|
||||
}
|
||||
}
|
||||
|
||||
availableMachines := make([]string, len(machines))
|
||||
for i := 0; i < len(machines); i++ {
|
||||
availableMachines[i] = machines[i].Name
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("there is no such machine for the repository: %s\nAvailable machines: %v", machine, availableMachines)
|
||||
} else if len(machines) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if len(machines) == 1 {
|
||||
// VS Code does not prompt for machine if there is only one, this makes us consistent with that behavior
|
||||
return machines[0].Name, nil
|
||||
}
|
||||
|
||||
machineNames := make([]string, 0, len(machines))
|
||||
machineByName := make(map[string]*api.Machine)
|
||||
for _, m := range machines {
|
||||
nameParts := camelcase.Split(m.Name)
|
||||
machineName := strings.Title(strings.ToLower(nameParts[0]))
|
||||
machineName = fmt.Sprintf("%s - %s", machineName, m.DisplayName)
|
||||
machineNames = append(machineNames, machineName)
|
||||
machineByName[machineName] = m
|
||||
}
|
||||
|
||||
machineSurvey := []*survey.Question{
|
||||
{
|
||||
Name: "machine",
|
||||
Prompt: &survey.Select{
|
||||
Message: "Choose Machine Type:",
|
||||
Options: machineNames,
|
||||
Default: machineNames[0],
|
||||
},
|
||||
Validate: survey.Required,
|
||||
},
|
||||
}
|
||||
|
||||
var machineAnswers struct{ Machine string }
|
||||
if err := ask(machineSurvey, &machineAnswers); err != nil {
|
||||
return "", fmt.Errorf("error getting machine: %w", err)
|
||||
}
|
||||
|
||||
selectedMachine := machineByName[machineAnswers.Machine]
|
||||
|
||||
return selectedMachine.Name, nil
|
||||
}
|
||||
175
pkg/cmd/codespace/delete.go
Normal file
175
pkg/cmd/codespace/delete.go
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
package codespace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/cli/cli/v2/internal/codespaces/api"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type deleteOptions struct {
|
||||
deleteAll bool
|
||||
skipConfirm bool
|
||||
codespaceName string
|
||||
repoFilter string
|
||||
keepDays uint16
|
||||
|
||||
isInteractive bool
|
||||
now func() time.Time
|
||||
prompter prompter
|
||||
}
|
||||
|
||||
//go:generate moq -fmt goimports -rm -skip-ensure -out mock_prompter.go . prompter
|
||||
type prompter interface {
|
||||
Confirm(message string) (bool, error)
|
||||
}
|
||||
|
||||
func newDeleteCmd(app *App) *cobra.Command {
|
||||
opts := deleteOptions{
|
||||
isInteractive: hasTTY,
|
||||
now: time.Now,
|
||||
prompter: &surveyPrompter{},
|
||||
}
|
||||
|
||||
deleteCmd := &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "Delete a codespace",
|
||||
Args: noArgsConstraint,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if opts.deleteAll && opts.repoFilter != "" {
|
||||
return errors.New("both --all and --repo is not supported")
|
||||
}
|
||||
return app.Delete(cmd.Context(), opts)
|
||||
},
|
||||
}
|
||||
|
||||
deleteCmd.Flags().StringVarP(&opts.codespaceName, "codespace", "c", "", "Name of the codespace")
|
||||
deleteCmd.Flags().BoolVar(&opts.deleteAll, "all", false, "Delete all codespaces")
|
||||
deleteCmd.Flags().StringVarP(&opts.repoFilter, "repo", "r", "", "Delete codespaces for a `repository`")
|
||||
deleteCmd.Flags().BoolVarP(&opts.skipConfirm, "force", "f", false, "Skip confirmation for codespaces that contain unsaved changes")
|
||||
deleteCmd.Flags().Uint16Var(&opts.keepDays, "days", 0, "Delete codespaces older than `N` days")
|
||||
|
||||
return deleteCmd
|
||||
}
|
||||
|
||||
func (a *App) Delete(ctx context.Context, opts deleteOptions) (err error) {
|
||||
var codespaces []*api.Codespace
|
||||
nameFilter := opts.codespaceName
|
||||
if nameFilter == "" {
|
||||
codespaces, err = a.apiClient.ListCodespaces(ctx, -1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting codespaces: %w", err)
|
||||
}
|
||||
|
||||
if !opts.deleteAll && opts.repoFilter == "" {
|
||||
c, err := chooseCodespaceFromList(ctx, codespaces)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error choosing codespace: %w", err)
|
||||
}
|
||||
nameFilter = c.Name
|
||||
}
|
||||
} else {
|
||||
codespace, err := a.apiClient.GetCodespace(ctx, nameFilter, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error fetching codespace information: %w", err)
|
||||
}
|
||||
|
||||
codespaces = []*api.Codespace{codespace}
|
||||
}
|
||||
|
||||
codespacesToDelete := make([]*api.Codespace, 0, len(codespaces))
|
||||
lastUpdatedCutoffTime := opts.now().AddDate(0, 0, -int(opts.keepDays))
|
||||
for _, c := range codespaces {
|
||||
if nameFilter != "" && c.Name != nameFilter {
|
||||
continue
|
||||
}
|
||||
if opts.repoFilter != "" && !strings.EqualFold(c.RepositoryNWO, opts.repoFilter) {
|
||||
continue
|
||||
}
|
||||
if opts.keepDays > 0 {
|
||||
t, err := time.Parse(time.RFC3339, c.LastUsedAt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing last_used_at timestamp %q: %w", c.LastUsedAt, err)
|
||||
}
|
||||
if t.After(lastUpdatedCutoffTime) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if !opts.skipConfirm {
|
||||
confirmed, err := confirmDeletion(opts.prompter, c, opts.isInteractive)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to confirm: %w", err)
|
||||
}
|
||||
if !confirmed {
|
||||
continue
|
||||
}
|
||||
}
|
||||
codespacesToDelete = append(codespacesToDelete, c)
|
||||
}
|
||||
|
||||
if len(codespacesToDelete) == 0 {
|
||||
return errors.New("no codespaces to delete")
|
||||
}
|
||||
|
||||
g := errgroup.Group{}
|
||||
for _, c := range codespacesToDelete {
|
||||
codespaceName := c.Name
|
||||
g.Go(func() error {
|
||||
if err := a.apiClient.DeleteCodespace(ctx, codespaceName); err != nil {
|
||||
_, _ = a.logger.Errorf("error deleting codespace %q: %v\n", codespaceName, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if err := g.Wait(); err != nil {
|
||||
return errors.New("some codespaces failed to delete")
|
||||
}
|
||||
|
||||
noun := "Codespace"
|
||||
if len(codespacesToDelete) > 1 {
|
||||
noun = noun + "s"
|
||||
}
|
||||
a.logger.Println(noun + " deleted.")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func confirmDeletion(p prompter, apiCodespace *api.Codespace, isInteractive bool) (bool, error) {
|
||||
cs := codespace{apiCodespace}
|
||||
if !cs.hasUnsavedChanges() {
|
||||
return true, nil
|
||||
}
|
||||
if !isInteractive {
|
||||
return false, fmt.Errorf("codespace %s has unsaved changes (use --force to override)", cs.Name)
|
||||
}
|
||||
return p.Confirm(fmt.Sprintf("Codespace %s has unsaved changes. OK to delete?", cs.Name))
|
||||
}
|
||||
|
||||
type surveyPrompter struct{}
|
||||
|
||||
func (p *surveyPrompter) Confirm(message string) (bool, error) {
|
||||
var confirmed struct {
|
||||
Confirmed bool
|
||||
}
|
||||
q := []*survey.Question{
|
||||
{
|
||||
Name: "confirmed",
|
||||
Prompt: &survey.Confirm{
|
||||
Message: message,
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := ask(q, &confirmed); err != nil {
|
||||
return false, fmt.Errorf("failed to prompt: %w", err)
|
||||
}
|
||||
|
||||
return confirmed.Confirmed, nil
|
||||
}
|
||||
240
pkg/cmd/codespace/delete_test.go
Normal file
240
pkg/cmd/codespace/delete_test.go
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
package codespace
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/internal/codespaces/api"
|
||||
"github.com/cli/cli/v2/pkg/cmd/codespace/output"
|
||||
)
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
user := &api.User{Login: "hubot"}
|
||||
now, _ := time.Parse(time.RFC3339, "2021-09-22T00:00:00Z")
|
||||
daysAgo := func(n int) string {
|
||||
return now.Add(time.Hour * -time.Duration(24*n)).Format(time.RFC3339)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
opts deleteOptions
|
||||
codespaces []*api.Codespace
|
||||
confirms map[string]bool
|
||||
deleteErr error
|
||||
wantErr bool
|
||||
wantDeleted []string
|
||||
wantStdout string
|
||||
wantStderr string
|
||||
}{
|
||||
{
|
||||
name: "by name",
|
||||
opts: deleteOptions{
|
||||
codespaceName: "hubot-robawt-abc",
|
||||
},
|
||||
codespaces: []*api.Codespace{
|
||||
{
|
||||
Name: "hubot-robawt-abc",
|
||||
},
|
||||
},
|
||||
wantDeleted: []string{"hubot-robawt-abc"},
|
||||
wantStdout: "Codespace deleted.\n",
|
||||
},
|
||||
{
|
||||
name: "by repo",
|
||||
opts: deleteOptions{
|
||||
repoFilter: "monalisa/spoon-knife",
|
||||
},
|
||||
codespaces: []*api.Codespace{
|
||||
{
|
||||
Name: "monalisa-spoonknife-123",
|
||||
RepositoryNWO: "monalisa/Spoon-Knife",
|
||||
},
|
||||
{
|
||||
Name: "hubot-robawt-abc",
|
||||
RepositoryNWO: "hubot/ROBAWT",
|
||||
},
|
||||
{
|
||||
Name: "monalisa-spoonknife-c4f3",
|
||||
RepositoryNWO: "monalisa/Spoon-Knife",
|
||||
},
|
||||
},
|
||||
wantDeleted: []string{"monalisa-spoonknife-123", "monalisa-spoonknife-c4f3"},
|
||||
wantStdout: "Codespaces deleted.\n",
|
||||
},
|
||||
{
|
||||
name: "unused",
|
||||
opts: deleteOptions{
|
||||
deleteAll: true,
|
||||
keepDays: 3,
|
||||
},
|
||||
codespaces: []*api.Codespace{
|
||||
{
|
||||
Name: "monalisa-spoonknife-123",
|
||||
LastUsedAt: daysAgo(1),
|
||||
},
|
||||
{
|
||||
Name: "hubot-robawt-abc",
|
||||
LastUsedAt: daysAgo(4),
|
||||
},
|
||||
{
|
||||
Name: "monalisa-spoonknife-c4f3",
|
||||
LastUsedAt: daysAgo(10),
|
||||
},
|
||||
},
|
||||
wantDeleted: []string{"hubot-robawt-abc", "monalisa-spoonknife-c4f3"},
|
||||
wantStdout: "Codespaces deleted.\n",
|
||||
},
|
||||
{
|
||||
name: "deletion failed",
|
||||
opts: deleteOptions{
|
||||
deleteAll: true,
|
||||
},
|
||||
codespaces: []*api.Codespace{
|
||||
{
|
||||
Name: "monalisa-spoonknife-123",
|
||||
},
|
||||
{
|
||||
Name: "hubot-robawt-abc",
|
||||
},
|
||||
},
|
||||
deleteErr: errors.New("aborted by test"),
|
||||
wantErr: true,
|
||||
wantDeleted: []string{"hubot-robawt-abc", "monalisa-spoonknife-123"},
|
||||
wantStderr: heredoc.Doc(`
|
||||
error deleting codespace "hubot-robawt-abc": aborted by test
|
||||
error deleting codespace "monalisa-spoonknife-123": aborted by test
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "with confirm",
|
||||
opts: deleteOptions{
|
||||
isInteractive: true,
|
||||
deleteAll: true,
|
||||
skipConfirm: false,
|
||||
},
|
||||
codespaces: []*api.Codespace{
|
||||
{
|
||||
Name: "monalisa-spoonknife-123",
|
||||
Environment: api.CodespaceEnvironment{
|
||||
GitStatus: api.CodespaceEnvironmentGitStatus{
|
||||
HasUnpushedChanges: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "hubot-robawt-abc",
|
||||
Environment: api.CodespaceEnvironment{
|
||||
GitStatus: api.CodespaceEnvironmentGitStatus{
|
||||
HasUncommitedChanges: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "monalisa-spoonknife-c4f3",
|
||||
Environment: api.CodespaceEnvironment{
|
||||
GitStatus: api.CodespaceEnvironmentGitStatus{
|
||||
HasUnpushedChanges: false,
|
||||
HasUncommitedChanges: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
confirms: map[string]bool{
|
||||
"Codespace monalisa-spoonknife-123 has unsaved changes. OK to delete?": false,
|
||||
"Codespace hubot-robawt-abc has unsaved changes. OK to delete?": true,
|
||||
},
|
||||
wantDeleted: []string{"hubot-robawt-abc", "monalisa-spoonknife-c4f3"},
|
||||
wantStdout: "Codespaces deleted.\n",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
apiMock := &apiClientMock{
|
||||
GetUserFunc: func(_ context.Context) (*api.User, error) {
|
||||
return user, nil
|
||||
},
|
||||
DeleteCodespaceFunc: func(_ context.Context, name string) error {
|
||||
if tt.deleteErr != nil {
|
||||
return tt.deleteErr
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
if tt.opts.codespaceName == "" {
|
||||
apiMock.ListCodespacesFunc = func(_ context.Context, num int) ([]*api.Codespace, error) {
|
||||
return tt.codespaces, nil
|
||||
}
|
||||
} else {
|
||||
apiMock.GetCodespaceFunc = func(_ context.Context, name string, includeConnection bool) (*api.Codespace, error) {
|
||||
return tt.codespaces[0], nil
|
||||
}
|
||||
}
|
||||
opts := tt.opts
|
||||
opts.now = func() time.Time { return now }
|
||||
opts.prompter = &prompterMock{
|
||||
ConfirmFunc: func(msg string) (bool, error) {
|
||||
res, found := tt.confirms[msg]
|
||||
if !found {
|
||||
return false, fmt.Errorf("unexpected prompt %q", msg)
|
||||
}
|
||||
return res, nil
|
||||
},
|
||||
}
|
||||
|
||||
stdout := &bytes.Buffer{}
|
||||
stderr := &bytes.Buffer{}
|
||||
app := &App{
|
||||
apiClient: apiMock,
|
||||
logger: output.NewLogger(stdout, stderr, false),
|
||||
}
|
||||
err := app.Delete(context.Background(), opts)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("delete() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
var gotDeleted []string
|
||||
for _, delArgs := range apiMock.DeleteCodespaceCalls() {
|
||||
gotDeleted = append(gotDeleted, delArgs.Name)
|
||||
}
|
||||
sort.Strings(gotDeleted)
|
||||
if !sliceEquals(gotDeleted, tt.wantDeleted) {
|
||||
t.Errorf("deleted %q, want %q", gotDeleted, tt.wantDeleted)
|
||||
}
|
||||
if out := stdout.String(); out != tt.wantStdout {
|
||||
t.Errorf("stdout = %q, want %q", out, tt.wantStdout)
|
||||
}
|
||||
if out := sortLines(stderr.String()); out != tt.wantStderr {
|
||||
t.Errorf("stderr = %q, want %q", out, tt.wantStderr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func sliceEquals(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func sortLines(s string) string {
|
||||
trailing := ""
|
||||
if strings.HasSuffix(s, "\n") {
|
||||
s = strings.TrimSuffix(s, "\n")
|
||||
trailing = "\n"
|
||||
}
|
||||
lines := strings.Split(s, "\n")
|
||||
sort.Strings(lines)
|
||||
return strings.Join(lines, "\n") + trailing
|
||||
}
|
||||
57
pkg/cmd/codespace/list.go
Normal file
57
pkg/cmd/codespace/list.go
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
package codespace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/cli/cli/v2/pkg/cmd/codespace/output"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newListCmd(app *App) *cobra.Command {
|
||||
var asJSON bool
|
||||
var limit int
|
||||
|
||||
listCmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List your codespaces",
|
||||
Args: noArgsConstraint,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if limit < 1 {
|
||||
return &cmdutil.FlagError{Err: fmt.Errorf("invalid limit: %v", limit)}
|
||||
}
|
||||
|
||||
return app.List(cmd.Context(), asJSON, limit)
|
||||
},
|
||||
}
|
||||
|
||||
listCmd.Flags().BoolVar(&asJSON, "json", false, "Output as JSON")
|
||||
listCmd.Flags().IntVarP(&limit, "limit", "L", 30, "Maximum number of codespaces to list")
|
||||
|
||||
return listCmd
|
||||
}
|
||||
|
||||
func (a *App) List(ctx context.Context, asJSON bool, limit int) error {
|
||||
codespaces, err := a.apiClient.ListCodespaces(ctx, limit)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting codespaces: %w", err)
|
||||
}
|
||||
|
||||
table := output.NewTable(os.Stdout, asJSON)
|
||||
table.SetHeader([]string{"Name", "Repository", "Branch", "State", "Created At"})
|
||||
for _, apiCodespace := range codespaces {
|
||||
cs := codespace{apiCodespace}
|
||||
table.Append([]string{
|
||||
cs.Name,
|
||||
cs.RepositoryNWO,
|
||||
cs.branchWithGitStatus(),
|
||||
cs.Environment.State,
|
||||
cs.CreatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
table.Render()
|
||||
return nil
|
||||
}
|
||||
112
pkg/cmd/codespace/logs.go
Normal file
112
pkg/cmd/codespace/logs.go
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
package codespace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/cli/cli/v2/internal/codespaces"
|
||||
"github.com/cli/cli/v2/pkg/liveshare"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newLogsCmd(app *App) *cobra.Command {
|
||||
var (
|
||||
codespace string
|
||||
follow bool
|
||||
)
|
||||
|
||||
logsCmd := &cobra.Command{
|
||||
Use: "logs",
|
||||
Short: "Access codespace logs",
|
||||
Args: noArgsConstraint,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return app.Logs(cmd.Context(), codespace, follow)
|
||||
},
|
||||
}
|
||||
|
||||
logsCmd.Flags().StringVarP(&codespace, "codespace", "c", "", "Name of the codespace")
|
||||
logsCmd.Flags().BoolVarP(&follow, "follow", "f", false, "Tail and follow the logs")
|
||||
|
||||
return logsCmd
|
||||
}
|
||||
|
||||
func (a *App) Logs(ctx context.Context, codespaceName string, follow bool) (err error) {
|
||||
// Ensure all child tasks (port forwarding, remote exec) terminate before return.
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
user, err := a.apiClient.GetUser(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting user: %w", err)
|
||||
}
|
||||
|
||||
authkeys := make(chan error, 1)
|
||||
go func() {
|
||||
authkeys <- checkAuthorizedKeys(ctx, a.apiClient, user.Login)
|
||||
}()
|
||||
|
||||
codespace, err := getOrChooseCodespace(ctx, a.apiClient, codespaceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get or choose codespace: %w", err)
|
||||
}
|
||||
|
||||
session, err := codespaces.ConnectToLiveshare(ctx, a.logger, noopLogger(), a.apiClient, codespace)
|
||||
if err != nil {
|
||||
return fmt.Errorf("connecting to Live Share: %w", err)
|
||||
}
|
||||
defer safeClose(session, &err)
|
||||
|
||||
if err := <-authkeys; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure local port is listening before client (getPostCreateOutput) connects.
|
||||
listen, err := net.Listen("tcp", ":0") // arbitrary port
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer listen.Close()
|
||||
localPort := listen.Addr().(*net.TCPAddr).Port
|
||||
|
||||
a.logger.Println("Fetching SSH Details...")
|
||||
remoteSSHServerPort, sshUser, err := session.StartSSHServer(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting ssh server details: %w", err)
|
||||
}
|
||||
|
||||
cmdType := "cat"
|
||||
if follow {
|
||||
cmdType = "tail -f"
|
||||
}
|
||||
|
||||
dst := fmt.Sprintf("%s@localhost", sshUser)
|
||||
cmd, err := codespaces.NewRemoteCommand(
|
||||
ctx, localPort, dst, fmt.Sprintf("%s /workspaces/.codespaces/.persistedshare/creation.log", cmdType),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("remote command: %w", err)
|
||||
}
|
||||
|
||||
tunnelClosed := make(chan error, 1)
|
||||
go func() {
|
||||
fwd := liveshare.NewPortForwarder(session, "sshd", remoteSSHServerPort, false)
|
||||
tunnelClosed <- fwd.ForwardToListener(ctx, listen) // error is non-nil
|
||||
}()
|
||||
|
||||
cmdDone := make(chan error, 1)
|
||||
go func() {
|
||||
cmdDone <- cmd.Run()
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-tunnelClosed:
|
||||
return fmt.Errorf("connection closed: %w", err)
|
||||
case err := <-cmdDone:
|
||||
if err != nil {
|
||||
return fmt.Errorf("error retrieving logs: %w", err)
|
||||
}
|
||||
|
||||
return nil // success
|
||||
}
|
||||
}
|
||||
629
pkg/cmd/codespace/mock_api.go
Normal file
629
pkg/cmd/codespace/mock_api.go
Normal file
|
|
@ -0,0 +1,629 @@
|
|||
// Code generated by moq; DO NOT EDIT.
|
||||
// github.com/matryer/moq
|
||||
|
||||
package codespace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/cli/cli/v2/internal/codespaces/api"
|
||||
)
|
||||
|
||||
// apiClientMock is a mock implementation of apiClient.
|
||||
//
|
||||
// func TestSomethingThatUsesapiClient(t *testing.T) {
|
||||
//
|
||||
// // make and configure a mocked apiClient
|
||||
// mockedapiClient := &apiClientMock{
|
||||
// AuthorizedKeysFunc: func(ctx context.Context, user string) ([]byte, error) {
|
||||
// panic("mock out the AuthorizedKeys method")
|
||||
// },
|
||||
// CreateCodespaceFunc: func(ctx context.Context, params *api.CreateCodespaceParams) (*api.Codespace, error) {
|
||||
// panic("mock out the CreateCodespace method")
|
||||
// },
|
||||
// DeleteCodespaceFunc: func(ctx context.Context, name string) error {
|
||||
// panic("mock out the DeleteCodespace method")
|
||||
// },
|
||||
// GetCodespaceFunc: func(ctx context.Context, name string, includeConnection bool) (*api.Codespace, error) {
|
||||
// panic("mock out the GetCodespace method")
|
||||
// },
|
||||
// GetCodespaceRegionLocationFunc: func(ctx context.Context) (string, error) {
|
||||
// panic("mock out the GetCodespaceRegionLocation method")
|
||||
// },
|
||||
// GetCodespaceRepositoryContentsFunc: func(ctx context.Context, codespace *api.Codespace, path string) ([]byte, error) {
|
||||
// panic("mock out the GetCodespaceRepositoryContents method")
|
||||
// },
|
||||
// GetCodespacesMachinesFunc: func(ctx context.Context, repoID int, branch string, location string) ([]*api.Machine, error) {
|
||||
// panic("mock out the GetCodespacesMachines method")
|
||||
// },
|
||||
// GetRepositoryFunc: func(ctx context.Context, nwo string) (*api.Repository, error) {
|
||||
// panic("mock out the GetRepository method")
|
||||
// },
|
||||
// GetUserFunc: func(ctx context.Context) (*api.User, error) {
|
||||
// panic("mock out the GetUser method")
|
||||
// },
|
||||
// ListCodespacesFunc: func(ctx context.Context, limit int) ([]*api.Codespace, error) {
|
||||
// panic("mock out the ListCodespaces method")
|
||||
// },
|
||||
// StartCodespaceFunc: func(ctx context.Context, name string) error {
|
||||
// panic("mock out the StartCodespace method")
|
||||
// },
|
||||
// StopCodespaceFunc: func(ctx context.Context, name string) error {
|
||||
// panic("mock out the StopCodespace method")
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// // use mockedapiClient in code that requires apiClient
|
||||
// // and then make assertions.
|
||||
//
|
||||
// }
|
||||
type apiClientMock struct {
|
||||
// AuthorizedKeysFunc mocks the AuthorizedKeys method.
|
||||
AuthorizedKeysFunc func(ctx context.Context, user string) ([]byte, error)
|
||||
|
||||
// CreateCodespaceFunc mocks the CreateCodespace method.
|
||||
CreateCodespaceFunc func(ctx context.Context, params *api.CreateCodespaceParams) (*api.Codespace, error)
|
||||
|
||||
// DeleteCodespaceFunc mocks the DeleteCodespace method.
|
||||
DeleteCodespaceFunc func(ctx context.Context, name string) error
|
||||
|
||||
// GetCodespaceFunc mocks the GetCodespace method.
|
||||
GetCodespaceFunc func(ctx context.Context, name string, includeConnection bool) (*api.Codespace, error)
|
||||
|
||||
// GetCodespaceRegionLocationFunc mocks the GetCodespaceRegionLocation method.
|
||||
GetCodespaceRegionLocationFunc func(ctx context.Context) (string, error)
|
||||
|
||||
// GetCodespaceRepositoryContentsFunc mocks the GetCodespaceRepositoryContents method.
|
||||
GetCodespaceRepositoryContentsFunc func(ctx context.Context, codespace *api.Codespace, path string) ([]byte, error)
|
||||
|
||||
// GetCodespacesMachinesFunc mocks the GetCodespacesMachines method.
|
||||
GetCodespacesMachinesFunc func(ctx context.Context, repoID int, branch string, location string) ([]*api.Machine, error)
|
||||
|
||||
// GetRepositoryFunc mocks the GetRepository method.
|
||||
GetRepositoryFunc func(ctx context.Context, nwo string) (*api.Repository, error)
|
||||
|
||||
// GetUserFunc mocks the GetUser method.
|
||||
GetUserFunc func(ctx context.Context) (*api.User, error)
|
||||
|
||||
// ListCodespacesFunc mocks the ListCodespaces method.
|
||||
ListCodespacesFunc func(ctx context.Context, limit int) ([]*api.Codespace, error)
|
||||
|
||||
// StartCodespaceFunc mocks the StartCodespace method.
|
||||
StartCodespaceFunc func(ctx context.Context, name string) error
|
||||
|
||||
// StopCodespaceFunc mocks the StopCodespace method.
|
||||
StopCodespaceFunc func(ctx context.Context, name string) error
|
||||
|
||||
// calls tracks calls to the methods.
|
||||
calls struct {
|
||||
// AuthorizedKeys holds details about calls to the AuthorizedKeys method.
|
||||
AuthorizedKeys []struct {
|
||||
// Ctx is the ctx argument value.
|
||||
Ctx context.Context
|
||||
// User is the user argument value.
|
||||
User string
|
||||
}
|
||||
// CreateCodespace holds details about calls to the CreateCodespace method.
|
||||
CreateCodespace []struct {
|
||||
// Ctx is the ctx argument value.
|
||||
Ctx context.Context
|
||||
// Params is the params argument value.
|
||||
Params *api.CreateCodespaceParams
|
||||
}
|
||||
// DeleteCodespace holds details about calls to the DeleteCodespace method.
|
||||
DeleteCodespace []struct {
|
||||
// Ctx is the ctx argument value.
|
||||
Ctx context.Context
|
||||
// Name is the name argument value.
|
||||
Name string
|
||||
}
|
||||
// GetCodespace holds details about calls to the GetCodespace method.
|
||||
GetCodespace []struct {
|
||||
// Ctx is the ctx argument value.
|
||||
Ctx context.Context
|
||||
// Name is the name argument value.
|
||||
Name string
|
||||
// IncludeConnection is the includeConnection argument value.
|
||||
IncludeConnection bool
|
||||
}
|
||||
// GetCodespaceRegionLocation holds details about calls to the GetCodespaceRegionLocation method.
|
||||
GetCodespaceRegionLocation []struct {
|
||||
// Ctx is the ctx argument value.
|
||||
Ctx context.Context
|
||||
}
|
||||
// GetCodespaceRepositoryContents holds details about calls to the GetCodespaceRepositoryContents method.
|
||||
GetCodespaceRepositoryContents []struct {
|
||||
// Ctx is the ctx argument value.
|
||||
Ctx context.Context
|
||||
// Codespace is the codespace argument value.
|
||||
Codespace *api.Codespace
|
||||
// Path is the path argument value.
|
||||
Path string
|
||||
}
|
||||
// GetCodespacesMachines holds details about calls to the GetCodespacesMachines method.
|
||||
GetCodespacesMachines []struct {
|
||||
// Ctx is the ctx argument value.
|
||||
Ctx context.Context
|
||||
// RepoID is the repoID argument value.
|
||||
RepoID int
|
||||
// Branch is the branch argument value.
|
||||
Branch string
|
||||
// Location is the location argument value.
|
||||
Location string
|
||||
}
|
||||
// GetRepository holds details about calls to the GetRepository method.
|
||||
GetRepository []struct {
|
||||
// Ctx is the ctx argument value.
|
||||
Ctx context.Context
|
||||
// Nwo is the nwo argument value.
|
||||
Nwo string
|
||||
}
|
||||
// GetUser holds details about calls to the GetUser method.
|
||||
GetUser []struct {
|
||||
// Ctx is the ctx argument value.
|
||||
Ctx context.Context
|
||||
}
|
||||
// ListCodespaces holds details about calls to the ListCodespaces method.
|
||||
ListCodespaces []struct {
|
||||
// Ctx is the ctx argument value.
|
||||
Ctx context.Context
|
||||
// Limit is the limit argument value.
|
||||
Limit int
|
||||
}
|
||||
// StartCodespace holds details about calls to the StartCodespace method.
|
||||
StartCodespace []struct {
|
||||
// Ctx is the ctx argument value.
|
||||
Ctx context.Context
|
||||
// Name is the name argument value.
|
||||
Name string
|
||||
}
|
||||
// StopCodespace holds details about calls to the StopCodespace method.
|
||||
StopCodespace []struct {
|
||||
// Ctx is the ctx argument value.
|
||||
Ctx context.Context
|
||||
// Name is the name argument value.
|
||||
Name string
|
||||
}
|
||||
}
|
||||
lockAuthorizedKeys sync.RWMutex
|
||||
lockCreateCodespace sync.RWMutex
|
||||
lockDeleteCodespace sync.RWMutex
|
||||
lockGetCodespace sync.RWMutex
|
||||
lockGetCodespaceRegionLocation sync.RWMutex
|
||||
lockGetCodespaceRepositoryContents sync.RWMutex
|
||||
lockGetCodespacesMachines sync.RWMutex
|
||||
lockGetRepository sync.RWMutex
|
||||
lockGetUser sync.RWMutex
|
||||
lockListCodespaces sync.RWMutex
|
||||
lockStartCodespace sync.RWMutex
|
||||
lockStopCodespace sync.RWMutex
|
||||
}
|
||||
|
||||
// AuthorizedKeys calls AuthorizedKeysFunc.
|
||||
func (mock *apiClientMock) AuthorizedKeys(ctx context.Context, user string) ([]byte, error) {
|
||||
if mock.AuthorizedKeysFunc == nil {
|
||||
panic("apiClientMock.AuthorizedKeysFunc: method is nil but apiClient.AuthorizedKeys was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Ctx context.Context
|
||||
User string
|
||||
}{
|
||||
Ctx: ctx,
|
||||
User: user,
|
||||
}
|
||||
mock.lockAuthorizedKeys.Lock()
|
||||
mock.calls.AuthorizedKeys = append(mock.calls.AuthorizedKeys, callInfo)
|
||||
mock.lockAuthorizedKeys.Unlock()
|
||||
return mock.AuthorizedKeysFunc(ctx, user)
|
||||
}
|
||||
|
||||
// AuthorizedKeysCalls gets all the calls that were made to AuthorizedKeys.
|
||||
// Check the length with:
|
||||
// len(mockedapiClient.AuthorizedKeysCalls())
|
||||
func (mock *apiClientMock) AuthorizedKeysCalls() []struct {
|
||||
Ctx context.Context
|
||||
User string
|
||||
} {
|
||||
var calls []struct {
|
||||
Ctx context.Context
|
||||
User string
|
||||
}
|
||||
mock.lockAuthorizedKeys.RLock()
|
||||
calls = mock.calls.AuthorizedKeys
|
||||
mock.lockAuthorizedKeys.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// CreateCodespace calls CreateCodespaceFunc.
|
||||
func (mock *apiClientMock) CreateCodespace(ctx context.Context, params *api.CreateCodespaceParams) (*api.Codespace, error) {
|
||||
if mock.CreateCodespaceFunc == nil {
|
||||
panic("apiClientMock.CreateCodespaceFunc: method is nil but apiClient.CreateCodespace was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Ctx context.Context
|
||||
Params *api.CreateCodespaceParams
|
||||
}{
|
||||
Ctx: ctx,
|
||||
Params: params,
|
||||
}
|
||||
mock.lockCreateCodespace.Lock()
|
||||
mock.calls.CreateCodespace = append(mock.calls.CreateCodespace, callInfo)
|
||||
mock.lockCreateCodespace.Unlock()
|
||||
return mock.CreateCodespaceFunc(ctx, params)
|
||||
}
|
||||
|
||||
// CreateCodespaceCalls gets all the calls that were made to CreateCodespace.
|
||||
// Check the length with:
|
||||
// len(mockedapiClient.CreateCodespaceCalls())
|
||||
func (mock *apiClientMock) CreateCodespaceCalls() []struct {
|
||||
Ctx context.Context
|
||||
Params *api.CreateCodespaceParams
|
||||
} {
|
||||
var calls []struct {
|
||||
Ctx context.Context
|
||||
Params *api.CreateCodespaceParams
|
||||
}
|
||||
mock.lockCreateCodespace.RLock()
|
||||
calls = mock.calls.CreateCodespace
|
||||
mock.lockCreateCodespace.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// DeleteCodespace calls DeleteCodespaceFunc.
|
||||
func (mock *apiClientMock) DeleteCodespace(ctx context.Context, name string) error {
|
||||
if mock.DeleteCodespaceFunc == nil {
|
||||
panic("apiClientMock.DeleteCodespaceFunc: method is nil but apiClient.DeleteCodespace was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Ctx context.Context
|
||||
Name string
|
||||
}{
|
||||
Ctx: ctx,
|
||||
Name: name,
|
||||
}
|
||||
mock.lockDeleteCodespace.Lock()
|
||||
mock.calls.DeleteCodespace = append(mock.calls.DeleteCodespace, callInfo)
|
||||
mock.lockDeleteCodespace.Unlock()
|
||||
return mock.DeleteCodespaceFunc(ctx, name)
|
||||
}
|
||||
|
||||
// DeleteCodespaceCalls gets all the calls that were made to DeleteCodespace.
|
||||
// Check the length with:
|
||||
// len(mockedapiClient.DeleteCodespaceCalls())
|
||||
func (mock *apiClientMock) DeleteCodespaceCalls() []struct {
|
||||
Ctx context.Context
|
||||
Name string
|
||||
} {
|
||||
var calls []struct {
|
||||
Ctx context.Context
|
||||
Name string
|
||||
}
|
||||
mock.lockDeleteCodespace.RLock()
|
||||
calls = mock.calls.DeleteCodespace
|
||||
mock.lockDeleteCodespace.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// GetCodespace calls GetCodespaceFunc.
|
||||
func (mock *apiClientMock) GetCodespace(ctx context.Context, name string, includeConnection bool) (*api.Codespace, error) {
|
||||
if mock.GetCodespaceFunc == nil {
|
||||
panic("apiClientMock.GetCodespaceFunc: method is nil but apiClient.GetCodespace was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Ctx context.Context
|
||||
Name string
|
||||
IncludeConnection bool
|
||||
}{
|
||||
Ctx: ctx,
|
||||
Name: name,
|
||||
IncludeConnection: includeConnection,
|
||||
}
|
||||
mock.lockGetCodespace.Lock()
|
||||
mock.calls.GetCodespace = append(mock.calls.GetCodespace, callInfo)
|
||||
mock.lockGetCodespace.Unlock()
|
||||
return mock.GetCodespaceFunc(ctx, name, includeConnection)
|
||||
}
|
||||
|
||||
// GetCodespaceCalls gets all the calls that were made to GetCodespace.
|
||||
// Check the length with:
|
||||
// len(mockedapiClient.GetCodespaceCalls())
|
||||
func (mock *apiClientMock) GetCodespaceCalls() []struct {
|
||||
Ctx context.Context
|
||||
Name string
|
||||
IncludeConnection bool
|
||||
} {
|
||||
var calls []struct {
|
||||
Ctx context.Context
|
||||
Name string
|
||||
IncludeConnection bool
|
||||
}
|
||||
mock.lockGetCodespace.RLock()
|
||||
calls = mock.calls.GetCodespace
|
||||
mock.lockGetCodespace.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// GetCodespaceRegionLocation calls GetCodespaceRegionLocationFunc.
|
||||
func (mock *apiClientMock) GetCodespaceRegionLocation(ctx context.Context) (string, error) {
|
||||
if mock.GetCodespaceRegionLocationFunc == nil {
|
||||
panic("apiClientMock.GetCodespaceRegionLocationFunc: method is nil but apiClient.GetCodespaceRegionLocation was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Ctx context.Context
|
||||
}{
|
||||
Ctx: ctx,
|
||||
}
|
||||
mock.lockGetCodespaceRegionLocation.Lock()
|
||||
mock.calls.GetCodespaceRegionLocation = append(mock.calls.GetCodespaceRegionLocation, callInfo)
|
||||
mock.lockGetCodespaceRegionLocation.Unlock()
|
||||
return mock.GetCodespaceRegionLocationFunc(ctx)
|
||||
}
|
||||
|
||||
// GetCodespaceRegionLocationCalls gets all the calls that were made to GetCodespaceRegionLocation.
|
||||
// Check the length with:
|
||||
// len(mockedapiClient.GetCodespaceRegionLocationCalls())
|
||||
func (mock *apiClientMock) GetCodespaceRegionLocationCalls() []struct {
|
||||
Ctx context.Context
|
||||
} {
|
||||
var calls []struct {
|
||||
Ctx context.Context
|
||||
}
|
||||
mock.lockGetCodespaceRegionLocation.RLock()
|
||||
calls = mock.calls.GetCodespaceRegionLocation
|
||||
mock.lockGetCodespaceRegionLocation.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// GetCodespaceRepositoryContents calls GetCodespaceRepositoryContentsFunc.
|
||||
func (mock *apiClientMock) GetCodespaceRepositoryContents(ctx context.Context, codespace *api.Codespace, path string) ([]byte, error) {
|
||||
if mock.GetCodespaceRepositoryContentsFunc == nil {
|
||||
panic("apiClientMock.GetCodespaceRepositoryContentsFunc: method is nil but apiClient.GetCodespaceRepositoryContents was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Ctx context.Context
|
||||
Codespace *api.Codespace
|
||||
Path string
|
||||
}{
|
||||
Ctx: ctx,
|
||||
Codespace: codespace,
|
||||
Path: path,
|
||||
}
|
||||
mock.lockGetCodespaceRepositoryContents.Lock()
|
||||
mock.calls.GetCodespaceRepositoryContents = append(mock.calls.GetCodespaceRepositoryContents, callInfo)
|
||||
mock.lockGetCodespaceRepositoryContents.Unlock()
|
||||
return mock.GetCodespaceRepositoryContentsFunc(ctx, codespace, path)
|
||||
}
|
||||
|
||||
// GetCodespaceRepositoryContentsCalls gets all the calls that were made to GetCodespaceRepositoryContents.
|
||||
// Check the length with:
|
||||
// len(mockedapiClient.GetCodespaceRepositoryContentsCalls())
|
||||
func (mock *apiClientMock) GetCodespaceRepositoryContentsCalls() []struct {
|
||||
Ctx context.Context
|
||||
Codespace *api.Codespace
|
||||
Path string
|
||||
} {
|
||||
var calls []struct {
|
||||
Ctx context.Context
|
||||
Codespace *api.Codespace
|
||||
Path string
|
||||
}
|
||||
mock.lockGetCodespaceRepositoryContents.RLock()
|
||||
calls = mock.calls.GetCodespaceRepositoryContents
|
||||
mock.lockGetCodespaceRepositoryContents.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// GetCodespacesMachines calls GetCodespacesMachinesFunc.
|
||||
func (mock *apiClientMock) GetCodespacesMachines(ctx context.Context, repoID int, branch string, location string) ([]*api.Machine, error) {
|
||||
if mock.GetCodespacesMachinesFunc == nil {
|
||||
panic("apiClientMock.GetCodespacesMachinesFunc: method is nil but apiClient.GetCodespacesMachines was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Ctx context.Context
|
||||
RepoID int
|
||||
Branch string
|
||||
Location string
|
||||
}{
|
||||
Ctx: ctx,
|
||||
RepoID: repoID,
|
||||
Branch: branch,
|
||||
Location: location,
|
||||
}
|
||||
mock.lockGetCodespacesMachines.Lock()
|
||||
mock.calls.GetCodespacesMachines = append(mock.calls.GetCodespacesMachines, callInfo)
|
||||
mock.lockGetCodespacesMachines.Unlock()
|
||||
return mock.GetCodespacesMachinesFunc(ctx, repoID, branch, location)
|
||||
}
|
||||
|
||||
// GetCodespacesMachinesCalls gets all the calls that were made to GetCodespacesMachines.
|
||||
// Check the length with:
|
||||
// len(mockedapiClient.GetCodespacesMachinesCalls())
|
||||
func (mock *apiClientMock) GetCodespacesMachinesCalls() []struct {
|
||||
Ctx context.Context
|
||||
RepoID int
|
||||
Branch string
|
||||
Location string
|
||||
} {
|
||||
var calls []struct {
|
||||
Ctx context.Context
|
||||
RepoID int
|
||||
Branch string
|
||||
Location string
|
||||
}
|
||||
mock.lockGetCodespacesMachines.RLock()
|
||||
calls = mock.calls.GetCodespacesMachines
|
||||
mock.lockGetCodespacesMachines.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// GetRepository calls GetRepositoryFunc.
|
||||
func (mock *apiClientMock) GetRepository(ctx context.Context, nwo string) (*api.Repository, error) {
|
||||
if mock.GetRepositoryFunc == nil {
|
||||
panic("apiClientMock.GetRepositoryFunc: method is nil but apiClient.GetRepository was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Ctx context.Context
|
||||
Nwo string
|
||||
}{
|
||||
Ctx: ctx,
|
||||
Nwo: nwo,
|
||||
}
|
||||
mock.lockGetRepository.Lock()
|
||||
mock.calls.GetRepository = append(mock.calls.GetRepository, callInfo)
|
||||
mock.lockGetRepository.Unlock()
|
||||
return mock.GetRepositoryFunc(ctx, nwo)
|
||||
}
|
||||
|
||||
// GetRepositoryCalls gets all the calls that were made to GetRepository.
|
||||
// Check the length with:
|
||||
// len(mockedapiClient.GetRepositoryCalls())
|
||||
func (mock *apiClientMock) GetRepositoryCalls() []struct {
|
||||
Ctx context.Context
|
||||
Nwo string
|
||||
} {
|
||||
var calls []struct {
|
||||
Ctx context.Context
|
||||
Nwo string
|
||||
}
|
||||
mock.lockGetRepository.RLock()
|
||||
calls = mock.calls.GetRepository
|
||||
mock.lockGetRepository.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// GetUser calls GetUserFunc.
|
||||
func (mock *apiClientMock) GetUser(ctx context.Context) (*api.User, error) {
|
||||
if mock.GetUserFunc == nil {
|
||||
panic("apiClientMock.GetUserFunc: method is nil but apiClient.GetUser was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Ctx context.Context
|
||||
}{
|
||||
Ctx: ctx,
|
||||
}
|
||||
mock.lockGetUser.Lock()
|
||||
mock.calls.GetUser = append(mock.calls.GetUser, callInfo)
|
||||
mock.lockGetUser.Unlock()
|
||||
return mock.GetUserFunc(ctx)
|
||||
}
|
||||
|
||||
// GetUserCalls gets all the calls that were made to GetUser.
|
||||
// Check the length with:
|
||||
// len(mockedapiClient.GetUserCalls())
|
||||
func (mock *apiClientMock) GetUserCalls() []struct {
|
||||
Ctx context.Context
|
||||
} {
|
||||
var calls []struct {
|
||||
Ctx context.Context
|
||||
}
|
||||
mock.lockGetUser.RLock()
|
||||
calls = mock.calls.GetUser
|
||||
mock.lockGetUser.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// ListCodespaces calls ListCodespacesFunc.
|
||||
func (mock *apiClientMock) ListCodespaces(ctx context.Context, limit int) ([]*api.Codespace, error) {
|
||||
if mock.ListCodespacesFunc == nil {
|
||||
panic("apiClientMock.ListCodespacesFunc: method is nil but apiClient.ListCodespaces was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Ctx context.Context
|
||||
Limit int
|
||||
}{
|
||||
Ctx: ctx,
|
||||
Limit: limit,
|
||||
}
|
||||
mock.lockListCodespaces.Lock()
|
||||
mock.calls.ListCodespaces = append(mock.calls.ListCodespaces, callInfo)
|
||||
mock.lockListCodespaces.Unlock()
|
||||
return mock.ListCodespacesFunc(ctx, limit)
|
||||
}
|
||||
|
||||
// ListCodespacesCalls gets all the calls that were made to ListCodespaces.
|
||||
// Check the length with:
|
||||
// len(mockedapiClient.ListCodespacesCalls())
|
||||
func (mock *apiClientMock) ListCodespacesCalls() []struct {
|
||||
Ctx context.Context
|
||||
Limit int
|
||||
} {
|
||||
var calls []struct {
|
||||
Ctx context.Context
|
||||
Limit int
|
||||
}
|
||||
mock.lockListCodespaces.RLock()
|
||||
calls = mock.calls.ListCodespaces
|
||||
mock.lockListCodespaces.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// StartCodespace calls StartCodespaceFunc.
|
||||
func (mock *apiClientMock) StartCodespace(ctx context.Context, name string) error {
|
||||
if mock.StartCodespaceFunc == nil {
|
||||
panic("apiClientMock.StartCodespaceFunc: method is nil but apiClient.StartCodespace was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Ctx context.Context
|
||||
Name string
|
||||
}{
|
||||
Ctx: ctx,
|
||||
Name: name,
|
||||
}
|
||||
mock.lockStartCodespace.Lock()
|
||||
mock.calls.StartCodespace = append(mock.calls.StartCodespace, callInfo)
|
||||
mock.lockStartCodespace.Unlock()
|
||||
return mock.StartCodespaceFunc(ctx, name)
|
||||
}
|
||||
|
||||
// StartCodespaceCalls gets all the calls that were made to StartCodespace.
|
||||
// Check the length with:
|
||||
// len(mockedapiClient.StartCodespaceCalls())
|
||||
func (mock *apiClientMock) StartCodespaceCalls() []struct {
|
||||
Ctx context.Context
|
||||
Name string
|
||||
} {
|
||||
var calls []struct {
|
||||
Ctx context.Context
|
||||
Name string
|
||||
}
|
||||
mock.lockStartCodespace.RLock()
|
||||
calls = mock.calls.StartCodespace
|
||||
mock.lockStartCodespace.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// StopCodespace calls StopCodespaceFunc.
|
||||
func (mock *apiClientMock) StopCodespace(ctx context.Context, name string) error {
|
||||
if mock.StopCodespaceFunc == nil {
|
||||
panic("apiClientMock.StopCodespaceFunc: method is nil but apiClient.StopCodespace was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Ctx context.Context
|
||||
Name string
|
||||
}{
|
||||
Ctx: ctx,
|
||||
Name: name,
|
||||
}
|
||||
mock.lockStopCodespace.Lock()
|
||||
mock.calls.StopCodespace = append(mock.calls.StopCodespace, callInfo)
|
||||
mock.lockStopCodespace.Unlock()
|
||||
return mock.StopCodespaceFunc(ctx, name)
|
||||
}
|
||||
|
||||
// StopCodespaceCalls gets all the calls that were made to StopCodespace.
|
||||
// Check the length with:
|
||||
// len(mockedapiClient.StopCodespaceCalls())
|
||||
func (mock *apiClientMock) StopCodespaceCalls() []struct {
|
||||
Ctx context.Context
|
||||
Name string
|
||||
} {
|
||||
var calls []struct {
|
||||
Ctx context.Context
|
||||
Name string
|
||||
}
|
||||
mock.lockStopCodespace.RLock()
|
||||
calls = mock.calls.StopCodespace
|
||||
mock.lockStopCodespace.RUnlock()
|
||||
return calls
|
||||
}
|
||||
69
pkg/cmd/codespace/mock_prompter.go
Normal file
69
pkg/cmd/codespace/mock_prompter.go
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
// Code generated by moq; DO NOT EDIT.
|
||||
// github.com/matryer/moq
|
||||
|
||||
package codespace
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// prompterMock is a mock implementation of prompter.
|
||||
//
|
||||
// func TestSomethingThatUsesprompter(t *testing.T) {
|
||||
//
|
||||
// // make and configure a mocked prompter
|
||||
// mockedprompter := &prompterMock{
|
||||
// ConfirmFunc: func(message string) (bool, error) {
|
||||
// panic("mock out the Confirm method")
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// // use mockedprompter in code that requires prompter
|
||||
// // and then make assertions.
|
||||
//
|
||||
// }
|
||||
type prompterMock struct {
|
||||
// ConfirmFunc mocks the Confirm method.
|
||||
ConfirmFunc func(message string) (bool, error)
|
||||
|
||||
// calls tracks calls to the methods.
|
||||
calls struct {
|
||||
// Confirm holds details about calls to the Confirm method.
|
||||
Confirm []struct {
|
||||
// Message is the message argument value.
|
||||
Message string
|
||||
}
|
||||
}
|
||||
lockConfirm sync.RWMutex
|
||||
}
|
||||
|
||||
// Confirm calls ConfirmFunc.
|
||||
func (mock *prompterMock) Confirm(message string) (bool, error) {
|
||||
if mock.ConfirmFunc == nil {
|
||||
panic("prompterMock.ConfirmFunc: method is nil but prompter.Confirm was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Message string
|
||||
}{
|
||||
Message: message,
|
||||
}
|
||||
mock.lockConfirm.Lock()
|
||||
mock.calls.Confirm = append(mock.calls.Confirm, callInfo)
|
||||
mock.lockConfirm.Unlock()
|
||||
return mock.ConfirmFunc(message)
|
||||
}
|
||||
|
||||
// ConfirmCalls gets all the calls that were made to Confirm.
|
||||
// Check the length with:
|
||||
// len(mockedprompter.ConfirmCalls())
|
||||
func (mock *prompterMock) ConfirmCalls() []struct {
|
||||
Message string
|
||||
} {
|
||||
var calls []struct {
|
||||
Message string
|
||||
}
|
||||
mock.lockConfirm.RLock()
|
||||
calls = mock.calls.Confirm
|
||||
mock.lockConfirm.RUnlock()
|
||||
return calls
|
||||
}
|
||||
55
pkg/cmd/codespace/output/format_json.go
Normal file
55
pkg/cmd/codespace/output/format_json.go
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
package output
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type jsonwriter struct {
|
||||
w io.Writer
|
||||
pretty bool
|
||||
cols []string
|
||||
data []interface{}
|
||||
}
|
||||
|
||||
func (j *jsonwriter) SetHeader(cols []string) {
|
||||
j.cols = cols
|
||||
}
|
||||
|
||||
func (j *jsonwriter) Append(values []string) {
|
||||
row := make(map[string]string)
|
||||
for i, v := range values {
|
||||
row[camelize(j.cols[i])] = v
|
||||
}
|
||||
j.data = append(j.data, row)
|
||||
}
|
||||
|
||||
func (j *jsonwriter) Render() {
|
||||
enc := json.NewEncoder(j.w)
|
||||
if j.pretty {
|
||||
enc.SetIndent("", " ")
|
||||
}
|
||||
_ = enc.Encode(j.data)
|
||||
}
|
||||
|
||||
func camelize(s string) string {
|
||||
var b strings.Builder
|
||||
capitalizeNext := false
|
||||
for i, r := range s {
|
||||
if r == ' ' {
|
||||
capitalizeNext = true
|
||||
continue
|
||||
}
|
||||
if capitalizeNext {
|
||||
b.WriteRune(unicode.ToUpper(r))
|
||||
capitalizeNext = false
|
||||
} else if i == 0 {
|
||||
b.WriteRune(unicode.ToLower(r))
|
||||
} else {
|
||||
b.WriteRune(r)
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
31
pkg/cmd/codespace/output/format_table.go
Normal file
31
pkg/cmd/codespace/output/format_table.go
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package output
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
type Table interface {
|
||||
SetHeader([]string)
|
||||
Append([]string)
|
||||
Render()
|
||||
}
|
||||
|
||||
func NewTable(w io.Writer, asJSON bool) Table {
|
||||
isTTY := isTTY(w)
|
||||
if asJSON {
|
||||
return &jsonwriter{w: w, pretty: isTTY}
|
||||
}
|
||||
if isTTY {
|
||||
return tablewriter.NewWriter(w)
|
||||
}
|
||||
return &tabwriter{w: w}
|
||||
}
|
||||
|
||||
func isTTY(w io.Writer) bool {
|
||||
f, ok := w.(*os.File)
|
||||
return ok && term.IsTerminal(int(f.Fd()))
|
||||
}
|
||||
25
pkg/cmd/codespace/output/format_tsv.go
Normal file
25
pkg/cmd/codespace/output/format_tsv.go
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
package output
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type tabwriter struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
func (j *tabwriter) SetHeader([]string) {}
|
||||
|
||||
func (j *tabwriter) Append(values []string) {
|
||||
var sep string
|
||||
for i, v := range values {
|
||||
if i == 1 {
|
||||
sep = "\t"
|
||||
}
|
||||
fmt.Fprintf(j.w, "%s%s", sep, v)
|
||||
}
|
||||
fmt.Fprint(j.w, "\n")
|
||||
}
|
||||
|
||||
func (j *tabwriter) Render() {}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue