├── .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 ![go](https://github.com/int128/oauth2cli/workflows/go/badge.svg) [![GoDoc](https://godoc.org/github.com/int128/oauth2cli?status.svg)](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 | ![diagram](docs/diagram.svg) 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 |
Command
Command
Browser
Browser
Authorization
server
Authorization<br>server
Run
Run
Exit
Exit
Open
Open
Close
Close
Local
server
[Not supported by viewer]
(redirect)
(redirect)
Success Response
Success Response
Authorization Request
Authorization Request
Login / Consent
Login / Consent
Token Request
Token Request
Token Response
Token Response
Authentication and Authorization
Authentication and Authorization
Authorization Response (redirect)
Authorization Response (redirect)
Authentication and Authorization
Authentication and Authorization
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 | --------------------------------------------------------------------------------