├── .github
├── release.yml
├── renovate.json5
└── workflows
│ └── go.yaml
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── docs
└── diagram.svg
├── e2e_test
├── authserver
│ └── handler.go
├── client
│ └── client.go
├── context_test.go
├── e2e_test.go
├── error_test.go
├── localserveropts_test.go
├── pkce_test.go
├── testdata
│ ├── Makefile
│ ├── ca.crt
│ ├── ca.csr
│ ├── ca.key
│ ├── ca.srl
│ ├── openssl.cnf
│ ├── server.crt
│ ├── server.csr
│ └── server.key
└── tls_test.go
├── example
├── README.md
└── main.go
├── go.mod
├── go.sum
├── oauth2cli.go
├── oauth2cli_test.go
├── oauth2params
└── params.go
├── server.go
└── tools
├── go.mod
└── go.sum
/.github/release.yml:
--------------------------------------------------------------------------------
1 | # https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes
2 | changelog:
3 | categories:
4 | - title: Features
5 | labels:
6 | - '*'
7 | exclude:
8 | labels:
9 | - renovate
10 | - refactoring
11 | - title: Refactoring
12 | labels:
13 | - refactoring
14 | - title: Dependencies
15 | labels:
16 | - renovate
17 |
--------------------------------------------------------------------------------
/.github/renovate.json5:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": [
4 | "github>int128/renovate-base",
5 | "github>int128/go-renovate-config#v1.7.2",
6 | "github>int128/go-renovate-config:github-actions#v1.7.2",
7 | "helpers:pinGitHubActionDigests",
8 | ],
9 | "packageRules": [
10 | {
11 | "description": "Update go version",
12 | "matchDatasources": ["golang-version"],
13 | "rangeStrategy": "bump",
14 | },
15 | {
16 | "description": "Do not update go directive in go.mod",
17 | "matchDatasources": ["golang-version"],
18 | "matchDepTypes": ["golang"],
19 | "matchFileNames": ["go.mod"],
20 | "enabled": false,
21 | },
22 | ],
23 | }
24 |
--------------------------------------------------------------------------------
/.github/workflows/go.yaml:
--------------------------------------------------------------------------------
1 | name: go
2 |
3 | on:
4 | pull_request:
5 | paths:
6 | - .github/workflows/go.yaml
7 | - '**/*.go'
8 | - '**/go.*'
9 | - aqua.yaml
10 | push:
11 | paths:
12 | - .github/workflows/go.yaml
13 | - '**/*.go'
14 | - '**/go.*'
15 | - aqua.yaml
16 | branches:
17 | - master
18 |
19 | jobs:
20 | test:
21 | runs-on: ubuntu-latest
22 | timeout-minutes: 10
23 | steps:
24 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
25 | - id: toolchain
26 | run: echo "version=$(sed -ne '/^toolchain /s/^toolchain go//p' go.mod)" >> "$GITHUB_OUTPUT"
27 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
28 | with:
29 | go-version: ${{ steps.toolchain.outputs.version }}
30 | cache-dependency-path: go.sum
31 | - run: make test
32 |
33 | lint:
34 | runs-on: ubuntu-latest
35 | timeout-minutes: 10
36 | steps:
37 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
38 | - id: toolchain
39 | run: echo "version=$(sed -ne '/^toolchain /s/^toolchain go//p' go.mod)" >> "$GITHUB_OUTPUT"
40 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
41 | with:
42 | go-version: ${{ steps.toolchain.outputs.version }}
43 | cache-dependency-path: |
44 | go.sum
45 | tools/go.sum
46 | - run: make lint
47 |
48 | generate:
49 | runs-on: ubuntu-latest
50 | timeout-minutes: 10
51 | steps:
52 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
53 | - id: toolchain
54 | run: echo "version=$(sed -ne '/^toolchain /s/^toolchain go//p' go.mod)" >> "$GITHUB_OUTPUT"
55 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
56 | with:
57 | go-version: ${{ steps.toolchain.outputs.version }}
58 | cache-dependency-path: go.sum
59 | - run: go mod tidy
60 | - uses: int128/update-generated-files-action@f6dc44e35ce252932e9018f1c38d1e2a4ff80e14 # v2.60.0
61 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea
2 | /tools/bin/
3 |
4 | /example/example
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2018 Hidetake Iwata
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | all: lint test
2 |
3 | .PHONY: lint
4 | lint:
5 | go tool -modfile=tools/go.mod golangci-lint run
6 |
7 | .PHONY: test
8 | test:
9 | go test -race -v ./...
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # oauth2cli  [](https://godoc.org/github.com/int128/oauth2cli)
2 |
3 | This is a Go package for OAuth 2.0 authorization in a command line interface (CLI) tool.
4 | You can create a CLI tool with the simple authorization flow for better UX.
5 |
6 | Take a look at the screencast of [the example application](example/).
7 |
8 |
9 |
10 |
11 | ## Purpose
12 |
13 | When we create a CLI tool which accesses an API with OAuth, it needs the complicated flow such as copy/paste of a URL and code, as follows:
14 |
15 | 1. User runs the command.
16 | 1. Command shows the URL for authorization.
17 | 1. User opens the browser, logs in to the server and approves the authorization.
18 | 1. Server shows an authorization code.
19 | 1. User copies the code and pastes into the command.
20 | 1. Command accesses the API with the token.
21 |
22 | You can make it simple by using oauth2cli as follows:
23 |
24 | 1. User runs the command.
25 | 1. Command opens the browser.
26 | 1. User logs in to the server and approves the authorization.
27 | 1. Command gets a token and access the API with the token.
28 |
29 |
30 | ## How it works
31 |
32 | oauth2cli starts the local server and initiates the flow of [OAuth 2.0 Authorization Code Grant](https://tools.ietf.org/html/rfc6749#section-4.1).
33 |
34 | Take a look at the sequence diagram:
35 |
36 | 
37 |
38 |
39 | ## Contributions
40 |
41 | This is an open source software licensed under Apache 2.0.
42 | Feel free to open issues and pull requests.
43 |
--------------------------------------------------------------------------------
/docs/diagram.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/e2e_test/authserver/handler.go:
--------------------------------------------------------------------------------
1 | // Package authserver provides a stub server of the OAuth 2.0 authorization server.
2 | // This supports the authorization code grant described as:
3 | // https://tools.ietf.org/html/rfc6749#section-4.1
4 | package authserver
5 |
6 | import (
7 | "errors"
8 | "fmt"
9 | "net/http"
10 | "net/url"
11 | "testing"
12 | )
13 |
14 | // AuthorizationRequest represents an authorization request described as:
15 | // https://tools.ietf.org/html/rfc6749#section-4.1.1
16 | type AuthorizationRequest struct {
17 | Scope string
18 | State string
19 | RedirectURI string
20 | Raw url.Values
21 | }
22 |
23 | // TokenRequest represents a token request described as:
24 | // https://tools.ietf.org/html/rfc6749#section-4.1.3
25 | type TokenRequest struct {
26 | Code string
27 | Raw url.Values
28 | }
29 |
30 | // Handler handles HTTP requests.
31 | type Handler struct {
32 | TestingT *testing.T
33 |
34 | // This should return a URL with query parameters of authorization response.
35 | // See https://tools.ietf.org/html/rfc6749#section-4.1.2
36 | NewAuthorizationResponse func(req AuthorizationRequest) string
37 |
38 | // This should return a JSON body of access token response or error response.
39 | // See https://tools.ietf.org/html/rfc6749#section-5.1
40 | // and https://tools.ietf.org/html/rfc6749#section-5.2
41 | NewTokenResponse func(req TokenRequest) (int, string)
42 | }
43 |
44 | func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
45 | h.TestingT.Logf("authServer: %s %s", r.Method, r.RequestURI)
46 | if err := h.serveHTTP(w, r); err != nil {
47 | h.TestingT.Errorf("Handler error: %s", err)
48 | http.Error(w, err.Error(), http.StatusInternalServerError)
49 | }
50 | }
51 |
52 | func (h *Handler) serveHTTP(w http.ResponseWriter, r *http.Request) error {
53 | switch {
54 | case r.Method == "GET" && r.URL.Path == "/auth":
55 | q := r.URL.Query()
56 | scope, state, redirectURI := q.Get("scope"), q.Get("state"), q.Get("redirect_uri")
57 | if scope == "" {
58 | return errors.New("scope is missing")
59 | }
60 | if state == "" {
61 | return errors.New("state is missing")
62 | }
63 | if redirectURI == "" {
64 | return errors.New("redirect_uri is missing")
65 | }
66 | authorizationResponseURL := h.NewAuthorizationResponse(AuthorizationRequest{
67 | Scope: scope,
68 | State: state,
69 | RedirectURI: redirectURI,
70 | Raw: q,
71 | })
72 | http.Redirect(w, r, authorizationResponseURL, http.StatusFound)
73 |
74 | case r.Method == "POST" && r.URL.Path == "/token":
75 | if err := r.ParseForm(); err != nil {
76 | return fmt.Errorf("error while parsing form: %w", err)
77 | }
78 | code, redirectURI := r.Form.Get("code"), r.Form.Get("redirect_uri")
79 | if code == "" {
80 | return errors.New("code is missing")
81 | }
82 | if redirectURI == "" {
83 | return errors.New("redirect_uri is missing")
84 | }
85 | status, body := h.NewTokenResponse(TokenRequest{
86 | Code: code,
87 | Raw: r.Form,
88 | })
89 | w.Header().Add("Content-Type", "application/json")
90 | w.WriteHeader(status)
91 | if _, err := w.Write([]byte(body)); err != nil {
92 | return fmt.Errorf("error while writing response body: %w", err)
93 | }
94 |
95 | default:
96 | http.NotFound(w, r)
97 | }
98 | return nil
99 | }
100 |
--------------------------------------------------------------------------------
/e2e_test/client/client.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "crypto/tls"
5 | "crypto/x509"
6 | "fmt"
7 | "io"
8 | "net/http"
9 | "os"
10 | "testing"
11 |
12 | "github.com/google/go-cmp/cmp"
13 | )
14 |
15 | var certPool = x509.NewCertPool()
16 |
17 | func init() {
18 | data, err := os.ReadFile("testdata/ca.crt")
19 | if err != nil {
20 | panic(err)
21 | }
22 | if !certPool.AppendCertsFromPEM(data) {
23 | panic("could not append certificate data")
24 | }
25 | }
26 |
27 | func Get(url string) (int, string, error) {
28 | client := http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{RootCAs: certPool}}}
29 | resp, err := client.Get(url)
30 | if err != nil {
31 | return 0, "", fmt.Errorf("could not send a request: %w", err)
32 | }
33 | defer func() {
34 | if err := resp.Body.Close(); err != nil {
35 | panic(err)
36 | }
37 | }()
38 | b, err := io.ReadAll(resp.Body)
39 | if err != nil {
40 | return resp.StatusCode, "", fmt.Errorf("could not read response body: %w", err)
41 | }
42 | return resp.StatusCode, string(b), nil
43 | }
44 |
45 | func GetAndVerify(t *testing.T, url string, code int, body string) {
46 | gotCode, gotBody, err := Get(url)
47 | if err != nil {
48 | t.Errorf("could not open browser request: %s", err)
49 | return
50 | }
51 | if gotCode != code {
52 | t.Errorf("status wants %d but %d", code, gotCode)
53 | }
54 | if gotBody != body {
55 | t.Errorf("response body did not match: %s", cmp.Diff(gotBody, body))
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/e2e_test/context_test.go:
--------------------------------------------------------------------------------
1 | package e2e_test
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 | "net/http/httptest"
8 | "testing"
9 | "time"
10 |
11 | "github.com/int128/oauth2cli"
12 | "github.com/int128/oauth2cli/e2e_test/authserver"
13 | "golang.org/x/oauth2"
14 | )
15 |
16 | func TestContextCancelOnWaitingForBrowser(t *testing.T) {
17 | ctx, cancel := context.WithTimeout(context.TODO(), 100*time.Millisecond)
18 | defer cancel()
19 | testServer := httptest.NewServer(&authserver.Handler{
20 | TestingT: t,
21 | NewAuthorizationResponse: func(req authserver.AuthorizationRequest) string {
22 | return fmt.Sprintf("%s?error=server_error", req.RedirectURI)
23 | },
24 | NewTokenResponse: func(req authserver.TokenRequest) (int, string) {
25 | return 500, "should not reach here"
26 | },
27 | })
28 | defer testServer.Close()
29 | cfg := oauth2cli.Config{
30 | OAuth2Config: oauth2.Config{
31 | ClientID: "YOUR_CLIENT_ID",
32 | ClientSecret: "YOUR_CLIENT_SECRET",
33 | Scopes: []string{"email", "profile"},
34 | Endpoint: oauth2.Endpoint{
35 | AuthURL: testServer.URL + "/auth",
36 | TokenURL: testServer.URL + "/token",
37 | },
38 | },
39 | Logf: t.Logf,
40 | }
41 | _, err := oauth2cli.GetToken(ctx, cfg)
42 | if err == nil {
43 | t.Errorf("GetToken wants error but was nil")
44 | return
45 | }
46 | if !errors.Is(err, context.DeadlineExceeded) {
47 | t.Errorf("err wants DeadlineExceeded but %+v", err)
48 | }
49 | }
50 |
51 | func TestContextCancelOnLocalServerReadyChan(t *testing.T) {
52 | ctx, cancel := context.WithTimeout(context.TODO(), 100*time.Millisecond)
53 | defer cancel()
54 | openBrowserCh := make(chan string)
55 | defer close(openBrowserCh)
56 | testServer := httptest.NewServer(&authserver.Handler{
57 | TestingT: t,
58 | NewAuthorizationResponse: func(req authserver.AuthorizationRequest) string {
59 | return fmt.Sprintf("%s?error=server_error", req.RedirectURI)
60 | },
61 | NewTokenResponse: func(req authserver.TokenRequest) (int, string) {
62 | return 500, "should not reach here"
63 | },
64 | })
65 | defer testServer.Close()
66 | cfg := oauth2cli.Config{
67 | OAuth2Config: oauth2.Config{
68 | ClientID: "YOUR_CLIENT_ID",
69 | ClientSecret: "YOUR_CLIENT_SECRET",
70 | Scopes: []string{"email", "profile"},
71 | Endpoint: oauth2.Endpoint{
72 | AuthURL: testServer.URL + "/auth",
73 | TokenURL: testServer.URL + "/token",
74 | },
75 | },
76 | LocalServerReadyChan: openBrowserCh,
77 | Logf: t.Logf,
78 | }
79 | _, err := oauth2cli.GetToken(ctx, cfg)
80 | if err == nil {
81 | t.Errorf("GetToken wants error but was nil")
82 | return
83 | }
84 | if !errors.Is(err, context.DeadlineExceeded) {
85 | t.Errorf("err wants DeadlineExceeded but %+v", err)
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/e2e_test/e2e_test.go:
--------------------------------------------------------------------------------
1 | package e2e_test
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "net/http"
7 | "net/http/httptest"
8 | "net/url"
9 | "sync"
10 | "testing"
11 | "time"
12 |
13 | "github.com/int128/oauth2cli"
14 | "github.com/int128/oauth2cli/e2e_test/authserver"
15 | "github.com/int128/oauth2cli/e2e_test/client"
16 | "golang.org/x/oauth2"
17 | )
18 |
19 | const invalidGrantResponse = `{"error":"invalid_grant"}`
20 | const validTokenResponse = `{"access_token": "ACCESS_TOKEN","token_type": "Bearer","expires_in": 3600,"refresh_token": "REFRESH_TOKEN"}`
21 |
22 | func TestHappyPath(t *testing.T) {
23 | ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second)
24 | defer cancel()
25 | openBrowserCh := make(chan string)
26 | var wg sync.WaitGroup
27 | wg.Add(1)
28 | go func() {
29 | defer wg.Done()
30 | defer close(openBrowserCh)
31 | // Start a local server and get a token.
32 | testServer := httptest.NewServer(&authserver.Handler{
33 | TestingT: t,
34 | NewAuthorizationResponse: func(req authserver.AuthorizationRequest) string {
35 | if want := "email profile"; req.Scope != want {
36 | t.Errorf("scope wants %s but %s", want, req.Scope)
37 | return fmt.Sprintf("%s?error=invalid_scope", req.RedirectURI)
38 | }
39 | if !assertRedirectURI(t, req.RedirectURI, "http", "localhost", "") {
40 | return fmt.Sprintf("%s?error=invalid_redirect_uri", req.RedirectURI)
41 | }
42 | return fmt.Sprintf("%s?state=%s&code=%s", req.RedirectURI, req.State, "AUTH_CODE")
43 | },
44 | NewTokenResponse: func(req authserver.TokenRequest) (int, string) {
45 | if want := "AUTH_CODE"; req.Code != want {
46 | t.Errorf("code wants %s but %s", want, req.Code)
47 | return 400, invalidGrantResponse
48 | }
49 | return 200, validTokenResponse
50 | },
51 | })
52 | defer testServer.Close()
53 | cfg := oauth2cli.Config{
54 | OAuth2Config: oauth2.Config{
55 | ClientID: "YOUR_CLIENT_ID",
56 | ClientSecret: "YOUR_CLIENT_SECRET",
57 | Scopes: []string{"email", "profile"},
58 | Endpoint: oauth2.Endpoint{
59 | AuthURL: testServer.URL + "/auth",
60 | TokenURL: testServer.URL + "/token",
61 | },
62 | },
63 | LocalServerReadyChan: openBrowserCh,
64 | LocalServerMiddleware: loggingMiddleware(t),
65 | Logf: t.Logf,
66 | }
67 | token, err := oauth2cli.GetToken(ctx, cfg)
68 | if err != nil {
69 | t.Errorf("could not get a token: %s", err)
70 | return
71 | }
72 | if token.AccessToken != "ACCESS_TOKEN" {
73 | t.Errorf("AccessToken wants %s but %s", "ACCESS_TOKEN", token.AccessToken)
74 | }
75 | if token.RefreshToken != "REFRESH_TOKEN" {
76 | t.Errorf("RefreshToken wants %s but %s", "REFRESH_TOKEN", token.RefreshToken)
77 | }
78 | }()
79 | wg.Add(1)
80 | go func() {
81 | defer wg.Done()
82 | toURL, ok := <-openBrowserCh
83 | if !ok {
84 | t.Errorf("server already closed")
85 | return
86 | }
87 | client.GetAndVerify(t, toURL, 200, oauth2cli.DefaultLocalServerSuccessHTML)
88 | }()
89 | wg.Wait()
90 | }
91 |
92 | func TestRedirectURLHostname(t *testing.T) {
93 | ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second)
94 | defer cancel()
95 | openBrowserCh := make(chan string)
96 | var wg sync.WaitGroup
97 | wg.Add(1)
98 | go func() {
99 | defer wg.Done()
100 | defer close(openBrowserCh)
101 | // Start a local server and get a token.
102 | testServer := httptest.NewServer(&authserver.Handler{
103 | TestingT: t,
104 | NewAuthorizationResponse: func(req authserver.AuthorizationRequest) string {
105 | if want := "email profile"; req.Scope != want {
106 | t.Errorf("scope wants %s but %s", want, req.Scope)
107 | return fmt.Sprintf("%s?error=invalid_scope", req.RedirectURI)
108 | }
109 | if !assertRedirectURI(t, req.RedirectURI, "http", "127.0.0.1", "") {
110 | return fmt.Sprintf("%s?error=invalid_redirect_uri", req.RedirectURI)
111 | }
112 | return fmt.Sprintf("%s?state=%s&code=%s", req.RedirectURI, req.State, "AUTH_CODE")
113 | },
114 | NewTokenResponse: func(req authserver.TokenRequest) (int, string) {
115 | if want := "AUTH_CODE"; req.Code != want {
116 | t.Errorf("code wants %s but %s", want, req.Code)
117 | return 400, invalidGrantResponse
118 | }
119 | return 200, validTokenResponse
120 | },
121 | })
122 | defer testServer.Close()
123 | cfg := oauth2cli.Config{
124 | OAuth2Config: oauth2.Config{
125 | ClientID: "YOUR_CLIENT_ID",
126 | ClientSecret: "YOUR_CLIENT_SECRET",
127 | Scopes: []string{"email", "profile"},
128 | Endpoint: oauth2.Endpoint{
129 | AuthURL: testServer.URL + "/auth",
130 | TokenURL: testServer.URL + "/token",
131 | },
132 | },
133 | RedirectURLHostname: "127.0.0.1",
134 | LocalServerReadyChan: openBrowserCh,
135 | LocalServerMiddleware: loggingMiddleware(t),
136 | Logf: t.Logf,
137 | }
138 | token, err := oauth2cli.GetToken(ctx, cfg)
139 | if err != nil {
140 | t.Errorf("could not get a token: %s", err)
141 | return
142 | }
143 | if token.AccessToken != "ACCESS_TOKEN" {
144 | t.Errorf("AccessToken wants %s but %s", "ACCESS_TOKEN", token.AccessToken)
145 | }
146 | if token.RefreshToken != "REFRESH_TOKEN" {
147 | t.Errorf("RefreshToken wants %s but %s", "REFRESH_TOKEN", token.RefreshToken)
148 | }
149 | }()
150 | wg.Add(1)
151 | go func() {
152 | defer wg.Done()
153 | toURL, ok := <-openBrowserCh
154 | if !ok {
155 | t.Errorf("server already closed")
156 | return
157 | }
158 | client.GetAndVerify(t, toURL, 200, oauth2cli.DefaultLocalServerSuccessHTML)
159 | }()
160 | wg.Wait()
161 | }
162 |
163 | func TestSuccessRedirect(t *testing.T) {
164 | ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second)
165 | defer cancel()
166 | openBrowserCh := make(chan string)
167 | var wg sync.WaitGroup
168 | wg.Add(1)
169 | go func() {
170 | defer wg.Done()
171 | defer close(openBrowserCh)
172 | // start a local server of oauth2 endpoint
173 | testServer := httptest.NewServer(&authserver.Handler{
174 | TestingT: t,
175 | NewAuthorizationResponse: func(req authserver.AuthorizationRequest) string {
176 | if want := "email profile"; req.Scope != want {
177 | t.Errorf("scope wants %s but %s", want, req.Scope)
178 | return fmt.Sprintf("%s?error=invalid_scope", req.RedirectURI)
179 | }
180 | if !assertRedirectURI(t, req.RedirectURI, "http", "localhost", "") {
181 | return fmt.Sprintf("%s?error=invalid_redirect_uri", req.RedirectURI)
182 | }
183 | return fmt.Sprintf("%s?state=%s&code=%s", req.RedirectURI, req.State, "AUTH_CODE")
184 | },
185 | NewTokenResponse: func(req authserver.TokenRequest) (int, string) {
186 | if want := "AUTH_CODE"; req.Code != want {
187 | t.Errorf("code wants %s but %s", want, req.Code)
188 | return 400, invalidGrantResponse
189 | }
190 | return 200, validTokenResponse
191 | },
192 | })
193 | defer testServer.Close()
194 | // start a local server to be redirected
195 | sr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
196 | if r.URL.Path == "/success" && r.Method == "GET" {
197 | _, _ = w.Write([]byte("success page"))
198 | return
199 | }
200 | http.NotFound(w, r)
201 | }))
202 | defer sr.Close()
203 | // get a token
204 | cfg := oauth2cli.Config{
205 | OAuth2Config: oauth2.Config{
206 | ClientID: "YOUR_CLIENT_ID",
207 | ClientSecret: "YOUR_CLIENT_SECRET",
208 | Scopes: []string{"email", "profile"},
209 | Endpoint: oauth2.Endpoint{
210 | AuthURL: testServer.URL + "/auth",
211 | TokenURL: testServer.URL + "/token",
212 | },
213 | },
214 | LocalServerReadyChan: openBrowserCh,
215 | LocalServerMiddleware: loggingMiddleware(t),
216 | SuccessRedirectURL: sr.URL + "/success",
217 | FailureRedirectURL: sr.URL + "/failure",
218 | Logf: t.Logf,
219 | }
220 | token, err := oauth2cli.GetToken(ctx, cfg)
221 | if err != nil {
222 | t.Errorf("could not get a token: %s", err)
223 | return
224 | }
225 | if token.AccessToken != "ACCESS_TOKEN" {
226 | t.Errorf("AccessToken wants %s but %s", "ACCESS_TOKEN", token.AccessToken)
227 | }
228 | if token.RefreshToken != "REFRESH_TOKEN" {
229 | t.Errorf("RefreshToken wants %s but %s", "REFRESH_TOKEN", token.RefreshToken)
230 | }
231 | }()
232 | wg.Add(1)
233 | go func() {
234 | defer wg.Done()
235 | toURL, ok := <-openBrowserCh
236 | if !ok {
237 | t.Errorf("server already closed")
238 | return
239 | }
240 | client.GetAndVerify(t, toURL, 200, "success page")
241 | }()
242 | wg.Wait()
243 | }
244 |
245 | func assertRedirectURI(t *testing.T, actualURI, scheme, hostname, path string) bool {
246 | redirect, err := url.Parse(actualURI)
247 | if err != nil {
248 | t.Errorf("could not parse redirect_uri: %s", err)
249 | return false
250 | }
251 | if redirect.Scheme != scheme {
252 | t.Errorf("redirect_uri wants scheme %s but was %s", scheme, redirect.Scheme)
253 | return false
254 | }
255 | if actualHostname := redirect.Hostname(); actualHostname != hostname {
256 | t.Errorf("redirect_uri wants hostname %s but was %s", hostname, actualHostname)
257 | return false
258 | }
259 | if actualPath := redirect.Path; actualPath != path {
260 | t.Errorf("redirect_uri wants path %s but was %s", path, actualPath)
261 | return false
262 | }
263 | return true
264 | }
265 |
266 | func loggingMiddleware(t *testing.T) func(h http.Handler) http.Handler {
267 | return func(h http.Handler) http.Handler {
268 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
269 | t.Logf("oauth2cli-local-server: %s %s", r.Method, r.URL)
270 | h.ServeHTTP(w, r)
271 | })
272 | }
273 | }
274 |
--------------------------------------------------------------------------------
/e2e_test/error_test.go:
--------------------------------------------------------------------------------
1 | package e2e_test
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "net/http"
7 | "net/http/httptest"
8 | "sync"
9 | "testing"
10 | "time"
11 |
12 | "github.com/int128/oauth2cli"
13 | "github.com/int128/oauth2cli/e2e_test/authserver"
14 | "github.com/int128/oauth2cli/e2e_test/client"
15 | "golang.org/x/oauth2"
16 | )
17 |
18 | func TestErrorAuthorizationResponse(t *testing.T) {
19 | ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second)
20 | defer cancel()
21 | openBrowserCh := make(chan string)
22 | var wg sync.WaitGroup
23 | wg.Add(1)
24 | go func() {
25 | defer wg.Done()
26 | defer close(openBrowserCh)
27 | // Start a local server and get a token.
28 | testServer := httptest.NewServer(&authserver.Handler{
29 | TestingT: t,
30 | NewAuthorizationResponse: func(req authserver.AuthorizationRequest) string {
31 | return fmt.Sprintf("%s?error=server_error", req.RedirectURI)
32 | },
33 | NewTokenResponse: func(req authserver.TokenRequest) (int, string) {
34 | return 500, "should not reach here"
35 | },
36 | })
37 | defer testServer.Close()
38 | cfg := oauth2cli.Config{
39 | OAuth2Config: oauth2.Config{
40 | ClientID: "YOUR_CLIENT_ID",
41 | ClientSecret: "YOUR_CLIENT_SECRET",
42 | Scopes: []string{"email", "profile"},
43 | Endpoint: oauth2.Endpoint{
44 | AuthURL: testServer.URL + "/auth",
45 | TokenURL: testServer.URL + "/token",
46 | },
47 | },
48 | LocalServerReadyChan: openBrowserCh,
49 | Logf: t.Logf,
50 | }
51 | _, err := oauth2cli.GetToken(ctx, cfg)
52 | if err == nil {
53 | t.Errorf("GetToken wants error but was nil")
54 | return
55 | }
56 | t.Logf("expected error: %s", err)
57 | }()
58 | wg.Add(1)
59 | go func() {
60 | defer wg.Done()
61 | toURL, ok := <-openBrowserCh
62 | if !ok {
63 | t.Errorf("server already closed")
64 | return
65 | }
66 | client.GetAndVerify(t, toURL, 500, "authorization error\n")
67 | }()
68 | wg.Wait()
69 | }
70 |
71 | func TestFailureRedirect(t *testing.T) {
72 | ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second)
73 | defer cancel()
74 |
75 | // start a local server of oauth2 endpoint
76 | authzServer := httptest.NewServer(&authserver.Handler{
77 | TestingT: t,
78 | NewAuthorizationResponse: func(req authserver.AuthorizationRequest) string {
79 | return fmt.Sprintf("%s?error=server_error", req.RedirectURI)
80 | },
81 | NewTokenResponse: func(req authserver.TokenRequest) (int, string) {
82 | return 500, "should not reach here"
83 | },
84 | })
85 | defer authzServer.Close()
86 |
87 | // start a local server to be redirected
88 | pageServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
89 | if r.URL.Path == "/failure" && r.Method == "GET" {
90 | _, _ = w.Write([]byte("failure page"))
91 | return
92 | }
93 | http.NotFound(w, r)
94 | }))
95 | defer pageServer.Close()
96 |
97 | openBrowserCh := make(chan string)
98 | var wg sync.WaitGroup
99 | wg.Add(1)
100 | go func() {
101 | defer wg.Done()
102 | defer close(openBrowserCh)
103 | // get a token
104 | cfg := oauth2cli.Config{
105 | OAuth2Config: oauth2.Config{
106 | ClientID: "YOUR_CLIENT_ID",
107 | ClientSecret: "YOUR_CLIENT_SECRET",
108 | Scopes: []string{"email", "profile"},
109 | Endpoint: oauth2.Endpoint{
110 | AuthURL: authzServer.URL + "/auth",
111 | TokenURL: authzServer.URL + "/token",
112 | },
113 | },
114 | LocalServerReadyChan: openBrowserCh,
115 | SuccessRedirectURL: pageServer.URL + "/success",
116 | FailureRedirectURL: pageServer.URL + "/failure",
117 | Logf: t.Logf,
118 | }
119 | _, err := oauth2cli.GetToken(ctx, cfg)
120 | if err == nil {
121 | t.Errorf("GetToken wants error but was nil")
122 | return
123 | }
124 | t.Logf("expected error: %s", err)
125 | }()
126 | wg.Add(1)
127 | go func() {
128 | defer wg.Done()
129 | toURL, ok := <-openBrowserCh
130 | if !ok {
131 | t.Errorf("server already closed")
132 | return
133 | }
134 | client.GetAndVerify(t, toURL, 200, "failure page")
135 | }()
136 | wg.Wait()
137 | }
138 |
139 | func TestErrorTokenResponse(t *testing.T) {
140 | ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second)
141 | defer cancel()
142 | openBrowserCh := make(chan string)
143 | var wg sync.WaitGroup
144 | wg.Add(1)
145 | go func() {
146 | defer wg.Done()
147 | defer close(openBrowserCh)
148 | // Start a local server and get a token.
149 | testServer := httptest.NewServer(&authserver.Handler{
150 | TestingT: t,
151 | NewAuthorizationResponse: func(req authserver.AuthorizationRequest) string {
152 | return fmt.Sprintf("%s?state=%s&code=%s", req.RedirectURI, req.State, "AUTH_CODE")
153 | },
154 | NewTokenResponse: func(req authserver.TokenRequest) (int, string) {
155 | return 400, `{"error":"invalid_request"}`
156 | },
157 | })
158 | defer testServer.Close()
159 | cfg := oauth2cli.Config{
160 | OAuth2Config: oauth2.Config{
161 | ClientID: "YOUR_CLIENT_ID",
162 | ClientSecret: "YOUR_CLIENT_SECRET",
163 | Scopes: []string{"email", "profile"},
164 | Endpoint: oauth2.Endpoint{
165 | AuthURL: testServer.URL + "/auth",
166 | TokenURL: testServer.URL + "/token",
167 | },
168 | },
169 | LocalServerReadyChan: openBrowserCh,
170 | Logf: t.Logf,
171 | }
172 | _, err := oauth2cli.GetToken(ctx, cfg)
173 | if err == nil {
174 | t.Errorf("GetToken wants error but nil")
175 | return
176 | }
177 | t.Logf("expected error: %s", err)
178 | }()
179 | wg.Add(1)
180 | go func() {
181 | defer wg.Done()
182 | toURL, ok := <-openBrowserCh
183 | if !ok {
184 | t.Errorf("server already closed")
185 | return
186 | }
187 | client.GetAndVerify(t, toURL, 200, oauth2cli.DefaultLocalServerSuccessHTML)
188 | }()
189 | wg.Wait()
190 | }
191 |
--------------------------------------------------------------------------------
/e2e_test/localserveropts_test.go:
--------------------------------------------------------------------------------
1 | package e2e_test
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "net/http/httptest"
7 | "sync"
8 | "testing"
9 | "time"
10 |
11 | "github.com/int128/oauth2cli"
12 | "github.com/int128/oauth2cli/e2e_test/authserver"
13 | "github.com/int128/oauth2cli/e2e_test/client"
14 | "golang.org/x/oauth2"
15 | )
16 |
17 | func TestLocalServerCallbackPath(t *testing.T) {
18 | ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second)
19 | defer cancel()
20 | openBrowserCh := make(chan string)
21 | var wg sync.WaitGroup
22 | wg.Add(1)
23 | go func() {
24 | defer wg.Done()
25 | defer close(openBrowserCh)
26 | // Start a local server and get a token.
27 | testServer := httptest.NewServer(&authserver.Handler{
28 | TestingT: t,
29 | NewAuthorizationResponse: func(req authserver.AuthorizationRequest) string {
30 | if want := "email profile"; req.Scope != want {
31 | t.Errorf("scope wants %s but %s", want, req.Scope)
32 | return fmt.Sprintf("%s?error=invalid_scope", req.RedirectURI)
33 | }
34 | if !assertRedirectURI(t, req.RedirectURI, "http", "localhost", "/callback") {
35 | return fmt.Sprintf("%s?error=invalid_redirect_uri", req.RedirectURI)
36 | }
37 | return fmt.Sprintf("%s?state=%s&code=%s", req.RedirectURI, req.State, "AUTH_CODE")
38 | },
39 | NewTokenResponse: func(req authserver.TokenRequest) (int, string) {
40 | if want := "AUTH_CODE"; req.Code != want {
41 | t.Errorf("code wants %s but %s", want, req.Code)
42 | return 400, invalidGrantResponse
43 | }
44 | return 200, validTokenResponse
45 | },
46 | })
47 | defer testServer.Close()
48 | cfg := oauth2cli.Config{
49 | OAuth2Config: oauth2.Config{
50 | ClientID: "YOUR_CLIENT_ID",
51 | ClientSecret: "YOUR_CLIENT_SECRET",
52 | Scopes: []string{"email", "profile"},
53 | Endpoint: oauth2.Endpoint{
54 | AuthURL: testServer.URL + "/auth",
55 | TokenURL: testServer.URL + "/token",
56 | },
57 | },
58 | LocalServerCallbackPath: "/callback",
59 | LocalServerReadyChan: openBrowserCh,
60 | LocalServerMiddleware: loggingMiddleware(t),
61 | Logf: t.Logf,
62 | }
63 | token, err := oauth2cli.GetToken(ctx, cfg)
64 | if err != nil {
65 | t.Errorf("could not get a token: %s", err)
66 | return
67 | }
68 | if token.AccessToken != "ACCESS_TOKEN" {
69 | t.Errorf("AccessToken wants %s but %s", "ACCESS_TOKEN", token.AccessToken)
70 | }
71 | if token.RefreshToken != "REFRESH_TOKEN" {
72 | t.Errorf("RefreshToken wants %s but %s", "REFRESH_TOKEN", token.RefreshToken)
73 | }
74 | }()
75 | wg.Add(1)
76 | go func() {
77 | defer wg.Done()
78 | toURL, ok := <-openBrowserCh
79 | if !ok {
80 | t.Errorf("server already closed")
81 | return
82 | }
83 | client.GetAndVerify(t, toURL, 200, oauth2cli.DefaultLocalServerSuccessHTML)
84 | }()
85 | wg.Wait()
86 | }
87 |
--------------------------------------------------------------------------------
/e2e_test/pkce_test.go:
--------------------------------------------------------------------------------
1 | package e2e_test
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "net/http/httptest"
7 | "sync"
8 | "testing"
9 | "time"
10 |
11 | "github.com/int128/oauth2cli"
12 | "github.com/int128/oauth2cli/e2e_test/authserver"
13 | "github.com/int128/oauth2cli/e2e_test/client"
14 | "golang.org/x/oauth2"
15 | )
16 |
17 | func TestPKCE(t *testing.T) {
18 | ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second)
19 | defer cancel()
20 | openBrowserCh := make(chan string)
21 | var wg sync.WaitGroup
22 | wg.Add(1)
23 | go func() {
24 | defer wg.Done()
25 | defer close(openBrowserCh)
26 | // PKCE test fixture: https://tools.ietf.org/html/rfc7636
27 | const codeChallenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
28 | const codeVerifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
29 | // Start a local server and get a token.
30 | testServer := httptest.NewServer(&authserver.Handler{
31 | TestingT: t,
32 | NewAuthorizationResponse: func(req authserver.AuthorizationRequest) string {
33 | if req.Raw.Get("code_challenge_method") != "S256" {
34 | t.Errorf("code_challenge_method wants S256 but was %s", req.Raw.Get("code_challenge_method"))
35 | }
36 | if req.Raw.Get("code_challenge") != codeChallenge {
37 | t.Errorf("code_challenge wants %s but was %s", codeChallenge, req.Raw.Get("code_challenge"))
38 | }
39 | if want := "email profile"; req.Scope != want {
40 | t.Errorf("scope wants %s but %s", want, req.Scope)
41 | return fmt.Sprintf("%s?error=invalid_scope", req.RedirectURI)
42 | }
43 | if !assertRedirectURI(t, req.RedirectURI, "http", "localhost", "") {
44 | return fmt.Sprintf("%s?error=invalid_redirect_uri", req.RedirectURI)
45 | }
46 | return fmt.Sprintf("%s?state=%s&code=%s", req.RedirectURI, req.State, "AUTH_CODE")
47 | },
48 | NewTokenResponse: func(req authserver.TokenRequest) (int, string) {
49 | if req.Raw.Get("code_verifier") != codeVerifier {
50 | t.Errorf("code_verifier wants %s but was %s", codeVerifier, req.Raw.Get("code_verifier"))
51 | }
52 | if want := "AUTH_CODE"; req.Code != want {
53 | t.Errorf("code wants %s but %s", want, req.Code)
54 | return 400, invalidGrantResponse
55 | }
56 | return 200, validTokenResponse
57 | },
58 | })
59 | defer testServer.Close()
60 | cfg := oauth2cli.Config{
61 | OAuth2Config: oauth2.Config{
62 | ClientID: "YOUR_CLIENT_ID",
63 | ClientSecret: "YOUR_CLIENT_SECRET",
64 | Scopes: []string{"email", "profile"},
65 | Endpoint: oauth2.Endpoint{
66 | AuthURL: testServer.URL + "/auth",
67 | TokenURL: testServer.URL + "/token",
68 | },
69 | },
70 | AuthCodeOptions: []oauth2.AuthCodeOption{
71 | oauth2.SetAuthURLParam("code_challenge_method", "S256"),
72 | oauth2.SetAuthURLParam("code_challenge", codeChallenge),
73 | },
74 | TokenRequestOptions: []oauth2.AuthCodeOption{
75 | oauth2.SetAuthURLParam("code_verifier", codeVerifier),
76 | },
77 | LocalServerReadyChan: openBrowserCh,
78 | LocalServerMiddleware: loggingMiddleware(t),
79 | Logf: t.Logf,
80 | }
81 | token, err := oauth2cli.GetToken(ctx, cfg)
82 | if err != nil {
83 | t.Errorf("could not get a token: %s", err)
84 | return
85 | }
86 | if token.AccessToken != "ACCESS_TOKEN" {
87 | t.Errorf("AccessToken wants %s but %s", "ACCESS_TOKEN", token.AccessToken)
88 | }
89 | if token.RefreshToken != "REFRESH_TOKEN" {
90 | t.Errorf("RefreshToken wants %s but %s", "REFRESH_TOKEN", token.RefreshToken)
91 | }
92 | }()
93 | wg.Add(1)
94 | go func() {
95 | defer wg.Done()
96 | toURL, ok := <-openBrowserCh
97 | if !ok {
98 | t.Errorf("server already closed")
99 | return
100 | }
101 | client.GetAndVerify(t, toURL, 200, oauth2cli.DefaultLocalServerSuccessHTML)
102 | }()
103 | wg.Wait()
104 | }
105 |
--------------------------------------------------------------------------------
/e2e_test/testdata/Makefile:
--------------------------------------------------------------------------------
1 | EXPIRY := 3650
2 | OUTPUT_DIR := testdata
3 | TARGETS := ca.key
4 | TARGETS += ca.crt
5 | TARGETS += server.key
6 | TARGETS += server.crt
7 |
8 | .PHONY: all
9 | all: $(TARGETS)
10 |
11 | ca.key:
12 | openssl genrsa -out $@ 2048
13 | ca.csr: ca.key
14 | openssl req -new -key ca.key -out $@ -subj "/CN=hello-ca" -config openssl.cnf
15 | ca.crt: ca.key ca.csr
16 | openssl x509 -req -in ca.csr -signkey ca.key -out $@ -days $(EXPIRY)
17 | server.key:
18 | openssl genrsa -out $@ 2048
19 | server.csr: openssl.cnf server.key
20 | openssl req -new -key server.key -out $@ -subj "/CN=localhost" -config openssl.cnf
21 | server.crt: openssl.cnf server.csr ca.crt ca.key
22 | openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out $@ -sha256 -days $(EXPIRY) -extensions v3_req -extfile openssl.cnf
23 |
24 | .PHONY: clean
25 | clean:
26 | -rm -v $(TARGETS)
27 |
--------------------------------------------------------------------------------
/e2e_test/testdata/ca.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIICojCCAYoCCQD71keGa3mFIDANBgkqhkiG9w0BAQUFADATMREwDwYDVQQDDAho
3 | ZWxsby1jYTAeFw0yMTEwMDkxMzI1MjJaFw0zMTEwMDcxMzI1MjJaMBMxETAPBgNV
4 | BAMMCGhlbGxvLWNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4vLw
5 | WJbFRUbC4dVKKIptPRSE3rjoTAnPSDQyNFbpK7+4Lkj5brURbxU8TtGG2y7XKRcZ
6 | mErD5s5k6f0kinw+ctcUdVEpVuqj1dKUpQsceh8h9MJofcKo/bWRHQyavdfFJV65
7 | 4CTWeVwHDd/YVe3Nknmu8znLIBLOxMwUy5opHfpTY6AZH6G7WVa7UJAIe0ZD7vQR
8 | vOHalI4ahA8gZgEeOItP8TOO3IuziXt8RZ/yr9+9on7vK0F8Rs8E8YNEW0wB9Ic4
9 | wMd0TboMqeHobcbOqAMWKfuWefO2S++A1XYxR7gp+uIXFDmUJh6baVo3jm/Lququ
10 | BQ/GGeN0l7YbPiirIwIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQAoR1B7jeldx5s1
11 | ICvbv6wDr7Oka1VZSFiK+Rns8syAmYUnoPIeU52ROOvPskFXcOwm/u3CPvyGTebf
12 | zrOJtl47L4Yua/nuKLA1hbomqho39itXBh1N2L9ycUOs+7UighfttiQy6fTRowYi
13 | /Ehg0t4ML2ql1V15h0aJf+e5EWFeCFDmdPc9geTaozjQIB1uNYcJ/4Zn8fKnufgn
14 | ZN16LNpFo+MKPGOimc1Gc7ZA3EUU3MacJK5SvoMP8NNxAztXUMI9ZtMJ6HLGG7LO
15 | WC/LnZnnCS2HRYop4xgb0IYQlPxQP+A6OWYpIzFSXOM4nYhxWViwpstj8GcO/kxu
16 | 33OSC148
17 | -----END CERTIFICATE-----
18 |
--------------------------------------------------------------------------------
/e2e_test/testdata/ca.csr:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE REQUEST-----
2 | MIICrDCCAZQCAQAwEzERMA8GA1UEAwwIaGVsbG8tY2EwggEiMA0GCSqGSIb3DQEB
3 | AQUAA4IBDwAwggEKAoIBAQDi8vBYlsVFRsLh1Uooim09FITeuOhMCc9INDI0Vukr
4 | v7guSPlutRFvFTxO0YbbLtcpFxmYSsPmzmTp/SSKfD5y1xR1USlW6qPV0pSlCxx6
5 | HyH0wmh9wqj9tZEdDJq918UlXrngJNZ5XAcN39hV7c2Sea7zOcsgEs7EzBTLmikd
6 | +lNjoBkfobtZVrtQkAh7RkPu9BG84dqUjhqEDyBmAR44i0/xM47ci7OJe3xFn/Kv
7 | 372ifu8rQXxGzwTxg0RbTAH0hzjAx3RNugyp4ehtxs6oAxYp+5Z587ZL74DVdjFH
8 | uCn64hcUOZQmHptpWjeOb8uq6q4FD8YZ43SXths+KKsjAgMBAAGgVDBSBgkqhkiG
9 | 9w0BCQ4xRTBDMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMBQGA1UdEQQNMAuCCWxv
10 | Y2FsaG9zdDATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAQEA
11 | VT2dfITMyjyzlEkuaF97E0a96I1nB/sdl+oU7OobUkbUPzFZtG6B0DUTEXhIxVcu
12 | dF4EULUBfQ1b+9IiC3St+xjklc/Gmk84XnqLFXXs94oMk63Y/w4zUYsWWwo1Pf4C
13 | sOZEUJkwwUFLZPRB3aDxsBaFFy2pm3pfXqzOdBdVG06u8/Ibuk6gT/ynlUiZMxbH
14 | vDgNhfmX3YAOyBCB8qgAy4wJ5e4kDRdOLccVcMS+xH9LTL0ce5JyAdUGv6uEfU9v
15 | W6A276dYcrVLvIeASY3VI3UAAryytzM8Vh4nv7/0gb7zfdZJ6/DT3JLMYJER37iK
16 | ML+tfaTi/vqBnteek5AkqQ==
17 | -----END CERTIFICATE REQUEST-----
18 |
--------------------------------------------------------------------------------
/e2e_test/testdata/ca.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEpAIBAAKCAQEA4vLwWJbFRUbC4dVKKIptPRSE3rjoTAnPSDQyNFbpK7+4Lkj5
3 | brURbxU8TtGG2y7XKRcZmErD5s5k6f0kinw+ctcUdVEpVuqj1dKUpQsceh8h9MJo
4 | fcKo/bWRHQyavdfFJV654CTWeVwHDd/YVe3Nknmu8znLIBLOxMwUy5opHfpTY6AZ
5 | H6G7WVa7UJAIe0ZD7vQRvOHalI4ahA8gZgEeOItP8TOO3IuziXt8RZ/yr9+9on7v
6 | K0F8Rs8E8YNEW0wB9Ic4wMd0TboMqeHobcbOqAMWKfuWefO2S++A1XYxR7gp+uIX
7 | FDmUJh6baVo3jm/LququBQ/GGeN0l7YbPiirIwIDAQABAoIBAQCiJqPVF/xgz/sj
8 | 8gnnR2hfcM6yd3j6AzRHyYtpXAODT5sf7uHQ2KxZKtoJEqmA50mxwSB3cEviF+uA
9 | R28ZR0YzNTsXy2J3CHUArFGqTnMNSjsvrsuLt06Y32aMU9on9AeW+MRjws4+raMY
10 | pavjUWJE2o5GmC9qfiWf9JSTGFVyZXVI7RhA5KY6iYJpF/yr6Ydbsuz82SaTRTRV
11 | CIWPZ58Q14SDB2pW0OOSRH1NnJXgxKTgHLCdV+hzx7YIHkDnwbRLl1Hu927L2nFH
12 | 64titcykbr5A2YaVvSPrwhD3rL4JgQNFvbwHNxYIA6ZMHZGFlACz277YpraKCxqJ
13 | t+bv3MhxAoGBAPE9X/IZhG6copTDPOQYg31AgL75nYISmkg/vrycknHiMisV59TV
14 | 0HrNKqkbUSlRcv100Rh71ErysQjK6YLFIFGLXMjHB/Qciad3HDL5eNGy5+VMl/kq
15 | 6CQe/GAUD+MEl/PG/aUaU3K2zufe6afi/nTeJ50bnjEKfvumMtjdNIXbAoGBAPDV
16 | uP9nRZ/5Q9KP6f5RhvmKjpb6UhDlelfW3h4JgxAqSmWn69dtbOFoYMT7nmhfuoXn
17 | vtZoACmBu/h9HIk/Vnr2UbMgkBtxEOuL5L0flDem00LX0yMF82Fygi7sHFeOF2DF
18 | ejr7c3SZzGUMy0Y+r+Bdi+Sexop86wskT6xKOQZZAoGAfbdXTcpAeohELbRFAjVI
19 | KLi5n0xj6coZ/rbBhnct1g1jyZJD22WFnMlYhwIGXLrmavE6mkDF1Dz/Ry6/W/ew
20 | rO4sbzFRksRgdBYdau8ZKGbMdHFi7WWPzmsbaFJsKQlWyqKTwjui6zAbSTigmW4w
21 | 3uR6zmP7H2Fx5WCqNNuqKGsCgYBFXnor7tYqLwUvGE7g9yC2rgdGS5Hp1f94X6aY
22 | 5kp/FH9bapPO+HDdOFBvTL6i97flLAxW+5vYWMsI1xiqG7lQL7T2sJFxpg6mmiPn
23 | qbnqkb3WSg5Bz0v8LJx84XuEaKQpNphvdtAZrNcn4BiJAjNsn+VUk7tCVprpGatt
24 | R+/G+QKBgQCoxqMbv5GGBMp4qkBtK8l8xqAa3VtL2tj+sJyEvYM2sMU6GrUxMxxB
25 | KloVFq3vqwlMTsGmXpA+B6zCZazjoQHYbPNkkfrNnY2Ki6Msb7fbJN8xVqYXwetd
26 | 8uBnsW0qkAYUhXzYcnFn51gtKJcEqKOOEM/QSXvEf0w87GdrOxgMRw==
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/e2e_test/testdata/ca.srl:
--------------------------------------------------------------------------------
1 | 9E770A10DAC66371
2 |
--------------------------------------------------------------------------------
/e2e_test/testdata/openssl.cnf:
--------------------------------------------------------------------------------
1 | [ req ]
2 | distinguished_name = req_distinguished_name
3 | req_extensions = v3_req
4 |
5 | [ req_distinguished_name ]
6 |
7 | [ v3_req ]
8 | basicConstraints = CA:FALSE
9 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment
10 | subjectAltName = @alt_names
11 | extendedKeyUsage = serverAuth
12 |
13 | [alt_names]
14 | DNS.1 = localhost
15 |
--------------------------------------------------------------------------------
/e2e_test/testdata/server.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIC7zCCAdegAwIBAgIJAJ53ChDaxmNxMA0GCSqGSIb3DQEBCwUAMBMxETAPBgNV
3 | BAMMCGhlbGxvLWNhMB4XDTIxMTAwOTEzMjUyMloXDTMxMTAwNzEzMjUyMlowFDES
4 | MBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
5 | AQEAu2KtLTftALlcoN+oCwQhC18BN8YKsj5INZfuIZfK/4YJb1W7Ko7NJ3MvAAoH
6 | LwRXu0BPSe8AhBdK4MplAuOWOzV0oCaCjvbjKbMDZXZcbPLeBQIYm0WVCy62UJhg
7 | L3OwOB3Z4kKTf64BI9eexQ8kmpQBYFkwngx5WE4RxMlRaZ21fIO0WpDvDPArFZGl
8 | gMyU8A/Fyj6Fivd5uAH20PWbiHYCnqmspsFJRIkhfiTIDR8SuzkMLKvkyAfy1VN8
9 | hiuVGXK0ccGLT+M8eA3ccpwMEXDULEE3fTWMiUR5ls102H3WXyxlBqIULvP1R0yD
10 | RFaa+pUzhSHff4jtfuL87ByezwIDAQABo0UwQzAJBgNVHRMEAjAAMAsGA1UdDwQE
11 | AwIF4DAUBgNVHREEDTALgglsb2NhbGhvc3QwEwYDVR0lBAwwCgYIKwYBBQUHAwEw
12 | DQYJKoZIhvcNAQELBQADggEBAL5nUEmDQhYcAHPaLAK5fjlGINatRjA2zFts5D/0
13 | fTE8WtamxALTF5r6A9strp/8V6r0MqPkA5dacs7BmP/hFxkCFqJDc62xOs/uP/Bg
14 | fekD0EV8q6nqhUQA/EbtOJSe9Y3ETtvRMjavnUwjfB/zfhaoyyhwpccwYQ1FCZxh
15 | 4iyW+eXqOzBTFGED356Q8itEPuv0ra2PKEelISd9+eSCQTp+ehabc2Vfc1/7v3K6
16 | It3hW7JvFlzkXIATjAiPWT4+8Yq/abu/eEJB3eUuI64t8VQwqpfioAnqUw1ppJ5k
17 | C8wMNPjliIsp6ahTzlJzN0CGeE8lRgspgPNbLf8qHMmJYGg=
18 | -----END CERTIFICATE-----
19 |
--------------------------------------------------------------------------------
/e2e_test/testdata/server.csr:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE REQUEST-----
2 | MIICrTCCAZUCAQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0B
3 | AQEFAAOCAQ8AMIIBCgKCAQEAu2KtLTftALlcoN+oCwQhC18BN8YKsj5INZfuIZfK
4 | /4YJb1W7Ko7NJ3MvAAoHLwRXu0BPSe8AhBdK4MplAuOWOzV0oCaCjvbjKbMDZXZc
5 | bPLeBQIYm0WVCy62UJhgL3OwOB3Z4kKTf64BI9eexQ8kmpQBYFkwngx5WE4RxMlR
6 | aZ21fIO0WpDvDPArFZGlgMyU8A/Fyj6Fivd5uAH20PWbiHYCnqmspsFJRIkhfiTI
7 | DR8SuzkMLKvkyAfy1VN8hiuVGXK0ccGLT+M8eA3ccpwMEXDULEE3fTWMiUR5ls10
8 | 2H3WXyxlBqIULvP1R0yDRFaa+pUzhSHff4jtfuL87ByezwIDAQABoFQwUgYJKoZI
9 | hvcNAQkOMUUwQzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAUBgNVHREEDTALggls
10 | b2NhbGhvc3QwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggEB
11 | ADwpPkbMK5684yx2Ntrt+KHkgzGCSiLa4V4zDtJ8g/HmibVVA93SUHHoq4IdIOI7
12 | iMzK50zbUU67rsPKo8UkdDIguyZEgdxVhppipNc7Fhq+6J5iZfeue2p9UwWlvlQ2
13 | VGV+EH/JPPos2nlHosmvXLMnWUc9sJA5TZAjkR/R3FOgKH3YyzZf3Wkvb8mlE4GA
14 | /sgAEwF5AD4iOP6m9TX6tBojwWJm6//TOocK+2bfo1SP8fxBCyAPDZV0PRjZr5xo
15 | /riB3vXJAMnEgP++h4NFEebesfz0r7WUaSp1744ch3ina5MDywO8aZxmzfVVf8a4
16 | T1ZXITzljsZXGdX4cq+gA88=
17 | -----END CERTIFICATE REQUEST-----
18 |
--------------------------------------------------------------------------------
/e2e_test/testdata/server.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEowIBAAKCAQEAu2KtLTftALlcoN+oCwQhC18BN8YKsj5INZfuIZfK/4YJb1W7
3 | Ko7NJ3MvAAoHLwRXu0BPSe8AhBdK4MplAuOWOzV0oCaCjvbjKbMDZXZcbPLeBQIY
4 | m0WVCy62UJhgL3OwOB3Z4kKTf64BI9eexQ8kmpQBYFkwngx5WE4RxMlRaZ21fIO0
5 | WpDvDPArFZGlgMyU8A/Fyj6Fivd5uAH20PWbiHYCnqmspsFJRIkhfiTIDR8SuzkM
6 | LKvkyAfy1VN8hiuVGXK0ccGLT+M8eA3ccpwMEXDULEE3fTWMiUR5ls102H3WXyxl
7 | BqIULvP1R0yDRFaa+pUzhSHff4jtfuL87ByezwIDAQABAoIBAB2k/sComFYE/SJ2
8 | P59+h7vEy+yG6CpKsCHKZ2HpIbRLADMc3P9emBYlosnezEQj670z71dC+FHtXWsy
9 | mchbOOklC85KSV1UsZtnNqJWl0ilI3qnWj02RN4ABde6Q30+UCKQlme1IKCSZpvF
10 | ANksDB5oS89yTL60On4925HGR5rtkuoUshh2XH0D2R1YjUsvOus0C8OpGYlMekpt
11 | kWVxxIfH0aPyMAeeDHXY/rK9cpbzIgWgMlgSRACGz0Bwq1PBYNCrlNJOjExkc/ZE
12 | 6hyG/yo7gPm4NRzZO4gzI8qnC/h0IdY7ctAT2gYGMPI41nytWVLVr9iQxazOGsMs
13 | 9JY/qAECgYEA5Qrfab5oBQO8QdMHZ6HJc/MhdTTllCPtqAM6tkNx7JSgiKOdq4+i
14 | I1F6FH8viw+ZhEbpEey02iJNXmkQKBtdLNoQvlMboT89AKSu2Y3c5Oyy6P+zVgPj
15 | Iutzg69XYXpXTHIqwqIl8h/+JAWrIcEsI37qeZl7EG333QQyPS73nscCgYEA0XCp
16 | 6q4ftJ6HHiswse1R/ZHlDmr0qqCRobuyBZ09Hbqj3HShom2wCQOERZhWSrV6qxPc
17 | 0Vq3I3eifIpKJnsVzPAQcW5eDCpPT7WFt8vUl7StDKAUoCn0k3UoXkFEh4c8A1lS
18 | AOw4uR+MKfABj5kx0yIDJ2VQupXRquqyiFnhF7kCgYBZmBs0ngrKi1+E0CvBWgQX
19 | my+nTX3QqQ05/6ljypYx2SHC0V3H0nO2JRUAF5BdcUi3+ZUKMfG8slZZa/ep+KpF
20 | 9Tc/e/r+5GHUcpcC+v9BfWWuxvc72mFrM3cIN/qSYuzrNm2LnSoCwIkFRkJe/dVq
21 | PSczUdpV3u5qQk30qP6r2QKBgQC+rldU3ZDqLcaQtfvQkmOqe/DSLphw3F2apTtQ
22 | Fzc0YN+c7+bU7g5uNnBvuGEgBZgYN648QN4qAVrDDliyLi/L9OjTjQs9AixRQs/q
23 | ZT45nHpM21XL+Ea/FdRZ9AxDY+FEn0akpmyCRvGioOua5HvTYM0ESh60sYBBnwpK
24 | T6I6qQKBgBBxAHe3wc9wqJOQxURNMFgPZx5hj+A6yTsUHGO2yrM5Ey8RdtLD4y+o
25 | 6+6a9dd6pO18g7nVqPFmGF0albR5kEVoZHdHdUKmyAvq6RIv6g6eaiWKQg2GHEbH
26 | 3XVbeV7jnXria94W/VDsBpzGSEO0KXuGbzPLDJtPWyMrKyFBN2RN
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/e2e_test/tls_test.go:
--------------------------------------------------------------------------------
1 | package e2e_test
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "net/http/httptest"
7 | "sync"
8 | "testing"
9 | "time"
10 |
11 | "github.com/int128/oauth2cli"
12 | "github.com/int128/oauth2cli/e2e_test/authserver"
13 | "github.com/int128/oauth2cli/e2e_test/client"
14 | "golang.org/x/oauth2"
15 | )
16 |
17 | func TestTLS(t *testing.T) {
18 | ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second)
19 | defer cancel()
20 | openBrowserCh := make(chan string)
21 | var wg sync.WaitGroup
22 | wg.Add(1)
23 | go func() {
24 | defer wg.Done()
25 | defer close(openBrowserCh)
26 | // Start a local server and get a token.
27 | testServer := httptest.NewServer(&authserver.Handler{
28 | TestingT: t,
29 | NewAuthorizationResponse: func(req authserver.AuthorizationRequest) string {
30 | if want := "email profile"; req.Scope != want {
31 | t.Errorf("scope wants %s but %s", want, req.Scope)
32 | return fmt.Sprintf("%s?error=invalid_scope", req.RedirectURI)
33 | }
34 | if !assertRedirectURI(t, req.RedirectURI, "https", "localhost", "") {
35 | return fmt.Sprintf("%s?error=invalid_redirect_uri", req.RedirectURI)
36 | }
37 | return fmt.Sprintf("%s?state=%s&code=%s", req.RedirectURI, req.State, "AUTH_CODE")
38 | },
39 | NewTokenResponse: func(req authserver.TokenRequest) (int, string) {
40 | if want := "AUTH_CODE"; req.Code != want {
41 | t.Errorf("code wants %s but %s", want, req.Code)
42 | return 400, invalidGrantResponse
43 | }
44 | return 200, validTokenResponse
45 | },
46 | })
47 | defer testServer.Close()
48 | cfg := oauth2cli.Config{
49 | OAuth2Config: oauth2.Config{
50 | ClientID: "YOUR_CLIENT_ID",
51 | ClientSecret: "YOUR_CLIENT_SECRET",
52 | Scopes: []string{"email", "profile"},
53 | Endpoint: oauth2.Endpoint{
54 | AuthURL: testServer.URL + "/auth",
55 | TokenURL: testServer.URL + "/token",
56 | },
57 | },
58 | LocalServerCertFile: "testdata/server.crt",
59 | LocalServerKeyFile: "testdata/server.key",
60 | LocalServerReadyChan: openBrowserCh,
61 | LocalServerMiddleware: loggingMiddleware(t),
62 | Logf: t.Logf,
63 | }
64 | token, err := oauth2cli.GetToken(ctx, cfg)
65 | if err != nil {
66 | t.Errorf("could not get a token: %s", err)
67 | return
68 | }
69 | if token.AccessToken != "ACCESS_TOKEN" {
70 | t.Errorf("AccessToken wants %s but %s", "ACCESS_TOKEN", token.AccessToken)
71 | }
72 | if token.RefreshToken != "REFRESH_TOKEN" {
73 | t.Errorf("RefreshToken wants %s but %s", "REFRESH_TOKEN", token.RefreshToken)
74 | }
75 | }()
76 | wg.Add(1)
77 | go func() {
78 | defer wg.Done()
79 | toURL, ok := <-openBrowserCh
80 | if !ok {
81 | t.Errorf("server already closed")
82 | return
83 | }
84 | client.GetAndVerify(t, toURL, 200, oauth2cli.DefaultLocalServerSuccessHTML)
85 | }()
86 | wg.Wait()
87 | }
88 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # oauth2cli example
2 |
3 | This is an example application using oauth2cli.
4 |
5 | To build this application,
6 |
7 | ```sh
8 | go build
9 | ```
10 |
11 | ## Google
12 |
13 | Create your OAuth client.
14 |
15 | 1. Open https://console.cloud.google.com/apis/credentials
16 | 1. Create an OAuth client ID where the application type is other.
17 |
18 | Run this application.
19 |
20 | ```sh
21 | ./example -client-id xxx.apps.googleusercontent.com -client-secret xxxxxxxx
22 | ```
23 |
24 | ```console
25 | 2019/10/03 00:01:35 Open http://localhost:53753
26 | ...
27 | 2019/10/03 00:01:40 You got a valid token until 2019-10-03 01:01:40.083238 +0900 JST m=+3604.526750517
28 | ```
29 |
30 | It will automatically open the browser and you can log in to Google.
31 |
32 | ### Use a TLS certificate
33 |
34 | You can set a certificate and key for the local server.
35 |
36 | ```sh
37 | ./example -client-id xxx.apps.googleusercontent.com -client-secret xxxxxxxx \
38 | -local-server-cert ../e2e_test/testdata/cert.pem -local-server-key ../e2e_test/testdata/cert-key.pem
39 | ```
40 |
41 | ## GitHub
42 |
43 | Create your OAuth App.
44 | Set the callback URL to `http://localhost`.
45 |
46 | Run this application.
47 |
48 | ```sh
49 | ./example -auth-url https://github.com/login/oauth/authorize -token-url https://github.com/login/oauth/access_token -client-id xxxxxxxx -client-secret xxxxxxxx
50 | ```
51 |
52 | ```console
53 | 09:52:45.384489 main.go:84: Open http://localhost:61865
54 | 09:52:45.384507 server.go:36: oauth2cli: starting a server at 127.0.0.1:61865
55 | 09:52:45.491072 server.go:135: oauth2cli: sending redirect to https://github.com/login/oauth/authorize?client_id=...
56 | ```
57 |
58 | You can set `-scopes` flag to request the scopes.
59 | See https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps.
60 |
61 | It will automatically open the browser and you can log in to GitHub.
62 |
--------------------------------------------------------------------------------
/example/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "flag"
6 | "fmt"
7 | "log"
8 | "os"
9 | "strings"
10 |
11 | "github.com/int128/oauth2cli"
12 | "github.com/pkg/browser"
13 | "golang.org/x/oauth2"
14 | "golang.org/x/sync/errgroup"
15 | )
16 |
17 | func init() {
18 | log.SetFlags(log.Lshortfile | log.Lmicroseconds)
19 | }
20 |
21 | type cmdOptions struct {
22 | authURL string
23 | tokenURL string
24 | clientID string
25 | clientSecret string
26 | scopes string
27 | localServerCert string
28 | localServerKey string
29 | }
30 |
31 | func main() {
32 | var o cmdOptions
33 | flag.StringVar(&o.authURL, "auth-url", "https://accounts.google.com/o/oauth2/auth", "Authorization URL of the endpoint")
34 | flag.StringVar(&o.tokenURL, "token-url", "https://oauth2.googleapis.com/token", "Authorization URL of the endpoint")
35 | flag.StringVar(&o.clientID, "client-id", "", "OAuth Client ID")
36 | flag.StringVar(&o.clientSecret, "client-secret", "", "OAuth Client Secret (optional)")
37 | flag.StringVar(&o.scopes, "scopes", "email", "Scopes to request, comma separated")
38 | flag.StringVar(&o.localServerCert, "local-server-cert", "", "Path to a certificate file for the local server (optional)")
39 | flag.StringVar(&o.localServerKey, "local-server-key", "", "Path to a key file for the local server (optional)")
40 | flag.Parse()
41 | if o.clientID == "" {
42 | log.Printf(`You need to set oauth2 credentials.
43 | Open https://console.cloud.google.com/apis/credentials and create a client.
44 | Then set the following options:`)
45 | flag.PrintDefaults()
46 | os.Exit(1)
47 | return
48 | }
49 | if o.localServerCert != "" {
50 | log.Printf("Using the TLS certificate: %s", o.localServerCert)
51 | }
52 |
53 | ready := make(chan string, 1)
54 | defer close(ready)
55 | pkceVerifier := oauth2.GenerateVerifier()
56 | cfg := oauth2cli.Config{
57 | OAuth2Config: oauth2.Config{
58 | ClientID: o.clientID,
59 | ClientSecret: o.clientSecret,
60 | Endpoint: oauth2.Endpoint{
61 | AuthURL: o.authURL,
62 | TokenURL: o.tokenURL,
63 | },
64 | Scopes: strings.Split(o.scopes, ","),
65 | },
66 | AuthCodeOptions: []oauth2.AuthCodeOption{oauth2.S256ChallengeOption(pkceVerifier)},
67 | TokenRequestOptions: []oauth2.AuthCodeOption{oauth2.VerifierOption(pkceVerifier)},
68 | LocalServerReadyChan: ready,
69 | LocalServerCertFile: o.localServerCert,
70 | LocalServerKeyFile: o.localServerKey,
71 | Logf: log.Printf,
72 | }
73 |
74 | ctx := context.Background()
75 | eg, ctx := errgroup.WithContext(ctx)
76 | eg.Go(func() error {
77 | select {
78 | case url := <-ready:
79 | log.Printf("Open %s", url)
80 | if err := browser.OpenURL(url); err != nil {
81 | log.Printf("could not open the browser: %s", err)
82 | }
83 | return nil
84 | case <-ctx.Done():
85 | return fmt.Errorf("context done while waiting for authorization: %w", ctx.Err())
86 | }
87 | })
88 | eg.Go(func() error {
89 | token, err := oauth2cli.GetToken(ctx, cfg)
90 | if err != nil {
91 | return fmt.Errorf("could not get a token: %w", err)
92 | }
93 | log.Printf("You got a valid token until %s", token.Expiry)
94 | return nil
95 | })
96 | if err := eg.Wait(); err != nil {
97 | log.Fatalf("authorization error: %s", err)
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/int128/oauth2cli
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.24.4
6 |
7 | require (
8 | github.com/google/go-cmp v0.7.0
9 | github.com/int128/listener v1.3.0
10 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
11 | golang.org/x/oauth2 v0.30.0
12 | golang.org/x/sync v0.15.0
13 | )
14 |
15 | require golang.org/x/sys v0.1.0 // indirect
16 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
2 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
3 | github.com/int128/listener v1.3.0 h1:ZFePbpzFUt1i6hBSY15rzqo8tHZHJPPQkqCtgOAwS8g=
4 | github.com/int128/listener v1.3.0/go.mod h1:zF9mx2wn+2J/7Idmxi5kgqrGgERr6vr8fK8KqENrRZ0=
5 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
6 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
7 | golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
8 | golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
9 | golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
10 | golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
11 | golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
12 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
13 |
--------------------------------------------------------------------------------
/oauth2cli.go:
--------------------------------------------------------------------------------
1 | // Package oauth2cli provides better user experience on OAuth 2.0 and OpenID Connect (OIDC) on CLI.
2 | // It allows simple and easy user interaction with Authorization Code Grant Flow and a local server.
3 | package oauth2cli
4 |
5 | import (
6 | "context"
7 | "fmt"
8 | "net/http"
9 |
10 | "github.com/int128/oauth2cli/oauth2params"
11 | "golang.org/x/oauth2"
12 | )
13 |
14 | var noopMiddleware = func(h http.Handler) http.Handler { return h }
15 |
16 | // DefaultLocalServerSuccessHTML is a default response body on authorization success.
17 | const DefaultLocalServerSuccessHTML = `
18 |
19 |
20 |
21 |
22 | Authorized
23 |
26 |
40 |
41 |
42 |
43 |
Authorized
44 |
You can close this window.
45 |
46 |
47 |
48 | `
49 |
50 | // Config represents a config for GetToken.
51 | type Config struct {
52 | // OAuth2 config.
53 | // If the RedirectURL field is not set, default to http://localhost with the allocated port and LocalServerCallbackPath.
54 | // If the RedirectURL field is set, make sure it matches the LocalServerBindAddress and LocalServerCallbackPath.
55 | OAuth2Config oauth2.Config
56 |
57 | // Options for an authorization request.
58 | // You can set oauth2.AccessTypeOffline or oauth2.S256ChallengeOption.
59 | AuthCodeOptions []oauth2.AuthCodeOption
60 |
61 | // Options for a token request.
62 | // You can set oauth2.VerifierOption.
63 | TokenRequestOptions []oauth2.AuthCodeOption
64 |
65 | // State parameter in the authorization request.
66 | // Default to a string of random 32 bytes.
67 | State string
68 |
69 | // DEPRECATED: Set OAuth2Config.RedirectURL instead.
70 | RedirectURLHostname string
71 |
72 | // Candidates of hostname and port which the local server binds to.
73 | // You can set port number to 0 to allocate a free port.
74 | // If multiple addresses are given, it will try the ports in order.
75 | // If nil or an empty slice is given, it defaults to "127.0.0.1:0" i.e. a free port.
76 | LocalServerBindAddress []string
77 |
78 | // A PEM-encoded certificate, and possibly the complete certificate chain.
79 | // When set, the server will serve TLS traffic using the specified
80 | // certificates. It's recommended that the public key's SANs contain
81 | // the loopback addresses - 'localhost', '127.0.0.1' and '::1'
82 | LocalServerCertFile string
83 |
84 | // A PEM-encoded private key for the certificate.
85 | // This is required when LocalServerCertFile is set.
86 | LocalServerKeyFile string
87 |
88 | // Callback path of the local server.
89 | // If your provider requires a specific path of the redirect URL, set this field.
90 | // Default to none.
91 | LocalServerCallbackPath string
92 |
93 | // Response HTML body on authorization completed.
94 | // Default to DefaultLocalServerSuccessHTML.
95 | LocalServerSuccessHTML string
96 |
97 | // Middleware for the local server.
98 | // Default to none.
99 | LocalServerMiddleware func(h http.Handler) http.Handler
100 |
101 | // A channel to send the local server URL when it is ready.
102 | // Default to none.
103 | LocalServerReadyChan chan<- string
104 |
105 | // Redirect URL upon successful login
106 | SuccessRedirectURL string
107 |
108 | // Redirect URL upon failed login
109 | FailureRedirectURL string
110 |
111 | // Logger function for debug.
112 | Logf func(format string, args ...interface{})
113 | }
114 |
115 | func (cfg *Config) isLocalServerHTTPS() bool {
116 | return cfg.LocalServerCertFile != "" && cfg.LocalServerKeyFile != ""
117 | }
118 |
119 | func (cfg *Config) validateAndSetDefaults() error {
120 | if (cfg.LocalServerCertFile != "" && cfg.LocalServerKeyFile == "") ||
121 | (cfg.LocalServerCertFile == "" && cfg.LocalServerKeyFile != "") {
122 | return fmt.Errorf("both LocalServerCertFile and LocalServerKeyFile must be set")
123 | }
124 | if cfg.State == "" {
125 | state, err := oauth2params.NewState()
126 | if err != nil {
127 | return fmt.Errorf("could not generate a state parameter: %w", err)
128 | }
129 | cfg.State = state
130 | }
131 | if cfg.LocalServerMiddleware == nil {
132 | cfg.LocalServerMiddleware = noopMiddleware
133 | }
134 | if cfg.LocalServerSuccessHTML == "" {
135 | cfg.LocalServerSuccessHTML = DefaultLocalServerSuccessHTML
136 | }
137 | if (cfg.SuccessRedirectURL != "" && cfg.FailureRedirectURL == "") ||
138 | (cfg.SuccessRedirectURL == "" && cfg.FailureRedirectURL != "") {
139 | return fmt.Errorf("when using success and failure redirect URLs, set both URLs")
140 | }
141 | if cfg.Logf == nil {
142 | cfg.Logf = func(string, ...interface{}) {}
143 | }
144 | return nil
145 | }
146 |
147 | // GetToken performs the Authorization Code Grant Flow and returns a token received from the provider.
148 | // See https://tools.ietf.org/html/rfc6749#section-4.1
149 | //
150 | // This performs the following steps:
151 | //
152 | // 1. Start a local server at the port.
153 | // 2. Open a browser and navigate it to the local server.
154 | // 3. Wait for the user authorization.
155 | // 4. Receive a code via an authorization response (HTTP redirect).
156 | // 5. Exchange the code and a token.
157 | // 6. Return the code.
158 | func GetToken(ctx context.Context, cfg Config) (*oauth2.Token, error) {
159 | if err := cfg.validateAndSetDefaults(); err != nil {
160 | return nil, fmt.Errorf("invalid config: %w", err)
161 | }
162 | code, err := receiveCodeViaLocalServer(ctx, &cfg)
163 | if err != nil {
164 | return nil, fmt.Errorf("authorization error: %w", err)
165 | }
166 | cfg.Logf("oauth2cli: exchanging the code and token")
167 | token, err := cfg.OAuth2Config.Exchange(ctx, code, cfg.TokenRequestOptions...)
168 | if err != nil {
169 | return nil, fmt.Errorf("could not exchange the code and token: %w", err)
170 | }
171 | return token, nil
172 | }
173 |
--------------------------------------------------------------------------------
/oauth2cli_test.go:
--------------------------------------------------------------------------------
1 | package oauth2cli
2 |
--------------------------------------------------------------------------------
/oauth2params/params.go:
--------------------------------------------------------------------------------
1 | // Package oauth2params provides the generators of parameters such as state and PKCE.
2 | package oauth2params
3 |
4 | import (
5 | "crypto/rand"
6 | "encoding/base64"
7 | "encoding/binary"
8 | "fmt"
9 | )
10 |
11 | // NewState returns a state parameter.
12 | // This generates 256 bits of random bytes.
13 | func NewState() (string, error) {
14 | b, err := random(32)
15 | if err != nil {
16 | return "", fmt.Errorf("could not generate a random: %w", err)
17 | }
18 | return base64URLEncode(b), nil
19 | }
20 |
21 | func random(bits int) ([]byte, error) {
22 | b := make([]byte, bits)
23 | if err := binary.Read(rand.Reader, binary.LittleEndian, b); err != nil {
24 | return nil, fmt.Errorf("read error: %w", err)
25 | }
26 | return b, nil
27 | }
28 |
29 | func base64URLEncode(b []byte) string {
30 | return base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(b)
31 | }
32 |
--------------------------------------------------------------------------------
/server.go:
--------------------------------------------------------------------------------
1 | package oauth2cli
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 | "net"
8 | "net/http"
9 | "net/url"
10 | "sync"
11 | "time"
12 |
13 | "github.com/int128/listener"
14 | "golang.org/x/sync/errgroup"
15 | )
16 |
17 | func receiveCodeViaLocalServer(ctx context.Context, cfg *Config) (string, error) {
18 | localServerListener, err := listener.New(cfg.LocalServerBindAddress)
19 | if err != nil {
20 | return "", fmt.Errorf("could not start a local server: %w", err)
21 | }
22 | defer func() {
23 | // The listener may be closed by the server. No need to check the error.
24 | _ = localServerListener.Close()
25 | }()
26 |
27 | if cfg.OAuth2Config.RedirectURL == "" {
28 | var localServerURL url.URL
29 | localServerHostname := "localhost"
30 | if cfg.RedirectURLHostname != "" {
31 | localServerHostname = cfg.RedirectURLHostname
32 | }
33 | localServerURL.Host = fmt.Sprintf("%s:%d", localServerHostname, localServerListener.Addr().(*net.TCPAddr).Port)
34 | localServerURL.Scheme = "http"
35 | if cfg.isLocalServerHTTPS() {
36 | localServerURL.Scheme = "https"
37 | }
38 | localServerURL.Path = cfg.LocalServerCallbackPath
39 | cfg.OAuth2Config.RedirectURL = localServerURL.String()
40 | }
41 |
42 | oauth2RedirectURL, err := url.Parse(cfg.OAuth2Config.RedirectURL)
43 | if err != nil {
44 | return "", fmt.Errorf("invalid OAuth2Config.RedirectURL: %w", err)
45 | }
46 | localServerIndexURL, err := oauth2RedirectURL.Parse("/")
47 | if err != nil {
48 | return "", fmt.Errorf("construct the index URL: %w", err)
49 | }
50 |
51 | respCh := make(chan *authorizationResponse)
52 | server := http.Server{
53 | Handler: cfg.LocalServerMiddleware(&localServerHandler{
54 | config: cfg,
55 | respCh: respCh,
56 | }),
57 | }
58 | shutdownCh := make(chan struct{})
59 | var resp *authorizationResponse
60 | var eg errgroup.Group
61 | eg.Go(func() error {
62 | defer close(respCh)
63 | cfg.Logf("oauth2cli: starting a server at %s", localServerListener.Addr())
64 | defer cfg.Logf("oauth2cli: stopped the server")
65 | if cfg.isLocalServerHTTPS() {
66 | if err := server.ServeTLS(localServerListener, cfg.LocalServerCertFile, cfg.LocalServerKeyFile); err != nil {
67 | if errors.Is(err, http.ErrServerClosed) {
68 | return nil
69 | }
70 | return fmt.Errorf("could not start HTTPS server: %w", err)
71 | }
72 | return nil
73 | }
74 | if err := server.Serve(localServerListener); err != nil && err != http.ErrServerClosed {
75 | return fmt.Errorf("could not start HTTP server: %w", err)
76 | }
77 | return nil
78 | })
79 | eg.Go(func() error {
80 | defer close(shutdownCh)
81 | select {
82 | case gotResp, ok := <-respCh:
83 | if ok {
84 | resp = gotResp
85 | }
86 | return nil
87 | case <-ctx.Done():
88 | return ctx.Err()
89 | }
90 | })
91 | eg.Go(func() error {
92 | <-shutdownCh
93 | // Gracefully shutdown the server in the timeout.
94 | // If the server has not started, Shutdown returns nil and this returns immediately.
95 | // If Shutdown has failed, force-close the server.
96 | cfg.Logf("oauth2cli: shutting down the server")
97 | ctx, cancel := context.WithTimeout(ctx, 500*time.Millisecond)
98 | defer cancel()
99 | if err := server.Shutdown(ctx); err != nil {
100 | cfg.Logf("oauth2cli: force-closing the server: shutdown failed: %s", err)
101 | _ = server.Close()
102 | return nil
103 | }
104 | return nil
105 | })
106 | eg.Go(func() error {
107 | if cfg.LocalServerReadyChan == nil {
108 | return nil
109 | }
110 | select {
111 | case cfg.LocalServerReadyChan <- localServerIndexURL.String():
112 | return nil
113 | case <-ctx.Done():
114 | return ctx.Err()
115 | }
116 | })
117 | if err := eg.Wait(); err != nil {
118 | return "", fmt.Errorf("authorization error: %w", err)
119 | }
120 | if resp == nil {
121 | return "", errors.New("no authorization response")
122 | }
123 | return resp.code, resp.err
124 | }
125 |
126 | type authorizationResponse struct {
127 | code string // non-empty if a valid code is received
128 | err error // non-nil if an error is received or any error occurs
129 | }
130 |
131 | type localServerHandler struct {
132 | config *Config
133 | respCh chan<- *authorizationResponse // channel to send a response to
134 | onceRespCh sync.Once // ensure send once
135 | }
136 |
137 | func (h *localServerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
138 | callbackPath := h.config.LocalServerCallbackPath
139 | if callbackPath == "" {
140 | callbackPath = "/"
141 | }
142 | q := r.URL.Query()
143 | switch {
144 | case r.Method == "GET" && r.URL.Path == callbackPath && q.Get("error") != "":
145 | h.onceRespCh.Do(func() {
146 | h.respCh <- h.handleErrorResponse(w, r)
147 | })
148 | case r.Method == "GET" && r.URL.Path == callbackPath && q.Get("code") != "":
149 | h.onceRespCh.Do(func() {
150 | h.respCh <- h.handleCodeResponse(w, r)
151 | })
152 | case r.Method == "GET" && r.URL.Path == "/":
153 | h.handleIndex(w, r)
154 | default:
155 | http.NotFound(w, r)
156 | }
157 | }
158 |
159 | func (h *localServerHandler) handleIndex(w http.ResponseWriter, r *http.Request) {
160 | authCodeURL := h.config.OAuth2Config.AuthCodeURL(h.config.State, h.config.AuthCodeOptions...)
161 | h.config.Logf("oauth2cli: sending redirect to %s", authCodeURL)
162 | http.Redirect(w, r, authCodeURL, http.StatusFound)
163 | }
164 |
165 | func (h *localServerHandler) handleCodeResponse(w http.ResponseWriter, r *http.Request) *authorizationResponse {
166 | q := r.URL.Query()
167 | code, state := q.Get("code"), q.Get("state")
168 |
169 | if state != h.config.State {
170 | h.authorizationError(w, r)
171 | return &authorizationResponse{err: fmt.Errorf("state does not match (wants %s but got %s)", h.config.State, state)}
172 | }
173 |
174 | if h.config.SuccessRedirectURL != "" {
175 | http.Redirect(w, r, h.config.SuccessRedirectURL, http.StatusFound)
176 | return &authorizationResponse{code: code}
177 | }
178 |
179 | w.Header().Add("Content-Type", "text/html")
180 | if _, err := fmt.Fprint(w, h.config.LocalServerSuccessHTML); err != nil {
181 | http.Error(w, "server error", http.StatusInternalServerError)
182 | return &authorizationResponse{err: fmt.Errorf("write error: %w", err)}
183 | }
184 | return &authorizationResponse{code: code}
185 | }
186 |
187 | func (h *localServerHandler) handleErrorResponse(w http.ResponseWriter, r *http.Request) *authorizationResponse {
188 | q := r.URL.Query()
189 | errorCode, errorDescription := q.Get("error"), q.Get("error_description")
190 | h.authorizationError(w, r)
191 | return &authorizationResponse{err: fmt.Errorf("authorization error from server: %s %s", errorCode, errorDescription)}
192 | }
193 |
194 | func (h *localServerHandler) authorizationError(w http.ResponseWriter, r *http.Request) {
195 | if h.config.FailureRedirectURL != "" {
196 | http.Redirect(w, r, h.config.FailureRedirectURL, http.StatusFound)
197 | } else {
198 | http.Error(w, "authorization error", http.StatusInternalServerError)
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/tools/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/int128/oauth2cli/tools
2 |
3 | go 1.24.4
4 |
5 | tool github.com/golangci/golangci-lint/v2/cmd/golangci-lint
6 |
7 | require (
8 | 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect
9 | 4d63.com/gochecknoglobals v0.2.2 // indirect
10 | github.com/4meepo/tagalign v1.4.2 // indirect
11 | github.com/Abirdcfly/dupword v0.1.3 // indirect
12 | github.com/Antonboom/errname v1.1.0 // indirect
13 | github.com/Antonboom/nilnil v1.1.0 // indirect
14 | github.com/Antonboom/testifylint v1.6.1 // indirect
15 | github.com/BurntSushi/toml v1.5.0 // indirect
16 | github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect
17 | github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 // indirect
18 | github.com/Masterminds/semver/v3 v3.3.1 // indirect
19 | github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect
20 | github.com/alecthomas/chroma/v2 v2.17.2 // indirect
21 | github.com/alecthomas/go-check-sumtype v0.3.1 // indirect
22 | github.com/alexkohler/nakedret/v2 v2.0.6 // indirect
23 | github.com/alexkohler/prealloc v1.0.0 // indirect
24 | github.com/alingse/asasalint v0.0.11 // indirect
25 | github.com/alingse/nilnesserr v0.2.0 // indirect
26 | github.com/ashanbrown/forbidigo v1.6.0 // indirect
27 | github.com/ashanbrown/makezero v1.2.0 // indirect
28 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
29 | github.com/beorn7/perks v1.0.1 // indirect
30 | github.com/bkielbasa/cyclop v1.2.3 // indirect
31 | github.com/blizzy78/varnamelen v0.8.0 // indirect
32 | github.com/bombsimon/wsl/v4 v4.7.0 // indirect
33 | github.com/breml/bidichk v0.3.3 // indirect
34 | github.com/breml/errchkjson v0.4.1 // indirect
35 | github.com/butuzov/ireturn v0.4.0 // indirect
36 | github.com/butuzov/mirror v1.3.0 // indirect
37 | github.com/catenacyber/perfsprint v0.9.1 // indirect
38 | github.com/ccojocar/zxcvbn-go v1.0.2 // indirect
39 | github.com/cespare/xxhash/v2 v2.3.0 // indirect
40 | github.com/charithe/durationcheck v0.0.10 // indirect
41 | github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
42 | github.com/charmbracelet/lipgloss v1.1.0 // indirect
43 | github.com/charmbracelet/x/ansi v0.8.0 // indirect
44 | github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
45 | github.com/charmbracelet/x/term v0.2.1 // indirect
46 | github.com/chavacava/garif v0.1.0 // indirect
47 | github.com/ckaznocha/intrange v0.3.1 // indirect
48 | github.com/curioswitch/go-reassign v0.3.0 // indirect
49 | github.com/daixiang0/gci v0.13.6 // indirect
50 | github.com/dave/dst v0.27.3 // indirect
51 | github.com/davecgh/go-spew v1.1.1 // indirect
52 | github.com/denis-tingaikin/go-header v0.5.0 // indirect
53 | github.com/dlclark/regexp2 v1.11.5 // indirect
54 | github.com/ettle/strcase v0.2.0 // indirect
55 | github.com/fatih/color v1.18.0 // indirect
56 | github.com/fatih/structtag v1.2.0 // indirect
57 | github.com/firefart/nonamedreturns v1.0.6 // indirect
58 | github.com/fsnotify/fsnotify v1.5.4 // indirect
59 | github.com/fzipp/gocyclo v0.6.0 // indirect
60 | github.com/ghostiam/protogetter v0.3.15 // indirect
61 | github.com/go-critic/go-critic v0.13.0 // indirect
62 | github.com/go-toolsmith/astcast v1.1.0 // indirect
63 | github.com/go-toolsmith/astcopy v1.1.0 // indirect
64 | github.com/go-toolsmith/astequal v1.2.0 // indirect
65 | github.com/go-toolsmith/astfmt v1.1.0 // indirect
66 | github.com/go-toolsmith/astp v1.1.0 // indirect
67 | github.com/go-toolsmith/strparse v1.1.0 // indirect
68 | github.com/go-toolsmith/typep v1.1.0 // indirect
69 | github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
70 | github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect
71 | github.com/gobwas/glob v0.2.3 // indirect
72 | github.com/gofrs/flock v0.12.1 // indirect
73 | github.com/golang/protobuf v1.5.3 // indirect
74 | github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect
75 | github.com/golangci/go-printf-func-name v0.1.0 // indirect
76 | github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect
77 | github.com/golangci/golangci-lint/v2 v2.1.6 // indirect
78 | github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95 // indirect
79 | github.com/golangci/misspell v0.6.0 // indirect
80 | github.com/golangci/plugin-module-register v0.1.1 // indirect
81 | github.com/golangci/revgrep v0.8.0 // indirect
82 | github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e // indirect
83 | github.com/google/go-cmp v0.7.0 // indirect
84 | github.com/gordonklaus/ineffassign v0.1.0 // indirect
85 | github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
86 | github.com/gostaticanalysis/comment v1.5.0 // indirect
87 | github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect
88 | github.com/gostaticanalysis/nilerr v0.1.1 // indirect
89 | github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect
90 | github.com/hashicorp/go-version v1.7.0 // indirect
91 | github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
92 | github.com/hashicorp/hcl v1.0.0 // indirect
93 | github.com/hexops/gotextdiff v1.0.3 // indirect
94 | github.com/inconshreveable/mousetrap v1.1.0 // indirect
95 | github.com/jgautheron/goconst v1.8.1 // indirect
96 | github.com/jingyugao/rowserrcheck v1.1.1 // indirect
97 | github.com/jjti/go-spancheck v0.6.4 // indirect
98 | github.com/julz/importas v0.2.0 // indirect
99 | github.com/karamaru-alpha/copyloopvar v1.2.1 // indirect
100 | github.com/kisielk/errcheck v1.9.0 // indirect
101 | github.com/kkHAIKE/contextcheck v1.1.6 // indirect
102 | github.com/kulti/thelper v0.6.3 // indirect
103 | github.com/kunwardeep/paralleltest v1.0.14 // indirect
104 | github.com/lasiar/canonicalheader v1.1.2 // indirect
105 | github.com/ldez/exptostd v0.4.3 // indirect
106 | github.com/ldez/gomoddirectives v0.6.1 // indirect
107 | github.com/ldez/grignotin v0.9.0 // indirect
108 | github.com/ldez/tagliatelle v0.7.1 // indirect
109 | github.com/ldez/usetesting v0.4.3 // indirect
110 | github.com/leonklingele/grouper v1.1.2 // indirect
111 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
112 | github.com/macabu/inamedparam v0.2.0 // indirect
113 | github.com/magiconair/properties v1.8.6 // indirect
114 | github.com/manuelarte/funcorder v0.2.1 // indirect
115 | github.com/maratori/testableexamples v1.0.0 // indirect
116 | github.com/maratori/testpackage v1.1.1 // indirect
117 | github.com/matoous/godox v1.1.0 // indirect
118 | github.com/mattn/go-colorable v0.1.14 // indirect
119 | github.com/mattn/go-isatty v0.0.20 // indirect
120 | github.com/mattn/go-runewidth v0.0.16 // indirect
121 | github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
122 | github.com/mgechev/revive v1.9.0 // indirect
123 | github.com/mitchellh/go-homedir v1.1.0 // indirect
124 | github.com/mitchellh/mapstructure v1.5.0 // indirect
125 | github.com/moricho/tparallel v0.3.2 // indirect
126 | github.com/muesli/termenv v0.16.0 // indirect
127 | github.com/nakabonne/nestif v0.3.1 // indirect
128 | github.com/nishanths/exhaustive v0.12.0 // indirect
129 | github.com/nishanths/predeclared v0.2.2 // indirect
130 | github.com/nunnatsa/ginkgolinter v0.19.1 // indirect
131 | github.com/olekukonko/tablewriter v0.0.5 // indirect
132 | github.com/pelletier/go-toml v1.9.5 // indirect
133 | github.com/pelletier/go-toml/v2 v2.2.4 // indirect
134 | github.com/pmezard/go-difflib v1.0.0 // indirect
135 | github.com/polyfloyd/go-errorlint v1.8.0 // indirect
136 | github.com/prometheus/client_golang v1.12.1 // indirect
137 | github.com/prometheus/client_model v0.2.0 // indirect
138 | github.com/prometheus/common v0.32.1 // indirect
139 | github.com/prometheus/procfs v0.7.3 // indirect
140 | github.com/quasilyte/go-ruleguard v0.4.4 // indirect
141 | github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect
142 | github.com/quasilyte/gogrep v0.5.0 // indirect
143 | github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
144 | github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect
145 | github.com/raeperd/recvcheck v0.2.0 // indirect
146 | github.com/rivo/uniseg v0.4.7 // indirect
147 | github.com/rogpeppe/go-internal v1.14.1 // indirect
148 | github.com/ryancurrah/gomodguard v1.4.1 // indirect
149 | github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect
150 | github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect
151 | github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect
152 | github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
153 | github.com/sashamelentyev/usestdlibvars v1.28.0 // indirect
154 | github.com/securego/gosec/v2 v2.22.3 // indirect
155 | github.com/sirupsen/logrus v1.9.3 // indirect
156 | github.com/sivchari/containedctx v1.0.3 // indirect
157 | github.com/sonatard/noctx v0.1.0 // indirect
158 | github.com/sourcegraph/go-diff v0.7.0 // indirect
159 | github.com/spf13/afero v1.14.0 // indirect
160 | github.com/spf13/cast v1.5.0 // indirect
161 | github.com/spf13/cobra v1.9.1 // indirect
162 | github.com/spf13/jwalterweatherman v1.1.0 // indirect
163 | github.com/spf13/pflag v1.0.6 // indirect
164 | github.com/spf13/viper v1.12.0 // indirect
165 | github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
166 | github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect
167 | github.com/stretchr/objx v0.5.2 // indirect
168 | github.com/stretchr/testify v1.10.0 // indirect
169 | github.com/subosito/gotenv v1.4.1 // indirect
170 | github.com/tdakkota/asciicheck v0.4.1 // indirect
171 | github.com/tetafro/godot v1.5.1 // indirect
172 | github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 // indirect
173 | github.com/timonwong/loggercheck v0.11.0 // indirect
174 | github.com/tomarrell/wrapcheck/v2 v2.11.0 // indirect
175 | github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
176 | github.com/ultraware/funlen v0.2.0 // indirect
177 | github.com/ultraware/whitespace v0.2.0 // indirect
178 | github.com/uudashr/gocognit v1.2.0 // indirect
179 | github.com/uudashr/iface v1.3.1 // indirect
180 | github.com/xen0n/gosmopolitan v1.3.0 // indirect
181 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
182 | github.com/yagipy/maintidx v1.0.0 // indirect
183 | github.com/yeya24/promlinter v0.3.0 // indirect
184 | github.com/ykadowak/zerologlint v0.1.5 // indirect
185 | gitlab.com/bosi/decorder v0.4.2 // indirect
186 | go-simpler.org/musttag v0.13.1 // indirect
187 | go-simpler.org/sloglint v0.11.0 // indirect
188 | go.augendre.info/fatcontext v0.8.0 // indirect
189 | go.uber.org/atomic v1.7.0 // indirect
190 | go.uber.org/automaxprocs v1.6.0 // indirect
191 | go.uber.org/multierr v1.6.0 // indirect
192 | go.uber.org/zap v1.24.0 // indirect
193 | golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect
194 | golang.org/x/mod v0.24.0 // indirect
195 | golang.org/x/sync v0.13.0 // indirect
196 | golang.org/x/sys v0.32.0 // indirect
197 | golang.org/x/text v0.24.0 // indirect
198 | golang.org/x/tools v0.32.0 // indirect
199 | google.golang.org/protobuf v1.36.6 // indirect
200 | gopkg.in/ini.v1 v1.67.0 // indirect
201 | gopkg.in/yaml.v2 v2.4.0 // indirect
202 | gopkg.in/yaml.v3 v3.0.1 // indirect
203 | honnef.co/go/tools v0.6.1 // indirect
204 | mvdan.cc/gofumpt v0.8.0 // indirect
205 | mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 // indirect
206 | )
207 |
--------------------------------------------------------------------------------
/tools/go.sum:
--------------------------------------------------------------------------------
1 | 4d63.com/gocheckcompilerdirectives v1.3.0 h1:Ew5y5CtcAAQeTVKUVFrE7EwHMrTO6BggtEj8BZSjZ3A=
2 | 4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY=
3 | 4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU=
4 | 4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0=
5 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
6 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
7 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
8 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
9 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
10 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
11 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
12 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
13 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
14 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
15 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
16 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
17 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
18 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
19 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
20 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
21 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
22 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
23 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
24 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
25 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
26 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
27 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
28 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
29 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
30 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
31 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
32 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
33 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
34 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
35 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
36 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
37 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
38 | github.com/4meepo/tagalign v1.4.2 h1:0hcLHPGMjDyM1gHG58cS73aQF8J4TdVR96TZViorO9E=
39 | github.com/4meepo/tagalign v1.4.2/go.mod h1:+p4aMyFM+ra7nb41CnFG6aSDXqRxU/w1VQqScKqDARI=
40 | github.com/Abirdcfly/dupword v0.1.3 h1:9Pa1NuAsZvpFPi9Pqkd93I7LIYRURj+A//dFd5tgBeE=
41 | github.com/Abirdcfly/dupword v0.1.3/go.mod h1:8VbB2t7e10KRNdwTVoxdBaxla6avbhGzb8sCTygUMhw=
42 | github.com/Antonboom/errname v1.1.0 h1:A+ucvdpMwlo/myWrkHEUEBWc/xuXdud23S8tmTb/oAE=
43 | github.com/Antonboom/errname v1.1.0/go.mod h1:O1NMrzgUcVBGIfi3xlVuvX8Q/VP/73sseCaAppfjqZw=
44 | github.com/Antonboom/nilnil v1.1.0 h1:jGxJxjgYS3VUUtOTNk8Z1icwT5ESpLH/426fjmQG+ng=
45 | github.com/Antonboom/nilnil v1.1.0/go.mod h1:b7sAlogQjFa1wV8jUW3o4PMzDVFLbTux+xnQdvzdcIE=
46 | github.com/Antonboom/testifylint v1.6.1 h1:6ZSytkFWatT8mwZlmRCHkWz1gPi+q6UBSbieji2Gj/o=
47 | github.com/Antonboom/testifylint v1.6.1/go.mod h1:k+nEkathI2NFjKO6HvwmSrbzUcQ6FAnbZV+ZRrnXPLI=
48 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
49 | github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
50 | github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
51 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
52 | github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM=
53 | github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
54 | github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 h1:Sz1JIXEcSfhz7fUi7xHnhpIE0thVASYjvosApmHuD2k=
55 | github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1/go.mod h1:n/LSCXNuIYqVfBlVXyHfMQkZDdp1/mmxfSjADd3z1Zg=
56 | github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
57 | github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
58 | github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4=
59 | github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo=
60 | github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
61 | github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
62 | github.com/alecthomas/chroma/v2 v2.17.2 h1:Rm81SCZ2mPoH+Q8ZCc/9YvzPUN/E7HgPiPJD8SLV6GI=
63 | github.com/alecthomas/chroma/v2 v2.17.2/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk=
64 | github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU=
65 | github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E=
66 | github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
67 | github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
68 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
69 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
70 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
71 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
72 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
73 | github.com/alexkohler/nakedret/v2 v2.0.6 h1:ME3Qef1/KIKr3kWX3nti3hhgNxw6aqN5pZmQiFSsuzQ=
74 | github.com/alexkohler/nakedret/v2 v2.0.6/go.mod h1:l3RKju/IzOMQHmsEvXwkqMDzHHvurNQfAgE1eVmT40Q=
75 | github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw=
76 | github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE=
77 | github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw=
78 | github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I=
79 | github.com/alingse/nilnesserr v0.2.0 h1:raLem5KG7EFVb4UIDAXgrv3N2JIaffeKNtcEXkEWd/w=
80 | github.com/alingse/nilnesserr v0.2.0/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg=
81 | github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY=
82 | github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU=
83 | github.com/ashanbrown/makezero v1.2.0 h1:/2Lp1bypdmK9wDIq7uWBlDF1iMUpIIS4A+pF6C9IEUU=
84 | github.com/ashanbrown/makezero v1.2.0/go.mod h1:dxlPhHbDMC6N6xICzFBSK+4njQDdK8euNO0qjQMtGY4=
85 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
86 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
87 | github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
88 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
89 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
90 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
91 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
92 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
93 | github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w=
94 | github.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8ztxG8Fuo=
95 | github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M=
96 | github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k=
97 | github.com/bombsimon/wsl/v4 v4.7.0 h1:1Ilm9JBPRczjyUs6hvOPKvd7VL1Q++PL8M0SXBDf+jQ=
98 | github.com/bombsimon/wsl/v4 v4.7.0/go.mod h1:uV/+6BkffuzSAVYD+yGyld1AChO7/EuLrCF/8xTiapg=
99 | github.com/breml/bidichk v0.3.3 h1:WSM67ztRusf1sMoqH6/c4OBCUlRVTKq+CbSeo0R17sE=
100 | github.com/breml/bidichk v0.3.3/go.mod h1:ISbsut8OnjB367j5NseXEGGgO/th206dVa427kR8YTE=
101 | github.com/breml/errchkjson v0.4.1 h1:keFSS8D7A2T0haP9kzZTi7o26r7kE3vymjZNeNDRDwg=
102 | github.com/breml/errchkjson v0.4.1/go.mod h1:a23OvR6Qvcl7DG/Z4o0el6BRAjKnaReoPQFciAl9U3s=
103 | github.com/butuzov/ireturn v0.4.0 h1:+s76bF/PfeKEdbG8b54aCocxXmi0wvYdOVsWxVO7n8E=
104 | github.com/butuzov/ireturn v0.4.0/go.mod h1:ghI0FrCmap8pDWZwfPisFD1vEc56VKH4NpQUxDHta70=
105 | github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc=
106 | github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI=
107 | github.com/catenacyber/perfsprint v0.9.1 h1:5LlTp4RwTooQjJCvGEFV6XksZvWE7wCOUvjD2z0vls0=
108 | github.com/catenacyber/perfsprint v0.9.1/go.mod h1:q//VWC2fWbcdSLEY1R3l8n0zQCDPdE4IjZwyY1HMunM=
109 | github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg=
110 | github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60=
111 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
112 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
113 | github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
114 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
115 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
116 | github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4=
117 | github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ=
118 | github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
119 | github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
120 | github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
121 | github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
122 | github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
123 | github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
124 | github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
125 | github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
126 | github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
127 | github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
128 | github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc=
129 | github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww=
130 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
131 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
132 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
133 | github.com/ckaznocha/intrange v0.3.1 h1:j1onQyXvHUsPWujDH6WIjhyH26gkRt/txNlV7LspvJs=
134 | github.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nWh/s6SHIJJk=
135 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
136 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
137 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
138 | github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs=
139 | github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88=
140 | github.com/daixiang0/gci v0.13.6 h1:RKuEOSkGpSadkGbvZ6hJ4ddItT3cVZ9Vn9Rybk6xjl8=
141 | github.com/daixiang0/gci v0.13.6/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk=
142 | github.com/dave/dst v0.27.3 h1:P1HPoMza3cMEquVf9kKy8yXsFirry4zEnWOdYPOoIzY=
143 | github.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc=
144 | github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo=
145 | github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
146 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
147 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
148 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
149 | github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8=
150 | github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY=
151 | github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
152 | github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
153 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
154 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
155 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
156 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
157 | github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q=
158 | github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A=
159 | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
160 | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
161 | github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
162 | github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
163 | github.com/firefart/nonamedreturns v1.0.6 h1:vmiBcKV/3EqKY3ZiPxCINmpS431OcE1S47AQUwhrg8E=
164 | github.com/firefart/nonamedreturns v1.0.6/go.mod h1:R8NisJnSIpvPWheCq0mNRXJok6D8h7fagJTF8EMEwCo=
165 | github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
166 | github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
167 | github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
168 | github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
169 | github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
170 | github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
171 | github.com/ghostiam/protogetter v0.3.15 h1:1KF5sXel0HE48zh1/vn0Loiw25A9ApyseLzQuif1mLY=
172 | github.com/ghostiam/protogetter v0.3.15/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA=
173 | github.com/go-critic/go-critic v0.13.0 h1:kJzM7wzltQasSUXtYyTl6UaPVySO6GkaR1thFnJ6afY=
174 | github.com/go-critic/go-critic v0.13.0/go.mod h1:M/YeuJ3vOCQDnP2SU+ZhjgRzwzcBW87JqLpMJLrZDLI=
175 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
176 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
177 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
178 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
179 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
180 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
181 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
182 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
183 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
184 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
185 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
186 | github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
187 | github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
188 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
189 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
190 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
191 | github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8=
192 | github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU=
193 | github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s=
194 | github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw=
195 | github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4=
196 | github.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ=
197 | github.com/go-toolsmith/astequal v1.2.0 h1:3Fs3CYZ1k9Vo4FzFhwwewC3CHISHDnVUPC4x0bI2+Cw=
198 | github.com/go-toolsmith/astequal v1.2.0/go.mod h1:c8NZ3+kSFtFY/8lPso4v8LuJjdJiUFVnSuU3s0qrrDY=
199 | github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco=
200 | github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4=
201 | github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA=
202 | github.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA=
203 | github.com/go-toolsmith/pkgload v1.2.2 h1:0CtmHq/02QhxcF7E9N5LIFcYFsMR5rdovfqTtRKkgIk=
204 | github.com/go-toolsmith/pkgload v1.2.2/go.mod h1:R2hxLNRKuAsiXCo2i5J6ZQPhnPMOVtU+f0arbFPWCus=
205 | github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8=
206 | github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw=
207 | github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ=
208 | github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus=
209 | github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig=
210 | github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
211 | github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
212 | github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY=
213 | github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
214 | github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
215 | github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
216 | github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
217 | github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
218 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
219 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
220 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
221 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
222 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
223 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
224 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
225 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
226 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
227 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
228 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
229 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
230 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
231 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
232 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
233 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
234 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
235 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
236 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
237 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
238 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
239 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
240 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
241 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
242 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
243 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
244 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
245 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
246 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
247 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
248 | github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw=
249 | github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E=
250 | github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUPPyAKJuzv8pEJU=
251 | github.com/golangci/go-printf-func-name v0.1.0/go.mod h1:wqhWFH5mUdJQhweRnldEywnR5021wTdZSNgwYceV14s=
252 | github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE=
253 | github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY=
254 | github.com/golangci/golangci-lint/v2 v2.1.6 h1:LXqShFfAGM5BDzEOWD2SL1IzJAgUOqES/HRBsfKjI+w=
255 | github.com/golangci/golangci-lint/v2 v2.1.6/go.mod h1:EPj+fgv4TeeBq3TcqaKZb3vkiV5dP4hHHKhXhEhzci8=
256 | github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95 h1:AkK+w9FZBXlU/xUmBtSJN1+tAI4FIvy5WtnUnY8e4p8=
257 | github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95/go.mod h1:k9mmcyWKSTMcPPvQUCfRWWQ9VHJ1U9Dc0R7kaXAgtnQ=
258 | github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs=
259 | github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo=
260 | github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c=
261 | github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc=
262 | github.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s=
263 | github.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k=
264 | github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e h1:gD6P7NEo7Eqtt0ssnqSJNNndxe69DOQ24A5h7+i3KpM=
265 | github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e/go.mod h1:h+wZwLjUTJnm/P2rwlbJdRPZXOzaT36/FwnPnY2inzc=
266 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
267 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
268 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
269 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
270 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
271 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
272 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
273 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
274 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
275 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
276 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
277 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
278 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
279 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
280 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
281 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
282 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
283 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
284 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
285 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
286 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
287 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
288 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
289 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
290 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
291 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
292 | github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
293 | github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
294 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
295 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
296 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
297 | github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s=
298 | github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0=
299 | github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk=
300 | github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc=
301 | github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado=
302 | github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM=
303 | github.com/gostaticanalysis/comment v1.5.0 h1:X82FLl+TswsUMpMh17srGRuKaaXprTaytmEpgnKIDu8=
304 | github.com/gostaticanalysis/comment v1.5.0/go.mod h1:V6eb3gpCv9GNVqb6amXzEUX3jXLVK/AdA+IrAMSqvEc=
305 | github.com/gostaticanalysis/forcetypeassert v0.2.0 h1:uSnWrrUEYDr86OCxWa4/Tp2jeYDlogZiZHzGkWFefTk=
306 | github.com/gostaticanalysis/forcetypeassert v0.2.0/go.mod h1:M5iPavzE9pPqWyeiVXSFghQjljW1+l/Uke3PXHS6ILY=
307 | github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3Uqrmrcpk=
308 | github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A=
309 | github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M=
310 | github.com/gostaticanalysis/testutil v0.5.0 h1:Dq4wT1DdTwTGCQQv3rl3IvD5Ld0E6HiY+3Zh0sUGqw8=
311 | github.com/gostaticanalysis/testutil v0.5.0/go.mod h1:OLQSbuM6zw2EvCcXTz1lVq5unyoNft372msDY0nY5Hs=
312 | github.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo=
313 | github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw=
314 | github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
315 | github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
316 | github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
317 | github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
318 | github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
319 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
320 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
321 | github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
322 | github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
323 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
324 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
325 | github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
326 | github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
327 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
328 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
329 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
330 | github.com/jgautheron/goconst v1.8.1 h1:PPqCYp3K/xlOj5JmIe6O1Mj6r1DbkdbLtR3AJuZo414=
331 | github.com/jgautheron/goconst v1.8.1/go.mod h1:A0oxgBCHy55NQn6sYpO7UdnA9p+h7cPtoOZUmvNIako=
332 | github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs=
333 | github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c=
334 | github.com/jjti/go-spancheck v0.6.4 h1:Tl7gQpYf4/TMU7AT84MN83/6PutY21Nb9fuQjFTpRRc=
335 | github.com/jjti/go-spancheck v0.6.4/go.mod h1:yAEYdKJ2lRkDA8g7X+oKUHXOWVAXSBJRv04OhF+QUjk=
336 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
337 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
338 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
339 | github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
340 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
341 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
342 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
343 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
344 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
345 | github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ=
346 | github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY=
347 | github.com/karamaru-alpha/copyloopvar v1.2.1 h1:wmZaZYIjnJ0b5UoKDjUHrikcV0zuPyyxI4SVplLd2CI=
348 | github.com/karamaru-alpha/copyloopvar v1.2.1/go.mod h1:nFmMlFNlClC2BPvNaHMdkirmTJxVCY0lhxBtlfOypMM=
349 | github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M=
350 | github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8=
351 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
352 | github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE=
353 | github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg=
354 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
355 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
356 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
357 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
358 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
359 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
360 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
361 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
362 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
363 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
364 | github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs=
365 | github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I=
366 | github.com/kunwardeep/paralleltest v1.0.14 h1:wAkMoMeGX/kGfhQBPODT/BL8XhK23ol/nuQ3SwFaUw8=
367 | github.com/kunwardeep/paralleltest v1.0.14/go.mod h1:di4moFqtfz3ToSKxhNjhOZL+696QtJGCFe132CbBLGk=
368 | github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4=
369 | github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI=
370 | github.com/ldez/exptostd v0.4.3 h1:Ag1aGiq2epGePuRJhez2mzOpZ8sI9Gimcb4Sb3+pk9Y=
371 | github.com/ldez/exptostd v0.4.3/go.mod h1:iZBRYaUmcW5jwCR3KROEZ1KivQQp6PHXbDPk9hqJKCQ=
372 | github.com/ldez/gomoddirectives v0.6.1 h1:Z+PxGAY+217f/bSGjNZr/b2KTXcyYLgiWI6geMBN2Qc=
373 | github.com/ldez/gomoddirectives v0.6.1/go.mod h1:cVBiu3AHR9V31em9u2kwfMKD43ayN5/XDgr+cdaFaKs=
374 | github.com/ldez/grignotin v0.9.0 h1:MgOEmjZIVNn6p5wPaGp/0OKWyvq42KnzAt/DAb8O4Ow=
375 | github.com/ldez/grignotin v0.9.0/go.mod h1:uaVTr0SoZ1KBii33c47O1M8Jp3OP3YDwhZCmzT9GHEk=
376 | github.com/ldez/tagliatelle v0.7.1 h1:bTgKjjc2sQcsgPiT902+aadvMjCeMHrY7ly2XKFORIk=
377 | github.com/ldez/tagliatelle v0.7.1/go.mod h1:3zjxUpsNB2aEZScWiZTHrAXOl1x25t3cRmzfK1mlo2I=
378 | github.com/ldez/usetesting v0.4.3 h1:pJpN0x3fMupdTf/IapYjnkhiY1nSTN+pox1/GyBRw3k=
379 | github.com/ldez/usetesting v0.4.3/go.mod h1:eEs46T3PpQ+9RgN9VjpY6qWdiw2/QmfiDeWmdZdrjIQ=
380 | github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY=
381 | github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA=
382 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
383 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
384 | github.com/macabu/inamedparam v0.2.0 h1:VyPYpOc10nkhI2qeNUdh3Zket4fcZjEWe35poddBCpE=
385 | github.com/macabu/inamedparam v0.2.0/go.mod h1:+Pee9/YfGe5LJ62pYXqB89lJ+0k5bsR8Wgz/C0Zlq3U=
386 | github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
387 | github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
388 | github.com/manuelarte/funcorder v0.2.1 h1:7QJsw3qhljoZ5rH0xapIvjw31EcQeFbF31/7kQ/xS34=
389 | github.com/manuelarte/funcorder v0.2.1/go.mod h1:BQQ0yW57+PF9ZpjpeJDKOffEsQbxDFKW8F8zSMe/Zd0=
390 | github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI=
391 | github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE=
392 | github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04=
393 | github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc=
394 | github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4=
395 | github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs=
396 | github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
397 | github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
398 | github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
399 | github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
400 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
401 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
402 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
403 | github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
404 | github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
405 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
406 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
407 | github.com/mgechev/revive v1.9.0 h1:8LaA62XIKrb8lM6VsBSQ92slt/o92z5+hTw3CmrvSrM=
408 | github.com/mgechev/revive v1.9.0/go.mod h1:LAPq3+MgOf7GcL5PlWIkHb0PT7XH4NuC2LdWymhb9Mo=
409 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
410 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
411 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
412 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
413 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
414 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
415 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
416 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
417 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
418 | github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI=
419 | github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U=
420 | github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
421 | github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
422 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
423 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
424 | github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U=
425 | github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE=
426 | github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg=
427 | github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs=
428 | github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk=
429 | github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c=
430 | github.com/nunnatsa/ginkgolinter v0.19.1 h1:mjwbOlDQxZi9Cal+KfbEJTCz327OLNfwNvoZ70NJ+c4=
431 | github.com/nunnatsa/ginkgolinter v0.19.1/go.mod h1:jkQ3naZDmxaZMXPWaS9rblH+i+GWXQCaS/JFIWcOH2s=
432 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
433 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
434 | github.com/onsi/ginkgo/v2 v2.23.3 h1:edHxnszytJ4lD9D5Jjc4tiDkPBZ3siDeJJkUZJJVkp0=
435 | github.com/onsi/ginkgo/v2 v2.23.3/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM=
436 | github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU=
437 | github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
438 | github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=
439 | github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
440 | github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
441 | github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
442 | github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
443 | github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
444 | github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
445 | github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
446 | github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
447 | github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
448 | github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
449 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
450 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
451 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
452 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
453 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
454 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
455 | github.com/polyfloyd/go-errorlint v1.8.0 h1:DL4RestQqRLr8U4LygLw8g2DX6RN1eBJOpa2mzsrl1Q=
456 | github.com/polyfloyd/go-errorlint v1.8.0/go.mod h1:G2W0Q5roxbLCt0ZQbdoxQxXktTjwNyDbEaj3n7jvl4s=
457 | github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
458 | github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
459 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
460 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
461 | github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
462 | github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
463 | github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
464 | github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
465 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
466 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
467 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
468 | github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
469 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
470 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
471 | github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
472 | github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
473 | github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
474 | github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
475 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
476 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
477 | github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
478 | github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
479 | github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
480 | github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
481 | github.com/quasilyte/go-ruleguard v0.4.4 h1:53DncefIeLX3qEpjzlS1lyUmQoUEeOWPFWqaTJq9eAQ=
482 | github.com/quasilyte/go-ruleguard v0.4.4/go.mod h1:Vl05zJ538vcEEwu16V/Hdu7IYZWyKSwIy4c88Ro1kRE=
483 | github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE=
484 | github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
485 | github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo=
486 | github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng=
487 | github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU=
488 | github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0=
489 | github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs=
490 | github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ=
491 | github.com/raeperd/recvcheck v0.2.0 h1:GnU+NsbiCqdC2XX5+vMZzP+jAJC5fht7rcVTAhX74UI=
492 | github.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU=
493 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
494 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
495 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
496 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
497 | github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
498 | github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
499 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
500 | github.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb7Elr+g=
501 | github.com/ryancurrah/gomodguard v1.4.1/go.mod h1:qnMJwV1hX9m+YJseXEBhd2s90+1Xn6x9dLz11ualI1I=
502 | github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU=
503 | github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ=
504 | github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0=
505 | github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4=
506 | github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw=
507 | github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
508 | github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw=
509 | github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ=
510 | github.com/sashamelentyev/usestdlibvars v1.28.0 h1:jZnudE2zKCtYlGzLVreNp5pmCdOxXUzwsMDBkR21cyQ=
511 | github.com/sashamelentyev/usestdlibvars v1.28.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8=
512 | github.com/securego/gosec/v2 v2.22.3 h1:mRrCNmRF2NgZp4RJ8oJ6yPJ7G4x6OCiAXHd8x4trLRc=
513 | github.com/securego/gosec/v2 v2.22.3/go.mod h1:42M9Xs0v1WseinaB/BmNGO8AVqG8vRfhC2686ACY48k=
514 | github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
515 | github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
516 | github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
517 | github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
518 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
519 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
520 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
521 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
522 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
523 | github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE=
524 | github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4=
525 | github.com/sonatard/noctx v0.1.0 h1:JjqOc2WN16ISWAjAk8M5ej0RfExEXtkEyExl2hLW+OM=
526 | github.com/sonatard/noctx v0.1.0/go.mod h1:0RvBxqY8D4j9cTTTWE8ylt2vqj2EPI8fHmrxHdsaZ2c=
527 | github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0=
528 | github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs=
529 | github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
530 | github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
531 | github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
532 | github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
533 | github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
534 | github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
535 | github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
536 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
537 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
538 | github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
539 | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
540 | github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ=
541 | github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI=
542 | github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0=
543 | github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I=
544 | github.com/stbenjam/no-sprintf-host-port v0.2.0 h1:i8pxvGrt1+4G0czLr/WnmyH7zbZ8Bg8etvARQ1rpyl4=
545 | github.com/stbenjam/no-sprintf-host-port v0.2.0/go.mod h1:eL0bQ9PasS0hsyTyfTjjG+E80QIyPnBVQbYZyv20Jfk=
546 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
547 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
548 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
549 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
550 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
551 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
552 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
553 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
554 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
555 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
556 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
557 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
558 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
559 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
560 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
561 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
562 | github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
563 | github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
564 | github.com/tdakkota/asciicheck v0.4.1 h1:bm0tbcmi0jezRA2b5kg4ozmMuGAFotKI3RZfrhfovg8=
565 | github.com/tdakkota/asciicheck v0.4.1/go.mod h1:0k7M3rCfRXb0Z6bwgvkEIMleKH3kXNz9UqJ9Xuqopr8=
566 | github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA=
567 | github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0=
568 | github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag=
569 | github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY=
570 | github.com/tetafro/godot v1.5.1 h1:PZnjCol4+FqaEzvZg5+O8IY2P3hfY9JzRBNPv1pEDS4=
571 | github.com/tetafro/godot v1.5.1/go.mod h1:cCdPtEndkmqqrhiCfkmxDodMQJ/f3L1BCNskCUZdTwk=
572 | github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 h1:9LPGD+jzxMlnk5r6+hJnar67cgpDIz/iyD+rfl5r2Vk=
573 | github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460=
574 | github.com/timonwong/loggercheck v0.11.0 h1:jdaMpYBl+Uq9mWPXv1r8jc5fC3gyXx4/WGwTnnNKn4M=
575 | github.com/timonwong/loggercheck v0.11.0/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8=
576 | github.com/tomarrell/wrapcheck/v2 v2.11.0 h1:BJSt36snX9+4WTIXeJ7nvHBQBcm1h2SjQMSlmQ6aFSU=
577 | github.com/tomarrell/wrapcheck/v2 v2.11.0/go.mod h1:wFL9pDWDAbXhhPZZt+nG8Fu+h29TtnZ2MW6Lx4BRXIU=
578 | github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw=
579 | github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw=
580 | github.com/ultraware/funlen v0.2.0 h1:gCHmCn+d2/1SemTdYMiKLAHFYxTYz7z9VIDRaTGyLkI=
581 | github.com/ultraware/funlen v0.2.0/go.mod h1:ZE0q4TsJ8T1SQcjmkhN/w+MceuatI6pBFSxxyteHIJA=
582 | github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSWoFa+g=
583 | github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8=
584 | github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA=
585 | github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU=
586 | github.com/uudashr/iface v1.3.1 h1:bA51vmVx1UIhiIsQFSNq6GZ6VPTk3WNMZgRiCe9R29U=
587 | github.com/uudashr/iface v1.3.1/go.mod h1:4QvspiRd3JLPAEXBQ9AiZpLbJlrWWgRChOKDJEuQTdg=
588 | github.com/xen0n/gosmopolitan v1.3.0 h1:zAZI1zefvo7gcpbCOrPSHJZJYA9ZgLfJqtKzZ5pHqQM=
589 | github.com/xen0n/gosmopolitan v1.3.0/go.mod h1:rckfr5T6o4lBtM1ga7mLGKZmLxswUoH1zxHgNXOsEt4=
590 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
591 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
592 | github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM=
593 | github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk=
594 | github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs=
595 | github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4=
596 | github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw=
597 | github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg=
598 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
599 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
600 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
601 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
602 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
603 | github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
604 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
605 | gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo=
606 | gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8=
607 | go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ=
608 | go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28=
609 | go-simpler.org/musttag v0.13.1 h1:lw2sJyu7S1X8lc8zWUAdH42y+afdcCnHhWpnkWvd6vU=
610 | go-simpler.org/musttag v0.13.1/go.mod h1:8r450ehpMLQgvpb6sg+hV5Ur47eH6olp/3yEanfG97k=
611 | go-simpler.org/sloglint v0.11.0 h1:JlR1X4jkbeaffiyjLtymeqmGDKBDO1ikC6rjiuFAOco=
612 | go-simpler.org/sloglint v0.11.0/go.mod h1:CFDO8R1i77dlciGfPEPvYke2ZMx4eyGiEIWkyeW2Pvw=
613 | go.augendre.info/fatcontext v0.8.0 h1:2dfk6CQbDGeu1YocF59Za5Pia7ULeAM6friJ3LP7lmk=
614 | go.augendre.info/fatcontext v0.8.0/go.mod h1:oVJfMgwngMsHO+KB2MdgzcO+RvtNdiCEOlWvSFtax/s=
615 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
616 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
617 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
618 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
619 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
620 | go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
621 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
622 | go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
623 | go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
624 | go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
625 | go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
626 | go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
627 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
628 | go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
629 | go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
630 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
631 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
632 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
633 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
634 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
635 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
636 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
637 | golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
638 | golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
639 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
640 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
641 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
642 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
643 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
644 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
645 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
646 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
647 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
648 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
649 | golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
650 | golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
651 | golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
652 | golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
653 | golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac h1:TSSpLIG4v+p0rPv1pNOQtl1I8knsO4S9trOxNMOLVP4=
654 | golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
655 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
656 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
657 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
658 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
659 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
660 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
661 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
662 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
663 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
664 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
665 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
666 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
667 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
668 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
669 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
670 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
671 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
672 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
673 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
674 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
675 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
676 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
677 | golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
678 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
679 | golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
680 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
681 | golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
682 | golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
683 | golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
684 | golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
685 | golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
686 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
687 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
688 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
689 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
690 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
691 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
692 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
693 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
694 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
695 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
696 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
697 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
698 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
699 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
700 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
701 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
702 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
703 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
704 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
705 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
706 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
707 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
708 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
709 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
710 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
711 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
712 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
713 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
714 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
715 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
716 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
717 | golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
718 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
719 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
720 | golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
721 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
722 | golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
723 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
724 | golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
725 | golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
726 | golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
727 | golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
728 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
729 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
730 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
731 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
732 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
733 | golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
734 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
735 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
736 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
737 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
738 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
739 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
740 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
741 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
742 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
743 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
744 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
745 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
746 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
747 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
748 | golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
749 | golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
750 | golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
751 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
752 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
753 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
754 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
755 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
756 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
757 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
758 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
759 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
760 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
761 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
762 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
763 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
764 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
765 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
766 | golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
767 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
768 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
769 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
770 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
771 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
772 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
773 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
774 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
775 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
776 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
777 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
778 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
779 | golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
780 | golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
781 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
782 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
783 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
784 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
785 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
786 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
787 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
788 | golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
789 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
790 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
791 | golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
792 | golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
793 | golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
794 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
795 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
796 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
797 | golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
798 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
799 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
800 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
801 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
802 | golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
803 | golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
804 | golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
805 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
806 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
807 | golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
808 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
809 | golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
810 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
811 | golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
812 | golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
813 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
814 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
815 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
816 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
817 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
818 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
819 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
820 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
821 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
822 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
823 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
824 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
825 | golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
826 | golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
827 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
828 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
829 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
830 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
831 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
832 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
833 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
834 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
835 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
836 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
837 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
838 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
839 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
840 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
841 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
842 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
843 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
844 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
845 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
846 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
847 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
848 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
849 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
850 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
851 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
852 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
853 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
854 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
855 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
856 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
857 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
858 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
859 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
860 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
861 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
862 | golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
863 | golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
864 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
865 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
866 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
867 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
868 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
869 | golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
870 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
871 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
872 | golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
873 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
874 | golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
875 | golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU=
876 | golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU=
877 | golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
878 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
879 | golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
880 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
881 | golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
882 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
883 | golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
884 | golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
885 | golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
886 | golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
887 | golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
888 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
889 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
890 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
891 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
892 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
893 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
894 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
895 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
896 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
897 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
898 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
899 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
900 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
901 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
902 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
903 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
904 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
905 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
906 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
907 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
908 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
909 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
910 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
911 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
912 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
913 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
914 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
915 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
916 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
917 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
918 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
919 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
920 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
921 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
922 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
923 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
924 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
925 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
926 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
927 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
928 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
929 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
930 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
931 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
932 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
933 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
934 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
935 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
936 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
937 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
938 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
939 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
940 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
941 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
942 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
943 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
944 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
945 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
946 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
947 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
948 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
949 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
950 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
951 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
952 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
953 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
954 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
955 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
956 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
957 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
958 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
959 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
960 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
961 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
962 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
963 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
964 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
965 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
966 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
967 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
968 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
969 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
970 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
971 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
972 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
973 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
974 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
975 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
976 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
977 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
978 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
979 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
980 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
981 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
982 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
983 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
984 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
985 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
986 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
987 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
988 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
989 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
990 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
991 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
992 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
993 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
994 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
995 | honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI=
996 | honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4=
997 | mvdan.cc/gofumpt v0.8.0 h1:nZUCeC2ViFaerTcYKstMmfysj6uhQrA2vJe+2vwGU6k=
998 | mvdan.cc/gofumpt v0.8.0/go.mod h1:vEYnSzyGPmjvFkqJWtXkh79UwPWP9/HMxQdGEXZHjpg=
999 | mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 h1:WjUu4yQoT5BHT1w8Zu56SP8367OuBV5jvo+4Ulppyf8=
1000 | mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4/go.mod h1:rthT7OuvRbaGcd5ginj6dA2oLE7YNlta9qhBNNdCaLE=
1001 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
1002 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
1003 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
1004 |
--------------------------------------------------------------------------------