Merge branch 'trunk' into gist-edit-large-file
This commit is contained in:
commit
cf7180379a
46 changed files with 1956 additions and 734 deletions
6
.github/workflows/codeql.yml
vendored
6
.github/workflows/codeql.yml
vendored
|
|
@ -34,13 +34,13 @@ jobs:
|
|||
go-version-file: "go.mod"
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
queries: security-and-quality
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@v4
|
||||
with:
|
||||
category: "/language:${{ matrix.language }}"
|
||||
upload: false
|
||||
|
|
@ -56,7 +56,7 @@ jobs:
|
|||
output: sarif-results/${{ matrix.language }}.sarif
|
||||
|
||||
- name: Upload filtered SARIF
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
uses: github/codeql-action/upload-sarif@v4
|
||||
with:
|
||||
sarif_file: sarif-results/${{ matrix.language }}.sarif
|
||||
category: "/language:${{ matrix.language }}"
|
||||
|
|
|
|||
18
.github/workflows/deployment.yml
vendored
18
.github/workflows/deployment.yml
vendored
|
|
@ -50,7 +50,7 @@ jobs:
|
|||
with:
|
||||
go-version-file: 'go.mod'
|
||||
- name: Install GoReleaser
|
||||
uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552
|
||||
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a
|
||||
with:
|
||||
version: "~1.17.1"
|
||||
install-only: true
|
||||
|
|
@ -62,7 +62,7 @@ jobs:
|
|||
run: |
|
||||
go run ./cmd/gen-docs --website --doc-path dist/manual
|
||||
tar -czvf dist/manual.tar.gz -C dist -- manual
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: linux
|
||||
if-no-files-found: error
|
||||
|
|
@ -103,7 +103,7 @@ jobs:
|
|||
security set-key-partition-list -S "apple-tool:,apple:,codesign:" -s -k "$keychain_password" "$keychain"
|
||||
rm "$RUNNER_TEMP/cert.p12"
|
||||
- name: Install GoReleaser
|
||||
uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552
|
||||
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a
|
||||
with:
|
||||
version: "~1.17.1"
|
||||
install-only: true
|
||||
|
|
@ -134,7 +134,7 @@ jobs:
|
|||
run: |
|
||||
shopt -s failglob
|
||||
script/pkgmacos "$TAG_NAME"
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: macos
|
||||
if-no-files-found: error
|
||||
|
|
@ -157,7 +157,7 @@ jobs:
|
|||
with:
|
||||
go-version-file: 'go.mod'
|
||||
- name: Install GoReleaser
|
||||
uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552
|
||||
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a
|
||||
with:
|
||||
version: "~1.17.1"
|
||||
install-only: true
|
||||
|
|
@ -238,7 +238,7 @@ jobs:
|
|||
Get-ChildItem -Path .\dist -Filter *.msi | ForEach-Object {
|
||||
.\script\sign.ps1 $_.FullName
|
||||
}
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: windows
|
||||
if-no-files-found: error
|
||||
|
|
@ -256,7 +256,7 @@ jobs:
|
|||
- name: Checkout cli/cli
|
||||
uses: actions/checkout@v5
|
||||
- name: Merge built artifacts
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v6
|
||||
- name: Checkout documentation site
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
|
|
@ -309,7 +309,7 @@ jobs:
|
|||
rpmsign --addsign dist/*.rpm
|
||||
- name: Attest release artifacts
|
||||
if: inputs.environment == 'production'
|
||||
uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0
|
||||
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
|
||||
with:
|
||||
subject-path: "dist/gh_*"
|
||||
- name: Run createrepo
|
||||
|
|
@ -384,7 +384,7 @@ jobs:
|
|||
git diff --name-status @{upstream}..
|
||||
fi
|
||||
- name: Bump homebrew-core formula
|
||||
uses: mislav/bump-homebrew-formula-action@8e2baa47daaa8db10fcdeb04105dfa6850eb0d68
|
||||
uses: mislav/bump-homebrew-formula-action@56a283fa15557e9abaa4bdb63b8212abc68e655c
|
||||
if: inputs.environment == 'production' && !contains(inputs.tag_name, '-')
|
||||
with:
|
||||
formula-name: gh
|
||||
|
|
|
|||
2
.github/workflows/govulncheck.yml
vendored
2
.github/workflows/govulncheck.yml
vendored
|
|
@ -24,6 +24,6 @@ jobs:
|
|||
go run golang.org/x/vuln/cmd/govulncheck@d1f380186385b4f64e00313f31743df8e4b89a77 -format sarif ./... > gh.sarif
|
||||
|
||||
- name: Upload SARIF report
|
||||
uses: github/codeql-action/upload-sarif@9b02dc2f60288b463e7a66e39c78829b62780db7 # 2.22.1
|
||||
uses: github/codeql-action/upload-sarif@v4
|
||||
with:
|
||||
sarif_file: gh.sarif
|
||||
|
|
|
|||
2
.github/workflows/homebrew-bump.yml
vendored
2
.github/workflows/homebrew-bump.yml
vendored
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Bump homebrew-core formula
|
||||
uses: mislav/bump-homebrew-formula-action@8e2baa47daaa8db10fcdeb04105dfa6850eb0d68
|
||||
uses: mislav/bump-homebrew-formula-action@56a283fa15557e9abaa4bdb63b8212abc68e655c
|
||||
if: inputs.environment == 'production' && !contains(inputs.tag_name, '-')
|
||||
with:
|
||||
formula-name: gh
|
||||
|
|
|
|||
|
|
@ -17,22 +17,24 @@ type tokenGetter interface {
|
|||
}
|
||||
|
||||
type HTTPClientOptions struct {
|
||||
AppVersion string
|
||||
CacheTTL time.Duration
|
||||
Config tokenGetter
|
||||
EnableCache bool
|
||||
Log io.Writer
|
||||
LogColorize bool
|
||||
LogVerboseHTTP bool
|
||||
AppVersion string
|
||||
CacheTTL time.Duration
|
||||
Config tokenGetter
|
||||
EnableCache bool
|
||||
Log io.Writer
|
||||
LogColorize bool
|
||||
LogVerboseHTTP bool
|
||||
SkipDefaultHeaders bool
|
||||
}
|
||||
|
||||
func NewHTTPClient(opts HTTPClientOptions) (*http.Client, error) {
|
||||
// Provide invalid host, and token values so gh.HTTPClient will not automatically resolve them.
|
||||
// The real host and token are inserted at request time.
|
||||
clientOpts := ghAPI.ClientOptions{
|
||||
Host: "none",
|
||||
AuthToken: "none",
|
||||
LogIgnoreEnv: true,
|
||||
Host: "none",
|
||||
AuthToken: "none",
|
||||
LogIgnoreEnv: true,
|
||||
SkipDefaultHeaders: opts.SkipDefaultHeaders,
|
||||
}
|
||||
|
||||
debugEnabled, debugValue := utils.IsDebugEnabled()
|
||||
|
|
|
|||
|
|
@ -18,15 +18,16 @@ import (
|
|||
|
||||
func TestNewHTTPClient(t *testing.T) {
|
||||
type args struct {
|
||||
config tokenGetter
|
||||
appVersion string
|
||||
logVerboseHTTP bool
|
||||
config tokenGetter
|
||||
appVersion string
|
||||
logVerboseHTTP bool
|
||||
skipDefaultHeaders bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
host string
|
||||
wantHeader map[string]string
|
||||
wantHeader map[string][]string
|
||||
wantStderr string
|
||||
}{
|
||||
{
|
||||
|
|
@ -37,10 +38,10 @@ func TestNewHTTPClient(t *testing.T) {
|
|||
logVerboseHTTP: false,
|
||||
},
|
||||
host: "github.com",
|
||||
wantHeader: map[string]string{
|
||||
"authorization": "token MYTOKEN",
|
||||
"user-agent": "GitHub CLI v1.2.3",
|
||||
"accept": "application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview",
|
||||
wantHeader: map[string][]string{
|
||||
"authorization": {"token MYTOKEN"},
|
||||
"user-agent": {"GitHub CLI v1.2.3"},
|
||||
"accept": {"application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview"},
|
||||
},
|
||||
wantStderr: "",
|
||||
},
|
||||
|
|
@ -51,10 +52,10 @@ func TestNewHTTPClient(t *testing.T) {
|
|||
appVersion: "v1.2.3",
|
||||
},
|
||||
host: "example.com",
|
||||
wantHeader: map[string]string{
|
||||
"authorization": "token GHETOKEN",
|
||||
"user-agent": "GitHub CLI v1.2.3",
|
||||
"accept": "application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview",
|
||||
wantHeader: map[string][]string{
|
||||
"authorization": {"token GHETOKEN"},
|
||||
"user-agent": {"GitHub CLI v1.2.3"},
|
||||
"accept": {"application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview"},
|
||||
},
|
||||
wantStderr: "",
|
||||
},
|
||||
|
|
@ -66,10 +67,10 @@ func TestNewHTTPClient(t *testing.T) {
|
|||
logVerboseHTTP: false,
|
||||
},
|
||||
host: "github.com",
|
||||
wantHeader: map[string]string{
|
||||
"authorization": "",
|
||||
"user-agent": "GitHub CLI v1.2.3",
|
||||
"accept": "application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview",
|
||||
wantHeader: map[string][]string{
|
||||
"authorization": nil, // should not be set
|
||||
"user-agent": {"GitHub CLI v1.2.3"},
|
||||
"accept": {"application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview"},
|
||||
},
|
||||
wantStderr: "",
|
||||
},
|
||||
|
|
@ -81,10 +82,10 @@ func TestNewHTTPClient(t *testing.T) {
|
|||
logVerboseHTTP: false,
|
||||
},
|
||||
host: "example.com",
|
||||
wantHeader: map[string]string{
|
||||
"authorization": "",
|
||||
"user-agent": "GitHub CLI v1.2.3",
|
||||
"accept": "application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview",
|
||||
wantHeader: map[string][]string{
|
||||
"authorization": nil, // should not be set
|
||||
"user-agent": {"GitHub CLI v1.2.3"},
|
||||
"accept": {"application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview"},
|
||||
},
|
||||
wantStderr: "",
|
||||
},
|
||||
|
|
@ -96,10 +97,10 @@ func TestNewHTTPClient(t *testing.T) {
|
|||
logVerboseHTTP: true,
|
||||
},
|
||||
host: "github.com",
|
||||
wantHeader: map[string]string{
|
||||
"authorization": "token MYTOKEN",
|
||||
"user-agent": "GitHub CLI v1.2.3",
|
||||
"accept": "application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview",
|
||||
wantHeader: map[string][]string{
|
||||
"authorization": {"token MYTOKEN"},
|
||||
"user-agent": {"GitHub CLI v1.2.3"},
|
||||
"accept": {"application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview"},
|
||||
},
|
||||
wantStderr: heredoc.Doc(`
|
||||
* Request at <time>
|
||||
|
|
@ -115,6 +116,34 @@ func TestNewHTTPClient(t *testing.T) {
|
|||
< HTTP/1.1 204 No Content
|
||||
< Date: <time>
|
||||
|
||||
* Request took <duration>
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "respect skip default headers option",
|
||||
args: args{
|
||||
appVersion: "v1.2.3",
|
||||
logVerboseHTTP: true,
|
||||
skipDefaultHeaders: true,
|
||||
},
|
||||
host: "github.com",
|
||||
wantHeader: map[string][]string{
|
||||
"accept": nil,
|
||||
"authorization": nil,
|
||||
"content-type": nil,
|
||||
"user-agent": {"GitHub CLI v1.2.3"},
|
||||
},
|
||||
wantStderr: heredoc.Doc(`
|
||||
* Request at <time>
|
||||
* Request to http://<host>:<port>
|
||||
> GET / HTTP/1.1
|
||||
> Host: github.com
|
||||
> Time-Zone: <timezone>
|
||||
> User-Agent: GitHub CLI v1.2.3
|
||||
|
||||
< HTTP/1.1 204 No Content
|
||||
< Date: <time>
|
||||
|
||||
* Request took <duration>
|
||||
`),
|
||||
},
|
||||
|
|
@ -131,10 +160,11 @@ func TestNewHTTPClient(t *testing.T) {
|
|||
t.Run(tt.name, func(t *testing.T) {
|
||||
ios, _, _, stderr := iostreams.Test()
|
||||
client, err := NewHTTPClient(HTTPClientOptions{
|
||||
AppVersion: tt.args.appVersion,
|
||||
Config: tt.args.config,
|
||||
Log: ios.ErrOut,
|
||||
LogVerboseHTTP: tt.args.logVerboseHTTP,
|
||||
AppVersion: tt.args.appVersion,
|
||||
Config: tt.args.config,
|
||||
Log: ios.ErrOut,
|
||||
LogVerboseHTTP: tt.args.logVerboseHTTP,
|
||||
SkipDefaultHeaders: tt.args.skipDefaultHeaders,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
|
@ -148,7 +178,7 @@ func TestNewHTTPClient(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
for name, value := range tt.wantHeader {
|
||||
assert.Equal(t, value, gotReq.Header.Get(name), name)
|
||||
assert.Equal(t, value, gotReq.Header.Values(name), name)
|
||||
}
|
||||
|
||||
assert.Equal(t, 204, res.StatusCode)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
|
@ -629,17 +631,58 @@ func CreatePullRequest(client *Client, repo *Repository, params map[string]inter
|
|||
return pr, nil
|
||||
}
|
||||
|
||||
func UpdatePullRequestReviews(client *Client, repo ghrepo.Interface, params githubv4.RequestReviewsInput) error {
|
||||
var mutation struct {
|
||||
RequestReviews struct {
|
||||
PullRequest struct {
|
||||
ID string
|
||||
}
|
||||
} `graphql:"requestReviews(input: $input)"`
|
||||
// AddPullRequestReviews adds the given user and team reviewers to a pull request using the REST API.
|
||||
func AddPullRequestReviews(client *Client, repo ghrepo.Interface, prNumber int, users, teams []string) error {
|
||||
if len(users) == 0 && len(teams) == 0 {
|
||||
return nil
|
||||
}
|
||||
variables := map[string]interface{}{"input": params}
|
||||
err := client.Mutate(repo.RepoHost(), "PullRequestUpdateRequestReviews", &mutation, variables)
|
||||
return err
|
||||
|
||||
path := fmt.Sprintf(
|
||||
"repos/%s/%s/pulls/%d/requested_reviewers",
|
||||
url.PathEscape(repo.RepoOwner()),
|
||||
url.PathEscape(repo.RepoName()),
|
||||
prNumber,
|
||||
)
|
||||
body := struct {
|
||||
Reviewers []string `json:"reviewers,omitempty"`
|
||||
TeamReviewers []string `json:"team_reviewers,omitempty"`
|
||||
}{
|
||||
Reviewers: users,
|
||||
TeamReviewers: teams,
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
if err := json.NewEncoder(buf).Encode(body); err != nil {
|
||||
return err
|
||||
}
|
||||
// The endpoint responds with the updated pull request object; we don't need it here.
|
||||
return client.REST(repo.RepoHost(), "POST", path, buf, nil)
|
||||
}
|
||||
|
||||
// RemovePullRequestReviews removes requested reviewers from a pull request using the REST API.
|
||||
func RemovePullRequestReviews(client *Client, repo ghrepo.Interface, prNumber int, users, teams []string) error {
|
||||
if len(users) == 0 && len(teams) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
path := fmt.Sprintf(
|
||||
"repos/%s/%s/pulls/%d/requested_reviewers",
|
||||
url.PathEscape(repo.RepoOwner()),
|
||||
url.PathEscape(repo.RepoName()),
|
||||
prNumber,
|
||||
)
|
||||
body := struct {
|
||||
Reviewers []string `json:"reviewers,omitempty"`
|
||||
TeamReviewers []string `json:"team_reviewers,omitempty"`
|
||||
}{
|
||||
Reviewers: users,
|
||||
TeamReviewers: teams,
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
if err := json.NewEncoder(buf).Encode(body); err != nil {
|
||||
return err
|
||||
}
|
||||
// The endpoint responds with the updated pull request object; we don't need it here.
|
||||
return client.REST(repo.RepoHost(), "DELETE", path, buf, nil)
|
||||
}
|
||||
|
||||
func UpdatePullRequestBranch(client *Client, repo ghrepo.Interface, params githubv4.UpdatePullRequestBranchInput) error {
|
||||
|
|
@ -721,6 +764,37 @@ func PullRequestReady(client *Client, repo ghrepo.Interface, pr *PullRequest) er
|
|||
return client.Mutate(repo.RepoHost(), "PullRequestReadyForReview", &mutation, variables)
|
||||
}
|
||||
|
||||
func PullRequestRevert(client *Client, repo ghrepo.Interface, params githubv4.RevertPullRequestInput) (*PullRequest, error) {
|
||||
var mutation struct {
|
||||
RevertPullRequest struct {
|
||||
PullRequest struct {
|
||||
ID githubv4.ID
|
||||
}
|
||||
RevertPullRequest struct {
|
||||
ID string
|
||||
Number int
|
||||
URL string
|
||||
}
|
||||
} `graphql:"revertPullRequest(input: $input)"`
|
||||
}
|
||||
|
||||
variables := map[string]interface{}{
|
||||
"input": params,
|
||||
}
|
||||
err := client.Mutate(repo.RepoHost(), "PullRequestRevert", &mutation, variables)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pr := &mutation.RevertPullRequest.RevertPullRequest
|
||||
revertPR := &PullRequest{
|
||||
ID: pr.ID,
|
||||
Number: pr.Number,
|
||||
URL: pr.URL,
|
||||
}
|
||||
|
||||
return revertPR, nil
|
||||
}
|
||||
|
||||
func ConvertPullRequestToDraft(client *Client, repo ghrepo.Interface, pr *PullRequest) error {
|
||||
var mutation struct {
|
||||
ConvertPullRequestToDraft struct {
|
||||
|
|
|
|||
85
go.mod
85
go.mod
|
|
@ -11,7 +11,7 @@ require (
|
|||
github.com/atotto/clipboard v0.1.4
|
||||
github.com/briandowns/spinner v1.23.2
|
||||
github.com/cenkalti/backoff/v4 v4.3.0
|
||||
github.com/cenkalti/backoff/v5 v5.0.2
|
||||
github.com/cenkalti/backoff/v5 v5.0.3
|
||||
github.com/charmbracelet/glamour v0.10.0
|
||||
github.com/charmbracelet/huh v0.7.0
|
||||
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834
|
||||
|
|
@ -23,8 +23,8 @@ require (
|
|||
github.com/creack/pty v1.1.24
|
||||
github.com/digitorus/timestamp v0.0.0-20250524132541-c45532741eea
|
||||
github.com/distribution/reference v0.6.0
|
||||
github.com/gabriel-vasile/mimetype v1.4.9
|
||||
github.com/gdamore/tcell/v2 v2.8.1
|
||||
github.com/gabriel-vasile/mimetype v1.4.10
|
||||
github.com/gdamore/tcell/v2 v2.9.0
|
||||
github.com/golang/snappy v1.0.0
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/google/go-containerregistry v0.20.6
|
||||
|
|
@ -32,7 +32,6 @@ require (
|
|||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/hashicorp/go-version v1.7.0
|
||||
github.com/henvic/httpretty v0.1.4
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec
|
||||
github.com/in-toto/attestation v1.1.2
|
||||
github.com/joho/godotenv v1.5.1
|
||||
|
|
@ -43,23 +42,23 @@ require (
|
|||
github.com/microsoft/dev-tunnels v0.1.13
|
||||
github.com/muhammadmuzzammil1998/jsonc v1.0.0
|
||||
github.com/opentracing/opentracing-go v1.2.0
|
||||
github.com/rivo/tview v0.0.0-20250625164341-a4a78f1e05cb
|
||||
github.com/rivo/tview v0.42.0
|
||||
github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7
|
||||
github.com/sigstore/protobuf-specs v0.5.0
|
||||
github.com/sigstore/sigstore-go v1.1.0
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/spf13/pflag v1.0.7
|
||||
github.com/stretchr/testify v1.11.0
|
||||
github.com/theupdateframework/go-tuf/v2 v2.1.1
|
||||
github.com/sigstore/sigstore-go v1.1.3
|
||||
github.com/spf13/cobra v1.10.1
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/theupdateframework/go-tuf/v2 v2.2.0
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1
|
||||
github.com/yuin/goldmark v1.7.13
|
||||
github.com/zalando/go-keyring v0.2.6
|
||||
golang.org/x/crypto v0.41.0
|
||||
golang.org/x/sync v0.16.0
|
||||
golang.org/x/term v0.34.0
|
||||
golang.org/x/text v0.28.0
|
||||
golang.org/x/crypto v0.42.0
|
||||
golang.org/x/sync v0.17.0
|
||||
golang.org/x/term v0.35.0
|
||||
golang.org/x/text v0.29.0
|
||||
google.golang.org/grpc v1.75.0
|
||||
google.golang.org/protobuf v1.36.8
|
||||
google.golang.org/protobuf v1.36.9
|
||||
gopkg.in/h2non/gock.v1 v1.1.2
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
|
@ -67,15 +66,15 @@ require (
|
|||
require (
|
||||
al.essio.dev/pkg/shellescape v1.6.0 // indirect
|
||||
cel.dev/expr v0.24.0 // indirect
|
||||
cloud.google.com/go v0.121.4 // indirect
|
||||
cloud.google.com/go v0.121.6 // indirect
|
||||
cloud.google.com/go/auth v0.16.5 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.8.0 // indirect
|
||||
cloud.google.com/go/iam v1.5.2 // indirect
|
||||
cloud.google.com/go/longrunning v0.6.7 // indirect
|
||||
cloud.google.com/go/monitoring v1.24.2 // indirect
|
||||
cloud.google.com/go/spanner v1.82.0 // indirect
|
||||
cloud.google.com/go/storage v1.55.0 // indirect
|
||||
cloud.google.com/go/spanner v1.84.1 // indirect
|
||||
cloud.google.com/go/storage v1.56.1 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.3 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect
|
||||
|
|
@ -86,7 +85,6 @@ require (
|
|||
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
|
||||
github.com/alecthomas/chroma/v2 v2.19.0 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/avast/retry-go/v4 v4.6.1 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/blang/semver v3.5.1+incompatible // indirect
|
||||
|
|
@ -120,8 +118,7 @@ require (
|
|||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/gdamore/encoding v1.0.1 // indirect
|
||||
github.com/globocom/go-buffer v1.2.2 // indirect
|
||||
github.com/go-chi/chi/v5 v5.2.2 // indirect
|
||||
github.com/go-chi/chi/v5 v5.2.3 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.1 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
|
|
@ -133,7 +130,18 @@ require (
|
|||
github.com/go-openapi/runtime v0.28.0 // indirect
|
||||
github.com/go-openapi/spec v0.21.0 // indirect
|
||||
github.com/go-openapi/strfmt v0.23.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/go-openapi/swag v0.24.1 // indirect
|
||||
github.com/go-openapi/swag/cmdutils v0.24.0 // indirect
|
||||
github.com/go-openapi/swag/conv v0.24.0 // indirect
|
||||
github.com/go-openapi/swag/fileutils v0.24.0 // indirect
|
||||
github.com/go-openapi/swag/jsonname v0.24.0 // indirect
|
||||
github.com/go-openapi/swag/jsonutils v0.24.0 // indirect
|
||||
github.com/go-openapi/swag/loading v0.24.0 // indirect
|
||||
github.com/go-openapi/swag/mangling v0.24.0 // indirect
|
||||
github.com/go-openapi/swag/netutils v0.24.0 // indirect
|
||||
github.com/go-openapi/swag/stringutils v0.24.0 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.24.0 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.24.0 // indirect
|
||||
github.com/go-openapi/validate v0.24.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
|
|
@ -144,10 +152,11 @@ require (
|
|||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/henvic/httpretty v0.1.4 // indirect
|
||||
github.com/huandu/xstrings v1.5.0 // indirect
|
||||
github.com/in-toto/in-toto-golang v0.9.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
|
|
@ -187,10 +196,10 @@ require (
|
|||
github.com/shibumi/go-pathspec v1.3.0 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 // indirect
|
||||
github.com/sigstore/rekor v1.4.1 // indirect
|
||||
github.com/sigstore/rekor-tiles v0.1.7-0.20250624231741-98cd4a77300f // indirect
|
||||
github.com/sigstore/sigstore v1.9.5 // indirect
|
||||
github.com/sigstore/timestamp-authority v1.2.8 // indirect
|
||||
github.com/sigstore/rekor v1.4.2 // indirect
|
||||
github.com/sigstore/rekor-tiles v0.1.11 // indirect
|
||||
github.com/sigstore/sigstore v1.9.6-0.20250729224751-181c5d3339b3 // indirect
|
||||
github.com/sigstore/timestamp-authority v1.2.9 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.14.0 // indirect
|
||||
|
|
@ -204,7 +213,7 @@ require (
|
|||
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
|
||||
github.com/transparency-dev/formats v0.0.0-20250421220931-bb8ad4d07c26 // indirect
|
||||
github.com/transparency-dev/merkle v0.0.2 // indirect
|
||||
github.com/transparency-dev/tessera v0.2.1-0.20250610150926-8ee4e93b2823 // indirect
|
||||
github.com/transparency-dev/tessera v1.0.0-rc3 // indirect
|
||||
github.com/vbatts/tar-split v0.12.1 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
|
|
@ -213,26 +222,26 @@ require (
|
|||
go.mongodb.org/mongo-driver v1.17.4 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.38.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||
go.opentelemetry.io/otel v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
|
||||
golang.org/x/mod v0.27.0 // indirect
|
||||
golang.org/x/mod v0.28.0 // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
golang.org/x/tools v0.35.0 // indirect
|
||||
golang.org/x/tools v0.36.0 // indirect
|
||||
google.golang.org/api v0.248.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
)
|
||||
|
|
|
|||
281
go.sum
281
go.sum
|
|
@ -40,8 +40,8 @@ cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRY
|
|||
cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM=
|
||||
cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I=
|
||||
cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY=
|
||||
cloud.google.com/go v0.121.4 h1:cVvUiY0sX0xwyxPwdSU2KsF9knOVmtRyAMt8xou0iTs=
|
||||
cloud.google.com/go v0.121.4/go.mod h1:XEBchUiHFJbz4lKBZwYBDHV/rSyfFktk737TLDU089s=
|
||||
cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c=
|
||||
cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI=
|
||||
cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4=
|
||||
cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw=
|
||||
cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E=
|
||||
|
|
@ -532,8 +532,8 @@ cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+
|
|||
cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos=
|
||||
cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk=
|
||||
cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M=
|
||||
cloud.google.com/go/spanner v1.82.0 h1:w9uO8RqEoBooBLX4nqV1RtgudyU2ZX780KTLRgeVg60=
|
||||
cloud.google.com/go/spanner v1.82.0/go.mod h1:BzybQHFQ/NqGxvE/M+/iU29xgutJf7Q85/4U9RWMto0=
|
||||
cloud.google.com/go/spanner v1.84.1 h1:ShH4Y3YeDtmHa55dFiSS3YtQ0dmCuP0okfAoHp/d68w=
|
||||
cloud.google.com/go/spanner v1.84.1/go.mod h1:3GMEIjOcXINJSvb42H3M6TdlGCDzaCFpiiNQpjHPlCM=
|
||||
cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM=
|
||||
cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ=
|
||||
cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0=
|
||||
|
|
@ -551,8 +551,8 @@ cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeL
|
|||
cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s=
|
||||
cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y=
|
||||
cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4=
|
||||
cloud.google.com/go/storage v1.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2znf0=
|
||||
cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY=
|
||||
cloud.google.com/go/storage v1.56.1 h1:n6gy+yLnHn0hTwBFzNn8zJ1kqWfR91wzdM8hjRF4wP0=
|
||||
cloud.google.com/go/storage v1.56.1/go.mod h1:C9xuCZgFl3buo2HZU/1FncgvvOgTAs/rnh4gF4lMg0s=
|
||||
cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w=
|
||||
cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I=
|
||||
cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4=
|
||||
|
|
@ -686,36 +686,34 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d
|
|||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/avast/retry-go/v4 v4.6.1 h1:VkOLRubHdisGrHnTu89g08aQEWEgRU7LVEop3GbIcMk=
|
||||
github.com/avast/retry-go/v4 v4.6.1/go.mod h1:V6oF8njAwxJ5gRo1Q7Cxab24xs5NCWZBeaHHBklR8mA=
|
||||
github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE=
|
||||
github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.38.0 h1:UCRQ5mlqcFk9HJDIqENSLR3wiG1VTWlyUfLDEvY7RxU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.38.0/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.0 h1:9yH0xiY5fUnVNLRWO0AtayqwU1ndriZdN78LlhruJR4=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.0/go.mod h1:VeV3K72nXnhbe4EuxxhzsDc/ByrCSlZwUnWH52Nde/I=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.4 h1:IPd0Algf1b+Qy9BcDp0sCUcIWdCQPSzDoMK3a8pcbUM=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.4/go.mod h1:nwg78FjH2qvsRM1EVZlX9WuGUJOL5od+0qvm0adEzHk=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.3 h1:GicIdnekoJsjq9wqnvyi2elW6CGMSYKhdozE7/Svh78=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.3/go.mod h1:R7BIi6WNC5mc1kfRM7XM/VHC3uRWkjc396sfabq4iOo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.3 h1:o9RnO+YZ4X+kt5Z7Nvcishlz0nksIt2PIzDglLMP0vA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.3/go.mod h1:+6aLJzOG1fvMOyzIySYjOFjcguGvVRL68R+uoRencN4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.3 h1:joyyUFhiTQQmVK6ImzNU9TQSNRNeD9kOklqTzyk5v6s=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.3/go.mod h1:+vNIyZQP3b3B1tSLI0lxvrU9cfM7gpdRXMFfm67ZcPc=
|
||||
github.com/aws/aws-sdk-go-v2 v1.38.1 h1:j7sc33amE74Rz0M/PoCpsZQ6OunLqys/m5antM0J+Z8=
|
||||
github.com/aws/aws-sdk-go-v2 v1.38.1/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.3 h1:RIb3yr/+PZ18YYNe6MDiG/3jVoJrPmdoCARwNkMGvco=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.3/go.mod h1:jjgx1n7x0FAKl6TnakqrpkHWWKcX3xfWtdnIJs5K9CE=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.7 h1:zqg4OMrKj+t5HlswDApgvAHjxKtlduKS7KicXB+7RLg=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.7/go.mod h1:/4M5OidTskkgkv+nCIfC9/tbiQ/c8qTox9QcUDV0cgc=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4 h1:lpdMwTzmuDLkgW7086jE94HweHCqG+uOJwHf3LZs7T0=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4/go.mod h1:9xzb8/SV62W6gHQGC/8rrvgNXU6ZoYM3sAIJCIrXJxY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4 h1:IdCLsiiIj5YJ3AFevsewURCPV+YWUlOW8JiPhoAy8vg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4/go.mod h1:l4bdfCD7XyyZA9BolKBo1eLqgaJxl0/x91PL4Yqe0ao=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4 h1:j7vjtr1YIssWQOMeOWRbh3z8g2oY/xPjnZH2gLY4sGw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4/go.mod h1:yDmJgqOiH4EA8Hndnv4KwAo8jCGTSnM5ASG1nBI+toA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.3 h1:ieRzyHXypu5ByllM7Sp4hC5f/1Fy5wqxqY0yB85hC7s=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.3/go.mod h1:O5ROz8jHiOAKAwx179v+7sHMhfobFVi6nZt8DEyiYoM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4 h1:ueB2Te0NacDMnaC+68za9jLwkjzxGWm0KB5HTUHjLTI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4/go.mod h1:nLEfLnVMmLvyIG58/6gsSA03F1voKGaCfHV7+lR8S7s=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.44.0 h1:Z95XCqqSnwXr0AY7PgsiOUBhUG2GoDM5getw6RfD1Lg=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.44.0/go.mod h1:DqcSngL7jJeU1fOzh5Ll5rSvX/MlMV6OZlE4mVdFAQc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.28.0 h1:Mc/MKBf2m4VynyJkABoVEN+QzkfLqGj0aiJuEe7cMeM=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.28.0/go.mod h1:iS5OmxEcN4QIPXARGhavH7S8kETNL11kym6jhoS7IUQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.0 h1:6csaS/aJmqZQbKhi1EyEMM7yBW653Wy/B9hnBofW+sw=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.0/go.mod h1:59qHWaY5B+Rs7HGTuVGaC32m0rdpQ68N8QCN3khYiqs=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.37.0 h1:MG9VFW43M4A8BYeAfaJJZWrroinxeTi2r3+SnmLQfSA=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.37.0/go.mod h1:JdeBDPgpJfuS6rU/hNglmOigKhyEZtBmbraLE4GK1J8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.28.2 h1:ve9dYBB8CfJGTFqcQ3ZLAAb/KXWgYlgu/2R2TZL2Ko0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.28.2/go.mod h1:n9bTZFZcBa9hGGqVz3i/a6+NG0zmZgtkB9qVVFDqPA8=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.0 h1:Bnr+fXrlrPEoR1MAFrHVsge3M/WoK4n23VNhRM7TPHI=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.0/go.mod h1:eknndR9rU8UpE/OmFpqU78V1EcXPKFTTm5l/buZYgvM=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.38.0 h1:iV1Ko4Em/lkJIsoKyGfc0nQySi+v0Udxr6Igq+y9JZc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.38.0/go.mod h1:bEPcjW7IbolPfK67G1nilqWyoxYMSPrDiIQ3RdIdKgo=
|
||||
github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw=
|
||||
github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
|
|
@ -736,8 +734,8 @@ github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
|
|||
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
|
||||
github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
|
||||
|
|
@ -815,6 +813,8 @@ github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUo
|
|||
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU=
|
||||
github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
|
||||
github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
|
|
@ -882,21 +882,17 @@ github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/
|
|||
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||
github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
|
||||
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
|
||||
github.com/gdamore/tcell/v2 v2.8.1 h1:KPNxyqclpWpWQlPLx6Xui1pMk8S+7+R37h3g07997NU=
|
||||
github.com/gdamore/tcell/v2 v2.8.1/go.mod h1:bj8ori1BG3OYMjmb3IklZVWfZUJ1UBQt9JXrOCOhGWw=
|
||||
github.com/gdamore/tcell/v2 v2.9.0 h1:N6t+eqK7/xwtRPwxzs1PXeRWnm0H9l02CrgJ7DLn1ys=
|
||||
github.com/gdamore/tcell/v2 v2.9.0/go.mod h1:8/ZoqM9rxzYphT9tH/9LnunhV9oPBqwS8WHGYm5nrmo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/globocom/go-buffer v1.2.2 h1:ICgtlUe5GIYIZFdAVj57+5WYBR4DA56cX+PYZDhGDwc=
|
||||
github.com/globocom/go-buffer v1.2.2/go.mod h1:kY1ALQS0ChiiThmWhsFoT5CYSiuad0t3keIew5LsWdM=
|
||||
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
||||
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g=
|
||||
github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks=
|
||||
github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=
|
||||
|
|
@ -930,8 +926,30 @@ github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9Z
|
|||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||
github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c=
|
||||
github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4=
|
||||
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
|
||||
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
|
||||
github.com/go-openapi/swag v0.24.1 h1:DPdYTZKo6AQCRqzwr/kGkxJzHhpKxZ9i/oX0zag+MF8=
|
||||
github.com/go-openapi/swag v0.24.1/go.mod h1:sm8I3lCPlspsBBwUm1t5oZeWZS0s7m/A+Psg0ooRU0A=
|
||||
github.com/go-openapi/swag/cmdutils v0.24.0 h1:KlRCffHwXFI6E5MV9n8o8zBRElpY4uK4yWyAMWETo9I=
|
||||
github.com/go-openapi/swag/cmdutils v0.24.0/go.mod h1:uxib2FAeQMByyHomTlsP8h1TtPd54Msu2ZDU/H5Vuf8=
|
||||
github.com/go-openapi/swag/conv v0.24.0 h1:ejB9+7yogkWly6pnruRX45D1/6J+ZxRu92YFivx54ik=
|
||||
github.com/go-openapi/swag/conv v0.24.0/go.mod h1:jbn140mZd7EW2g8a8Y5bwm8/Wy1slLySQQ0ND6DPc2c=
|
||||
github.com/go-openapi/swag/fileutils v0.24.0 h1:U9pCpqp4RUytnD689Ek/N1d2N/a//XCeqoH508H5oak=
|
||||
github.com/go-openapi/swag/fileutils v0.24.0/go.mod h1:3SCrCSBHyP1/N+3oErQ1gP+OX1GV2QYFSnrTbzwli90=
|
||||
github.com/go-openapi/swag/jsonname v0.24.0 h1:2wKS9bgRV/xB8c62Qg16w4AUiIrqqiniJFtZGi3dg5k=
|
||||
github.com/go-openapi/swag/jsonname v0.24.0/go.mod h1:GXqrPzGJe611P7LG4QB9JKPtUZ7flE4DOVechNaDd7Q=
|
||||
github.com/go-openapi/swag/jsonutils v0.24.0 h1:F1vE1q4pg1xtO3HTyJYRmEuJ4jmIp2iZ30bzW5XgZts=
|
||||
github.com/go-openapi/swag/jsonutils v0.24.0/go.mod h1:vBowZtF5Z4DDApIoxcIVfR8v0l9oq5PpYRUuteVu6f0=
|
||||
github.com/go-openapi/swag/loading v0.24.0 h1:ln/fWTwJp2Zkj5DdaX4JPiddFC5CHQpvaBKycOlceYc=
|
||||
github.com/go-openapi/swag/loading v0.24.0/go.mod h1:gShCN4woKZYIxPxbfbyHgjXAhO61m88tmjy0lp/LkJk=
|
||||
github.com/go-openapi/swag/mangling v0.24.0 h1:PGOQpViCOUroIeak/Uj/sjGAq9LADS3mOyjznmHy2pk=
|
||||
github.com/go-openapi/swag/mangling v0.24.0/go.mod h1:Jm5Go9LHkycsz0wfoaBDkdc4CkpuSnIEf62brzyCbhc=
|
||||
github.com/go-openapi/swag/netutils v0.24.0 h1:Bz02HRjYv8046Ycg/w80q3g9QCWeIqTvlyOjQPDjD8w=
|
||||
github.com/go-openapi/swag/netutils v0.24.0/go.mod h1:WRgiHcYTnx+IqfMCtu0hy9oOaPR0HnPbmArSRN1SkZM=
|
||||
github.com/go-openapi/swag/stringutils v0.24.0 h1:i4Z/Jawf9EvXOLUbT97O0HbPUja18VdBxeadyAqS1FM=
|
||||
github.com/go-openapi/swag/stringutils v0.24.0/go.mod h1:5nUXB4xA0kw2df5PRipZDslPJgJut+NjL7D25zPZ/4w=
|
||||
github.com/go-openapi/swag/typeutils v0.24.0 h1:d3szEGzGDf4L2y1gYOSSLeK6h46F+zibnEas2Jm/wIw=
|
||||
github.com/go-openapi/swag/typeutils v0.24.0/go.mod h1:q8C3Kmk/vh2VhpCLaoR2MVWOGP8y7Jc8l82qCTd1DYI=
|
||||
github.com/go-openapi/swag/yamlutils v0.24.0 h1:bhw4894A7Iw6ne+639hsBNRHg9iZg/ISrOVr+sJGp4c=
|
||||
github.com/go-openapi/swag/yamlutils v0.24.0/go.mod h1:DpKv5aYuaGm/sULePoeiG8uwMpZSfReo1HR3Ik0yaG8=
|
||||
github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
|
||||
github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
|
||||
github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
|
||||
|
|
@ -1083,8 +1101,8 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vb
|
|||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.0 h1:+epNPbD5EqgpEMm5wrl4Hqts3jZt8+kYaqUisuuIGTk=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.0/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
|
|
@ -1122,7 +1140,6 @@ github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u
|
|||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
||||
github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM=
|
||||
github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||
|
|
@ -1240,20 +1257,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
|
|||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
|
||||
github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
|
||||
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
|
||||
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
|
|
@ -1279,23 +1284,22 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1
|
|||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
|
||||
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
|
||||
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/tview v0.0.0-20250625164341-a4a78f1e05cb h1:n7UJ8X9UnrTZBYXnd1kAIBc067SWyuPIrsocjketYW8=
|
||||
github.com/rivo/tview v0.0.0-20250625164341-a4a78f1e05cb/go.mod h1:cSfIYfhpSGCjp3r/ECJb+GKS7cGJnqV8vfjQPwoXyfY=
|
||||
github.com/rivo/tview v0.42.0 h1:b/ftp+RxtDsHSaynXTbJb+/n/BxDEi+W3UfF5jILK6c=
|
||||
github.com/rivo/tview v0.42.0/go.mod h1:cSfIYfhpSGCjp3r/ECJb+GKS7cGJnqV8vfjQPwoXyfY=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rodaine/table v1.3.0 h1:4/3S3SVkHnVZX91EHFvAMV7K42AnJ0XuymRR2C5HlGE=
|
||||
|
|
@ -1318,9 +1322,10 @@ github.com/sassoftware/relic v7.2.1+incompatible h1:Pwyh1F3I0r4clFJXkSI8bOyJINGq
|
|||
github.com/sassoftware/relic v7.2.1+incompatible/go.mod h1:CWfAxv73/iLZ17rbyhIEq3K9hs5w6FpNMdUT//qR+zk=
|
||||
github.com/sassoftware/relic/v7 v7.6.2 h1:rS44Lbv9G9eXsukknS4mSjIAuuX+lMq/FnStgmZlUv4=
|
||||
github.com/sassoftware/relic/v7 v7.6.2/go.mod h1:kjmP0IBVkJZ6gXeAu35/KCEfca//+PKM6vTAsyDPY+k=
|
||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.9.1 h1:nZZaNz4DiERIQguNy0cL5qTdn9lR8XKHf4RUyG1Sx3g=
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.9.1/go.mod h1:np53YzT0zXGMv6x4iEWc9Z59uR+x+ndLwCLqPYpLXVU=
|
||||
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
|
||||
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
|
||||
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
||||
github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI=
|
||||
|
|
@ -1333,14 +1338,14 @@ github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 h1:17JxqqJY66GmZV
|
|||
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466/go.mod h1:9dIRpgIY7hVhoqfe0/FcYp0bpInZaT7dc3BYOprrIUE=
|
||||
github.com/sigstore/protobuf-specs v0.5.0 h1:F8YTI65xOHw70NrvPwJ5PhAzsvTnuJMGLkA4FIkofAY=
|
||||
github.com/sigstore/protobuf-specs v0.5.0/go.mod h1:+gXR+38nIa2oEupqDdzg4qSBT0Os+sP7oYv6alWewWc=
|
||||
github.com/sigstore/rekor v1.4.1 h1:KK3McuHnptIE9mdNlrc9qh/OVE0AXf4rnScMxJE6xH4=
|
||||
github.com/sigstore/rekor v1.4.1/go.mod h1:/McBsz/vrtfi4EInxSIk/MGbDXzgv2+1FQUg1R/uSnE=
|
||||
github.com/sigstore/rekor-tiles v0.1.7-0.20250624231741-98cd4a77300f h1:zaqWahYAlVouSm5qwCH+2vZ3eenZFBwzzuBz/IZyy5c=
|
||||
github.com/sigstore/rekor-tiles v0.1.7-0.20250624231741-98cd4a77300f/go.mod h1:1Epq0PQ73v5Z276rAY241JyaP8gtD64I6sgYIECHPvc=
|
||||
github.com/sigstore/sigstore v1.9.5 h1:Wm1LT9yF4LhQdEMy5A2JeGRHTrAWGjT3ubE5JUSrGVU=
|
||||
github.com/sigstore/sigstore v1.9.5/go.mod h1:VtxgvGqCmEZN9X2zhFSOkfXxvKUjpy8RpUW39oCtoII=
|
||||
github.com/sigstore/sigstore-go v1.1.0 h1:NBfyvL/LiBIplnIZAtC7GtDZ7qj82A/GTpn0+5WV7BM=
|
||||
github.com/sigstore/sigstore-go v1.1.0/go.mod h1:97lDVpZVBCTFX114KPAManEsShVe934KyaVhZGhPVBM=
|
||||
github.com/sigstore/rekor v1.4.2 h1:Lx2xby7loviFYdg2C9pB1mESk2QU/LqcYSGsqqZwmg8=
|
||||
github.com/sigstore/rekor v1.4.2/go.mod h1:nX/OYaLqpTeCOuMEt7ELE0+5cVjZWFnFKM+cZ+3hQRA=
|
||||
github.com/sigstore/rekor-tiles v0.1.11 h1:0NAJ2EhD1r6DH95FUuDTqUDd+c31LSKzoXGW5ZCzFq0=
|
||||
github.com/sigstore/rekor-tiles v0.1.11/go.mod h1:eGIeqASh52pgWpmp/j5KZDjmKdVwob7eTYskVVRCu5k=
|
||||
github.com/sigstore/sigstore v1.9.6-0.20250729224751-181c5d3339b3 h1:IEhSeWfhTd0kaBpHUXniWU2Tl5K5OUACN69mi1WGd+8=
|
||||
github.com/sigstore/sigstore v1.9.6-0.20250729224751-181c5d3339b3/go.mod h1:JuqyPRJYnkNl6OTnQiG503EUnKih4P5EV6FUw+1B0iA=
|
||||
github.com/sigstore/sigstore-go v1.1.3 h1:5lKcbXZa5JC7wb/UVywyCulccfYTUju1D5h4tkn+fXE=
|
||||
github.com/sigstore/sigstore-go v1.1.3/go.mod h1:3jKC4IDh7TEVtCSJCjx0lpq5YfJbDJmfp65WsMvY2mg=
|
||||
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.9.5 h1:qp2VFyKuFQvTGmZwk5Q7m5nE4NwnF9tHwkyz0gtWAck=
|
||||
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.9.5/go.mod h1:DKlQjjr+GsWljEYPycI0Sf8URLCk4EbGA9qYjF47j4g=
|
||||
github.com/sigstore/sigstore/pkg/signature/kms/azure v1.9.5 h1:CRZcdYn5AOptStsLRAAACudAVmb1qUbhMlzrvm7ju3o=
|
||||
|
|
@ -1349,10 +1354,12 @@ github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.9.6-0.20250729224751-181c5
|
|||
github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.9.6-0.20250729224751-181c5d3339b3/go.mod h1:tRtJzSZ48MXJV9bmS8pkb3mP36PCad/Cs+BmVJ3Z4O4=
|
||||
github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.9.5 h1:S2ukEfN1orLKw2wEQIUHDDlzk0YcylhcheeZ5TGk8LI=
|
||||
github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.9.5/go.mod h1:m7sQxVJmDa+rsmS1m6biQxaLX83pzNS7ThUEyjOqkCU=
|
||||
github.com/sigstore/timestamp-authority v1.2.8 h1:BEV3fkphwU4zBp3allFAhCqQb99HkiyCXB853RIwuEE=
|
||||
github.com/sigstore/timestamp-authority v1.2.8/go.mod h1:G2/0hAZmLPnevEwT1S9IvtNHUm9Ktzvso6xuRhl94ZY=
|
||||
github.com/sigstore/timestamp-authority v1.2.9 h1:L9Fj070/EbMC8qUk8BchkrYCS1BT5i93Bl6McwydkFs=
|
||||
github.com/sigstore/timestamp-authority v1.2.9/go.mod h1:QyRnZchz4o+xdHyK5rvCWacCHxWmpX+mgvJwB1OXcLY=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
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/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
|
|
@ -1363,11 +1370,11 @@ github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
|||
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
|
||||
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
|
||||
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
||||
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
|
||||
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
|
||||
github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
|
||||
|
|
@ -1389,14 +1396,14 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
|
|||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
|
||||
github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI=
|
||||
github.com/theupdateframework/go-tuf v0.7.0/go.mod h1:uEB7WSY+7ZIugK6R1hiBMBjQftaFzn7ZCDJcp1tCUug=
|
||||
github.com/theupdateframework/go-tuf/v2 v2.1.1 h1:OWcoHItwsGO+7m0wLa7FDWPR4oB1cj0zOr1kosE4G+I=
|
||||
github.com/theupdateframework/go-tuf/v2 v2.1.1/go.mod h1:V675cQGhZONR0OGQ8r1feO0uwtsTBYPDWHzAAPn5rjE=
|
||||
github.com/theupdateframework/go-tuf/v2 v2.2.0 h1:Hmb+Azgd7IKOZeNJFT2C91y+YZ+F+TeloSIvQIaXCQw=
|
||||
github.com/theupdateframework/go-tuf/v2 v2.2.0/go.mod h1:CubcJiJlBHQ2YkA5j9hlBO4B+tHFlLjRbWCJCT7EIKU=
|
||||
github.com/thlib/go-timezone-local v0.0.6 h1:Ii3QJ4FhosL/+eCZl6Hsdr4DDU4tfevNoV83yAEo2tU=
|
||||
github.com/thlib/go-timezone-local v0.0.6/go.mod h1:/Tnicc6m/lsJE0irFMA0LfIwTBo4QP7A8IfyIv4zZKI=
|
||||
github.com/tink-crypto/tink-go-awskms/v2 v2.1.0 h1:N9UxlsOzu5mttdjhxkDLbzwtEecuXmlxZVo/ds7JKJI=
|
||||
|
|
@ -1413,8 +1420,8 @@ github.com/transparency-dev/formats v0.0.0-20250421220931-bb8ad4d07c26 h1:YTbkeF
|
|||
github.com/transparency-dev/formats v0.0.0-20250421220931-bb8ad4d07c26/go.mod h1:ODywn0gGarHMMdSkWT56ULoK8Hk71luOyRseKek9COw=
|
||||
github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG81+twTK4=
|
||||
github.com/transparency-dev/merkle v0.0.2/go.mod h1:pqSy+OXefQ1EDUVmAJ8MUhHB9TXGuzVAT58PqBoHz1A=
|
||||
github.com/transparency-dev/tessera v0.2.1-0.20250610150926-8ee4e93b2823 h1:s3p7wNrK/mnKI2bdp9PrQd9eBVxo1i5rU6O5hKkN0zc=
|
||||
github.com/transparency-dev/tessera v0.2.1-0.20250610150926-8ee4e93b2823/go.mod h1:Jv2IDwG1q8QNXZTaI1X6QX8s96WlJn73ka2hT1n4N5c=
|
||||
github.com/transparency-dev/tessera v1.0.0-rc3 h1:v385KqMekDUKI3ZVJHCHE5MAz8LBrWsEKa6OzYLrz0k=
|
||||
github.com/transparency-dev/tessera v1.0.0-rc3/go.mod h1:aaLlvG/sEPMzT96iIF4hua6Z9pLzkfDtkbaUAR4IL8I=
|
||||
github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo=
|
||||
github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
|
||||
|
|
@ -1453,24 +1460,24 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
|||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
|
||||
go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
|
||||
|
|
@ -1493,11 +1500,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
|||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
|
|
@ -1557,14 +1561,10 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
|
|||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||
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-20180906233101-161cd47e91fd/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=
|
||||
|
|
@ -1585,7 +1585,6 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/
|
|||
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-20200520004742-59133d7f0dd7/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=
|
||||
|
|
@ -1621,10 +1620,6 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
|
|
@ -1674,14 +1669,9 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/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=
|
||||
|
|
@ -1691,10 +1681,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
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-20190904154756-749cb33beabd/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-20191120155948-bd437916bb0e/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=
|
||||
|
|
@ -1709,7 +1696,6 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
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-20200519105757-fe76b779f299/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=
|
||||
|
|
@ -1766,14 +1752,8 @@ golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
|
|
@ -1782,13 +1762,8 @@ golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
|||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
||||
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
||||
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=
|
||||
|
|
@ -1805,12 +1780,9 @@ golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
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/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
|
@ -1880,10 +1852,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
|||
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
||||
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
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=
|
||||
|
|
@ -2102,8 +2072,8 @@ google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOl
|
|||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
|
||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074 h1:mVXdvnmR3S3BQOqHECm9NGMjYiRtEvDYcqAqedTXY6s=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074/go.mod h1:vYFwMYFbmA8vl6Z/krj/h7+U/AqpHknwJX4Uqgfyc7I=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
|
|
@ -2167,22 +2137,17 @@ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
|
|||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
|
||||
gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
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.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
|||
|
|
@ -6,17 +6,13 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/atotto/clipboard"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghinstance"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/cli/oauth"
|
||||
"github.com/henvic/httpretty"
|
||||
|
||||
ghauth "github.com/cli/go-gh/v2/pkg/auth"
|
||||
)
|
||||
|
|
@ -26,21 +22,15 @@ var (
|
|||
oauthClientID = "178c6fc778ccc68e1d6a"
|
||||
// This value is safe to be embedded in version control
|
||||
oauthClientSecret = "34ddeff2b558a23d38fba8a6de74f086ede1cc0b"
|
||||
|
||||
jsonTypeRE = regexp.MustCompile(`[/+]json($|;)`)
|
||||
)
|
||||
|
||||
func AuthFlow(oauthHost string, IO *iostreams.IOStreams, notice string, additionalScopes []string, isInteractive bool, b browser.Browser, isCopyToClipboard bool) (string, string, error) {
|
||||
// AuthFlow initiates an OAuth device or web application flow to acquire a
|
||||
// token. The provided HTTP client should be a plain client that does not set
|
||||
// auth or other headers.
|
||||
func AuthFlow(httpClient *http.Client, oauthHost string, IO *iostreams.IOStreams, notice string, additionalScopes []string, isInteractive bool, b browser.Browser, isCopyToClipboard bool) (string, string, error) {
|
||||
w := IO.ErrOut
|
||||
cs := IO.ColorScheme()
|
||||
|
||||
httpClient := &http.Client{}
|
||||
debugEnabled, debugValue := utils.IsDebugEnabled()
|
||||
if debugEnabled {
|
||||
logTraffic := strings.Contains(debugValue, "api")
|
||||
httpClient.Transport = verboseLog(IO.ErrOut, logTraffic, IO.ColorEnabled())(httpClient.Transport)
|
||||
}
|
||||
|
||||
minimumScopes := []string{"repo", "read:org", "gist"}
|
||||
scopes := append(minimumScopes, additionalScopes...)
|
||||
|
||||
|
|
@ -150,28 +140,3 @@ func waitForEnter(r io.Reader) error {
|
|||
scanner.Scan()
|
||||
return scanner.Err()
|
||||
}
|
||||
|
||||
func verboseLog(out io.Writer, logTraffic bool, colorize bool) func(http.RoundTripper) http.RoundTripper {
|
||||
logger := &httpretty.Logger{
|
||||
Time: true,
|
||||
TLS: false,
|
||||
Colors: colorize,
|
||||
RequestHeader: logTraffic,
|
||||
RequestBody: logTraffic,
|
||||
ResponseHeader: logTraffic,
|
||||
ResponseBody: logTraffic,
|
||||
Formatters: []httpretty.Formatter{&httpretty.JSONFormatter{}},
|
||||
MaxResponseBody: 10000,
|
||||
}
|
||||
logger.SetOutput(out)
|
||||
logger.SetBodyFilter(func(h http.Header) (skip bool, err error) {
|
||||
return !inspectableMIMEType(h.Get("Content-Type")), nil
|
||||
})
|
||||
return logger.RoundTripper
|
||||
}
|
||||
|
||||
func inspectableMIMEType(t string) bool {
|
||||
return strings.HasPrefix(t, "text/") ||
|
||||
strings.HasPrefix(t, "application/x-www-form-urlencoded") ||
|
||||
jsonTypeRE.MatchString(t)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -166,6 +166,8 @@ func createRun(opts *CreateOptions) error {
|
|||
}
|
||||
|
||||
if opts.Follow {
|
||||
opts.IO.StopProgressIndicator()
|
||||
fmt.Fprintf(opts.IO.Out, "Displaying session logs for job %s. Press Ctrl+C to stop.\n", job.ID)
|
||||
return followLogs(opts, client, job.SessionID)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -488,6 +488,7 @@ func Test_createRun(t *testing.T) {
|
|||
}
|
||||
},
|
||||
wantStdout: heredoc.Doc(`
|
||||
Displaying session logs for job job123. Press Ctrl+C to stop.
|
||||
(rendered:) <raw-logs-one>
|
||||
(rendered:) <raw-logs-two>
|
||||
`),
|
||||
|
|
|
|||
|
|
@ -41,8 +41,10 @@ func OnGetByDigestSuccess(params FetchParams) ([]*Attestation, error) {
|
|||
att3 := makeTestReleaseAttestation()
|
||||
attestations := []*Attestation{&att1, &att2}
|
||||
if params.PredicateType != "" {
|
||||
if params.PredicateType == "https://in-toto.io/attestation/release/v0.1" {
|
||||
attestations = append(attestations, &att3)
|
||||
// "release" is a sentinel value that returns all release attestations (v0.1, v0.2, etc.)
|
||||
// This mimics the GitHub API behavior which handles this server-side
|
||||
if params.PredicateType == "release" {
|
||||
return []*Attestation{&att3}, nil
|
||||
}
|
||||
return FilterAttestations(params.PredicateType, attestations)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,27 +63,39 @@ func NewLiveSigstoreVerifier(config SigstoreConfig) (*LiveSigstoreVerifier, erro
|
|||
Logger: config.Logger,
|
||||
NoPublicGood: config.NoPublicGood,
|
||||
}
|
||||
// if a custom trusted root is set, configure custom verifiers
|
||||
// if a custom trusted root is set, configure custom verifiers and assume no Public Good or GitHub verifiers
|
||||
// are needed
|
||||
if config.TrustedRoot != "" {
|
||||
customVerifiers, err := createCustomVerifiers(config.TrustedRoot, config.NoPublicGood)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("error creating custom verifiers: %s", err)
|
||||
}
|
||||
liveVerifier.Custom = customVerifiers
|
||||
return liveVerifier, nil
|
||||
}
|
||||
|
||||
// No custom trusted root is set, so configure Public Good and GitHub verifiers
|
||||
if !config.NoPublicGood {
|
||||
publicGoodVerifier, err := newPublicGoodVerifier(config.TUFMetadataDir, config.HttpClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// Log warning but continue - PGI unavailability should not block GitHub attestation verification
|
||||
config.Logger.VerbosePrintf("Warning: failed to initialize Sigstore Public Good verifier: %v\n", err)
|
||||
config.Logger.VerbosePrintf("Continuing without Public Good Instance verification\n")
|
||||
} else {
|
||||
liveVerifier.PublicGood = publicGoodVerifier
|
||||
}
|
||||
liveVerifier.PublicGood = publicGoodVerifier
|
||||
}
|
||||
|
||||
github, err := newGitHubVerifier(config.TrustDomain, config.TUFMetadataDir, config.HttpClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
config.Logger.VerbosePrintf("Warning: failed to initialize GitHub verifier: %v\n", err)
|
||||
} else {
|
||||
liveVerifier.GitHub = github
|
||||
}
|
||||
|
||||
if liveVerifier.noVerifierSet() {
|
||||
return nil, fmt.Errorf("no valid Sigstore verifiers could be initialized")
|
||||
}
|
||||
liveVerifier.GitHub = github
|
||||
|
||||
return liveVerifier, nil
|
||||
}
|
||||
|
|
@ -206,6 +218,9 @@ func (v *LiveSigstoreVerifier) chooseVerifier(issuer string) (*verify.Verifier,
|
|||
if v.NoPublicGood {
|
||||
return nil, fmt.Errorf("detected public good instance but requested verification without public good instance")
|
||||
}
|
||||
if v.PublicGood == nil {
|
||||
return nil, fmt.Errorf("public good verifier is not available (initialization may have failed)")
|
||||
}
|
||||
return v.PublicGood, nil
|
||||
case GitHubIssuerOrg:
|
||||
return v.GitHub, nil
|
||||
|
|
@ -372,3 +387,7 @@ func newPublicGoodVerifierWithTrustedRoot(trustedRoot *root.TrustedRoot) (*verif
|
|||
|
||||
return sv, nil
|
||||
}
|
||||
|
||||
func (v *LiveSigstoreVerifier) noVerifierSet() bool {
|
||||
return v.PublicGood == nil && v.GitHub == nil && len(v.Custom) == 0
|
||||
}
|
||||
|
|
|
|||
53
pkg/cmd/attestation/verification/sigstore_test.go
Normal file
53
pkg/cmd/attestation/verification/sigstore_test.go
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package verification
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/io"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Note: Tests that require network access and TUF client initialization
|
||||
// are in sigstore_integration_test.go with the //go:build integration tag.
|
||||
// These unit tests focus on testing the logic without requiring network access.
|
||||
|
||||
// TestChooseVerifierWithNilPublicGood tests that chooseVerifier returns an error
|
||||
// when a PGI attestation is encountered but the PGI verifier is nil (failed initialization).
|
||||
func TestChooseVerifierWithNilPublicGood(t *testing.T) {
|
||||
verifier := &LiveSigstoreVerifier{
|
||||
Logger: io.NewTestHandler(),
|
||||
NoPublicGood: false,
|
||||
PublicGood: nil, // Simulate failed PGI initialization
|
||||
GitHub: nil, // Not needed for this test
|
||||
}
|
||||
|
||||
_, err := verifier.chooseVerifier(PublicGoodIssuerOrg)
|
||||
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "public good verifier is not available")
|
||||
}
|
||||
|
||||
// TestChooseVerifierUnrecognizedIssuer tests that an error is returned
|
||||
// for unrecognized issuers.
|
||||
func TestChooseVerifierUnrecognizedIssuer(t *testing.T) {
|
||||
verifier := &LiveSigstoreVerifier{
|
||||
Logger: io.NewTestHandler(),
|
||||
NoPublicGood: false,
|
||||
}
|
||||
|
||||
_, err := verifier.chooseVerifier("unknown-issuer")
|
||||
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "leaf certificate issuer is not recognized")
|
||||
}
|
||||
|
||||
func TestLiveSigstoreVerifier_noVerifierSet(t *testing.T) {
|
||||
verifier := &LiveSigstoreVerifier{
|
||||
Logger: io.NewTestHandler(),
|
||||
NoPublicGood: true,
|
||||
PublicGood: nil,
|
||||
GitHub: nil,
|
||||
}
|
||||
|
||||
require.True(t, verifier.noVerifierSet())
|
||||
}
|
||||
|
|
@ -20,12 +20,13 @@ import (
|
|||
)
|
||||
|
||||
type LoginOptions struct {
|
||||
IO *iostreams.IOStreams
|
||||
Config func() (gh.Config, error)
|
||||
HttpClient func() (*http.Client, error)
|
||||
GitClient *git.Client
|
||||
Prompter shared.Prompt
|
||||
Browser browser.Browser
|
||||
IO *iostreams.IOStreams
|
||||
Config func() (gh.Config, error)
|
||||
HttpClient func() (*http.Client, error)
|
||||
PlainHttpClient func() (*http.Client, error)
|
||||
GitClient *git.Client
|
||||
Prompter shared.Prompt
|
||||
Browser browser.Browser
|
||||
|
||||
MainExecutable string
|
||||
|
||||
|
|
@ -43,12 +44,13 @@ type LoginOptions struct {
|
|||
|
||||
func NewCmdLogin(f *cmdutil.Factory, runF func(*LoginOptions) error) *cobra.Command {
|
||||
opts := &LoginOptions{
|
||||
IO: f.IOStreams,
|
||||
Config: f.Config,
|
||||
HttpClient: f.HttpClient,
|
||||
GitClient: f.GitClient,
|
||||
Prompter: f.Prompter,
|
||||
Browser: f.Browser,
|
||||
IO: f.IOStreams,
|
||||
Config: f.Config,
|
||||
HttpClient: f.HttpClient,
|
||||
PlainHttpClient: f.PlainHttpClient,
|
||||
GitClient: f.GitClient,
|
||||
Prompter: f.Prompter,
|
||||
Browser: f.Browser,
|
||||
}
|
||||
|
||||
var tokenStdin bool
|
||||
|
|
@ -190,6 +192,11 @@ func loginRun(opts *LoginOptions) error {
|
|||
return cmdutil.SilentError
|
||||
}
|
||||
|
||||
plainHTTPClient, err := opts.PlainHttpClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpClient, err := opts.HttpClient()
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -210,16 +217,17 @@ func loginRun(opts *LoginOptions) error {
|
|||
}
|
||||
|
||||
return shared.Login(&shared.LoginOptions{
|
||||
IO: opts.IO,
|
||||
Config: authCfg,
|
||||
HTTPClient: httpClient,
|
||||
Hostname: hostname,
|
||||
Interactive: opts.Interactive,
|
||||
Web: opts.Web,
|
||||
Scopes: opts.Scopes,
|
||||
GitProtocol: opts.GitProtocol,
|
||||
Prompter: opts.Prompter,
|
||||
Browser: opts.Browser,
|
||||
IO: opts.IO,
|
||||
Config: authCfg,
|
||||
HTTPClient: httpClient,
|
||||
PlainHTTPClient: plainHTTPClient,
|
||||
Hostname: hostname,
|
||||
Interactive: opts.Interactive,
|
||||
Web: opts.Web,
|
||||
Scopes: opts.Scopes,
|
||||
GitProtocol: opts.GitProtocol,
|
||||
Prompter: opts.Prompter,
|
||||
Browser: opts.Browser,
|
||||
CredentialFlow: &shared.GitCredentialFlow{
|
||||
Prompter: opts.Prompter,
|
||||
HelperConfig: &gitcredentials.HelperConfig{
|
||||
|
|
|
|||
|
|
@ -483,6 +483,9 @@ func Test_loginRun_nontty(t *testing.T) {
|
|||
tt.opts.HttpClient = func() (*http.Client, error) {
|
||||
return &http.Client{Transport: reg}, nil
|
||||
}
|
||||
tt.opts.PlainHttpClient = func() (*http.Client, error) {
|
||||
return &http.Client{Transport: reg}, nil
|
||||
}
|
||||
if tt.httpStubs != nil {
|
||||
tt.httpStubs(reg)
|
||||
}
|
||||
|
|
@ -775,6 +778,9 @@ func Test_loginRun_Survey(t *testing.T) {
|
|||
tt.opts.HttpClient = func() (*http.Client, error) {
|
||||
return &http.Client{Transport: reg}, nil
|
||||
}
|
||||
tt.opts.PlainHttpClient = func() (*http.Client, error) {
|
||||
return &http.Client{Transport: reg}, nil
|
||||
}
|
||||
if tt.httpStubs != nil {
|
||||
tt.httpStubs(reg)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -21,11 +21,11 @@ type token string
|
|||
type username string
|
||||
|
||||
type RefreshOptions struct {
|
||||
IO *iostreams.IOStreams
|
||||
Config func() (gh.Config, error)
|
||||
HttpClient *http.Client
|
||||
GitClient *git.Client
|
||||
Prompter shared.Prompt
|
||||
IO *iostreams.IOStreams
|
||||
Config func() (gh.Config, error)
|
||||
PlainHttpClient func() (*http.Client, error)
|
||||
GitClient *git.Client
|
||||
Prompter shared.Prompt
|
||||
|
||||
MainExecutable string
|
||||
|
||||
|
|
@ -33,7 +33,7 @@ type RefreshOptions struct {
|
|||
Scopes []string
|
||||
RemoveScopes []string
|
||||
ResetScopes bool
|
||||
AuthFlow func(*iostreams.IOStreams, string, []string, bool, bool) (token, username, error)
|
||||
AuthFlow func(*http.Client, *iostreams.IOStreams, string, []string, bool, bool) (token, username, error)
|
||||
|
||||
Interactive bool
|
||||
InsecureStorage bool
|
||||
|
|
@ -44,13 +44,13 @@ func NewCmdRefresh(f *cmdutil.Factory, runF func(*RefreshOptions) error) *cobra.
|
|||
opts := &RefreshOptions{
|
||||
IO: f.IOStreams,
|
||||
Config: f.Config,
|
||||
AuthFlow: func(io *iostreams.IOStreams, hostname string, scopes []string, interactive bool, clipboard bool) (token, username, error) {
|
||||
t, u, err := authflow.AuthFlow(hostname, io, "", scopes, interactive, f.Browser, clipboard)
|
||||
AuthFlow: func(httpClient *http.Client, io *iostreams.IOStreams, hostname string, scopes []string, interactive bool, clipboard bool) (token, username, error) {
|
||||
t, u, err := authflow.AuthFlow(httpClient, hostname, io, "", scopes, interactive, f.Browser, clipboard)
|
||||
return token(t), username(u), err
|
||||
},
|
||||
HttpClient: &http.Client{},
|
||||
GitClient: f.GitClient,
|
||||
Prompter: f.Prompter,
|
||||
PlainHttpClient: f.PlainHttpClient,
|
||||
GitClient: f.GitClient,
|
||||
Prompter: f.Prompter,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
|
@ -125,6 +125,11 @@ func NewCmdRefresh(f *cmdutil.Factory, runF func(*RefreshOptions) error) *cobra.
|
|||
}
|
||||
|
||||
func refreshRun(opts *RefreshOptions) error {
|
||||
plainHTTPClient, err := opts.PlainHttpClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg, err := opts.Config()
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -171,7 +176,7 @@ func refreshRun(opts *RefreshOptions) error {
|
|||
|
||||
if !opts.ResetScopes {
|
||||
if oldToken, _ := authCfg.ActiveToken(hostname); oldToken != "" {
|
||||
if oldScopes, err := shared.GetScopes(opts.HttpClient, hostname, oldToken); err == nil {
|
||||
if oldScopes, err := shared.GetScopes(plainHTTPClient, hostname, oldToken); err == nil {
|
||||
for _, s := range strings.Split(oldScopes, ",") {
|
||||
s = strings.TrimSpace(s)
|
||||
if s != "" {
|
||||
|
|
@ -204,7 +209,7 @@ func refreshRun(opts *RefreshOptions) error {
|
|||
|
||||
additionalScopes.RemoveValues(opts.RemoveScopes)
|
||||
|
||||
authedToken, authedUser, err := opts.AuthFlow(opts.IO, hostname, additionalScopes.ToSlice(), opts.Interactive, opts.Clipboard)
|
||||
authedToken, authedUser, err := opts.AuthFlow(plainHTTPClient, opts.IO, hostname, additionalScopes.ToSlice(), opts.Interactive, opts.Clipboard)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -471,7 +471,7 @@ func Test_refreshRun(t *testing.T) {
|
|||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
aa := authArgs{}
|
||||
tt.opts.AuthFlow = func(_ *iostreams.IOStreams, hostname string, scopes []string, interactive bool, clipboard bool) (token, username, error) {
|
||||
tt.opts.AuthFlow = func(_ *http.Client, _ *iostreams.IOStreams, hostname string, scopes []string, interactive bool, clipboard bool) (token, username, error) {
|
||||
aa.hostname = hostname
|
||||
aa.scopes = scopes
|
||||
aa.interactive = interactive
|
||||
|
|
@ -514,7 +514,9 @@ func Test_refreshRun(t *testing.T) {
|
|||
}, nil
|
||||
},
|
||||
)
|
||||
tt.opts.HttpClient = &http.Client{Transport: httpReg}
|
||||
tt.opts.PlainHttpClient = func() (*http.Client, error) {
|
||||
return &http.Client{Transport: httpReg}, nil
|
||||
}
|
||||
|
||||
pm := &prompter.PrompterMock{}
|
||||
if tt.prompterStubs != nil {
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ type LoginOptions struct {
|
|||
IO *iostreams.IOStreams
|
||||
Config iconfig
|
||||
HTTPClient *http.Client
|
||||
PlainHTTPClient *http.Client
|
||||
Hostname string
|
||||
Interactive bool
|
||||
Web bool
|
||||
|
|
@ -149,7 +150,7 @@ func Login(opts *LoginOptions) error {
|
|||
|
||||
if authMode == 0 {
|
||||
var err error
|
||||
authToken, username, err = authflow.AuthFlow(hostname, opts.IO, "", append(opts.Scopes, additionalScopes...), opts.Interactive, opts.Browser, opts.CopyToClipboard)
|
||||
authToken, username, err = authflow.AuthFlow(opts.PlainHTTPClient, hostname, opts.IO, "", append(opts.Scopes, additionalScopes...), opts.Interactive, opts.Browser, opts.CopyToClipboard)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to authenticate via web browser: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,113 +19,106 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type validEntry struct {
|
||||
active bool
|
||||
host string
|
||||
user string
|
||||
token string
|
||||
tokenSource string
|
||||
gitProtocol string
|
||||
scopes string
|
||||
type authEntryState string
|
||||
|
||||
const (
|
||||
authEntryStateSuccess = "success"
|
||||
authEntryStateTimeout = "timeout"
|
||||
authEntryStateError = "error"
|
||||
)
|
||||
|
||||
type authEntry struct {
|
||||
State authEntryState `json:"state"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Active bool `json:"active"`
|
||||
Host string `json:"host"`
|
||||
Login string `json:"login"`
|
||||
TokenSource string `json:"tokenSource"`
|
||||
Token string `json:"token,omitempty"`
|
||||
Scopes string `json:"scopes,omitempty"`
|
||||
GitProtocol string `json:"gitProtocol"`
|
||||
}
|
||||
|
||||
func (e validEntry) String(cs *iostreams.ColorScheme) string {
|
||||
type authStatus struct {
|
||||
Hosts map[string][]authEntry `json:"hosts"`
|
||||
}
|
||||
|
||||
func newAuthStatus() *authStatus {
|
||||
return &authStatus{
|
||||
Hosts: make(map[string][]authEntry),
|
||||
}
|
||||
}
|
||||
|
||||
var authStatusFields = []string{
|
||||
"hosts",
|
||||
}
|
||||
|
||||
func (a authStatus) ExportData(fields []string) map[string]interface{} {
|
||||
return cmdutil.StructExportData(a, fields)
|
||||
}
|
||||
|
||||
func (e authEntry) String(cs *iostreams.ColorScheme) string {
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString(
|
||||
fmt.Sprintf(" %s Logged in to %s account %s (%s)\n", cs.SuccessIcon(), e.host, cs.Bold(e.user), e.tokenSource),
|
||||
)
|
||||
activeStr := fmt.Sprintf("%v", e.active)
|
||||
sb.WriteString(fmt.Sprintf(" - Active account: %s\n", cs.Bold(activeStr)))
|
||||
sb.WriteString(fmt.Sprintf(" - Git operations protocol: %s\n", cs.Bold(e.gitProtocol)))
|
||||
sb.WriteString(fmt.Sprintf(" - Token: %s\n", cs.Bold(e.token)))
|
||||
switch e.State {
|
||||
case authEntryStateSuccess:
|
||||
sb.WriteString(
|
||||
fmt.Sprintf(" %s Logged in to %s account %s (%s)\n", cs.SuccessIcon(), e.Host, cs.Bold(e.Login), e.TokenSource),
|
||||
)
|
||||
activeStr := fmt.Sprintf("%v", e.Active)
|
||||
sb.WriteString(fmt.Sprintf(" - Active account: %s\n", cs.Bold(activeStr)))
|
||||
sb.WriteString(fmt.Sprintf(" - Git operations protocol: %s\n", cs.Bold(e.GitProtocol)))
|
||||
sb.WriteString(fmt.Sprintf(" - Token: %s\n", cs.Bold(e.Token)))
|
||||
|
||||
if expectScopes(e.token) {
|
||||
sb.WriteString(fmt.Sprintf(" - Token scopes: %s\n", cs.Bold(displayScopes(e.scopes))))
|
||||
if err := shared.HeaderHasMinimumScopes(e.scopes); err != nil {
|
||||
var missingScopesError *shared.MissingScopesError
|
||||
if errors.As(err, &missingScopesError) {
|
||||
missingScopes := strings.Join(missingScopesError.MissingScopes, ",")
|
||||
sb.WriteString(fmt.Sprintf(" %s Missing required token scopes: %s\n",
|
||||
cs.WarningIcon(),
|
||||
cs.Bold(displayScopes(missingScopes))))
|
||||
refreshInstructions := fmt.Sprintf("gh auth refresh -h %s", e.host)
|
||||
sb.WriteString(fmt.Sprintf(" - To request missing scopes, run: %s\n", cs.Bold(refreshInstructions)))
|
||||
if expectScopes(e.Token) {
|
||||
sb.WriteString(fmt.Sprintf(" - Token scopes: %s\n", cs.Bold(displayScopes(e.Scopes))))
|
||||
if err := shared.HeaderHasMinimumScopes(e.Scopes); err != nil {
|
||||
var missingScopesError *shared.MissingScopesError
|
||||
if errors.As(err, &missingScopesError) {
|
||||
missingScopes := strings.Join(missingScopesError.MissingScopes, ",")
|
||||
sb.WriteString(fmt.Sprintf(" %s Missing required token scopes: %s\n",
|
||||
cs.WarningIcon(),
|
||||
cs.Bold(displayScopes(missingScopes))))
|
||||
refreshInstructions := fmt.Sprintf("gh auth refresh -h %s", e.Host)
|
||||
sb.WriteString(fmt.Sprintf(" - To request missing scopes, run: %s\n", cs.Bold(refreshInstructions)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case authEntryStateError:
|
||||
if e.Login != "" {
|
||||
sb.WriteString(fmt.Sprintf(" %s Failed to log in to %s account %s (%s)\n", cs.Red("X"), e.Host, cs.Bold(e.Login), e.TokenSource))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf(" %s Failed to log in to %s using token (%s)\n", cs.Red("X"), e.Host, e.TokenSource))
|
||||
}
|
||||
activeStr := fmt.Sprintf("%v", e.Active)
|
||||
sb.WriteString(fmt.Sprintf(" - Active account: %s\n", cs.Bold(activeStr)))
|
||||
sb.WriteString(fmt.Sprintf(" - The token in %s is invalid.\n", e.TokenSource))
|
||||
if authTokenWriteable(e.TokenSource) {
|
||||
loginInstructions := fmt.Sprintf("gh auth login -h %s", e.Host)
|
||||
logoutInstructions := fmt.Sprintf("gh auth logout -h %s -u %s", e.Host, e.Login)
|
||||
sb.WriteString(fmt.Sprintf(" - To re-authenticate, run: %s\n", cs.Bold(loginInstructions)))
|
||||
sb.WriteString(fmt.Sprintf(" - To forget about this account, run: %s\n", cs.Bold(logoutInstructions)))
|
||||
}
|
||||
|
||||
case authEntryStateTimeout:
|
||||
if e.Login != "" {
|
||||
sb.WriteString(fmt.Sprintf(" %s Timeout trying to log in to %s account %s (%s)\n", cs.Red("X"), e.Host, cs.Bold(e.Login), e.TokenSource))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf(" %s Timeout trying to log in to %s using token (%s)\n", cs.Red("X"), e.Host, e.TokenSource))
|
||||
}
|
||||
activeStr := fmt.Sprintf("%v", e.Active)
|
||||
sb.WriteString(fmt.Sprintf(" - Active account: %s\n", cs.Bold(activeStr)))
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
type invalidTokenEntry struct {
|
||||
active bool
|
||||
host string
|
||||
user string
|
||||
tokenSource string
|
||||
tokenIsWriteable bool
|
||||
}
|
||||
|
||||
func (e invalidTokenEntry) String(cs *iostreams.ColorScheme) string {
|
||||
var sb strings.Builder
|
||||
|
||||
if e.user != "" {
|
||||
sb.WriteString(fmt.Sprintf(" %s Failed to log in to %s account %s (%s)\n", cs.Red("X"), e.host, cs.Bold(e.user), e.tokenSource))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf(" %s Failed to log in to %s using token (%s)\n", cs.Red("X"), e.host, e.tokenSource))
|
||||
}
|
||||
activeStr := fmt.Sprintf("%v", e.active)
|
||||
sb.WriteString(fmt.Sprintf(" - Active account: %s\n", cs.Bold(activeStr)))
|
||||
sb.WriteString(fmt.Sprintf(" - The token in %s is invalid.\n", e.tokenSource))
|
||||
if e.tokenIsWriteable {
|
||||
loginInstructions := fmt.Sprintf("gh auth login -h %s", e.host)
|
||||
logoutInstructions := fmt.Sprintf("gh auth logout -h %s -u %s", e.host, e.user)
|
||||
sb.WriteString(fmt.Sprintf(" - To re-authenticate, run: %s\n", cs.Bold(loginInstructions)))
|
||||
sb.WriteString(fmt.Sprintf(" - To forget about this account, run: %s\n", cs.Bold(logoutInstructions)))
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
type timeoutErrorEntry struct {
|
||||
active bool
|
||||
host string
|
||||
user string
|
||||
tokenSource string
|
||||
}
|
||||
|
||||
func (e timeoutErrorEntry) String(cs *iostreams.ColorScheme) string {
|
||||
var sb strings.Builder
|
||||
|
||||
if e.user != "" {
|
||||
sb.WriteString(fmt.Sprintf(" %s Timeout trying to log in to %s account %s (%s)\n", cs.Red("X"), e.host, cs.Bold(e.user), e.tokenSource))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf(" %s Timeout trying to log in to %s using token (%s)\n", cs.Red("X"), e.host, e.tokenSource))
|
||||
}
|
||||
activeStr := fmt.Sprintf("%v", e.active)
|
||||
sb.WriteString(fmt.Sprintf(" - Active account: %s\n", cs.Bold(activeStr)))
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
type Entry interface {
|
||||
String(cs *iostreams.ColorScheme) string
|
||||
}
|
||||
|
||||
type Entries []Entry
|
||||
|
||||
func (e Entries) Strings(cs *iostreams.ColorScheme) []string {
|
||||
var out []string
|
||||
for _, entry := range e {
|
||||
out = append(out, entry.String(cs))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type StatusOptions struct {
|
||||
HttpClient func() (*http.Client, error)
|
||||
IO *iostreams.IOStreams
|
||||
Config func() (gh.Config, error)
|
||||
Exporter cmdutil.Exporter
|
||||
|
||||
Hostname string
|
||||
ShowToken bool
|
||||
|
|
@ -148,11 +141,32 @@ func NewCmdStatus(f *cmdutil.Factory, runF func(*StatusOptions) error) *cobra.Co
|
|||
|
||||
For each host, the authentication state of each known account is tested and any issues are included in the output.
|
||||
Each host section will indicate the active account, which will be used when targeting that host.
|
||||
|
||||
If an account on any host (or only the one given via %[1]s--hostname%[1]s) has authentication issues,
|
||||
the command will exit with 1 and output to stderr.
|
||||
the command will exit with 1 and output to stderr. Note that when using the %[1]s--json%[1]s option, the command
|
||||
will always exit with zero regardless of any authentication issues, unless there is a fatal error.
|
||||
|
||||
To change the active account for a host, see %[1]sgh auth switch%[1]s.
|
||||
`, "`"),
|
||||
Example: heredoc.Doc(`
|
||||
# Display authentication status for all accounts on all hosts
|
||||
$ gh auth status
|
||||
|
||||
# Display authentication status for the active account on a specific host
|
||||
$ gh auth status --active --hostname github.example.com
|
||||
|
||||
# Display tokens in plain text
|
||||
$ gh auth status --show-token
|
||||
|
||||
# Format authentication status as JSON
|
||||
$ gh auth status --json hosts
|
||||
|
||||
# Include plain text token in JSON output
|
||||
$ gh auth status --json hosts --show-token
|
||||
|
||||
# Format hosts as a flat JSON array
|
||||
$ gh auth status --json hosts --jq '.hosts | add'
|
||||
`),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if runF != nil {
|
||||
return runF(opts)
|
||||
|
|
@ -166,6 +180,9 @@ func NewCmdStatus(f *cmdutil.Factory, runF func(*StatusOptions) error) *cobra.Co
|
|||
cmd.Flags().BoolVarP(&opts.ShowToken, "show-token", "t", false, "Display the auth token")
|
||||
cmd.Flags().BoolVarP(&opts.Active, "active", "a", false, "Display the active account only")
|
||||
|
||||
// the json flags are intentionally not given a shorthand to avoid conflict with -t/--show-token
|
||||
cmdutil.AddJSONFlagsWithoutShorthand(cmd, &opts.Exporter, authStatusFields)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
|
@ -180,18 +197,26 @@ func statusRun(opts *StatusOptions) error {
|
|||
stdout := opts.IO.Out
|
||||
cs := opts.IO.ColorScheme()
|
||||
|
||||
statuses := make(map[string]Entries)
|
||||
|
||||
hostnames := authCfg.Hosts()
|
||||
if len(hostnames) == 0 {
|
||||
fmt.Fprintf(stderr,
|
||||
"You are not logged into any GitHub hosts. To log in, run: %s\n", cs.Bold("gh auth login"))
|
||||
if opts.Exporter != nil {
|
||||
// In machine-friendly mode, we always exit with no error.
|
||||
opts.Exporter.Write(opts.IO, newAuthStatus())
|
||||
return nil
|
||||
}
|
||||
return cmdutil.SilentError
|
||||
}
|
||||
|
||||
if opts.Hostname != "" && !slices.Contains(hostnames, opts.Hostname) {
|
||||
fmt.Fprintf(stderr,
|
||||
"You are not logged into any accounts on %s\n", opts.Hostname)
|
||||
if opts.Exporter != nil {
|
||||
// In machine-friendly mode, we always exit with no error.
|
||||
opts.Exporter.Write(opts.IO, newAuthStatus())
|
||||
return nil
|
||||
}
|
||||
return cmdutil.SilentError
|
||||
}
|
||||
|
||||
|
|
@ -200,6 +225,9 @@ func statusRun(opts *StatusOptions) error {
|
|||
return err
|
||||
}
|
||||
|
||||
var finalErr error
|
||||
statuses := newAuthStatus()
|
||||
|
||||
for _, hostname := range hostnames {
|
||||
if opts.Hostname != "" && opts.Hostname != hostname {
|
||||
continue
|
||||
|
|
@ -215,15 +243,14 @@ func statusRun(opts *StatusOptions) error {
|
|||
active: true,
|
||||
gitProtocol: gitProtocol,
|
||||
hostname: hostname,
|
||||
showToken: opts.ShowToken,
|
||||
token: activeUserToken,
|
||||
tokenSource: activeUserTokenSource,
|
||||
username: activeUser,
|
||||
})
|
||||
statuses[hostname] = append(statuses[hostname], entry)
|
||||
statuses.Hosts[hostname] = append(statuses.Hosts[hostname], entry)
|
||||
|
||||
if err == nil && !isValidEntry(entry) {
|
||||
err = cmdutil.SilentError
|
||||
if finalErr == nil && entry.State != authEntryStateSuccess {
|
||||
finalErr = cmdutil.SilentError
|
||||
}
|
||||
|
||||
if opts.Active {
|
||||
|
|
@ -240,28 +267,46 @@ func statusRun(opts *StatusOptions) error {
|
|||
active: false,
|
||||
gitProtocol: gitProtocol,
|
||||
hostname: hostname,
|
||||
showToken: opts.ShowToken,
|
||||
token: token,
|
||||
tokenSource: tokenSource,
|
||||
username: username,
|
||||
})
|
||||
statuses[hostname] = append(statuses[hostname], entry)
|
||||
statuses.Hosts[hostname] = append(statuses.Hosts[hostname], entry)
|
||||
|
||||
if err == nil && !isValidEntry(entry) {
|
||||
err = cmdutil.SilentError
|
||||
if finalErr == nil && entry.State != authEntryStateSuccess {
|
||||
finalErr = cmdutil.SilentError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !opts.ShowToken {
|
||||
for _, host := range statuses.Hosts {
|
||||
for i := range host {
|
||||
if opts.Exporter != nil {
|
||||
// In machine-readable we just drop the token
|
||||
host[i].Token = ""
|
||||
} else {
|
||||
host[i].Token = maskToken(host[i].Token)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Exporter != nil {
|
||||
// In machine-friendly mode, we always exit with no error.
|
||||
opts.Exporter.Write(opts.IO, statuses)
|
||||
return nil
|
||||
}
|
||||
|
||||
prevEntry := false
|
||||
for _, hostname := range hostnames {
|
||||
entries, ok := statuses[hostname]
|
||||
entries, ok := statuses.Hosts[hostname]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
stream := stdout
|
||||
if err != nil {
|
||||
if finalErr != nil {
|
||||
stream = stderr
|
||||
}
|
||||
|
||||
|
|
@ -270,22 +315,22 @@ func statusRun(opts *StatusOptions) error {
|
|||
}
|
||||
prevEntry = true
|
||||
fmt.Fprintf(stream, "%s\n", cs.Bold(hostname))
|
||||
fmt.Fprintf(stream, "%s", strings.Join(entries.Strings(cs), "\n"))
|
||||
for i, entry := range entries {
|
||||
fmt.Fprintf(stream, "%s", entry.String(cs))
|
||||
if i < len(entries)-1 {
|
||||
fmt.Fprint(stream, "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
return finalErr
|
||||
}
|
||||
|
||||
func displayToken(token string, printRaw bool) string {
|
||||
if printRaw {
|
||||
return token
|
||||
}
|
||||
|
||||
func maskToken(token string) string {
|
||||
if idx := strings.LastIndexByte(token, '_'); idx > -1 {
|
||||
prefix := token[0 : idx+1]
|
||||
return prefix + strings.Repeat("*", len(token)-len(prefix))
|
||||
}
|
||||
|
||||
return strings.Repeat("*", len(token))
|
||||
}
|
||||
|
||||
|
|
@ -308,37 +353,39 @@ type buildEntryOptions struct {
|
|||
active bool
|
||||
gitProtocol string
|
||||
hostname string
|
||||
showToken bool
|
||||
token string
|
||||
tokenSource string
|
||||
username string
|
||||
}
|
||||
|
||||
func buildEntry(httpClient *http.Client, opts buildEntryOptions) Entry {
|
||||
tokenIsWriteable := authTokenWriteable(opts.tokenSource)
|
||||
|
||||
if opts.tokenSource == "oauth_token" {
|
||||
func buildEntry(httpClient *http.Client, opts buildEntryOptions) authEntry {
|
||||
tokenSource := opts.tokenSource
|
||||
if tokenSource == "oauth_token" {
|
||||
// The go-gh function TokenForHost returns this value as source for tokens read from the
|
||||
// config file, but we want the file path instead. This attempts to reconstruct it.
|
||||
opts.tokenSource = filepath.Join(config.ConfigDir(), "hosts.yml")
|
||||
tokenSource = filepath.Join(config.ConfigDir(), "hosts.yml")
|
||||
}
|
||||
entry := authEntry{
|
||||
Active: opts.active,
|
||||
Host: opts.hostname,
|
||||
Login: opts.username,
|
||||
TokenSource: tokenSource,
|
||||
Token: opts.token,
|
||||
GitProtocol: opts.gitProtocol,
|
||||
}
|
||||
|
||||
// If token is not writeable, then it came from an environment variable and
|
||||
// we need to fetch the username as it won't be stored in the config.
|
||||
if !tokenIsWriteable {
|
||||
if !authTokenWriteable(tokenSource) {
|
||||
// The httpClient will automatically use the correct token here as
|
||||
// the token from the environment variable take highest precedence.
|
||||
apiClient := api.NewClientFromHTTP(httpClient)
|
||||
var err error
|
||||
opts.username, err = api.CurrentLoginName(apiClient, opts.hostname)
|
||||
entry.Login, err = api.CurrentLoginName(apiClient, opts.hostname)
|
||||
if err != nil {
|
||||
return invalidTokenEntry{
|
||||
active: opts.active,
|
||||
host: opts.hostname,
|
||||
user: opts.username,
|
||||
tokenIsWriteable: tokenIsWriteable,
|
||||
tokenSource: opts.tokenSource,
|
||||
}
|
||||
entry.State = authEntryStateError
|
||||
entry.Error = err.Error()
|
||||
return entry
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -347,39 +394,21 @@ func buildEntry(httpClient *http.Client, opts buildEntryOptions) Entry {
|
|||
if err != nil {
|
||||
var networkError net.Error
|
||||
if errors.As(err, &networkError) && networkError.Timeout() {
|
||||
return timeoutErrorEntry{
|
||||
active: opts.active,
|
||||
host: opts.hostname,
|
||||
user: opts.username,
|
||||
tokenSource: opts.tokenSource,
|
||||
}
|
||||
entry.State = authEntryStateTimeout
|
||||
entry.Error = err.Error()
|
||||
return entry
|
||||
}
|
||||
|
||||
return invalidTokenEntry{
|
||||
active: opts.active,
|
||||
host: opts.hostname,
|
||||
user: opts.username,
|
||||
tokenIsWriteable: tokenIsWriteable,
|
||||
tokenSource: opts.tokenSource,
|
||||
}
|
||||
entry.State = authEntryStateError
|
||||
entry.Error = err.Error()
|
||||
return entry
|
||||
}
|
||||
entry.Scopes = scopesHeader
|
||||
|
||||
return validEntry{
|
||||
active: opts.active,
|
||||
gitProtocol: opts.gitProtocol,
|
||||
host: opts.hostname,
|
||||
scopes: scopesHeader,
|
||||
token: displayToken(opts.token, opts.showToken),
|
||||
tokenSource: opts.tokenSource,
|
||||
user: opts.username,
|
||||
}
|
||||
entry.State = authEntryStateSuccess
|
||||
return entry
|
||||
}
|
||||
|
||||
func authTokenWriteable(src string) bool {
|
||||
return !strings.HasSuffix(src, "_TOKEN")
|
||||
}
|
||||
|
||||
func isValidEntry(entry Entry) bool {
|
||||
_, ok := entry.(validEntry)
|
||||
return ok
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package status
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
|
@ -14,6 +15,7 @@ import (
|
|||
"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/jsonfieldstest"
|
||||
"github.com/google/shlex"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
|
@ -78,14 +80,23 @@ func Test_NewCmdStatus(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.wants.Hostname, gotOpts.Hostname)
|
||||
assert.Equal(t, tt.wants.ShowToken, gotOpts.ShowToken)
|
||||
assert.Equal(t, tt.wants.Active, gotOpts.Active)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONFields(t *testing.T) {
|
||||
jsonfieldstest.ExpectCommandToSupportJSONFields(t, NewCmdStatus, []string{
|
||||
"hosts",
|
||||
})
|
||||
}
|
||||
|
||||
func Test_statusRun(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
opts StatusOptions
|
||||
jsonFields []string
|
||||
env map[string]string
|
||||
httpStubs func(*httpmock.Registry)
|
||||
cfgStubs func(*testing.T, gh.Config)
|
||||
|
|
@ -528,16 +539,185 @@ func Test_statusRun(t *testing.T) {
|
|||
- To forget about this account, run: gh auth logout -h ghe.io -u monalisa-ghe-2
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "json, no tokens",
|
||||
opts: StatusOptions{},
|
||||
jsonFields: []string{"hosts"},
|
||||
wantOut: "{\"hosts\":{}}\n",
|
||||
wantErrOut: "You are not logged into any GitHub hosts. To log in, run: gh auth login\n",
|
||||
wantErr: nil, // should not return error in machine-readable mode
|
||||
},
|
||||
{
|
||||
name: "json, no token for given --hostname",
|
||||
opts: StatusOptions{
|
||||
Hostname: "foo.com",
|
||||
},
|
||||
jsonFields: []string{"hosts"},
|
||||
cfgStubs: func(t *testing.T, c gh.Config) {
|
||||
login(t, c, "github.com", "monalisa", "gho_abc123", "https")
|
||||
},
|
||||
wantOut: "{\"hosts\":{}}\n",
|
||||
wantErrOut: "You are not logged into any accounts on foo.com\n",
|
||||
wantErr: nil, // should not return error in machine-readable mode
|
||||
},
|
||||
{
|
||||
name: "json, all valid tokens",
|
||||
opts: StatusOptions{},
|
||||
jsonFields: []string{"hosts"},
|
||||
cfgStubs: func(t *testing.T, c gh.Config) {
|
||||
login(t, c, "github.com", "monalisa", "gho_abc123", "https")
|
||||
login(t, c, "github.com", "monalisa2", "gho_abc123", "https")
|
||||
login(t, c, "ghe.io", "monalisa-ghe", "gho_abc123", "https")
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
// mock for HeaderHasMinimumScopes api requests to github.com
|
||||
reg.Register(
|
||||
httpmock.REST("GET", ""),
|
||||
httpmock.WithHeader(httpmock.ScopesResponder("repo,read:org"), "X-Oauth-Scopes", "repo, read:org"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", ""),
|
||||
httpmock.WithHeader(httpmock.ScopesResponder("repo,read:org"), "X-Oauth-Scopes", "repo, read:org"))
|
||||
|
||||
// mock for HeaderHasMinimumScopes api requests to a non-github.com host
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "api/v3/"),
|
||||
httpmock.WithHeader(httpmock.ScopesResponder("repo,read:org"), "X-Oauth-Scopes", "repo, read:org"))
|
||||
},
|
||||
wantOut: `{"hosts":{"ghe.io":[{"state":"success","active":true,"host":"ghe.io","login":"monalisa-ghe","tokenSource":"GH_CONFIG_DIR/hosts.yml","scopes":"repo, read:org","gitProtocol":"https"}],"github.com":[{"state":"success","active":true,"host":"github.com","login":"monalisa2","tokenSource":"GH_CONFIG_DIR/hosts.yml","scopes":"repo, read:org","gitProtocol":"https"},{"state":"success","active":false,"host":"github.com","login":"monalisa","tokenSource":"GH_CONFIG_DIR/hosts.yml","scopes":"repo, read:org","gitProtocol":"https"}]}}` + "\n",
|
||||
},
|
||||
{
|
||||
name: "json, all valid tokens with hostname",
|
||||
opts: StatusOptions{
|
||||
Hostname: "github.com",
|
||||
},
|
||||
jsonFields: []string{"hosts"},
|
||||
cfgStubs: func(t *testing.T, c gh.Config) {
|
||||
login(t, c, "github.com", "monalisa", "gho_abc123", "https")
|
||||
login(t, c, "github.com", "monalisa2", "gho_abc123", "https")
|
||||
login(t, c, "ghe.io", "monalisa-ghe", "gho_abc123", "https")
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
// mocks for HeaderHasMinimumScopes api requests to github.com
|
||||
reg.Register(
|
||||
httpmock.REST("GET", ""),
|
||||
httpmock.WithHeader(httpmock.ScopesResponder("repo,read:org"), "X-Oauth-Scopes", "repo, read:org"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", ""),
|
||||
httpmock.WithHeader(httpmock.ScopesResponder("repo,read:org"), "X-Oauth-Scopes", "repo, read:org"))
|
||||
},
|
||||
wantOut: `{"hosts":{"github.com":[{"state":"success","active":true,"host":"github.com","login":"monalisa2","tokenSource":"GH_CONFIG_DIR/hosts.yml","scopes":"repo, read:org","gitProtocol":"https"},{"state":"success","active":false,"host":"github.com","login":"monalisa","tokenSource":"GH_CONFIG_DIR/hosts.yml","scopes":"repo, read:org","gitProtocol":"https"}]}}` + "\n",
|
||||
},
|
||||
{
|
||||
name: "json, all valid tokens with active",
|
||||
opts: StatusOptions{
|
||||
Active: true,
|
||||
},
|
||||
jsonFields: []string{"hosts"},
|
||||
cfgStubs: func(t *testing.T, c gh.Config) {
|
||||
login(t, c, "github.com", "monalisa", "gho_abc123", "https")
|
||||
login(t, c, "github.com", "monalisa2", "gho_abc123", "https")
|
||||
login(t, c, "ghe.io", "monalisa-ghe", "gho_abc123", "https")
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
// mocks for HeaderHasMinimumScopes api requests to github.com
|
||||
reg.Register(
|
||||
httpmock.REST("GET", ""),
|
||||
httpmock.WithHeader(httpmock.ScopesResponder("repo,read:org"), "X-Oauth-Scopes", "repo, read:org"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "api/v3/"),
|
||||
httpmock.WithHeader(httpmock.ScopesResponder("repo,read:org"), "X-Oauth-Scopes", "repo, read:org"))
|
||||
},
|
||||
wantOut: `{"hosts":{"ghe.io":[{"state":"success","active":true,"host":"ghe.io","login":"monalisa-ghe","tokenSource":"GH_CONFIG_DIR/hosts.yml","scopes":"repo, read:org","gitProtocol":"https"}],"github.com":[{"state":"success","active":true,"host":"github.com","login":"monalisa2","tokenSource":"GH_CONFIG_DIR/hosts.yml","scopes":"repo, read:org","gitProtocol":"https"}]}}` + "\n",
|
||||
},
|
||||
{
|
||||
name: "json, token from env",
|
||||
opts: StatusOptions{},
|
||||
jsonFields: []string{"hosts"},
|
||||
env: map[string]string{"GH_TOKEN": "gho_abc123"},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", ""),
|
||||
httpmock.ScopesResponder(""))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`{"data":{"viewer":{"login":"monalisa"}}}`))
|
||||
},
|
||||
wantOut: `{"hosts":{"github.com":[{"state":"success","active":true,"host":"github.com","login":"monalisa","tokenSource":"GH_TOKEN","gitProtocol":"https"}]}}` + "\n",
|
||||
},
|
||||
{
|
||||
name: "json, bad token",
|
||||
opts: StatusOptions{},
|
||||
jsonFields: []string{"hosts"},
|
||||
cfgStubs: func(t *testing.T, c gh.Config) {
|
||||
login(t, c, "ghe.io", "monalisa-ghe", "gho_abc123", "https")
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
// mock for HeaderHasMinimumScopes api requests to a non-github.com host
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.StatusStringResponse(400, "no bueno"))
|
||||
},
|
||||
wantOut: `{"hosts":{"ghe.io":[{"state":"error","error":"HTTP 400 (https://ghe.io/api/v3/)","active":true,"host":"ghe.io","login":"monalisa-ghe","tokenSource":"GH_CONFIG_DIR/hosts.yml","gitProtocol":"https"}]}}` + "\n",
|
||||
wantErr: nil, // should not return error in machine-readable mode
|
||||
},
|
||||
{
|
||||
name: "json, bad token from env",
|
||||
opts: StatusOptions{},
|
||||
jsonFields: []string{"hosts"},
|
||||
env: map[string]string{"GH_TOKEN": "gho_abc123"},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
// mock for HeaderHasMinimumScopes api requests to a non-github.com host
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StatusStringResponse(400, `no bueno`))
|
||||
},
|
||||
wantOut: `{"hosts":{"github.com":[{"state":"error","error":"non-200 OK status code: body: \"no bueno\"","active":true,"host":"github.com","login":"","tokenSource":"GH_TOKEN","gitProtocol":"https"}]}}` + "\n",
|
||||
wantErr: nil, // should not return error in machine-readable mode
|
||||
},
|
||||
{
|
||||
name: "json, timeout error",
|
||||
opts: StatusOptions{
|
||||
Hostname: "github.com",
|
||||
},
|
||||
jsonFields: []string{"hosts"},
|
||||
cfgStubs: func(t *testing.T, c gh.Config) {
|
||||
login(t, c, "github.com", "monalisa", "abc123", "https")
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", ""), func(req *http.Request) (*http.Response, error) {
|
||||
// timeout error
|
||||
return nil, context.DeadlineExceeded
|
||||
})
|
||||
},
|
||||
wantOut: `{"hosts":{"github.com":[{"state":"timeout","error":"Get \"https://api.github.com/\": context deadline exceeded","active":true,"host":"github.com","login":"monalisa","tokenSource":"GH_CONFIG_DIR/hosts.yml","gitProtocol":"https"}]}}` + "\n",
|
||||
wantErr: nil, // should not return error in machine-readable mode
|
||||
},
|
||||
{
|
||||
name: "json, with show token",
|
||||
opts: StatusOptions{
|
||||
Hostname: "github.com",
|
||||
ShowToken: true,
|
||||
},
|
||||
jsonFields: []string{"hosts"},
|
||||
cfgStubs: func(t *testing.T, c gh.Config) {
|
||||
login(t, c, "github.com", "monalisa", "abc123", "https")
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
// mocks for HeaderHasMinimumScopes api requests to github.com
|
||||
reg.Register(
|
||||
httpmock.REST("GET", ""),
|
||||
httpmock.WithHeader(httpmock.ScopesResponder("repo,read:org"), "X-Oauth-Scopes", "repo, read:org"))
|
||||
},
|
||||
wantOut: `{"hosts":{"github.com":[{"state":"success","active":true,"host":"github.com","login":"monalisa","tokenSource":"GH_CONFIG_DIR/hosts.yml","token":"abc123","scopes":"repo, read:org","gitProtocol":"https"}]}}` + "\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ios, _, stdout, stderr := iostreams.Test()
|
||||
|
||||
ios.SetStdinTTY(true)
|
||||
ios.SetStderrTTY(true)
|
||||
ios.SetStdoutTTY(true)
|
||||
tt.opts.IO = ios
|
||||
|
||||
cfg, _ := config.NewIsolatedTestConfig(t)
|
||||
if tt.cfgStubs != nil {
|
||||
tt.cfgStubs(t, cfg)
|
||||
|
|
@ -555,6 +735,12 @@ func Test_statusRun(t *testing.T) {
|
|||
tt.httpStubs(reg)
|
||||
}
|
||||
|
||||
if tt.jsonFields != nil {
|
||||
jsonExporter := cmdutil.NewJSONExporter()
|
||||
jsonExporter.SetFields(tt.jsonFields)
|
||||
tt.opts.Exporter = jsonExporter
|
||||
}
|
||||
|
||||
for k, v := range tt.env {
|
||||
t.Setenv(k, v)
|
||||
}
|
||||
|
|
@ -565,8 +751,9 @@ func Test_statusRun(t *testing.T) {
|
|||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
output := strings.ReplaceAll(stdout.String(), config.ConfigDir()+string(filepath.Separator), "GH_CONFIG_DIR/")
|
||||
errorOutput := strings.ReplaceAll(stderr.String(), config.ConfigDir()+string(filepath.Separator), "GH_CONFIG_DIR/")
|
||||
|
||||
output := replaceAll(stdout.String(), config.ConfigDir()+string(filepath.Separator), "GH_CONFIG_DIR/")
|
||||
errorOutput := replaceAll(stderr.String(), config.ConfigDir()+string(filepath.Separator), "GH_CONFIG_DIR/")
|
||||
|
||||
require.Equal(t, tt.wantErrOut, errorOutput)
|
||||
require.Equal(t, tt.wantOut, output)
|
||||
|
|
@ -574,8 +761,24 @@ func Test_statusRun(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func login(t *testing.T, c gh.Config, hostname, username, protocol, token string) {
|
||||
func login(t *testing.T, c gh.Config, hostname, username, token, protocol string) {
|
||||
t.Helper()
|
||||
_, err := c.Authentication().Login(hostname, username, protocol, token, false)
|
||||
_, err := c.Authentication().Login(hostname, username, token, protocol, false)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// replaceAll replaces all instances of old with new in s, as well as all instances
|
||||
// of the JSON-escaped version of old with the JSON-escaped version of new.
|
||||
// This is because when the test is run on Windows the paths will have backslashes
|
||||
// escaped in JSON and a simple strings.ReplaceAll won't catch them.
|
||||
func replaceAll(s string, old string, new string) string {
|
||||
jsonEscapedOld, _ := json.Marshal(old)
|
||||
jsonEscapedOld = jsonEscapedOld[1 : len(jsonEscapedOld)-1]
|
||||
|
||||
jsonEscapedNew, _ := json.Marshal(new)
|
||||
jsonEscapedNew = jsonEscapedNew[1 : len(jsonEscapedNew)-1]
|
||||
|
||||
replaced := strings.ReplaceAll(s, string(jsonEscapedOld), string(jsonEscapedNew))
|
||||
replaced = strings.ReplaceAll(replaced, old, new)
|
||||
return replaced
|
||||
}
|
||||
|
|
|
|||
57
pkg/cmd/cache/delete/delete.go
vendored
57
pkg/cmd/cache/delete/delete.go
vendored
|
|
@ -164,33 +164,18 @@ func deleteCaches(opts *DeleteOptions, client *api.Client, repo ghrepo.Interface
|
|||
cs := opts.IO.ColorScheme()
|
||||
repoName := ghrepo.FullName(repo)
|
||||
opts.IO.StartProgressIndicator()
|
||||
base := fmt.Sprintf("repos/%s/actions/caches", repoName)
|
||||
|
||||
totalDeleted := 0
|
||||
for _, cache := range toDelete {
|
||||
// TODO(babakks): We use two different endpoints here which have different
|
||||
// response schemas:
|
||||
//
|
||||
// 1. /repos/OWNER/REPO/actions/caches/ID (for deleting by cache ID)
|
||||
// - returns HTTP 204 (NO CONTENT) on success
|
||||
// 2. /repos/OWNER/REPO/actions/caches?key=KEY[&ref=REF] (for deleting by cache key, and optionally a ref)
|
||||
// - returns HTTP 200 on success including information about the deleted caches
|
||||
//
|
||||
// So, if/when we decided to use the data in the response body we need
|
||||
// to be careful with parsing. Probably want to split these API calls
|
||||
// into separate functions.
|
||||
|
||||
path := ""
|
||||
var count int
|
||||
var err error
|
||||
if id, ok := parseCacheID(cache); ok {
|
||||
path = fmt.Sprintf("%s/%d", base, id)
|
||||
err = deleteCacheByID(client, repo, id)
|
||||
count = 1
|
||||
} else {
|
||||
path = fmt.Sprintf("%s?key=%s", base, url.QueryEscape(cache))
|
||||
|
||||
if opts.Ref != "" {
|
||||
path += fmt.Sprintf("&ref=%s", url.QueryEscape(opts.Ref))
|
||||
}
|
||||
count, err = deleteCacheByKey(client, repo, cache, opts.Ref)
|
||||
}
|
||||
|
||||
err := client.REST(repo.RepoHost(), "DELETE", path, nil, nil)
|
||||
if err != nil {
|
||||
var httpErr api.HTTPError
|
||||
if errors.As(err, &httpErr) {
|
||||
|
|
@ -207,17 +192,45 @@ func deleteCaches(opts *DeleteOptions, client *api.Client, repo ghrepo.Interface
|
|||
opts.IO.StopProgressIndicator()
|
||||
return err
|
||||
}
|
||||
|
||||
totalDeleted += count
|
||||
}
|
||||
|
||||
opts.IO.StopProgressIndicator()
|
||||
|
||||
if opts.IO.IsStdoutTTY() {
|
||||
fmt.Fprintf(opts.IO.Out, "%s Deleted %s from %s\n", cs.SuccessIcon(), text.Pluralize(len(toDelete), "cache"), repoName)
|
||||
fmt.Fprintf(opts.IO.Out, "%s Deleted %s from %s\n", cs.SuccessIcon(), text.Pluralize(totalDeleted, "cache"), repoName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteCacheByID(client *api.Client, repo ghrepo.Interface, id int) error {
|
||||
// returns HTTP 204 (NO CONTENT) on success
|
||||
path := fmt.Sprintf("repos/%s/actions/caches/%d", ghrepo.FullName(repo), id)
|
||||
return client.REST(repo.RepoHost(), "DELETE", path, nil, nil)
|
||||
}
|
||||
|
||||
// deleteCacheByKey deletes cache entries by given key (and optional ref) and
|
||||
// returns the number of deleted entries.
|
||||
//
|
||||
// Note that a key/ref combination does not necessarily map to a single cache
|
||||
// entry. There may be more than one entries with the same key/ref combination,
|
||||
// but those entries will have different IDs.
|
||||
func deleteCacheByKey(client *api.Client, repo ghrepo.Interface, key, ref string) (int, error) {
|
||||
path := fmt.Sprintf("repos/%s/actions/caches?key=%s", ghrepo.FullName(repo), url.QueryEscape(key))
|
||||
if ref != "" {
|
||||
path += fmt.Sprintf("&ref=%s", url.QueryEscape(ref))
|
||||
}
|
||||
var payload shared.CachePayload
|
||||
err := client.REST(repo.RepoHost(), "DELETE", path, nil, &payload)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return payload.TotalCount, nil
|
||||
}
|
||||
|
||||
func parseCacheID(arg string) (int, bool) {
|
||||
id, err := strconv.Atoi(arg)
|
||||
return id, err == nil
|
||||
|
|
|
|||
48
pkg/cmd/cache/delete/delete_test.go
vendored
48
pkg/cmd/cache/delete/delete_test.go
vendored
|
|
@ -235,13 +235,30 @@ func TestDeleteRun(t *testing.T) {
|
|||
httpmock.QueryMatcher("DELETE", "repos/OWNER/REPO/actions/caches", url.Values{
|
||||
"key": []string{"a weird_cache+key"},
|
||||
}),
|
||||
// The response is a JSON object but we don't need it here.
|
||||
httpmock.StatusStringResponse(200, "{}"),
|
||||
httpmock.JSONResponse(shared.CachePayload{
|
||||
TotalCount: 1,
|
||||
}),
|
||||
)
|
||||
},
|
||||
tty: true,
|
||||
wantStdout: "✓ Deleted 1 cache from OWNER/REPO\n",
|
||||
},
|
||||
{
|
||||
name: "deletes multiple caches by key",
|
||||
opts: DeleteOptions{Identifier: "shared-cache-key"},
|
||||
stubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.QueryMatcher("DELETE", "repos/OWNER/REPO/actions/caches", url.Values{
|
||||
"key": []string{"shared-cache-key"},
|
||||
}),
|
||||
httpmock.JSONResponse(shared.CachePayload{
|
||||
TotalCount: 5,
|
||||
}),
|
||||
)
|
||||
},
|
||||
tty: true,
|
||||
wantStdout: "✓ Deleted 5 caches from OWNER/REPO\n",
|
||||
},
|
||||
{
|
||||
name: "no caches to delete when deleting all",
|
||||
opts: DeleteOptions{DeleteAll: true},
|
||||
|
|
@ -299,8 +316,9 @@ func TestDeleteRun(t *testing.T) {
|
|||
"key": []string{"cache-key"},
|
||||
"ref": []string{"refs/heads/main"},
|
||||
}),
|
||||
// The response is a JSON object but we don't need it here.
|
||||
httpmock.StatusStringResponse(200, "{}"),
|
||||
httpmock.JSONResponse(shared.CachePayload{
|
||||
TotalCount: 1,
|
||||
}),
|
||||
)
|
||||
},
|
||||
tty: true,
|
||||
|
|
@ -315,13 +333,31 @@ func TestDeleteRun(t *testing.T) {
|
|||
"key": []string{"cache-key"},
|
||||
"ref": []string{"refs/heads/main"},
|
||||
}),
|
||||
// The response is a JSON object but we don't need it here.
|
||||
httpmock.StatusStringResponse(200, "{}"),
|
||||
httpmock.JSONResponse(shared.CachePayload{
|
||||
TotalCount: 1,
|
||||
}),
|
||||
)
|
||||
},
|
||||
tty: false,
|
||||
wantStdout: "",
|
||||
},
|
||||
{
|
||||
name: "deletes multiple caches by key and ref",
|
||||
opts: DeleteOptions{Identifier: "cache-key", Ref: "refs/heads/feature"},
|
||||
stubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.QueryMatcher("DELETE", "repos/OWNER/REPO/actions/caches", url.Values{
|
||||
"key": []string{"cache-key"},
|
||||
"ref": []string{"refs/heads/feature"},
|
||||
}),
|
||||
httpmock.JSONResponse(shared.CachePayload{
|
||||
TotalCount: 3,
|
||||
}),
|
||||
)
|
||||
},
|
||||
tty: true,
|
||||
wantStdout: "✓ Deleted 3 caches from OWNER/REPO\n",
|
||||
},
|
||||
{
|
||||
// As of now, the API returns HTTP 404 for invalid or non-existent refs.
|
||||
name: "cache key exists but ref is invalid/not-found",
|
||||
|
|
|
|||
|
|
@ -33,15 +33,16 @@ func New(appVersion string) *cmdutil.Factory {
|
|||
ExecutableName: "gh",
|
||||
}
|
||||
|
||||
f.IOStreams = ioStreams(f) // Depends on Config
|
||||
f.HttpClient = httpClientFunc(f, appVersion) // Depends on Config, IOStreams, and appVersion
|
||||
f.GitClient = newGitClient(f) // Depends on IOStreams, and Executable
|
||||
f.Remotes = remotesFunc(f) // Depends on Config, and GitClient
|
||||
f.BaseRepo = BaseRepoFunc(f) // Depends on Remotes
|
||||
f.Prompter = newPrompter(f) // Depends on Config and IOStreams
|
||||
f.Browser = newBrowser(f) // Depends on Config, and IOStreams
|
||||
f.ExtensionManager = extensionManager(f) // Depends on Config, HttpClient, and IOStreams
|
||||
f.Branch = branchFunc(f) // Depends on GitClient
|
||||
f.IOStreams = ioStreams(f) // Depends on Config
|
||||
f.HttpClient = httpClientFunc(f, appVersion) // Depends on Config, IOStreams, and appVersion
|
||||
f.PlainHttpClient = plainHttpClientFunc(f, appVersion) // Depends on IOStreams, and appVersion
|
||||
f.GitClient = newGitClient(f) // Depends on IOStreams, and Executable
|
||||
f.Remotes = remotesFunc(f) // Depends on Config, and GitClient
|
||||
f.BaseRepo = BaseRepoFunc(f) // Depends on Remotes
|
||||
f.Prompter = newPrompter(f) // Depends on Config and IOStreams
|
||||
f.Browser = newBrowser(f) // Depends on Config, and IOStreams
|
||||
f.ExtensionManager = extensionManager(f) // Depends on Config, HttpClient, and IOStreams
|
||||
f.Branch = branchFunc(f) // Depends on GitClient
|
||||
|
||||
return f
|
||||
}
|
||||
|
|
@ -207,6 +208,24 @@ func httpClientFunc(f *cmdutil.Factory, appVersion string) func() (*http.Client,
|
|||
}
|
||||
}
|
||||
|
||||
func plainHttpClientFunc(f *cmdutil.Factory, appVersion string) func() (*http.Client, error) {
|
||||
return func() (*http.Client, error) {
|
||||
io := f.IOStreams
|
||||
opts := api.HTTPClientOptions{
|
||||
Log: io.ErrOut,
|
||||
LogColorize: io.ColorEnabled(),
|
||||
AppVersion: appVersion,
|
||||
// This is required to prevent automatic setting of auth and other headers.
|
||||
SkipDefaultHeaders: true,
|
||||
}
|
||||
client, err := api.NewHTTPClient(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
}
|
||||
|
||||
func newGitClient(f *cmdutil.Factory) *git.Client {
|
||||
io := f.IOStreams
|
||||
ghPath := f.Executable()
|
||||
|
|
|
|||
|
|
@ -710,6 +710,36 @@ func TestSSOURL(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPlainHttpClient(t *testing.T) {
|
||||
var receivedHeaders *http.Header
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
receivedHeaders = &r.Header
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
f := New("1")
|
||||
f.Config = func() (gh.Config, error) {
|
||||
return config.NewBlankConfig(), nil
|
||||
}
|
||||
ios, _, _, _ := iostreams.Test()
|
||||
f.IOStreams = ios
|
||||
client, err := plainHttpClientFunc(f, "v1.2.3")()
|
||||
require.NoError(t, err)
|
||||
|
||||
req, err := http.NewRequest("GET", ts.URL, nil)
|
||||
require.NoError(t, err)
|
||||
res, err := client.Do(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 204, res.StatusCode)
|
||||
assert.Equal(t, []string{"GitHub CLI v1.2.3"}, receivedHeaders.Values("User-Agent"))
|
||||
assert.Nil(t, receivedHeaders.Values("Authorization"))
|
||||
assert.Nil(t, receivedHeaders.Values("Content-Type"))
|
||||
assert.Nil(t, receivedHeaders.Values("Accept"))
|
||||
assert.Nil(t, receivedHeaders.Values("Time-Zone"))
|
||||
}
|
||||
|
||||
func TestNewGitClient(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ func printContent(io *iostreams.IOStreams, gists []shared.Gist, filter *regexp.R
|
|||
}
|
||||
|
||||
if file.Content != "" {
|
||||
for _, line := range strings.FieldsFunc(file.Content, split) {
|
||||
for line := range strings.FieldsFuncSeq(file.Content, split) {
|
||||
if filter.MatchString(line) {
|
||||
if line, err = highlightMatch(line, filter, &matched, normal, cs.Highlight); err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ func NewCmdDevelop(f *cmdutil.Factory, runF func(*DevelopOptions) error) *cobra.
|
|||
# Create a branch for issue 123 based on the my-feature branch
|
||||
$ gh issue develop 123 --base my-feature
|
||||
|
||||
# Create a branch for issue 123 and checkout it out
|
||||
# Create a branch for issue 123 and check it out
|
||||
$ gh issue develop 123 --checkout
|
||||
|
||||
# Create a branch in repo monalisa/cli for issue 123 in repo cli/cli
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ type EditOptions struct {
|
|||
DetermineEditor func() (string, error)
|
||||
FieldsToEditSurvey func(prShared.EditPrompter, *prShared.Editable) error
|
||||
EditFieldsSurvey func(prShared.EditPrompter, *prShared.Editable, string) error
|
||||
FetchOptions func(*api.Client, ghrepo.Interface, *prShared.Editable) error
|
||||
FetchOptions func(*api.Client, ghrepo.Interface, *prShared.Editable, gh.ProjectsV1Support) error
|
||||
|
||||
IssueNumbers []int
|
||||
Interactive bool
|
||||
|
|
@ -248,7 +248,7 @@ func editRun(opts *EditOptions) error {
|
|||
// Fetch editable shared fields once for all issues.
|
||||
apiClient := api.NewClientFromHTTP(httpClient)
|
||||
opts.IO.StartProgressIndicatorWithLabel("Fetching repository information")
|
||||
err = opts.FetchOptions(apiClient, baseRepo, &editable)
|
||||
err = opts.FetchOptions(apiClient, baseRepo, &editable, opts.Detector.ProjectsV1())
|
||||
opts.IO.StopProgressIndicator()
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -347,6 +347,7 @@ func Test_editRun(t *testing.T) {
|
|||
{
|
||||
name: "non-interactive",
|
||||
input: &EditOptions{
|
||||
Detector: &fd.EnabledDetectorMock{},
|
||||
IssueNumbers: []int{123},
|
||||
Interactive: false,
|
||||
Editable: prShared.Editable{
|
||||
|
|
@ -403,6 +404,7 @@ func Test_editRun(t *testing.T) {
|
|||
{
|
||||
name: "non-interactive multiple issues",
|
||||
input: &EditOptions{
|
||||
Detector: &fd.EnabledDetectorMock{},
|
||||
IssueNumbers: []int{456, 123},
|
||||
Interactive: false,
|
||||
Editable: prShared.Editable{
|
||||
|
|
@ -457,6 +459,7 @@ func Test_editRun(t *testing.T) {
|
|||
{
|
||||
name: "non-interactive multiple issues with fetch failures",
|
||||
input: &EditOptions{
|
||||
Detector: &fd.EnabledDetectorMock{},
|
||||
IssueNumbers: []int{123, 9999},
|
||||
Interactive: false,
|
||||
Editable: prShared.Editable{
|
||||
|
|
@ -504,6 +507,7 @@ func Test_editRun(t *testing.T) {
|
|||
{
|
||||
name: "non-interactive multiple issues with update failures",
|
||||
input: &EditOptions{
|
||||
Detector: &fd.EnabledDetectorMock{},
|
||||
IssueNumbers: []int{123, 456},
|
||||
Interactive: false,
|
||||
Editable: prShared.Editable{
|
||||
|
|
@ -584,6 +588,7 @@ func Test_editRun(t *testing.T) {
|
|||
{
|
||||
name: "interactive",
|
||||
input: &EditOptions{
|
||||
Detector: &fd.EnabledDetectorMock{},
|
||||
IssueNumbers: []int{123},
|
||||
Interactive: true,
|
||||
FieldsToEditSurvey: func(p prShared.EditPrompter, eo *prShared.Editable) error {
|
||||
|
|
@ -623,6 +628,7 @@ func Test_editRun(t *testing.T) {
|
|||
{
|
||||
name: "interactive prompts with actor assignee display names when actors available",
|
||||
input: &EditOptions{
|
||||
Detector: &fd.EnabledDetectorMock{},
|
||||
IssueNumbers: []int{123},
|
||||
Interactive: true,
|
||||
FieldsToEditSurvey: func(p prShared.EditPrompter, eo *prShared.Editable) error {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package edit
|
|||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
|
|
@ -13,7 +15,7 @@ import (
|
|||
shared "github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/shurcooL/githubv4"
|
||||
"github.com/cli/cli/v2/pkg/set"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
|
@ -170,7 +172,7 @@ func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Comman
|
|||
}
|
||||
|
||||
if opts.Interactive && !opts.IO.CanPrompt() {
|
||||
return cmdutil.FlagErrorf("--tile, --body, --reviewer, --assignee, --label, --project, or --milestone required when not running interactively")
|
||||
return cmdutil.FlagErrorf("--title, --body, --reviewer, --assignee, --label, --project, or --milestone required when not running interactively")
|
||||
}
|
||||
|
||||
if runF != nil {
|
||||
|
|
@ -237,7 +239,7 @@ func editRun(opts *EditOptions) error {
|
|||
|
||||
findOptions := shared.FindOptions{
|
||||
Selector: opts.SelectorArg,
|
||||
Fields: []string{"id", "url", "title", "body", "baseRefName", "reviewRequests", "labels", "projectCards", "projectItems", "milestone"},
|
||||
Fields: []string{"id", "author", "url", "title", "body", "baseRefName", "reviewRequests", "labels", "projectCards", "projectItems", "milestone"},
|
||||
Detector: opts.Detector,
|
||||
}
|
||||
|
||||
|
|
@ -291,13 +293,22 @@ func editRun(opts *EditOptions) error {
|
|||
apiClient := api.NewClientFromHTTP(httpClient)
|
||||
|
||||
opts.IO.StartProgressIndicator()
|
||||
err = opts.Fetcher.EditableOptionsFetch(apiClient, repo, &editable)
|
||||
err = opts.Fetcher.EditableOptionsFetch(apiClient, repo, &editable, opts.Detector.ProjectsV1())
|
||||
opts.IO.StopProgressIndicator()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if opts.Interactive {
|
||||
// Remove PR author from reviewer options;
|
||||
// REST API errors if author is included (GraphQL silently ignores).
|
||||
if editable.Reviewers.Edited {
|
||||
s := set.NewStringSet()
|
||||
s.AddValues(editable.Reviewers.Options)
|
||||
s.Remove(pr.Author.Login)
|
||||
editable.Reviewers.Options = s.ToSlice()
|
||||
}
|
||||
|
||||
editorCommand, err := opts.EditorRetriever.Retrieve()
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -309,7 +320,7 @@ func editRun(opts *EditOptions) error {
|
|||
}
|
||||
|
||||
opts.IO.StartProgressIndicator()
|
||||
err = updatePullRequest(httpClient, repo, pr.ID, editable)
|
||||
err = updatePullRequest(httpClient, repo, pr.ID, pr.Number, editable)
|
||||
opts.IO.StopProgressIndicator()
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -320,36 +331,53 @@ func editRun(opts *EditOptions) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func updatePullRequest(httpClient *http.Client, repo ghrepo.Interface, id string, editable shared.Editable) error {
|
||||
func updatePullRequest(httpClient *http.Client, repo ghrepo.Interface, id string, number int, editable shared.Editable) error {
|
||||
var wg errgroup.Group
|
||||
wg.Go(func() error {
|
||||
return shared.UpdateIssue(httpClient, repo, id, true, editable)
|
||||
})
|
||||
if editable.Reviewers.Edited {
|
||||
wg.Go(func() error {
|
||||
return updatePullRequestReviews(httpClient, repo, id, editable)
|
||||
return updatePullRequestReviews(httpClient, repo, number, editable)
|
||||
})
|
||||
}
|
||||
return wg.Wait()
|
||||
}
|
||||
|
||||
func updatePullRequestReviews(httpClient *http.Client, repo ghrepo.Interface, id string, editable shared.Editable) error {
|
||||
userIds, teamIds, err := editable.ReviewerIds()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if userIds == nil && teamIds == nil {
|
||||
func updatePullRequestReviews(httpClient *http.Client, repo ghrepo.Interface, number int, editable shared.Editable) error {
|
||||
if !editable.Reviewers.Edited {
|
||||
return nil
|
||||
}
|
||||
union := githubv4.Boolean(false)
|
||||
reviewsRequestParams := githubv4.RequestReviewsInput{
|
||||
PullRequestID: id,
|
||||
Union: &union,
|
||||
UserIDs: ghIds(userIds),
|
||||
TeamIDs: ghIds(teamIds),
|
||||
|
||||
// Rebuild the Value slice from non-interactive flag input.
|
||||
if len(editable.Reviewers.Add) != 0 || len(editable.Reviewers.Remove) != 0 {
|
||||
s := set.NewStringSet()
|
||||
s.AddValues(editable.Reviewers.Add)
|
||||
s.AddValues(editable.Reviewers.Default)
|
||||
s.RemoveValues(editable.Reviewers.Remove)
|
||||
editable.Reviewers.Value = s.ToSlice()
|
||||
}
|
||||
|
||||
addUsers, addTeams := partitionUsersAndTeams(editable.Reviewers.Value)
|
||||
|
||||
// Reviewers in Default but not in the Value have been removed interactively.
|
||||
var toRemove []string
|
||||
for _, r := range editable.Reviewers.Default {
|
||||
if !slices.Contains(editable.Reviewers.Value, r) {
|
||||
toRemove = append(toRemove, r)
|
||||
}
|
||||
}
|
||||
removeUsers, removeTeams := partitionUsersAndTeams(toRemove)
|
||||
|
||||
client := api.NewClientFromHTTP(httpClient)
|
||||
return api.UpdatePullRequestReviews(client, repo, reviewsRequestParams)
|
||||
wg := errgroup.Group{}
|
||||
wg.Go(func() error {
|
||||
return api.AddPullRequestReviews(client, repo, number, addUsers, addTeams)
|
||||
})
|
||||
wg.Go(func() error {
|
||||
return api.RemovePullRequestReviews(client, repo, number, removeUsers, removeTeams)
|
||||
})
|
||||
return wg.Wait()
|
||||
}
|
||||
|
||||
type Surveyor interface {
|
||||
|
|
@ -370,13 +398,13 @@ func (s surveyor) EditFields(editable *shared.Editable, editorCmd string) error
|
|||
}
|
||||
|
||||
type EditableOptionsFetcher interface {
|
||||
EditableOptionsFetch(*api.Client, ghrepo.Interface, *shared.Editable) error
|
||||
EditableOptionsFetch(*api.Client, ghrepo.Interface, *shared.Editable, gh.ProjectsV1Support) error
|
||||
}
|
||||
|
||||
type fetcher struct{}
|
||||
|
||||
func (f fetcher) EditableOptionsFetch(client *api.Client, repo ghrepo.Interface, opts *shared.Editable) error {
|
||||
return shared.FetchOptions(client, repo, opts)
|
||||
func (f fetcher) EditableOptionsFetch(client *api.Client, repo ghrepo.Interface, opts *shared.Editable, projectsV1Support gh.ProjectsV1Support) error {
|
||||
return shared.FetchOptions(client, repo, opts, projectsV1Support)
|
||||
}
|
||||
|
||||
type EditorRetriever interface {
|
||||
|
|
@ -391,13 +419,18 @@ func (e editorRetriever) Retrieve() (string, error) {
|
|||
return cmdutil.DetermineEditor(e.config)
|
||||
}
|
||||
|
||||
func ghIds(s *[]string) *[]githubv4.ID {
|
||||
if s == nil {
|
||||
return nil
|
||||
// partitionUsersAndTeams splits reviewer identifiers into user logins and team slugs.
|
||||
// Team identifiers are in the form "org/slug"; only the slug portion is returned for teams.
|
||||
func partitionUsersAndTeams(values []string) (users []string, teams []string) {
|
||||
for _, v := range values {
|
||||
if strings.ContainsRune(v, '/') {
|
||||
parts := strings.SplitN(v, "/", 2)
|
||||
if len(parts) == 2 && parts[1] != "" {
|
||||
teams = append(teams, parts[1])
|
||||
}
|
||||
} else if v != "" {
|
||||
users = append(users, v)
|
||||
}
|
||||
}
|
||||
ids := make([]githubv4.ID, len(*s))
|
||||
for i, v := range *s {
|
||||
ids[i] = v
|
||||
}
|
||||
return &ids
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"github.com/cli/cli/v2/api"
|
||||
fd "github.com/cli/cli/v2/internal/featuredetection"
|
||||
"github.com/cli/cli/v2/internal/gh"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
shared "github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
|
|
@ -354,7 +355,7 @@ func Test_editRun(t *testing.T) {
|
|||
tests := []struct {
|
||||
name string
|
||||
input *EditOptions
|
||||
httpStubs func(*httpmock.Registry)
|
||||
httpStubs func(*testing.T, *httpmock.Registry)
|
||||
stdout string
|
||||
stderr string
|
||||
}{
|
||||
|
|
@ -411,11 +412,11 @@ func Test_editRun(t *testing.T) {
|
|||
},
|
||||
Fetcher: testFetcher{},
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
mockRepoMetadata(reg, false)
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
mockRepoMetadata(reg, mockRepoMetadataOptions{reviewers: true, teamReviewers: false, assignees: true, labels: true, projects: true, milestones: true})
|
||||
mockPullRequestUpdate(reg)
|
||||
mockPullRequestUpdateActorAssignees(reg)
|
||||
mockPullRequestReviewersUpdate(reg)
|
||||
mockPullRequestAddReviewers(reg)
|
||||
mockPullRequestUpdateLabels(reg)
|
||||
mockProjectV2ItemUpdate(reg)
|
||||
},
|
||||
|
|
@ -469,8 +470,8 @@ func Test_editRun(t *testing.T) {
|
|||
},
|
||||
Fetcher: testFetcher{},
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
mockRepoMetadata(reg, true)
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
mockRepoMetadata(reg, mockRepoMetadataOptions{assignees: true, labels: true, projects: true, milestones: true})
|
||||
mockPullRequestUpdate(reg)
|
||||
mockPullRequestUpdateActorAssignees(reg)
|
||||
mockPullRequestUpdateLabels(reg)
|
||||
|
|
@ -483,8 +484,19 @@ func Test_editRun(t *testing.T) {
|
|||
input: &EditOptions{
|
||||
Detector: &fd.EnabledDetectorMock{},
|
||||
SelectorArg: "123",
|
||||
Finder: shared.NewMockFinder("123", &api.PullRequest{
|
||||
Finder: shared.NewMockFinder("123", &api.PullRequest{ // include existing reviewers so removal logic triggers
|
||||
URL: "https://github.com/OWNER/REPO/pull/123",
|
||||
ReviewRequests: api.ReviewRequests{Nodes: []struct{ RequestedReviewer api.RequestedReviewer }{
|
||||
{RequestedReviewer: api.RequestedReviewer{TypeName: "Team", Slug: "core", Organization: struct {
|
||||
Login string `json:"login"`
|
||||
}{Login: "OWNER"}}},
|
||||
{RequestedReviewer: api.RequestedReviewer{TypeName: "Team", Slug: "external", Organization: struct {
|
||||
Login string `json:"login"`
|
||||
}{Login: "OWNER"}}},
|
||||
{RequestedReviewer: api.RequestedReviewer{TypeName: "User", Login: "monalisa"}},
|
||||
{RequestedReviewer: api.RequestedReviewer{TypeName: "User", Login: "hubot"}},
|
||||
{RequestedReviewer: api.RequestedReviewer{TypeName: "User", Login: "dependabot"}},
|
||||
}},
|
||||
}, ghrepo.New("OWNER", "REPO")),
|
||||
Interactive: false,
|
||||
Editable: shared.Editable{
|
||||
|
|
@ -501,8 +513,9 @@ func Test_editRun(t *testing.T) {
|
|||
Edited: true,
|
||||
},
|
||||
Reviewers: shared.EditableSlice{
|
||||
Remove: []string{"OWNER/core", "OWNER/external", "monalisa", "hubot", "dependabot"},
|
||||
Edited: true,
|
||||
Default: []string{"OWNER/core", "OWNER/external", "monalisa", "hubot", "dependabot"},
|
||||
Remove: []string{"OWNER/core", "OWNER/external", "monalisa", "hubot", "dependabot"},
|
||||
Edited: true,
|
||||
},
|
||||
Assignees: shared.EditableAssignees{
|
||||
EditableSlice: shared.EditableSlice{
|
||||
|
|
@ -530,16 +543,109 @@ func Test_editRun(t *testing.T) {
|
|||
},
|
||||
Fetcher: testFetcher{},
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
mockRepoMetadata(reg, false)
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
mockRepoMetadata(reg, mockRepoMetadataOptions{reviewers: true, teamReviewers: false, assignees: true, labels: true, projects: true, milestones: true})
|
||||
mockPullRequestUpdate(reg)
|
||||
mockPullRequestReviewersUpdate(reg)
|
||||
mockPullRequestRemoveReviewers(reg)
|
||||
mockPullRequestUpdateLabels(reg)
|
||||
mockPullRequestUpdateActorAssignees(reg)
|
||||
mockProjectV2ItemUpdate(reg)
|
||||
},
|
||||
stdout: "https://github.com/OWNER/REPO/pull/123\n",
|
||||
},
|
||||
// Conditional team fetching cases
|
||||
{
|
||||
name: "non-interactive add only user reviewers skips team fetch",
|
||||
input: &EditOptions{
|
||||
Detector: &fd.EnabledDetectorMock{},
|
||||
SelectorArg: "123",
|
||||
Finder: shared.NewMockFinder("123", &api.PullRequest{URL: "https://github.com/OWNER/REPO/pull/123"}, ghrepo.New("OWNER", "REPO")),
|
||||
Interactive: false,
|
||||
Editable: shared.Editable{
|
||||
Reviewers: shared.EditableSlice{Add: []string{"monalisa", "hubot"}, Edited: true},
|
||||
},
|
||||
Fetcher: testFetcher{},
|
||||
},
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
// reviewers only (users), no team reviewers fetched
|
||||
mockRepoMetadata(reg, mockRepoMetadataOptions{reviewers: true})
|
||||
// explicitly assert that no OrganizationTeamList query occurs
|
||||
reg.Exclude(t, httpmock.GraphQL(`query OrganizationTeamList\b`))
|
||||
mockPullRequestUpdate(reg)
|
||||
mockPullRequestAddReviewers(reg)
|
||||
},
|
||||
stdout: "https://github.com/OWNER/REPO/pull/123\n",
|
||||
},
|
||||
{
|
||||
name: "non-interactive add contains team reviewers skips team fetch",
|
||||
input: &EditOptions{
|
||||
Detector: &fd.EnabledDetectorMock{},
|
||||
SelectorArg: "123",
|
||||
Finder: shared.NewMockFinder("123", &api.PullRequest{URL: "https://github.com/OWNER/REPO/pull/123"}, ghrepo.New("OWNER", "REPO")),
|
||||
Interactive: false,
|
||||
Editable: shared.Editable{
|
||||
Reviewers: shared.EditableSlice{Add: []string{"monalisa", "OWNER/core"}, Edited: true},
|
||||
},
|
||||
Fetcher: testFetcher{},
|
||||
},
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
// reviewer add includes team but non-interactive Add/Remove provided -> no team fetch
|
||||
mockRepoMetadata(reg, mockRepoMetadataOptions{reviewers: true})
|
||||
// explicitly assert that no OrganizationTeamList query occurs
|
||||
reg.Exclude(t, httpmock.GraphQL(`query OrganizationTeamList\b`))
|
||||
mockPullRequestUpdate(reg)
|
||||
mockPullRequestAddReviewers(reg)
|
||||
},
|
||||
stdout: "https://github.com/OWNER/REPO/pull/123\n",
|
||||
},
|
||||
{
|
||||
name: "non-interactive reviewers remove contains team skips team fetch",
|
||||
input: &EditOptions{
|
||||
Detector: &fd.EnabledDetectorMock{},
|
||||
SelectorArg: "123",
|
||||
Finder: shared.NewMockFinder("123", &api.PullRequest{URL: "https://github.com/OWNER/REPO/pull/123", ReviewRequests: api.ReviewRequests{Nodes: []struct{ RequestedReviewer api.RequestedReviewer }{
|
||||
{RequestedReviewer: api.RequestedReviewer{TypeName: "Team", Slug: "core", Organization: struct {
|
||||
Login string `json:"login"`
|
||||
}{Login: "OWNER"}}},
|
||||
{RequestedReviewer: api.RequestedReviewer{TypeName: "User", Login: "monalisa"}},
|
||||
}}}, ghrepo.New("OWNER", "REPO")),
|
||||
Interactive: false,
|
||||
Editable: shared.Editable{
|
||||
Reviewers: shared.EditableSlice{Remove: []string{"monalisa", "OWNER/core"}, Edited: true},
|
||||
},
|
||||
Fetcher: testFetcher{},
|
||||
},
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
mockRepoMetadata(reg, mockRepoMetadataOptions{reviewers: true})
|
||||
// explicitly assert that no OrganizationTeamList query occurs
|
||||
reg.Exclude(t, httpmock.GraphQL(`query OrganizationTeamList\b`))
|
||||
mockPullRequestUpdate(reg)
|
||||
mockPullRequestRemoveReviewers(reg)
|
||||
},
|
||||
stdout: "https://github.com/OWNER/REPO/pull/123\n",
|
||||
},
|
||||
{
|
||||
name: "non-interactive mutate reviewers with no change to existing team reviewers skips team fetch",
|
||||
input: &EditOptions{
|
||||
Detector: &fd.EnabledDetectorMock{},
|
||||
SelectorArg: "123",
|
||||
Finder: shared.NewMockFinder("123", &api.PullRequest{URL: "https://github.com/OWNER/REPO/pull/123"}, ghrepo.New("OWNER", "REPO")),
|
||||
Interactive: false,
|
||||
Editable: shared.Editable{
|
||||
Reviewers: shared.EditableSlice{Add: []string{"monalisa"}, Remove: []string{"hubot"}, Default: []string{"OWNER/core"}, Edited: true},
|
||||
},
|
||||
Fetcher: testFetcher{},
|
||||
},
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
// reviewers only (users), no team reviewers fetched
|
||||
mockRepoMetadata(reg, mockRepoMetadataOptions{reviewers: true})
|
||||
// explicitly assert that no OrganizationTeamList query occurs
|
||||
reg.Exclude(t, httpmock.GraphQL(`query OrganizationTeamList\b`))
|
||||
mockPullRequestUpdate(reg)
|
||||
mockPullRequestAddReviewers(reg)
|
||||
},
|
||||
stdout: "https://github.com/OWNER/REPO/pull/123\n",
|
||||
},
|
||||
{
|
||||
name: "interactive",
|
||||
input: &EditOptions{
|
||||
|
|
@ -576,11 +682,11 @@ func Test_editRun(t *testing.T) {
|
|||
Fetcher: testFetcher{},
|
||||
EditorRetriever: testEditorRetriever{},
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
mockRepoMetadata(reg, false)
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
mockRepoMetadata(reg, mockRepoMetadataOptions{reviewers: true, teamReviewers: true, assignees: true, labels: true, projects: true, milestones: true})
|
||||
mockPullRequestUpdate(reg)
|
||||
mockPullRequestUpdateActorAssignees(reg)
|
||||
mockPullRequestReviewersUpdate(reg)
|
||||
mockPullRequestAddReviewers(reg)
|
||||
mockPullRequestUpdateLabels(reg)
|
||||
mockProjectV2ItemUpdate(reg)
|
||||
},
|
||||
|
|
@ -620,8 +726,9 @@ func Test_editRun(t *testing.T) {
|
|||
Fetcher: testFetcher{},
|
||||
EditorRetriever: testEditorRetriever{},
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
mockRepoMetadata(reg, true)
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
// interactive but reviewers not chosen; need everything except reviewers/teams
|
||||
mockRepoMetadata(reg, mockRepoMetadataOptions{assignees: true, labels: true, projects: true, milestones: true})
|
||||
mockPullRequestUpdate(reg)
|
||||
mockPullRequestUpdateActorAssignees(reg)
|
||||
mockPullRequestUpdateLabels(reg)
|
||||
|
|
@ -634,8 +741,19 @@ func Test_editRun(t *testing.T) {
|
|||
input: &EditOptions{
|
||||
Detector: &fd.EnabledDetectorMock{},
|
||||
SelectorArg: "123",
|
||||
Finder: shared.NewMockFinder("123", &api.PullRequest{
|
||||
Finder: shared.NewMockFinder("123", &api.PullRequest{ // include existing reviewers
|
||||
URL: "https://github.com/OWNER/REPO/pull/123",
|
||||
ReviewRequests: api.ReviewRequests{Nodes: []struct{ RequestedReviewer api.RequestedReviewer }{
|
||||
{RequestedReviewer: api.RequestedReviewer{TypeName: "Team", Slug: "core", Organization: struct {
|
||||
Login string `json:"login"`
|
||||
}{Login: "OWNER"}}},
|
||||
{RequestedReviewer: api.RequestedReviewer{TypeName: "Team", Slug: "external", Organization: struct {
|
||||
Login string `json:"login"`
|
||||
}{Login: "OWNER"}}},
|
||||
{RequestedReviewer: api.RequestedReviewer{TypeName: "User", Login: "monalisa"}},
|
||||
{RequestedReviewer: api.RequestedReviewer{TypeName: "User", Login: "hubot"}},
|
||||
{RequestedReviewer: api.RequestedReviewer{TypeName: "User", Login: "dependabot"}},
|
||||
}},
|
||||
}, ghrepo.New("OWNER", "REPO")),
|
||||
Interactive: true,
|
||||
Surveyor: testSurveyor{
|
||||
|
|
@ -665,10 +783,10 @@ func Test_editRun(t *testing.T) {
|
|||
Fetcher: testFetcher{},
|
||||
EditorRetriever: testEditorRetriever{},
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
mockRepoMetadata(reg, false)
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
mockRepoMetadata(reg, mockRepoMetadataOptions{reviewers: true, teamReviewers: true, assignees: true, labels: true, projects: true, milestones: true})
|
||||
mockPullRequestUpdate(reg)
|
||||
mockPullRequestReviewersUpdate(reg)
|
||||
mockPullRequestRemoveReviewers(reg)
|
||||
mockPullRequestUpdateActorAssignees(reg)
|
||||
mockPullRequestUpdateLabels(reg)
|
||||
mockProjectV2ItemUpdate(reg)
|
||||
|
|
@ -712,7 +830,7 @@ func Test_editRun(t *testing.T) {
|
|||
Fetcher: testFetcher{},
|
||||
EditorRetriever: testEditorRetriever{},
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query RepositoryAssignableActors\b`),
|
||||
httpmock.StringResponse(`
|
||||
|
|
@ -759,7 +877,7 @@ func Test_editRun(t *testing.T) {
|
|||
},
|
||||
Fetcher: testFetcher{},
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
// Notice there is no call to mockReplaceActorsForAssignable()
|
||||
// and no GraphQL call to RepositoryAssignableActors below.
|
||||
reg.Register(
|
||||
|
|
@ -777,6 +895,67 @@ func Test_editRun(t *testing.T) {
|
|||
},
|
||||
stdout: "https://github.com/OWNER/REPO/pull/123\n",
|
||||
},
|
||||
{
|
||||
name: "non-interactive projects v1 unsupported doesn't fetch v1 metadata",
|
||||
input: &EditOptions{
|
||||
Detector: &fd.DisabledDetectorMock{},
|
||||
SelectorArg: "123",
|
||||
Finder: shared.NewMockFinder("123", &api.PullRequest{
|
||||
URL: "https://github.com/OWNER/REPO/pull/123",
|
||||
}, ghrepo.New("OWNER", "REPO")),
|
||||
Interactive: false,
|
||||
Editable: shared.Editable{
|
||||
Projects: shared.EditableProjects{
|
||||
EditableSlice: shared.EditableSlice{
|
||||
Add: []string{"CleanupV2"},
|
||||
Remove: []string{"RoadmapV2"},
|
||||
Edited: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Fetcher: testFetcher{},
|
||||
},
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
// Ensure v1 project queries are NOT made.
|
||||
reg.Exclude(t, httpmock.GraphQL(`query RepositoryProjectList\b`))
|
||||
reg.Exclude(t, httpmock.GraphQL(`query OrganizationProjectList\b`))
|
||||
// Provide only v2 project metadata queries.
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query RepositoryProjectV2List\b`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "repository": { "projectsV2": {
|
||||
"nodes": [
|
||||
{ "title": "CleanupV2", "id": "CLEANUPV2ID" },
|
||||
{ "title": "RoadmapV2", "id": "ROADMAPV2ID" }
|
||||
],
|
||||
"pageInfo": { "hasNextPage": false }
|
||||
} } } }
|
||||
`))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query OrganizationProjectV2List\b`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "organization": { "projectsV2": {
|
||||
"nodes": [
|
||||
{ "title": "TriageV2", "id": "TRIAGEV2ID" }
|
||||
],
|
||||
"pageInfo": { "hasNextPage": false }
|
||||
} } } }
|
||||
`))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserProjectV2List\b`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "viewer": { "projectsV2": {
|
||||
"nodes": [
|
||||
{ "title": "MonalisaV2", "id": "MONALISAV2ID" }
|
||||
],
|
||||
"pageInfo": { "hasNextPage": false }
|
||||
} } } }
|
||||
`))
|
||||
mockProjectV2ItemUpdate(reg)
|
||||
mockPullRequestUpdate(reg)
|
||||
},
|
||||
stdout: "https://github.com/OWNER/REPO/pull/123\n",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
@ -787,7 +966,7 @@ func Test_editRun(t *testing.T) {
|
|||
|
||||
reg := &httpmock.Registry{}
|
||||
defer reg.Verify(t)
|
||||
tt.httpStubs(reg)
|
||||
tt.httpStubs(t, reg)
|
||||
|
||||
httpClient := func() (*http.Client, error) { return &http.Client{Transport: reg}, nil }
|
||||
baseRepo := func() (ghrepo.Interface, error) { return ghrepo.New("OWNER", "REPO"), nil }
|
||||
|
|
@ -804,10 +983,21 @@ func Test_editRun(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func mockRepoMetadata(reg *httpmock.Registry, skipReviewers bool) {
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query RepositoryAssignableActors\b`),
|
||||
httpmock.StringResponse(`
|
||||
type mockRepoMetadataOptions struct {
|
||||
reviewers bool
|
||||
teamReviewers bool // reviewers must also be true for this to have an effect.
|
||||
assignees bool
|
||||
labels bool
|
||||
projects bool // includes both legacy (v1) and v2
|
||||
milestones bool
|
||||
}
|
||||
|
||||
func mockRepoMetadata(reg *httpmock.Registry, opt mockRepoMetadataOptions) {
|
||||
// Assignable actors (users/bots) are fetched when reviewers OR assignees edited with ActorAssignees enabled.
|
||||
if opt.reviewers || opt.assignees {
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query RepositoryAssignableActors\b`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "repository": { "suggestedActors": {
|
||||
"nodes": [
|
||||
{ "login": "hubot", "id": "HUBOTID", "__typename": "Bot" },
|
||||
|
|
@ -816,9 +1006,11 @@ func mockRepoMetadata(reg *httpmock.Registry, skipReviewers bool) {
|
|||
"pageInfo": { "hasNextPage": false }
|
||||
} } } }
|
||||
`))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query RepositoryLabelList\b`),
|
||||
httpmock.StringResponse(`
|
||||
}
|
||||
if opt.labels {
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query RepositoryLabelList\b`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "repository": { "labels": {
|
||||
"nodes": [
|
||||
{ "name": "feature", "id": "FEATUREID" },
|
||||
|
|
@ -829,9 +1021,11 @@ func mockRepoMetadata(reg *httpmock.Registry, skipReviewers bool) {
|
|||
"pageInfo": { "hasNextPage": false }
|
||||
} } } }
|
||||
`))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query RepositoryMilestoneList\b`),
|
||||
httpmock.StringResponse(`
|
||||
}
|
||||
if opt.milestones {
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query RepositoryMilestoneList\b`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "repository": { "milestones": {
|
||||
"nodes": [
|
||||
{ "title": "GA", "id": "GAID" },
|
||||
|
|
@ -840,9 +1034,11 @@ func mockRepoMetadata(reg *httpmock.Registry, skipReviewers bool) {
|
|||
"pageInfo": { "hasNextPage": false }
|
||||
} } } }
|
||||
`))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query RepositoryProjectList\b`),
|
||||
httpmock.StringResponse(`
|
||||
}
|
||||
if opt.projects {
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query RepositoryProjectList\b`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "repository": { "projects": {
|
||||
"nodes": [
|
||||
{ "name": "Cleanup", "id": "CLEANUPID" },
|
||||
|
|
@ -851,9 +1047,9 @@ func mockRepoMetadata(reg *httpmock.Registry, skipReviewers bool) {
|
|||
"pageInfo": { "hasNextPage": false }
|
||||
} } } }
|
||||
`))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query OrganizationProjectList\b`),
|
||||
httpmock.StringResponse(`
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query OrganizationProjectList\b`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "organization": { "projects": {
|
||||
"nodes": [
|
||||
{ "name": "Triage", "id": "TRIAGEID" }
|
||||
|
|
@ -861,9 +1057,9 @@ func mockRepoMetadata(reg *httpmock.Registry, skipReviewers bool) {
|
|||
"pageInfo": { "hasNextPage": false }
|
||||
} } } }
|
||||
`))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query RepositoryProjectV2List\b`),
|
||||
httpmock.StringResponse(`
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query RepositoryProjectV2List\b`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "repository": { "projectsV2": {
|
||||
"nodes": [
|
||||
{ "title": "CleanupV2", "id": "CLEANUPV2ID" },
|
||||
|
|
@ -872,9 +1068,9 @@ func mockRepoMetadata(reg *httpmock.Registry, skipReviewers bool) {
|
|||
"pageInfo": { "hasNextPage": false }
|
||||
} } } }
|
||||
`))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query OrganizationProjectV2List\b`),
|
||||
httpmock.StringResponse(`
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query OrganizationProjectV2List\b`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "organization": { "projectsV2": {
|
||||
"nodes": [
|
||||
{ "title": "TriageV2", "id": "TRIAGEV2ID" }
|
||||
|
|
@ -882,9 +1078,9 @@ func mockRepoMetadata(reg *httpmock.Registry, skipReviewers bool) {
|
|||
"pageInfo": { "hasNextPage": false }
|
||||
} } } }
|
||||
`))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserProjectV2List\b`),
|
||||
httpmock.StringResponse(`
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserProjectV2List\b`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "viewer": { "projectsV2": {
|
||||
"nodes": [
|
||||
{ "title": "MonalisaV2", "id": "MONALISAV2ID" }
|
||||
|
|
@ -892,7 +1088,8 @@ func mockRepoMetadata(reg *httpmock.Registry, skipReviewers bool) {
|
|||
"pageInfo": { "hasNextPage": false }
|
||||
} } } }
|
||||
`))
|
||||
if !skipReviewers {
|
||||
}
|
||||
if opt.teamReviewers && opt.reviewers { // teams only relevant if reviewers edited
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query OrganizationTeamList\b`),
|
||||
httpmock.StringResponse(`
|
||||
|
|
@ -904,11 +1101,13 @@ func mockRepoMetadata(reg *httpmock.Registry, skipReviewers bool) {
|
|||
"pageInfo": { "hasNextPage": false }
|
||||
} } } }
|
||||
`))
|
||||
}
|
||||
if opt.reviewers { // Current user fetched only when reviewers requested
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "viewer": { "login": "monalisa" } } }
|
||||
`))
|
||||
{ "data": { "viewer": { "login": "monalisa" } } }
|
||||
`))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -927,9 +1126,15 @@ func mockPullRequestUpdateActorAssignees(reg *httpmock.Registry) {
|
|||
)
|
||||
}
|
||||
|
||||
func mockPullRequestReviewersUpdate(reg *httpmock.Registry) {
|
||||
func mockPullRequestAddReviewers(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`mutation PullRequestUpdateRequestReviews\b`),
|
||||
httpmock.REST("POST", "repos/OWNER/REPO/pulls/0/requested_reviewers"),
|
||||
httpmock.StringResponse(`{}`))
|
||||
}
|
||||
|
||||
func mockPullRequestRemoveReviewers(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("DELETE", "repos/OWNER/REPO/pulls/0/requested_reviewers"),
|
||||
httpmock.StringResponse(`{}`))
|
||||
}
|
||||
|
||||
|
|
@ -959,8 +1164,8 @@ func mockProjectV2ItemUpdate(reg *httpmock.Registry) {
|
|||
|
||||
type testFetcher struct{}
|
||||
|
||||
func (f testFetcher) EditableOptionsFetch(client *api.Client, repo ghrepo.Interface, opts *shared.Editable) error {
|
||||
return shared.FetchOptions(client, repo, opts)
|
||||
func (f testFetcher) EditableOptionsFetch(client *api.Client, repo ghrepo.Interface, opts *shared.Editable, projectsV1Support gh.ProjectsV1Support) error {
|
||||
return shared.FetchOptions(client, repo, opts, projectsV1Support)
|
||||
}
|
||||
|
||||
type testSurveyor struct {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import (
|
|||
cmdMerge "github.com/cli/cli/v2/pkg/cmd/pr/merge"
|
||||
cmdReady "github.com/cli/cli/v2/pkg/cmd/pr/ready"
|
||||
cmdReopen "github.com/cli/cli/v2/pkg/cmd/pr/reopen"
|
||||
cmdRevert "github.com/cli/cli/v2/pkg/cmd/pr/revert"
|
||||
cmdReview "github.com/cli/cli/v2/pkg/cmd/pr/review"
|
||||
cmdStatus "github.com/cli/cli/v2/pkg/cmd/pr/status"
|
||||
cmdUpdateBranch "github.com/cli/cli/v2/pkg/cmd/pr/update-branch"
|
||||
|
|
@ -63,6 +64,7 @@ func NewCmdPR(f *cmdutil.Factory) *cobra.Command {
|
|||
cmdComment.NewCmdComment(f, nil),
|
||||
cmdClose.NewCmdClose(f, nil),
|
||||
cmdReopen.NewCmdReopen(f, nil),
|
||||
cmdRevert.NewCmdRevert(f, nil),
|
||||
cmdEdit.NewCmdEdit(f, nil),
|
||||
cmdLock.NewCmdLock(f, cmd.Name(), nil),
|
||||
cmdLock.NewCmdUnlock(f, cmd.Name(), nil),
|
||||
|
|
|
|||
132
pkg/cmd/pr/revert/revert.go
Normal file
132
pkg/cmd/pr/revert/revert.go
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
package revert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/shurcooL/githubv4"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type RevertOptions struct {
|
||||
HttpClient func() (*http.Client, error)
|
||||
IO *iostreams.IOStreams
|
||||
|
||||
Finder shared.PRFinder
|
||||
|
||||
SelectorArg string
|
||||
|
||||
Body string
|
||||
BodySet bool
|
||||
Title string
|
||||
IsDraft bool
|
||||
}
|
||||
|
||||
func NewCmdRevert(f *cmdutil.Factory, runF func(*RevertOptions) error) *cobra.Command {
|
||||
opts := &RevertOptions{
|
||||
IO: f.IOStreams,
|
||||
HttpClient: f.HttpClient,
|
||||
}
|
||||
|
||||
var bodyFile string
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "revert {<number> | <url> | <branch>}",
|
||||
Short: "Revert a pull request",
|
||||
Args: cmdutil.ExactArgs(1, "cannot revert pull request: number, url, or branch required"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.Finder = shared.NewFinder(f)
|
||||
|
||||
if len(args) > 0 {
|
||||
opts.SelectorArg = args[0]
|
||||
}
|
||||
|
||||
bodyProvided := cmd.Flags().Changed("body")
|
||||
bodyFileProvided := bodyFile != ""
|
||||
|
||||
if err := cmdutil.MutuallyExclusive(
|
||||
"specify only one of `--body` or `--body-file`",
|
||||
bodyProvided,
|
||||
bodyFileProvided,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if bodyProvided || bodyFileProvided {
|
||||
opts.BodySet = true
|
||||
if bodyFileProvided {
|
||||
b, err := cmdutil.ReadFile(bodyFile, opts.IO.In)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts.Body = string(b)
|
||||
}
|
||||
}
|
||||
|
||||
if runF != nil {
|
||||
return runF(opts)
|
||||
}
|
||||
return revertRun(opts)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVarP(&opts.IsDraft, "draft", "d", false, "Mark revert pull request as a draft")
|
||||
cmd.Flags().StringVarP(&opts.Title, "title", "t", "", "Title for the revert pull request")
|
||||
cmd.Flags().StringVarP(&opts.Body, "body", "b", "", "Body for the revert pull request")
|
||||
cmd.Flags().StringVarP(&bodyFile, "body-file", "F", "", "Read body text from `file` (use \"-\" to read from standard input)")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func revertRun(opts *RevertOptions) error {
|
||||
cs := opts.IO.ColorScheme()
|
||||
|
||||
findOptions := shared.FindOptions{
|
||||
Selector: opts.SelectorArg,
|
||||
Fields: []string{"id", "number", "state", "title"},
|
||||
}
|
||||
pr, baseRepo, err := opts.Finder.Find(findOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pr.State != "MERGED" {
|
||||
fmt.Fprintf(opts.IO.ErrOut, "%s Pull request %s#%d (%s) can't be reverted because it has not been merged\n", cs.FailureIcon(), ghrepo.FullName(baseRepo), pr.Number, pr.Title)
|
||||
return cmdutil.SilentError
|
||||
}
|
||||
|
||||
httpClient, err := opts.HttpClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
apiClient := api.NewClientFromHTTP(httpClient)
|
||||
|
||||
params := githubv4.RevertPullRequestInput{
|
||||
PullRequestID: pr.ID,
|
||||
Draft: githubv4.NewBoolean(githubv4.Boolean(opts.IsDraft)),
|
||||
}
|
||||
// Only set the Body field when opts.BodySet is true to avoid overriding
|
||||
// GitHub's default revert body generation.
|
||||
if opts.BodySet {
|
||||
params.Body = githubv4.NewString(githubv4.String(opts.Body))
|
||||
}
|
||||
// Only set the Title field when opts.Title is not empty to avoid overriding
|
||||
// GitHub's default revert title generation.
|
||||
if opts.Title != "" {
|
||||
params.Title = githubv4.NewString(githubv4.String(opts.Title))
|
||||
}
|
||||
|
||||
revertPR, err := api.PullRequestRevert(apiClient, baseRepo, params)
|
||||
if err != nil {
|
||||
fmt.Fprintf(opts.IO.ErrOut, "%s %s\n", cs.FailureIcon(), err)
|
||||
return fmt.Errorf("API call failed: %w", err)
|
||||
}
|
||||
|
||||
if revertPR != nil {
|
||||
fmt.Fprintln(opts.IO.Out, revertPR.URL)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
325
pkg/cmd/pr/revert/revert_test.go
Normal file
325
pkg/cmd/pr/revert/revert_test.go
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
package revert
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
||||
"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/test"
|
||||
"github.com/google/shlex"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func runCommand(rt http.RoundTripper, isTTY bool, cli string) (*test.CmdOut, error) {
|
||||
ios, _, stdout, stderr := iostreams.Test()
|
||||
ios.SetStdoutTTY(isTTY)
|
||||
ios.SetStdinTTY(isTTY)
|
||||
ios.SetStderrTTY(isTTY)
|
||||
|
||||
factory := &cmdutil.Factory{
|
||||
IOStreams: ios,
|
||||
HttpClient: func() (*http.Client, error) {
|
||||
return &http.Client{Transport: rt}, nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd := NewCmdRevert(factory, nil)
|
||||
|
||||
argv, err := shlex.Split(cli)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd.SetArgs(argv)
|
||||
|
||||
cmd.SetIn(&bytes.Buffer{})
|
||||
cmd.SetOut(io.Discard)
|
||||
cmd.SetErr(io.Discard)
|
||||
|
||||
_, err = cmd.ExecuteC()
|
||||
return &test.CmdOut{
|
||||
OutBuf: stdout,
|
||||
ErrBuf: stderr,
|
||||
}, err
|
||||
}
|
||||
|
||||
func TestPRRevert_missingArgument(t *testing.T) {
|
||||
http := &httpmock.Registry{}
|
||||
defer http.Verify(t)
|
||||
|
||||
shared.StubFinderForRunCommandStyleTests(t, "123", &api.PullRequest{
|
||||
ID: "SOME-ID",
|
||||
Number: 123,
|
||||
State: "MERGED",
|
||||
Title: "The title of the PR",
|
||||
}, ghrepo.New("OWNER", "REPO"))
|
||||
|
||||
// No arguments provided.
|
||||
_, err := runCommand(http, true, "")
|
||||
// Exits non-zero and prints an argument error.
|
||||
assert.EqualError(t, err, "cannot revert pull request: number, url, or branch required")
|
||||
}
|
||||
|
||||
func TestPRRevert_acceptedIdentifierFormats(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
}{
|
||||
{
|
||||
name: "Revert by pull request number",
|
||||
args: "123",
|
||||
},
|
||||
{
|
||||
name: "Revert by pull request identifier",
|
||||
args: "owner/repo#123",
|
||||
},
|
||||
{
|
||||
name: "Revert by pull request URL",
|
||||
args: "https://github.com/owner/repo/pull/123",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
http := &httpmock.Registry{}
|
||||
defer http.Verify(t)
|
||||
|
||||
shared.StubFinderForRunCommandStyleTests(t, tt.args, &api.PullRequest{
|
||||
ID: "SOME-ID",
|
||||
Number: 123,
|
||||
State: "MERGED",
|
||||
Title: "The title of the PR",
|
||||
}, ghrepo.New("OWNER", "REPO"))
|
||||
|
||||
http.Register(
|
||||
httpmock.GraphQL(`mutation PullRequestRevert\b`),
|
||||
httpmock.GraphQLMutation(`
|
||||
{ "data": { "revertPullRequest": { "pullRequest": {
|
||||
"ID": "SOME-ID"
|
||||
}, "revertPullRequest": {
|
||||
"ID": "NEW-ID",
|
||||
"Number": 456,
|
||||
"URL": "https://github.com/OWNER/REPO/pull/456"
|
||||
} } } }
|
||||
`,
|
||||
func(inputs map[string]interface{}) {
|
||||
assert.Equal(t, inputs["pullRequestId"], "SOME-ID")
|
||||
}),
|
||||
)
|
||||
|
||||
output, err := runCommand(http, true, tt.args)
|
||||
// Revert PR is created and only its URL is printed.
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://github.com/OWNER/REPO/pull/456\n", output.String())
|
||||
assert.Equal(t, "", output.Stderr())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPRRevert_notRevertable(t *testing.T) {
|
||||
http := &httpmock.Registry{}
|
||||
defer http.Verify(t)
|
||||
|
||||
shared.StubFinderForRunCommandStyleTests(t, "123", &api.PullRequest{
|
||||
ID: "SOME-ID",
|
||||
Number: 123,
|
||||
State: "OPEN",
|
||||
Title: "The title of the PR",
|
||||
}, ghrepo.New("OWNER", "REPO"))
|
||||
|
||||
// Target PR is not merged.
|
||||
output, err := runCommand(http, true, "123")
|
||||
// API error, non-zero exit.
|
||||
assert.EqualError(t, err, "SilentError")
|
||||
assert.Equal(t, "X Pull request OWNER/REPO#123 (The title of the PR) can't be reverted because it has not been merged\n", output.Stderr())
|
||||
// No URL printed.
|
||||
assert.Equal(t, "", output.String())
|
||||
}
|
||||
|
||||
func TestPRRevert_withTitleAndBody(t *testing.T) {
|
||||
http := &httpmock.Registry{}
|
||||
defer http.Verify(t)
|
||||
|
||||
shared.StubFinderForRunCommandStyleTests(t, "123", &api.PullRequest{
|
||||
ID: "SOME-ID",
|
||||
Number: 123,
|
||||
State: "MERGED",
|
||||
Title: "The title of the PR",
|
||||
}, ghrepo.New("OWNER", "REPO"))
|
||||
|
||||
http.Register(
|
||||
httpmock.GraphQL(`mutation PullRequestRevert\b`),
|
||||
httpmock.GraphQLMutation(`
|
||||
{ "data": { "revertPullRequest": { "pullRequest": {
|
||||
"ID": "SOME-ID"
|
||||
}, "revertPullRequest": {
|
||||
"ID": "NEW-ID",
|
||||
"Number": 456,
|
||||
"URL": "https://github.com/OWNER/REPO/pull/456"
|
||||
} } } }
|
||||
`,
|
||||
func(inputs map[string]interface{}) {
|
||||
assert.Equal(t, inputs["pullRequestId"], "SOME-ID")
|
||||
assert.Equal(t, inputs["title"], "Revert PR title")
|
||||
assert.Equal(t, inputs["body"], "Revert PR body")
|
||||
}),
|
||||
)
|
||||
|
||||
output, err := runCommand(http, true, "123 --title 'Revert PR title' --body 'Revert PR body'")
|
||||
// Revert PR created.
|
||||
assert.NoError(t, err)
|
||||
// Only URL printed.
|
||||
assert.Equal(t, "https://github.com/OWNER/REPO/pull/456\n", output.String())
|
||||
assert.Equal(t, "", output.Stderr())
|
||||
}
|
||||
|
||||
func TestPRRevert_withDraft(t *testing.T) {
|
||||
http := &httpmock.Registry{}
|
||||
defer http.Verify(t)
|
||||
|
||||
shared.StubFinderForRunCommandStyleTests(t, "123", &api.PullRequest{
|
||||
ID: "SOME-ID",
|
||||
Number: 123,
|
||||
State: "MERGED",
|
||||
Title: "The title of the PR",
|
||||
}, ghrepo.New("OWNER", "REPO"))
|
||||
|
||||
http.Register(
|
||||
httpmock.GraphQL(`mutation PullRequestRevert\b`),
|
||||
httpmock.GraphQLMutation(`
|
||||
{ "data": { "revertPullRequest": { "pullRequest": {
|
||||
"ID": "SOME-ID"
|
||||
}, "revertPullRequest": {
|
||||
"ID": "NEW-ID",
|
||||
"Number": 456,
|
||||
"URL": "https://github.com/OWNER/REPO/pull/456"
|
||||
} } } }
|
||||
`,
|
||||
func(inputs map[string]interface{}) {
|
||||
assert.Equal(t, inputs["pullRequestId"], "SOME-ID")
|
||||
assert.Equal(t, inputs["draft"], true)
|
||||
}),
|
||||
)
|
||||
|
||||
output, err := runCommand(http, true, "123 --draft")
|
||||
// Revert PR created as a draft.
|
||||
assert.NoError(t, err)
|
||||
// Only URL printed.
|
||||
assert.Equal(t, "https://github.com/OWNER/REPO/pull/456\n", output.String())
|
||||
assert.Equal(t, "", output.Stderr())
|
||||
}
|
||||
|
||||
func TestPRRevert_APIFailure(t *testing.T) {
|
||||
http := &httpmock.Registry{}
|
||||
defer http.Verify(t)
|
||||
|
||||
shared.StubFinderForRunCommandStyleTests(t, "123", &api.PullRequest{
|
||||
ID: "SOME-ID",
|
||||
Number: 123,
|
||||
State: "MERGED",
|
||||
Title: "The title of the PR",
|
||||
}, ghrepo.New("OWNER", "REPO"))
|
||||
|
||||
http.Register(
|
||||
httpmock.GraphQL(`mutation PullRequestRevert\b`),
|
||||
httpmock.GraphQLMutation(`
|
||||
{ "errors": [{
|
||||
"message": "Authorization error"
|
||||
}]}`,
|
||||
func(inputs map[string]interface{}) {
|
||||
assert.Equal(t, inputs["pullRequestId"], "SOME-ID")
|
||||
}),
|
||||
)
|
||||
|
||||
output, err := runCommand(http, true, "123")
|
||||
// Non-zero exit, stderr shows the API error, stdout empty.
|
||||
assert.EqualError(t, err, "API call failed: GraphQL: Authorization error")
|
||||
assert.Equal(t, "X GraphQL: Authorization error\n", output.Stderr())
|
||||
assert.Equal(t, "", output.String())
|
||||
}
|
||||
|
||||
func TestPRRevert_multipleInvocations(t *testing.T) {
|
||||
http := &httpmock.Registry{}
|
||||
defer http.Verify(t)
|
||||
|
||||
shared.StubFinderForRunCommandStyleTests(t, "123", &api.PullRequest{
|
||||
ID: "SOME-ID",
|
||||
Number: 123,
|
||||
State: "MERGED",
|
||||
Title: "The title of the PR",
|
||||
}, ghrepo.New("OWNER", "REPO"))
|
||||
|
||||
http.Register(
|
||||
httpmock.GraphQL(`mutation PullRequestRevert\b`),
|
||||
httpmock.GraphQLMutation(`
|
||||
{ "data": { "revertPullRequest": { "pullRequest": {
|
||||
"ID": "SOME-ID"
|
||||
}, "revertPullRequest": {
|
||||
"ID": "NEW-ID",
|
||||
"Number": 456,
|
||||
"URL": "https://github.com/OWNER/REPO/pull/456"
|
||||
} } } }
|
||||
`,
|
||||
func(inputs map[string]interface{}) {
|
||||
assert.Equal(t, inputs["pullRequestId"], "SOME-ID")
|
||||
}),
|
||||
)
|
||||
|
||||
output, err := runCommand(http, true, "123")
|
||||
// Revert PR is created and only its URL is printed.
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://github.com/OWNER/REPO/pull/456\n", output.String())
|
||||
assert.Equal(t, "", output.Stderr())
|
||||
|
||||
// Invoke the same command, behavior depends solely on API response
|
||||
shared.StubFinderForRunCommandStyleTests(t, "123", &api.PullRequest{
|
||||
ID: "SOME-ID",
|
||||
Number: 123,
|
||||
State: "MERGED",
|
||||
Title: "The title of the PR",
|
||||
}, ghrepo.New("OWNER", "REPO"))
|
||||
|
||||
http.Register(
|
||||
httpmock.GraphQL(`mutation PullRequestRevert\b`),
|
||||
httpmock.GraphQLMutation(`
|
||||
{ "data": { "revertPullRequest": { "pullRequest": {
|
||||
"ID": "SOME-ID"
|
||||
}, "revertPullRequest": {
|
||||
"ID": "NEW-ID",
|
||||
"Number": 456,
|
||||
"URL": "https://github.com/OWNER/REPO/pull/456"
|
||||
} } } }
|
||||
`,
|
||||
func(inputs map[string]interface{}) {
|
||||
assert.Equal(t, inputs["pullRequestId"], "SOME-ID")
|
||||
}),
|
||||
)
|
||||
|
||||
output, err = runCommand(http, true, "123")
|
||||
// Revert PR is created and only its URL is printed.
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://github.com/OWNER/REPO/pull/456\n", output.String())
|
||||
assert.Equal(t, "", output.Stderr())
|
||||
|
||||
// Invoke the same command, behavior depends solely on API response.
|
||||
shared.StubFinderForRunCommandStyleTests(t, "123", &api.PullRequest{
|
||||
ID: "SOME-ID",
|
||||
Number: 123,
|
||||
State: "OPEN",
|
||||
Title: "The title of the PR",
|
||||
}, ghrepo.New("OWNER", "REPO"))
|
||||
|
||||
output, err = runCommand(http, true, "123")
|
||||
// Revert PR is not created, API error, non-zero exit.
|
||||
assert.EqualError(t, err, "SilentError")
|
||||
assert.Equal(t, "X Pull request OWNER/REPO#123 (The title of the PR) can't be reverted because it has not been merged\n", output.Stderr())
|
||||
// No URL printed.
|
||||
assert.Equal(t, "", output.String())
|
||||
}
|
||||
|
|
@ -2,9 +2,9 @@ package shared
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/gh"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/set"
|
||||
)
|
||||
|
|
@ -78,37 +78,6 @@ func (e Editable) BodyValue() *string {
|
|||
return &e.Body.Value
|
||||
}
|
||||
|
||||
func (e Editable) ReviewerIds() (*[]string, *[]string, error) {
|
||||
if !e.Reviewers.Edited {
|
||||
return nil, nil, nil
|
||||
}
|
||||
if len(e.Reviewers.Add) != 0 || len(e.Reviewers.Remove) != 0 {
|
||||
s := set.NewStringSet()
|
||||
s.AddValues(e.Reviewers.Default)
|
||||
s.AddValues(e.Reviewers.Add)
|
||||
s.RemoveValues(e.Reviewers.Remove)
|
||||
e.Reviewers.Value = s.ToSlice()
|
||||
}
|
||||
var userReviewers []string
|
||||
var teamReviewers []string
|
||||
for _, r := range e.Reviewers.Value {
|
||||
if strings.ContainsRune(r, '/') {
|
||||
teamReviewers = append(teamReviewers, r)
|
||||
} else {
|
||||
userReviewers = append(userReviewers, r)
|
||||
}
|
||||
}
|
||||
userIds, err := e.Metadata.MembersToIDs(userReviewers)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
teamIds, err := e.Metadata.TeamsToIDs(teamReviewers)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return &userIds, &teamIds, nil
|
||||
}
|
||||
|
||||
func (e Editable) AssigneeIds(client *api.Client, repo ghrepo.Interface) (*[]string, error) {
|
||||
if !e.Assignees.Edited {
|
||||
return nil, nil
|
||||
|
|
@ -427,22 +396,25 @@ func FieldsToEditSurvey(p EditPrompter, editable *Editable) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func FetchOptions(client *api.Client, repo ghrepo.Interface, editable *Editable) error {
|
||||
func FetchOptions(client *api.Client, repo ghrepo.Interface, editable *Editable, projectV1Support gh.ProjectsV1Support) error {
|
||||
// Determine whether to fetch organization teams.
|
||||
// Interactive reviewer editing (Edited true, but no Add/Remove slices) still needs
|
||||
// team data for selection UI. For non-interactive flows, we never need to fetch teams.
|
||||
teamReviewers := false
|
||||
if editable.Reviewers.Edited {
|
||||
// This is likely an interactive flow since edited is set but no mutations to
|
||||
// Add/Remove slices, so we need to load the teams.
|
||||
if len(editable.Reviewers.Add) == 0 && len(editable.Reviewers.Remove) == 0 {
|
||||
teamReviewers = true
|
||||
}
|
||||
}
|
||||
input := api.RepoMetadataInput{
|
||||
Reviewers: editable.Reviewers.Edited,
|
||||
// TeamReviewers is always true if Reviewers is true because
|
||||
// this is the existing `pr edit` behavior. This means
|
||||
// always fetch teams.
|
||||
// TODO: evaluate whether this can follow the same logic as
|
||||
// `pr create` to conditionally fetch teams if a reviewer contains
|
||||
// a slash.
|
||||
// See https://github.com/cli/cli/blob/449920b40fc8a5015d1578ca10a301aa385a1914/pkg/cmd/pr/shared/params.go#L67-L71
|
||||
// See https://github.com/cli/cli/issues/11360
|
||||
TeamReviewers: editable.Reviewers.Edited,
|
||||
Reviewers: editable.Reviewers.Edited,
|
||||
TeamReviewers: teamReviewers,
|
||||
Assignees: editable.Assignees.Edited,
|
||||
ActorAssignees: editable.Assignees.ActorAssignees,
|
||||
Labels: editable.Labels.Edited,
|
||||
ProjectsV1: editable.Projects.Edited,
|
||||
ProjectsV1: editable.Projects.Edited && projectV1Support == gh.ProjectsV1Supported,
|
||||
ProjectsV2: editable.Projects.Edited,
|
||||
Milestones: editable.Milestone.Edited,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,6 +106,19 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co
|
|||
This may result in the same or duplicate release which may not be desirable in some cases.
|
||||
Use %[1]s--fail-on-no-commits%[1]s to fail if no new commits are available. This flag has no
|
||||
effect if there are no existing releases or this is the very first release.
|
||||
|
||||
## Immutable Releases
|
||||
|
||||
When release immutability is enabled for a repository, the following protections are enforced:
|
||||
- Git tags associated with a release cannot be modified or deleted.
|
||||
- Release assets cannot be modified or deleted.
|
||||
|
||||
Immutability is enforced only after a release is published. Draft releases can be modified
|
||||
or deleted, and the associated git tags can be modified or deleted as well.
|
||||
|
||||
When using the %[1]screate%[1]s command to attach assets to a release, separate API calls
|
||||
are made to create the release as a draft, upload the assets, and then publish the release.
|
||||
Immutability protections will be enforced ONLY after the release is published.
|
||||
`, "`"),
|
||||
Example: heredoc.Doc(`
|
||||
# Interactively create a release
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@ import (
|
|||
"google.golang.org/protobuf/encoding/protojson"
|
||||
)
|
||||
|
||||
const ReleasePredicateType = "https://in-toto.io/attestation/release/v0.1"
|
||||
|
||||
type Verifier interface {
|
||||
// VerifyAttestation verifies the attestation for a given artifact
|
||||
VerifyAttestation(art *artifact.DigestedArtifact, att *api.Attestation) (*verification.AttestationProcessingResult, error)
|
||||
|
|
|
|||
|
|
@ -40,23 +40,16 @@ func NewCmdVerifyAsset(f *cmdutil.Factory, runF func(*VerifyAssetConfig) error)
|
|||
|
||||
cmd := &cobra.Command{
|
||||
Use: "verify-asset [<tag>] <file-path>",
|
||||
Short: "Verify that a given asset originated from a specific GitHub Release.",
|
||||
Short: "Verify that a given asset originated from a release",
|
||||
Long: heredoc.Doc(`
|
||||
Verify that a given asset file originated from a specific GitHub Release using cryptographically signed attestations.
|
||||
|
||||
## Understanding Verification
|
||||
|
||||
An attestation is a claim made by GitHub regarding a release and its assets.
|
||||
|
||||
## What This Command Does
|
||||
|
||||
This command checks that the asset you provide matches an attestation produced by GitHub for a particular release.
|
||||
It ensures the asset's integrity by validating:
|
||||
* The asset's digest matches the subject in the attestation
|
||||
* The attestation is associated with the specified release
|
||||
This command checks that the asset you provide matches a valid attestation for the specified release (or the latest release, if no tag is given).
|
||||
It ensures the asset's integrity by validating that the asset's digest matches the subject in the attestation and that the attestation is associated with the release.
|
||||
`),
|
||||
Hidden: true,
|
||||
Args: cobra.MaximumNArgs(2),
|
||||
Args: cobra.MaximumNArgs(2),
|
||||
Example: heredoc.Doc(`
|
||||
# Verify an asset from the latest release
|
||||
$ gh release verify-asset ./dist/my-asset.zip
|
||||
|
|
@ -154,7 +147,7 @@ func verifyAssetRun(config *VerifyAssetConfig) error {
|
|||
// Find attestations for the release tag SHA
|
||||
attestations, err := config.AttClient.GetByDigest(api.FetchParams{
|
||||
Digest: releaseRefDigest.DigestWithAlg(),
|
||||
PredicateType: shared.ReleasePredicateType,
|
||||
PredicateType: "release",
|
||||
Owner: baseRepo.RepoOwner(),
|
||||
Repo: baseRepo.RepoOwner() + "/" + baseRepo.RepoName(),
|
||||
// TODO: Allow this value to be set via a flag.
|
||||
|
|
|
|||
|
|
@ -41,21 +41,16 @@ func NewCmdVerify(f *cmdutil.Factory, runF func(config *VerifyConfig) error) *co
|
|||
opts := &VerifyOptions{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "verify [<tag>]",
|
||||
Short: "Verify the attestation for a GitHub Release.",
|
||||
Hidden: true,
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
Use: "verify [<tag>]",
|
||||
Short: "Verify the attestation for a release",
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
Long: heredoc.Doc(`
|
||||
Verify that a GitHub Release is accompanied by a valid cryptographically signed attestation.
|
||||
|
||||
## Understanding Verification
|
||||
|
||||
An attestation is a claim made by GitHub regarding a release and its assets.
|
||||
|
||||
## What This Command Does
|
||||
|
||||
This command checks that the specified release (or the latest release, if no tag is given) has a valid attestation.
|
||||
It fetches the attestation for the release and prints out metadata about all assets referenced in the attestation, including their digests.
|
||||
This command checks that the specified release (or the latest release, if no tag is given) has a valid attestation.
|
||||
It fetches the attestation for the release and prints metadata about all assets referenced in the attestation, including their digests.
|
||||
`),
|
||||
Example: heredoc.Doc(`
|
||||
# Verify the latest release
|
||||
|
|
@ -140,7 +135,7 @@ func verifyRun(config *VerifyConfig) error {
|
|||
// Find all the attestations for the release tag SHA
|
||||
attestations, err := config.AttClient.GetByDigest(api.FetchParams{
|
||||
Digest: releaseRefDigest.DigestWithAlg(),
|
||||
PredicateType: shared.ReleasePredicateType,
|
||||
PredicateType: "release",
|
||||
Owner: baseRepo.RepoOwner(),
|
||||
Repo: baseRepo.RepoOwner() + "/" + baseRepo.RepoName(),
|
||||
Initiator: "github",
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Co
|
|||
if !opts.IO.CanPrompt() {
|
||||
return cmdutil.FlagErrorf("cannot non-interactively delete current repository. Please specify a repository or run interactively")
|
||||
}
|
||||
_, _ = fmt.Fprintln(opts.IO.ErrOut, "Warning: `--yes` is ignored since no repository was specified")
|
||||
opts.Confirmed = false
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,12 +16,13 @@ import (
|
|||
|
||||
func TestNewCmdDelete(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
tty bool
|
||||
output DeleteOptions
|
||||
wantErr bool
|
||||
errMsg string
|
||||
name string
|
||||
input string
|
||||
tty bool
|
||||
output DeleteOptions
|
||||
wantErr bool
|
||||
wantErrMsg string
|
||||
wantStderr string
|
||||
}{
|
||||
{
|
||||
name: "confirm flag",
|
||||
|
|
@ -36,11 +37,11 @@ func TestNewCmdDelete(t *testing.T) {
|
|||
output: DeleteOptions{RepoArg: "OWNER/REPO", Confirmed: true},
|
||||
},
|
||||
{
|
||||
name: "no confirmation notty",
|
||||
input: "OWNER/REPO",
|
||||
output: DeleteOptions{RepoArg: "OWNER/REPO"},
|
||||
wantErr: true,
|
||||
errMsg: "--yes required when not running interactively",
|
||||
name: "no confirmation notty",
|
||||
input: "OWNER/REPO",
|
||||
output: DeleteOptions{RepoArg: "OWNER/REPO"},
|
||||
wantErr: true,
|
||||
wantErrMsg: "--yes required when not running interactively",
|
||||
},
|
||||
{
|
||||
name: "base repo resolution",
|
||||
|
|
@ -49,33 +50,37 @@ func TestNewCmdDelete(t *testing.T) {
|
|||
output: DeleteOptions{},
|
||||
},
|
||||
{
|
||||
name: "yes flag ignored when no argument tty",
|
||||
tty: true,
|
||||
input: "--yes",
|
||||
output: DeleteOptions{Confirmed: false}, // --yes should be ignored
|
||||
name: "yes flag ignored when no argument tty",
|
||||
tty: true,
|
||||
input: "--yes",
|
||||
output: DeleteOptions{Confirmed: false}, // --yes should be ignored
|
||||
wantErr: false,
|
||||
wantStderr: "Warning: `--yes` is ignored since no repository was specified\n",
|
||||
},
|
||||
{
|
||||
name: "yes flag error when no argument notty",
|
||||
input: "--yes",
|
||||
wantErr: true,
|
||||
errMsg: "cannot non-interactively delete current repository. Please specify a repository or run interactively",
|
||||
name: "yes flag error when no argument notty",
|
||||
input: "--yes",
|
||||
wantErr: true,
|
||||
wantErrMsg: "cannot non-interactively delete current repository. Please specify a repository or run interactively",
|
||||
},
|
||||
{
|
||||
name: "confirm flag error when no argument notty",
|
||||
input: "--confirm",
|
||||
wantErr: true,
|
||||
errMsg: "cannot non-interactively delete current repository. Please specify a repository or run interactively",
|
||||
name: "confirm flag error when no argument notty",
|
||||
input: "--confirm",
|
||||
wantErr: true,
|
||||
wantErrMsg: "cannot non-interactively delete current repository. Please specify a repository or run interactively",
|
||||
},
|
||||
{
|
||||
name: "confirm flag also ignored when no argument tty",
|
||||
tty: true,
|
||||
input: "--confirm",
|
||||
output: DeleteOptions{Confirmed: false}, // --confirm should also be ignored
|
||||
name: "confirm flag also ignored when no argument tty",
|
||||
tty: true,
|
||||
input: "--confirm",
|
||||
output: DeleteOptions{Confirmed: false}, // --confirm should also be ignored
|
||||
wantStderr: "Warning: `--yes` is ignored since no repository was specified\n",
|
||||
// Note: This does not confuse the user, as deprecation warnings are shown: "Flag --confirm has been deprecated, use `--yes` instead"
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ios, _, _, _ := iostreams.Test()
|
||||
ios, _, _, stdErr := iostreams.Test()
|
||||
ios.SetStdinTTY(tt.tty)
|
||||
ios.SetStdoutTTY(tt.tty)
|
||||
f := &cmdutil.Factory{
|
||||
|
|
@ -91,15 +96,17 @@ func TestNewCmdDelete(t *testing.T) {
|
|||
cmd.SetArgs(argv)
|
||||
cmd.SetIn(&bytes.Buffer{})
|
||||
cmd.SetOut(&bytes.Buffer{})
|
||||
cmd.SetErr(&bytes.Buffer{})
|
||||
cmd.SetErr(stdErr)
|
||||
|
||||
_, err = cmd.ExecuteC()
|
||||
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, tt.errMsg, err.Error())
|
||||
assert.Equal(t, tt.wantErrMsg, err.Error())
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.wantStderr, stdErr.String())
|
||||
assert.Equal(t, tt.output.RepoArg, gotOpts.RepoArg)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,7 +127,6 @@ func NewCmdRoot(f *cmdutil.Factory, version, buildDate string) (*cobra.Command,
|
|||
cmd.AddCommand(versionCmd.NewCmdVersion(f, version, buildDate))
|
||||
cmd.AddCommand(accessibilityCmd.NewCmdAccessibility(f))
|
||||
cmd.AddCommand(actionsCmd.NewCmdActions(f))
|
||||
cmd.AddCommand(agentTaskCmd.NewCmdAgentTask(f))
|
||||
cmd.AddCommand(aliasCmd.NewCmdAlias(f))
|
||||
cmd.AddCommand(authCmd.NewCmdAuth(f))
|
||||
cmd.AddCommand(attestationCmd.NewCmdAttestation(f))
|
||||
|
|
@ -150,6 +149,7 @@ func NewCmdRoot(f *cmdutil.Factory, version, buildDate string) (*cobra.Command,
|
|||
repoResolvingCmdFactory := *f
|
||||
repoResolvingCmdFactory.BaseRepo = factory.SmartBaseRepoFunc(f)
|
||||
|
||||
cmd.AddCommand(agentTaskCmd.NewCmdAgentTask(&repoResolvingCmdFactory))
|
||||
cmd.AddCommand(browseCmd.NewCmdBrowse(&repoResolvingCmdFactory, nil))
|
||||
cmd.AddCommand(prCmd.NewCmdPR(&repoResolvingCmdFactory))
|
||||
cmd.AddCommand(orgCmd.NewCmdOrg(&repoResolvingCmdFactory))
|
||||
|
|
|
|||
|
|
@ -30,7 +30,11 @@ type Factory struct {
|
|||
Branch func() (string, error)
|
||||
Config func() (gh.Config, error)
|
||||
HttpClient func() (*http.Client, error)
|
||||
Remotes func() (context.Remotes, error)
|
||||
// PlainHttpClient is a special HTTP client that does not automatically set
|
||||
// auth and other headers. This is meant to be used in situations where the
|
||||
// client needs to specify the headers itself (e.g. during login).
|
||||
PlainHttpClient func() (*http.Client, error)
|
||||
Remotes func() (context.Remotes, error)
|
||||
}
|
||||
|
||||
// Executable is the path to the currently invoked binary
|
||||
|
|
|
|||
|
|
@ -25,9 +25,33 @@ type JSONFlagError struct {
|
|||
|
||||
func AddJSONFlags(cmd *cobra.Command, exportTarget *Exporter, fields []string) {
|
||||
f := cmd.Flags()
|
||||
addJsonFlag(f)
|
||||
addJqFlag(f, "q")
|
||||
addTemplateFlag(f, "t")
|
||||
|
||||
setupJsonFlags(cmd, exportTarget, fields)
|
||||
}
|
||||
|
||||
func AddJSONFlagsWithoutShorthand(cmd *cobra.Command, exportTarget *Exporter, fields []string) {
|
||||
f := cmd.Flags()
|
||||
addJsonFlag(f)
|
||||
addJqFlag(f, "")
|
||||
addTemplateFlag(f, "")
|
||||
|
||||
setupJsonFlags(cmd, exportTarget, fields)
|
||||
}
|
||||
|
||||
func addJsonFlag(f *pflag.FlagSet) {
|
||||
f.StringSlice("json", nil, "Output JSON with the specified `fields`")
|
||||
f.StringP("jq", "q", "", "Filter JSON output using a jq `expression`")
|
||||
f.StringP("template", "t", "", "Format JSON output using a Go template; see \"gh help formatting\"")
|
||||
}
|
||||
func addJqFlag(f *pflag.FlagSet, shorthand string) {
|
||||
f.StringP("jq", shorthand, "", "Filter JSON output using a jq `expression`")
|
||||
}
|
||||
func addTemplateFlag(f *pflag.FlagSet, shorthand string) {
|
||||
f.StringP("template", shorthand, "", "Format JSON output using a Go template; see \"gh help formatting\"")
|
||||
}
|
||||
|
||||
func setupJsonFlags(cmd *cobra.Command, exportTarget *Exporter, fields []string) {
|
||||
|
||||
_ = cmd.RegisterFlagCompletionFunc("json", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
var results []string
|
||||
|
|
|
|||
|
|
@ -119,6 +119,44 @@ func TestAddJSONFlags(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAddJSONFlagsWithoutShorthand(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setFlags func(cmd *cobra.Command)
|
||||
wantFlags map[string]string
|
||||
}{
|
||||
{
|
||||
name: "no conflicting flags",
|
||||
setFlags: func(cmd *cobra.Command) {
|
||||
cmd.Flags().StringP("web", "w", "", "")
|
||||
cmd.Flags().StringP("token", "t", "", "")
|
||||
},
|
||||
wantFlags: map[string]string{
|
||||
"web": "w",
|
||||
"token": "t",
|
||||
"jq": "",
|
||||
"template": "",
|
||||
"json": "",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
cmd := &cobra.Command{Run: func(*cobra.Command, []string) {}}
|
||||
tt.setFlags(cmd)
|
||||
|
||||
AddJSONFlagsWithoutShorthand(cmd, nil, []string{})
|
||||
|
||||
for f, shorthand := range tt.wantFlags {
|
||||
flag := cmd.Flags().Lookup(f)
|
||||
require.NotNil(t, flag)
|
||||
require.Equal(t, shorthand, flag.Shorthand)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestAddJSONFlagsSetsAnnotations asserts that `AddJSONFlags` function adds the
|
||||
// appropriate annotation to the command, which could later be used by doc
|
||||
// generator functions.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue