├── .github ├── renovate.json5 └── workflows │ ├── e2e-test.yaml │ ├── go.yaml │ ├── manifest.yaml │ └── release.yaml ├── .gitignore ├── .krew.yaml ├── LICENSE ├── Makefile ├── README.md ├── docs └── kauthproxy.svg ├── e2e_test ├── Makefile ├── kauthproxy-role.yaml ├── kustomization.yaml ├── main.go └── publish-screenshot.sh ├── go.mod ├── go.sum ├── internal ├── authproxy │ ├── auth_proxy.go │ └── auth_proxy_test.go ├── browser │ └── browser.go ├── cmd │ └── cmd.go ├── di │ ├── di.go │ └── wire_gen.go ├── env │ └── env.go ├── logger │ ├── logger.go │ └── mock_logger │ │ └── mock_logger.go ├── mocks │ ├── mock_browser │ │ └── mock.go │ ├── mock_env │ │ └── mock.go │ ├── mock_portforwarder │ │ └── mock.go │ ├── mock_resolver │ │ └── mock.go │ └── mock_reverseproxy │ │ └── mock.go ├── portforwarder │ └── port_forwarder.go ├── resolver │ └── resolver.go ├── reverseproxy │ └── reverse_proxy.go └── transport │ └── transport.go └── main.go /.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:go-directive#v1.7.2", 7 | "github>int128/go-renovate-config:github-actions#v1.7.2", 8 | "github>int128/go-renovate-config:kubernetes#v1.7.2", 9 | "github>int128/go-renovate-config:kustomization-github-releases#v1.7.2", 10 | ], 11 | } 12 | -------------------------------------------------------------------------------- /.github/workflows/e2e-test.yaml: -------------------------------------------------------------------------------- 1 | name: e2e-test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - .github/workflows/e2e-test.yaml 9 | - internal/** 10 | - e2e_test/** 11 | - go.* 12 | pull_request: 13 | branches: 14 | - master 15 | paths: 16 | - .github/workflows/e2e-test.yaml 17 | - internal/** 18 | - e2e_test/** 19 | - go.* 20 | 21 | jobs: 22 | e2e-test: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 26 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 27 | with: 28 | go-version-file: go.mod 29 | cache-dependency-path: go.sum 30 | - run: make -C e2e_test -j3 31 | - run: git config --global user.email ci@example.com 32 | - run: git config --global user.name ci 33 | - run: make -C e2e_test ci-publish-screenshot 34 | env: 35 | GITHUB_TOKEN: ${{ github.token }} 36 | -------------------------------------------------------------------------------- /.github/workflows/go.yaml: -------------------------------------------------------------------------------- 1 | name: go 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - .github/workflows/go.yaml 9 | - internal/** 10 | - '**/go.*' 11 | pull_request: 12 | branches: 13 | - master 14 | paths: 15 | - .github/workflows/go.yaml 16 | - internal/** 17 | - '**/go.*' 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 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 26 | with: 27 | go-version-file: go.mod 28 | cache-dependency-path: go.sum 29 | - run: make test 30 | 31 | lint: 32 | runs-on: ubuntu-latest 33 | timeout-minutes: 10 34 | steps: 35 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 36 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 37 | with: 38 | go-version-file: go.mod 39 | cache-dependency-path: go.sum 40 | - run: make lint 41 | 42 | generate: 43 | runs-on: ubuntu-latest 44 | timeout-minutes: 10 45 | steps: 46 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 47 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 48 | with: 49 | go-version-file: go.mod 50 | cache-dependency-path: go.sum 51 | - run: go mod tidy 52 | - run: make generate 53 | - uses: int128/update-generated-files-action@759eb040026a5d97663b33e61243a56949f86bea # v2.59.0 54 | -------------------------------------------------------------------------------- /.github/workflows/manifest.yaml: -------------------------------------------------------------------------------- 1 | name: manifest 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | paths: 8 | - e2e_test/*.yaml 9 | - .github/workflows/manifest.yaml 10 | 11 | jobs: 12 | manifest: 13 | uses: int128/kubebuilder-workflows/.github/workflows/manifest.yaml@e4ad31a438f188f70bde87827a8dbe466e623dea # v1.5.0 14 | permissions: 15 | contents: read 16 | pull-requests: write 17 | with: 18 | kustomization: | 19 | e2e_test/kustomization.yaml 20 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | branches: 8 | - master 9 | paths: 10 | - .github/workflows/release.yaml 11 | - internal/** 12 | - go.* 13 | pull_request: 14 | branches: 15 | - master 16 | paths: 17 | - .github/workflows/release.yaml 18 | - internal/** 19 | - go.* 20 | 21 | jobs: 22 | build: 23 | strategy: 24 | matrix: 25 | platform: 26 | - runs-on: ubuntu-latest 27 | GOOS: linux 28 | GOARCH: amd64 29 | - runs-on: ubuntu-latest 30 | GOOS: linux 31 | GOARCH: arm64 32 | - runs-on: ubuntu-latest 33 | GOOS: linux 34 | GOARCH: arm 35 | - runs-on: ubuntu-latest 36 | GOOS: darwin 37 | GOARCH: amd64 38 | - runs-on: ubuntu-latest 39 | GOOS: darwin 40 | GOARCH: arm64 41 | - runs-on: ubuntu-latest 42 | GOOS: windows 43 | GOARCH: amd64 44 | runs-on: ${{ matrix.platform.runs-on }} 45 | env: 46 | GOOS: ${{ matrix.platform.GOOS }} 47 | GOARCH: ${{ matrix.platform.GOARCH }} 48 | CGO_ENABLED: 0 49 | timeout-minutes: 10 50 | steps: 51 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 52 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 53 | with: 54 | go-version-file: go.mod 55 | cache-dependency-path: go.sum 56 | - run: go build -ldflags '-X main.version=${{ github.ref_name }}' 57 | - uses: int128/go-release-action@2979cc5b15ceb7ae458e95b0a9467afc7ae25259 # v2.0.0 58 | with: 59 | binary: kauthproxy 60 | 61 | publish: 62 | if: github.ref_type == 'tag' 63 | needs: 64 | - build 65 | runs-on: ubuntu-latest 66 | timeout-minutes: 10 67 | steps: 68 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 69 | - uses: rajatjindal/krew-release-bot@3d9faef30a82761d610544f62afddca00993eef9 # v0.0.47 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /tools/bin/ 3 | 4 | /dist/output 5 | /coverage.out 6 | /kauthproxy 7 | 8 | /e2e_test/output 9 | -------------------------------------------------------------------------------- /.krew.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: krew.googlecontainertools.github.com/v1alpha2 2 | kind: Plugin 3 | metadata: 4 | name: auth-proxy 5 | spec: 6 | homepage: https://github.com/int128/kauthproxy 7 | shortDescription: Authentication proxy to a pod or service 8 | description: | 9 | This is a kubectl plugin to forward a local port to a pod or service via the authentication proxy. 10 | It gets a token from the current credential plugin (e.g. aws-iam-authenticator or kubelogin). 11 | Then it appends the authorization header to HTTP requests, like `authorization: Bearer token`. 12 | All traffic is routed by the authentication proxy and port forwarder as follows: 13 | [browser] -> [authentication proxy] -> [port forwarder] -> [pod] 14 | 15 | caveats: | 16 | You need to configure authentication in the kubeconfig. 17 | See https://github.com/int128/kauthproxy for more. 18 | 19 | version: {{ .TagName }} 20 | platforms: 21 | - bin: kauthproxy 22 | {{ addURIAndSha "https://github.com/int128/kauthproxy/releases/download/{{ .TagName }}/kauthproxy_linux_amd64.zip" .TagName }} 23 | selector: 24 | matchLabels: 25 | os: linux 26 | arch: amd64 27 | - bin: kauthproxy 28 | {{ addURIAndSha "https://github.com/int128/kauthproxy/releases/download/{{ .TagName }}/kauthproxy_linux_arm64.zip" .TagName }} 29 | selector: 30 | matchLabels: 31 | os: linux 32 | arch: arm64 33 | - bin: kauthproxy 34 | {{ addURIAndSha "https://github.com/int128/kauthproxy/releases/download/{{ .TagName }}/kauthproxy_linux_arm.zip" .TagName }} 35 | selector: 36 | matchLabels: 37 | os: linux 38 | arch: arm 39 | - bin: kauthproxy 40 | {{ addURIAndSha "https://github.com/int128/kauthproxy/releases/download/{{ .TagName }}/kauthproxy_darwin_amd64.zip" .TagName }} 41 | selector: 42 | matchLabels: 43 | os: darwin 44 | arch: amd64 45 | - bin: kauthproxy 46 | {{ addURIAndSha "https://github.com/int128/kauthproxy/releases/download/{{ .TagName }}/kauthproxy_darwin_arm64.zip" .TagName }} 47 | selector: 48 | matchLabels: 49 | os: darwin 50 | arch: arm64 51 | - bin: kauthproxy.exe 52 | {{ addURIAndSha "https://github.com/int128/kauthproxy/releases/download/{{ .TagName }}/kauthproxy_windows_amd64.zip" .TagName }} 53 | selector: 54 | matchLabels: 55 | os: windows 56 | arch: amd64 57 | -------------------------------------------------------------------------------- /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 | Copyright 2019 Hidetake Iwata 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); 181 | you may not use this file except in compliance with the License. 182 | You may obtain a copy of the License at 183 | 184 | http://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software 187 | distributed under the License is distributed on an "AS IS" BASIS, 188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 189 | See the License for the specific language governing permissions and 190 | limitations under the License. 191 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all 2 | all: 3 | 4 | .PHONY: test 5 | test: 6 | go test -v -race ./internal/... 7 | 8 | .PHONY: generate 9 | generate: 10 | go tool github.com/google/wire/cmd/wire ./internal/di 11 | rm -fr internal/mocks 12 | go tool go.uber.org/mock/mockgen -destination internal/mocks/mock_browser/mock.go github.com/int128/kauthproxy/internal/browser Interface 13 | go tool go.uber.org/mock/mockgen -destination internal/mocks/mock_env/mock.go github.com/int128/kauthproxy/internal/env Interface 14 | go tool go.uber.org/mock/mockgen -destination internal/mocks/mock_portforwarder/mock.go github.com/int128/kauthproxy/internal/portforwarder Interface 15 | go tool go.uber.org/mock/mockgen -destination internal/mocks/mock_resolver/mock.go github.com/int128/kauthproxy/internal/resolver FactoryInterface,Interface 16 | go tool go.uber.org/mock/mockgen -destination internal/mocks/mock_reverseproxy/mock.go github.com/int128/kauthproxy/internal/reverseproxy Interface,Instance 17 | 18 | .PHONY: lint 19 | lint: 20 | go tool github.com/golangci/golangci-lint/cmd/golangci-lint run 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kauthproxy [![go](https://github.com/int128/kauthproxy/actions/workflows/go.yaml/badge.svg)](https://github.com/int128/kauthproxy/actions/workflows/go.yaml) [![e2e-test](https://github.com/int128/kauthproxy/actions/workflows/e2e-test.yaml/badge.svg)](https://github.com/int128/kauthproxy/actions/workflows/e2e-test.yaml) 2 | 3 | This is a kubectl plugin of the authentication proxy to access [Kubernetes Dashboard](https://github.com/kubernetes/dashboard). 4 | 5 | You can access Kubernetes Dashboard with your credentials instead of [entering a service account token](https://github.com/kubernetes/dashboard/blob/master/docs/user/access-control/creating-sample-user.md). 6 | It provides better **user experience and security**. 7 | 8 | kauthproxy supports the following environments: 9 | 10 | - Amazon EKS 11 | - Azure Kubernetes Service (with Azure AD) 12 | - Self-hosted Kubernetes cluster 13 | - [OpenID Connect tokens authentication](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens) 14 | - [Webhook token authentication](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication) 15 | - [aws-iam-authenticator](https://github.com/kubernetes-sigs/aws-iam-authenticator) 16 | 17 | Note that kauthproxy does not work with [client certificate authentication](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#x509-client-certs). 18 | 19 | 20 | ## Getting Started 21 | 22 | ### Install 23 | 24 | Install the latest release from [Homebrew](https://brew.sh/), [Krew](https://github.com/kubernetes-sigs/krew), [aqua](https://aquaproj.github.io/), or [GitHub Releases](https://github.com/int128/kauthproxy/releases). 25 | 26 | ```sh 27 | # Homebrew (macOS) 28 | brew install int128/kauthproxy/kauthproxy 29 | 30 | # Krew (macOS, Linux and Windows) 31 | kubectl krew install auth-proxy 32 | 33 | # aqua 34 | aqua g -i int128/kauthproxy 35 | ``` 36 | 37 | You can deploy the manifest of Kubernetes Dashboard from [here](https://github.com/kubernetes/dashboard). 38 | 39 | ### Run 40 | 41 | To access Kubernetes Dashboard in your cluster: 42 | 43 | ``` 44 | % kubectl auth-proxy -n kubernetes-dashboard https://kubernetes-dashboard.svc 45 | Starting an authentication proxy for pod/kubernetes-dashboard-57fc4fcb74-jjg77:8443 46 | Open http://127.0.0.1:18000 47 | Forwarding from 127.0.0.1:57866 -> 8443 48 | Forwarding from [::1]:57866 -> 8443 49 | ``` 50 | 51 | It will automatically open the browser. 52 | You can see Kubernetes Dashboard logged in as you. 53 | 54 | [![screenshot](https://github.com/int128/kauthproxy/wiki/refs/heads/master/screenshot.png)](e2e_test) 55 | 56 | 57 | ## How it works 58 | 59 | ### Authentication 60 | 61 | Kubernetes Dashboard supports [header based authentication](https://github.com/kubernetes/dashboard/blob/master/docs/user/access-control/README.md#authorization-header). 62 | kauthproxy forwards HTTP requests from the browser to Kubernetes Dashboard. 63 | 64 | Take a look at the diagram: 65 | 66 | ![diagram](docs/kauthproxy.svg) 67 | 68 | When you access Kubernetes Dashboard, kauthproxy forwards HTTP requests by the following process: 69 | 70 | 1. Acquire your token from the credential plugin or authentication provider. 71 | 1. Set `authorization: bearer TOKEN` header to a request and forward the request to the pod. 72 | 73 | ### Authorization 74 | 75 | kauthproxy requires the following privileges: 76 | 77 | - Get the Service of Kubernetes Dashboard. 78 | - List the Pods of Kubernetes Dashboard. 79 | - Port-forward to the Pod of Kubernetes Dashboard. 80 | 81 | If you need to assign the least privilege for production, 82 | see [an example of `Role`](e2e_test/kauthproxy-role.yaml). 83 | 84 | 85 | ## Usage 86 | 87 | ``` 88 | Usage: 89 | kubectl auth-proxy POD_OR_SERVICE_URL [flags] 90 | 91 | Flags: 92 | --add_dir_header If true, adds the file directory to the header 93 | --address stringArray The address on which to run the proxy. If set multiple times, it will try binding the address in order (default [127.0.0.1:18000,127.0.0.1:28000]) 94 | --alsologtostderr log to standard error as well as files 95 | --as string Username to impersonate for the operation 96 | --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. 97 | --cache-dir string Default HTTP cache directory (default "~/.kube/http-cache") 98 | --certificate-authority string Path to a cert file for the certificate authority 99 | --client-certificate string Path to a client certificate file for TLS 100 | --client-key string Path to a client key file for TLS 101 | --cluster string The name of the kubeconfig cluster to use 102 | --context string The name of the kubeconfig context to use 103 | -h, --help help for kubectl 104 | --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure 105 | --kubeconfig string Path to the kubeconfig file to use for CLI requests. 106 | --log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0) 107 | --log_dir string If non-empty, write log files in this directory 108 | --log_file string If non-empty, use this log file 109 | --log_file_max_size uint Defines the maximum size a log file can grow to. Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800) 110 | --logtostderr log to standard error instead of files (default true) 111 | -n, --namespace string If present, the namespace scope for this CLI request 112 | --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") 113 | -s, --server string The address and port of the Kubernetes API server 114 | --skip-open-browser If set, skip opening the browser 115 | --skip_headers If true, avoid header prefixes in the log messages 116 | --skip_log_headers If true, avoid headers when opening log files 117 | --stderrthreshold severity logs at or above this threshold go to stderr (default 2) 118 | --token string Bearer token for authentication to the API server 119 | --user string The name of the kubeconfig user to use 120 | -v, --v Level number for the log level verbosity 121 | --version version for kubectl 122 | --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging 123 | ``` 124 | 125 | 126 | ## Contributions 127 | 128 | This is an open source software. 129 | Feel free to open issues and pull requests. 130 | 131 | ### End-to-end test 132 | 133 | To provision a cluster: 134 | 135 | ```sh 136 | # requires Docker, Kind and Chrome 137 | brew cask install docker google-chrome 138 | brew install kind 139 | 140 | # provision a cluster and deploy Kubernetes Dashboard 141 | make -C e2e_test deploy 142 | ``` 143 | 144 | You can access the cluster as follows: 145 | 146 | ```sh 147 | export KUBECONFIG=e2e_test/output/kubeconfig.yaml 148 | 149 | # show all pods 150 | kubectl get pods -A 151 | 152 | # open Kubernetes Dashboard 153 | ./kauthproxy -n kubernetes-dashboard --user=tester https://kubernetes-dashboard.svc 154 | ``` 155 | 156 | To run the automated test: 157 | 158 | ```sh 159 | make -C e2e_test test 160 | ``` 161 | 162 | To delete the cluster. 163 | 164 | ```sh 165 | make -C e2e_test delete-cluster 166 | ``` 167 | -------------------------------------------------------------------------------- /docs/kauthproxy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 |
client-go credential plugin
client-go credential plugin
kauthproxy
kauthproxy
http://localhost:18000
http://localhost:18000
User
User
Reverse Proxy
Reverse Proxy
http://pod_ip:cluster_port
http://pod_ip:cluster_port
Port Forwarder
Port Forwarder
Kubernetes cluster
Kubernetes cluster
Pod
Pod
token
token
aws-iam-authenticator /
kubelogin
aws-iam-authenticator /<br>kubelogin
-------------------------------------------------------------------------------- /e2e_test/Makefile: -------------------------------------------------------------------------------- 1 | CLUSTER_NAME := kauthproxy-e2e 2 | KUBECONFIG := output/kubeconfig.yaml 3 | export KUBECONFIG 4 | 5 | .PHONY: all 6 | all: test 7 | 8 | .PHONY: cluster 9 | cluster: $(KUBECONFIG) 10 | $(KUBECONFIG): 11 | kind create cluster --name $(CLUSTER_NAME) 12 | 13 | .PHONY: deploy 14 | deploy: cluster 15 | kustomize build | kubectl apply -f - 16 | kubectl -n kube-system rollout status deployment metrics-server 17 | kubectl -n kubernetes-dashboard rollout status deployment kubernetes-dashboard 18 | kubectl get secret tester-token '-ojsonpath={.data.token}' | base64 --decode | xargs kubectl config set-credentials tester --token 19 | 20 | output/kauthproxy: 21 | go build -o $@ .. 22 | 23 | output/e2e_test: 24 | go build -o $@ . 25 | 26 | .PHONY: test 27 | test: deploy output/e2e_test output/kauthproxy 28 | ./output/e2e_test 29 | 30 | .PHONY: delete-cluster 31 | delete-cluster: 32 | kind delete cluster --name $(CLUSTER_NAME) 33 | -rm $(KUBECONFIG) 34 | 35 | .PHONY: ci-publish-screenshot 36 | ci-publish-screenshot: 37 | ./publish-screenshot.sh 38 | -------------------------------------------------------------------------------- /e2e_test/kauthproxy-role.yaml: -------------------------------------------------------------------------------- 1 | # role for kauthproxy 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | namespace: kubernetes-dashboard 6 | name: kauthproxy 7 | rules: 8 | - apiGroups: [""] 9 | resources: ["services"] 10 | resourceNames: ["kubernetes-dashboard"] 11 | verbs: ["get"] 12 | - apiGroups: [""] 13 | resources: ["pods"] 14 | verbs: ["list"] 15 | - apiGroups: [""] 16 | resources: ["pods/portforward"] 17 | verbs: ["create"] 18 | 19 | --- 20 | # service account for end-to-end test 21 | apiVersion: v1 22 | kind: ServiceAccount 23 | metadata: 24 | name: tester 25 | 26 | --- 27 | apiVersion: v1 28 | kind: Secret 29 | type: kubernetes.io/service-account-token 30 | metadata: 31 | name: tester-token 32 | annotations: 33 | kubernetes.io/service-account.name: tester 34 | 35 | --- 36 | # allow kauthproxy access 37 | apiVersion: rbac.authorization.k8s.io/v1 38 | kind: RoleBinding 39 | metadata: 40 | namespace: kubernetes-dashboard 41 | name: tester 42 | roleRef: 43 | apiGroup: rbac.authorization.k8s.io 44 | kind: Role 45 | name: kauthproxy 46 | subjects: 47 | - kind: ServiceAccount 48 | namespace: default 49 | name: tester 50 | 51 | --- 52 | # allow cluster read-only access 53 | apiVersion: rbac.authorization.k8s.io/v1 54 | kind: ClusterRoleBinding 55 | metadata: 56 | name: tester 57 | roleRef: 58 | apiGroup: rbac.authorization.k8s.io 59 | kind: ClusterRole 60 | name: view 61 | subjects: 62 | - kind: ServiceAccount 63 | namespace: default 64 | name: tester 65 | -------------------------------------------------------------------------------- /e2e_test/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | # https://github.com/kubernetes/dashboard 3 | - https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml 4 | # https://github.com/kubernetes-sigs/metrics-server 5 | - https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.7.2/components.yaml 6 | # Service account for end-to-end test 7 | - kauthproxy-role.yaml 8 | 9 | patchesJson6902: 10 | - target: 11 | group: apps 12 | version: v1 13 | kind: Deployment 14 | name: metrics-server 15 | patch: | 16 | - op: add 17 | path: /spec/template/spec/containers/0/args/-1 18 | value: '--kubelet-insecure-tls' 19 | -------------------------------------------------------------------------------- /e2e_test/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "strings" 10 | "time" 11 | 12 | "github.com/chromedp/chromedp" 13 | "golang.org/x/sync/errgroup" 14 | ) 15 | 16 | func init() { 17 | log.SetFlags(log.Lmicroseconds | log.Lshortfile) 18 | } 19 | 20 | func main() { 21 | ctx := context.Background() 22 | eg, ctx := errgroup.WithContext(ctx) 23 | chInterrupt := make(chan struct{}) 24 | eg.Go(func() error { 25 | defer close(chInterrupt) 26 | return runBrowser(ctx) 27 | }) 28 | eg.Go(func() error { 29 | return runKauthproxy(chInterrupt) 30 | }) 31 | if err := eg.Wait(); err != nil { 32 | log.Fatal(err) 33 | } 34 | } 35 | 36 | func runKauthproxy(chInterrupt <-chan struct{}) error { 37 | c := exec.Command("output/kauthproxy", 38 | "--namespace=kubernetes-dashboard", 39 | "--user=tester", 40 | "--skip-open-browser", 41 | "https://kubernetes-dashboard.svc", 42 | ) 43 | c.Stdout = os.Stdout 44 | c.Stderr = os.Stderr 45 | if err := c.Start(); err != nil { 46 | return fmt.Errorf("could not start a process: %w", err) 47 | } 48 | log.Printf("started %s", c.String()) 49 | <-chInterrupt 50 | if err := c.Process.Signal(os.Interrupt); err != nil { 51 | return fmt.Errorf("could not send SIGINT to the process: %w", err) 52 | } 53 | if err := c.Wait(); err != nil { 54 | return fmt.Errorf("could not wait for the process: %w", err) 55 | } 56 | return nil 57 | } 58 | 59 | func runBrowser(ctx context.Context) error { 60 | execOpts := chromedp.DefaultExecAllocatorOptions[:] 61 | execOpts = append(execOpts, chromedp.NoSandbox) 62 | ctx, cancel := chromedp.NewExecAllocator(ctx, execOpts...) 63 | defer cancel() 64 | ctx, cancel = chromedp.NewContext(ctx, chromedp.WithLogf(log.Printf)) 65 | defer cancel() 66 | ctx, cancel = context.WithTimeout(ctx, 30*time.Second) 67 | defer cancel() 68 | err := chromedp.Run(ctx, 69 | chromedp.EmulateViewport(2048, 1152), 70 | // open the page of pod list 71 | navigate("http://localhost:18000/#/pod?namespace=kube-system"), 72 | // wait for a link on the page 73 | chromedp.WaitReady(`a[href^='#/pod/kube-system']`, chromedp.ByQuery), 74 | takeScreenshot("output/screenshot.png"), 75 | ) 76 | if err != nil { 77 | return fmt.Errorf("could not run the browser: %w", err) 78 | } 79 | return nil 80 | } 81 | 82 | // navigate to the URL and retry on network errors 83 | func navigate(urlstr string) chromedp.Action { 84 | return chromedp.ActionFunc(func(ctx context.Context) error { 85 | for { 86 | var l string 87 | if err := chromedp.Run(ctx, 88 | chromedp.Navigate(urlstr), 89 | chromedp.Location(&l), 90 | ); err != nil { 91 | return err 92 | } 93 | log.Printf("opened %s", l) 94 | if strings.HasPrefix(l, "http://") { 95 | return nil 96 | } 97 | if err := chromedp.Sleep(1 * time.Second).Do(ctx); err != nil { 98 | return err 99 | } 100 | } 101 | }) 102 | } 103 | 104 | // https://github.com/chromedp/examples/blob/master/screenshot/main.go 105 | func takeScreenshot(name string) chromedp.Action { 106 | return chromedp.ActionFunc(func(ctx context.Context) error { 107 | var b []byte 108 | if err := chromedp.FullScreenshot(&b, 90).Do(ctx); err != nil { 109 | return fmt.Errorf("could not capture a screenshot: %w", err) 110 | } 111 | if err := os.WriteFile(name, b, 0644); err != nil { 112 | return fmt.Errorf("could not write: %w", err) 113 | } 114 | log.Printf("saved screenshot to %s", name) 115 | return nil 116 | }) 117 | } 118 | -------------------------------------------------------------------------------- /e2e_test/publish-screenshot.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -o pipefail 3 | set -eux 4 | 5 | wiki_dir="$(mktemp -d)" 6 | screenshot_path="$GITHUB_REF/screenshot.png" 7 | comment_body="## e2e-test 8 | ![screenshot](https://github.com/$GITHUB_REPOSITORY/wiki/$screenshot_path)" 9 | 10 | # publish the screenshot 11 | git clone --depth=1 "https://x:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.wiki.git" "$wiki_dir" 12 | mkdir -p $(dirname "$wiki_dir/$screenshot_path") 13 | cp output/screenshot.png "$wiki_dir/$screenshot_path" 14 | git -C "$wiki_dir" add "$wiki_dir/$screenshot_path" 15 | git -C "$wiki_dir" commit -m "ci-publish-screenshot: $GITHUB_REF" 16 | git -C "$wiki_dir" push origin HEAD 17 | 18 | # comment it to the pull request 19 | if [ "$GITHUB_HEAD_REF" ]; then 20 | gh pr comment "$GITHUB_HEAD_REF" --body "$comment_body" 21 | fi 22 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/int128/kauthproxy 2 | 3 | go 1.24.3 4 | 5 | require ( 6 | github.com/cenkalti/backoff/v4 v4.3.0 7 | github.com/chromedp/chromedp v0.13.6 8 | github.com/google/wire v0.6.0 9 | github.com/int128/listener v1.3.0 10 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c 11 | github.com/spf13/cobra v1.9.1 12 | github.com/spf13/pflag v1.0.6 13 | go.uber.org/mock v0.5.2 14 | golang.org/x/sync v0.14.0 15 | k8s.io/api v0.33.1 16 | k8s.io/apimachinery v0.33.1 17 | k8s.io/cli-runtime v0.33.1 18 | k8s.io/client-go v0.33.1 19 | k8s.io/klog/v2 v2.130.1 20 | ) 21 | 22 | require ( 23 | 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect 24 | 4d63.com/gochecknoglobals v0.2.2 // indirect 25 | github.com/4meepo/tagalign v1.4.2 // indirect 26 | github.com/Abirdcfly/dupword v0.1.3 // indirect 27 | github.com/Antonboom/errname v1.0.0 // indirect 28 | github.com/Antonboom/nilnil v1.0.1 // indirect 29 | github.com/Antonboom/testifylint v1.5.2 // indirect 30 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect 31 | github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect 32 | github.com/Crocmagnon/fatcontext v0.7.1 // indirect 33 | github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect 34 | github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 // indirect 35 | github.com/Masterminds/semver/v3 v3.3.0 // indirect 36 | github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect 37 | github.com/alecthomas/go-check-sumtype v0.3.1 // indirect 38 | github.com/alexkohler/nakedret/v2 v2.0.5 // indirect 39 | github.com/alexkohler/prealloc v1.0.0 // indirect 40 | github.com/alingse/asasalint v0.0.11 // indirect 41 | github.com/alingse/nilnesserr v0.1.2 // indirect 42 | github.com/ashanbrown/forbidigo v1.6.0 // indirect 43 | github.com/ashanbrown/makezero v1.2.0 // indirect 44 | github.com/beorn7/perks v1.0.1 // indirect 45 | github.com/bkielbasa/cyclop v1.2.3 // indirect 46 | github.com/blang/semver/v4 v4.0.0 // indirect 47 | github.com/blizzy78/varnamelen v0.8.0 // indirect 48 | github.com/bombsimon/wsl/v4 v4.5.0 // indirect 49 | github.com/breml/bidichk v0.3.2 // indirect 50 | github.com/breml/errchkjson v0.4.0 // indirect 51 | github.com/butuzov/ireturn v0.3.1 // indirect 52 | github.com/butuzov/mirror v1.3.0 // indirect 53 | github.com/catenacyber/perfsprint v0.8.2 // indirect 54 | github.com/ccojocar/zxcvbn-go v1.0.2 // indirect 55 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 56 | github.com/charithe/durationcheck v0.0.10 // indirect 57 | github.com/chavacava/garif v0.1.0 // indirect 58 | github.com/chromedp/cdproto v0.0.0-20250403032234-65de8f5d025b // indirect 59 | github.com/chromedp/sysutil v1.1.0 // indirect 60 | github.com/ckaznocha/intrange v0.3.0 // indirect 61 | github.com/curioswitch/go-reassign v0.3.0 // indirect 62 | github.com/daixiang0/gci v0.13.5 // indirect 63 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 64 | github.com/denis-tingaikin/go-header v0.5.0 // indirect 65 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 66 | github.com/ettle/strcase v0.2.0 // indirect 67 | github.com/fatih/color v1.18.0 // indirect 68 | github.com/fatih/structtag v1.2.0 // indirect 69 | github.com/firefart/nonamedreturns v1.0.5 // indirect 70 | github.com/fsnotify/fsnotify v1.5.4 // indirect 71 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 72 | github.com/fzipp/gocyclo v0.6.0 // indirect 73 | github.com/ghostiam/protogetter v0.3.9 // indirect 74 | github.com/go-critic/go-critic v0.12.0 // indirect 75 | github.com/go-errors/errors v1.4.2 // indirect 76 | github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535 // indirect 77 | github.com/go-logr/logr v1.4.2 // indirect 78 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 79 | github.com/go-openapi/jsonreference v0.20.2 // indirect 80 | github.com/go-openapi/swag v0.23.0 // indirect 81 | github.com/go-toolsmith/astcast v1.1.0 // indirect 82 | github.com/go-toolsmith/astcopy v1.1.0 // indirect 83 | github.com/go-toolsmith/astequal v1.2.0 // indirect 84 | github.com/go-toolsmith/astfmt v1.1.0 // indirect 85 | github.com/go-toolsmith/astp v1.1.0 // indirect 86 | github.com/go-toolsmith/strparse v1.1.0 // indirect 87 | github.com/go-toolsmith/typep v1.1.0 // indirect 88 | github.com/go-viper/mapstructure/v2 v2.2.1 // indirect 89 | github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect 90 | github.com/gobwas/glob v0.2.3 // indirect 91 | github.com/gobwas/httphead v0.1.0 // indirect 92 | github.com/gobwas/pool v0.2.1 // indirect 93 | github.com/gobwas/ws v1.4.0 // indirect 94 | github.com/gofrs/flock v0.12.1 // indirect 95 | github.com/gogo/protobuf v1.3.2 // indirect 96 | github.com/golang/protobuf v1.5.4 // indirect 97 | github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect 98 | github.com/golangci/go-printf-func-name v0.1.0 // indirect 99 | github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect 100 | github.com/golangci/golangci-lint v1.64.8 // indirect 101 | github.com/golangci/misspell v0.6.0 // indirect 102 | github.com/golangci/plugin-module-register v0.1.1 // indirect 103 | github.com/golangci/revgrep v0.8.0 // indirect 104 | github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect 105 | github.com/google/btree v1.1.3 // indirect 106 | github.com/google/gnostic-models v0.6.9 // indirect 107 | github.com/google/go-cmp v0.7.0 // indirect 108 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 109 | github.com/google/subcommands v1.2.0 // indirect 110 | github.com/google/uuid v1.6.0 // indirect 111 | github.com/gordonklaus/ineffassign v0.1.0 // indirect 112 | github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect 113 | github.com/gostaticanalysis/analysisutil v0.7.1 // indirect 114 | github.com/gostaticanalysis/comment v1.5.0 // indirect 115 | github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect 116 | github.com/gostaticanalysis/nilerr v0.1.1 // indirect 117 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect 118 | github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect 119 | github.com/hashicorp/go-version v1.7.0 // indirect 120 | github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect 121 | github.com/hashicorp/hcl v1.0.0 // indirect 122 | github.com/hexops/gotextdiff v1.0.3 // indirect 123 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 124 | github.com/jgautheron/goconst v1.7.1 // indirect 125 | github.com/jingyugao/rowserrcheck v1.1.1 // indirect 126 | github.com/jjti/go-spancheck v0.6.4 // indirect 127 | github.com/josharian/intern v1.0.0 // indirect 128 | github.com/json-iterator/go v1.1.12 // indirect 129 | github.com/julz/importas v0.2.0 // indirect 130 | github.com/karamaru-alpha/copyloopvar v1.2.1 // indirect 131 | github.com/kisielk/errcheck v1.9.0 // indirect 132 | github.com/kkHAIKE/contextcheck v1.1.6 // indirect 133 | github.com/kulti/thelper v0.6.3 // indirect 134 | github.com/kunwardeep/paralleltest v1.0.10 // indirect 135 | github.com/lasiar/canonicalheader v1.1.2 // indirect 136 | github.com/ldez/exptostd v0.4.2 // indirect 137 | github.com/ldez/gomoddirectives v0.6.1 // indirect 138 | github.com/ldez/grignotin v0.9.0 // indirect 139 | github.com/ldez/tagliatelle v0.7.1 // indirect 140 | github.com/ldez/usetesting v0.4.2 // indirect 141 | github.com/leonklingele/grouper v1.1.2 // indirect 142 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect 143 | github.com/macabu/inamedparam v0.1.3 // indirect 144 | github.com/magiconair/properties v1.8.6 // indirect 145 | github.com/mailru/easyjson v0.9.0 // indirect 146 | github.com/maratori/testableexamples v1.0.0 // indirect 147 | github.com/maratori/testpackage v1.1.1 // indirect 148 | github.com/matoous/godox v1.1.0 // indirect 149 | github.com/mattn/go-colorable v0.1.14 // indirect 150 | github.com/mattn/go-isatty v0.0.20 // indirect 151 | github.com/mattn/go-runewidth v0.0.16 // indirect 152 | github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect 153 | github.com/mgechev/revive v1.7.0 // indirect 154 | github.com/mitchellh/go-homedir v1.1.0 // indirect 155 | github.com/mitchellh/mapstructure v1.5.0 // indirect 156 | github.com/moby/spdystream v0.5.0 // indirect 157 | github.com/moby/term v0.5.0 // indirect 158 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 159 | github.com/modern-go/reflect2 v1.0.2 // indirect 160 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect 161 | github.com/moricho/tparallel v0.3.2 // indirect 162 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 163 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect 164 | github.com/nakabonne/nestif v0.3.1 // indirect 165 | github.com/nishanths/exhaustive v0.12.0 // indirect 166 | github.com/nishanths/predeclared v0.2.2 // indirect 167 | github.com/nunnatsa/ginkgolinter v0.19.1 // indirect 168 | github.com/olekukonko/tablewriter v0.0.5 // indirect 169 | github.com/pelletier/go-toml v1.9.5 // indirect 170 | github.com/pelletier/go-toml/v2 v2.2.3 // indirect 171 | github.com/peterbourgon/diskv v2.0.1+incompatible // indirect 172 | github.com/pkg/errors v0.9.1 // indirect 173 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 174 | github.com/polyfloyd/go-errorlint v1.7.1 // indirect 175 | github.com/prometheus/client_golang v1.12.1 // indirect 176 | github.com/prometheus/client_model v0.2.0 // indirect 177 | github.com/prometheus/common v0.32.1 // indirect 178 | github.com/prometheus/procfs v0.7.3 // indirect 179 | github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 // indirect 180 | github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect 181 | github.com/quasilyte/gogrep v0.5.0 // indirect 182 | github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect 183 | github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect 184 | github.com/raeperd/recvcheck v0.2.0 // indirect 185 | github.com/rivo/uniseg v0.4.7 // indirect 186 | github.com/rogpeppe/go-internal v1.14.1 // indirect 187 | github.com/ryancurrah/gomodguard v1.3.5 // indirect 188 | github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect 189 | github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect 190 | github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect 191 | github.com/sashamelentyev/interfacebloat v1.1.0 // indirect 192 | github.com/sashamelentyev/usestdlibvars v1.28.0 // indirect 193 | github.com/securego/gosec/v2 v2.22.2 // indirect 194 | github.com/sirupsen/logrus v1.9.3 // indirect 195 | github.com/sivchari/containedctx v1.0.3 // indirect 196 | github.com/sivchari/tenv v1.12.1 // indirect 197 | github.com/sonatard/noctx v0.1.0 // indirect 198 | github.com/sourcegraph/go-diff v0.7.0 // indirect 199 | github.com/spf13/afero v1.12.0 // indirect 200 | github.com/spf13/cast v1.5.0 // indirect 201 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 202 | github.com/spf13/viper v1.12.0 // indirect 203 | github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect 204 | github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect 205 | github.com/stretchr/objx v0.5.2 // indirect 206 | github.com/stretchr/testify v1.10.0 // indirect 207 | github.com/subosito/gotenv v1.4.1 // indirect 208 | github.com/tdakkota/asciicheck v0.4.1 // indirect 209 | github.com/tetafro/godot v1.5.0 // indirect 210 | github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 // indirect 211 | github.com/timonwong/loggercheck v0.10.1 // indirect 212 | github.com/tomarrell/wrapcheck/v2 v2.10.0 // indirect 213 | github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect 214 | github.com/ultraware/funlen v0.2.0 // indirect 215 | github.com/ultraware/whitespace v0.2.0 // indirect 216 | github.com/uudashr/gocognit v1.2.0 // indirect 217 | github.com/uudashr/iface v1.3.1 // indirect 218 | github.com/x448/float16 v0.8.4 // indirect 219 | github.com/xen0n/gosmopolitan v1.2.2 // indirect 220 | github.com/xlab/treeprint v1.2.0 // indirect 221 | github.com/yagipy/maintidx v1.0.0 // indirect 222 | github.com/yeya24/promlinter v0.3.0 // indirect 223 | github.com/ykadowak/zerologlint v0.1.5 // indirect 224 | gitlab.com/bosi/decorder v0.4.2 // indirect 225 | go-simpler.org/musttag v0.13.0 // indirect 226 | go-simpler.org/sloglint v0.9.0 // indirect 227 | go.uber.org/atomic v1.7.0 // indirect 228 | go.uber.org/automaxprocs v1.6.0 // indirect 229 | go.uber.org/multierr v1.6.0 // indirect 230 | go.uber.org/zap v1.24.0 // indirect 231 | golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect 232 | golang.org/x/mod v0.24.0 // indirect 233 | golang.org/x/net v0.38.0 // indirect 234 | golang.org/x/oauth2 v0.27.0 // indirect 235 | golang.org/x/sys v0.31.0 // indirect 236 | golang.org/x/term v0.30.0 // indirect 237 | golang.org/x/text v0.23.0 // indirect 238 | golang.org/x/time v0.10.0 // indirect 239 | golang.org/x/tools v0.31.0 // indirect 240 | google.golang.org/protobuf v1.36.5 // indirect 241 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 242 | gopkg.in/inf.v0 v0.9.1 // indirect 243 | gopkg.in/ini.v1 v1.67.0 // indirect 244 | gopkg.in/yaml.v2 v2.4.0 // indirect 245 | gopkg.in/yaml.v3 v3.0.1 // indirect 246 | honnef.co/go/tools v0.6.1 // indirect 247 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect 248 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect 249 | mvdan.cc/gofumpt v0.7.0 // indirect 250 | mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect 251 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 252 | sigs.k8s.io/kustomize/api v0.19.0 // indirect 253 | sigs.k8s.io/kustomize/kyaml v0.19.0 // indirect 254 | sigs.k8s.io/randfill v1.0.0 // indirect 255 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect 256 | sigs.k8s.io/yaml v1.4.0 // indirect 257 | ) 258 | 259 | tool ( 260 | github.com/golangci/golangci-lint/cmd/golangci-lint 261 | github.com/google/wire/cmd/wire 262 | go.uber.org/mock/mockgen 263 | ) 264 | -------------------------------------------------------------------------------- /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.0.0 h1:oJOOWR07vS1kRusl6YRSlat7HFnb3mSfMl6sDMRoTBA= 43 | github.com/Antonboom/errname v1.0.0/go.mod h1:gMOBFzK/vrTiXN9Oh+HFs+e6Ndl0eTFbtsRTSRdXyGI= 44 | github.com/Antonboom/nilnil v1.0.1 h1:C3Tkm0KUxgfO4Duk3PM+ztPncTFlOf0b2qadmS0s4xs= 45 | github.com/Antonboom/nilnil v1.0.1/go.mod h1:CH7pW2JsRNFgEh8B2UaPZTEPhCMuFowP/e8Udp9Nnb0= 46 | github.com/Antonboom/testifylint v1.5.2 h1:4s3Xhuv5AvdIgbd8wOOEeo0uZG7PbDKQyKY5lGoQazk= 47 | github.com/Antonboom/testifylint v1.5.2/go.mod h1:vxy8VJ0bc6NavlYqjZfmp6EfqXMtBgQ4+mhCojwC1P8= 48 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= 49 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 50 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 51 | github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= 52 | github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 53 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 54 | github.com/Crocmagnon/fatcontext v0.7.1 h1:SC/VIbRRZQeQWj/TcQBS6JmrXcfA+BU4OGSVUt54PjM= 55 | github.com/Crocmagnon/fatcontext v0.7.1/go.mod h1:1wMvv3NXEBJucFGfwOJBxSVWcoIO6emV215SMkW9MFU= 56 | github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= 57 | github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= 58 | github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 h1:Sz1JIXEcSfhz7fUi7xHnhpIE0thVASYjvosApmHuD2k= 59 | github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1/go.mod h1:n/LSCXNuIYqVfBlVXyHfMQkZDdp1/mmxfSjADd3z1Zg= 60 | github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= 61 | github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= 62 | github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4= 63 | github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo= 64 | github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= 65 | github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= 66 | github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU= 67 | github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E= 68 | github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= 69 | github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= 70 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 71 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 72 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 73 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 74 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= 75 | github.com/alexkohler/nakedret/v2 v2.0.5 h1:fP5qLgtwbx9EJE8dGEERT02YwS8En4r9nnZ71RK+EVU= 76 | github.com/alexkohler/nakedret/v2 v2.0.5/go.mod h1:bF5i0zF2Wo2o4X4USt9ntUWve6JbFv02Ff4vlkmS/VU= 77 | github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= 78 | github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= 79 | github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= 80 | github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= 81 | github.com/alingse/nilnesserr v0.1.2 h1:Yf8Iwm3z2hUUrP4muWfW83DF4nE3r1xZ26fGWUKCZlo= 82 | github.com/alingse/nilnesserr v0.1.2/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg= 83 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= 84 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 85 | github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY= 86 | github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= 87 | github.com/ashanbrown/makezero v1.2.0 h1:/2Lp1bypdmK9wDIq7uWBlDF1iMUpIIS4A+pF6C9IEUU= 88 | github.com/ashanbrown/makezero v1.2.0/go.mod h1:dxlPhHbDMC6N6xICzFBSK+4njQDdK8euNO0qjQMtGY4= 89 | github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= 90 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 91 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 92 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 93 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 94 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 95 | github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w= 96 | github.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8ztxG8Fuo= 97 | github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= 98 | github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= 99 | github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= 100 | github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= 101 | github.com/bombsimon/wsl/v4 v4.5.0 h1:iZRsEvDdyhd2La0FVi5k6tYehpOR/R7qIUjmKk7N74A= 102 | github.com/bombsimon/wsl/v4 v4.5.0/go.mod h1:NOQ3aLF4nD7N5YPXMruR6ZXDOAqLoM0GEpLwTdvmOSc= 103 | github.com/breml/bidichk v0.3.2 h1:xV4flJ9V5xWTqxL+/PMFF6dtJPvZLPsyixAoPe8BGJs= 104 | github.com/breml/bidichk v0.3.2/go.mod h1:VzFLBxuYtT23z5+iVkamXO386OB+/sVwZOpIj6zXGos= 105 | github.com/breml/errchkjson v0.4.0 h1:gftf6uWZMtIa/Is3XJgibewBm2ksAQSY/kABDNFTAdk= 106 | github.com/breml/errchkjson v0.4.0/go.mod h1:AuBOSTHyLSaaAFlWsRSuRBIroCh3eh7ZHh5YeelDIk8= 107 | github.com/butuzov/ireturn v0.3.1 h1:mFgbEI6m+9W8oP/oDdfA34dLisRFCj2G6o/yiI1yZrY= 108 | github.com/butuzov/ireturn v0.3.1/go.mod h1:ZfRp+E7eJLC0NQmk1Nrm1LOrn/gQlOykv+cVPdiXH5M= 109 | github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc= 110 | github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI= 111 | github.com/catenacyber/perfsprint v0.8.2 h1:+o9zVmCSVa7M4MvabsWvESEhpsMkhfE7k0sHNGL95yw= 112 | github.com/catenacyber/perfsprint v0.8.2/go.mod h1:q//VWC2fWbcdSLEY1R3l8n0zQCDPdE4IjZwyY1HMunM= 113 | github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= 114 | github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= 115 | github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= 116 | github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 117 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 118 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 119 | github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 120 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 121 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 122 | github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4= 123 | github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ= 124 | github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= 125 | github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= 126 | github.com/chromedp/cdproto v0.0.0-20250403032234-65de8f5d025b h1:jJmiCljLNTaq/O1ju9Bzz2MPpFlmiTn0F7LwCoeDZVw= 127 | github.com/chromedp/cdproto v0.0.0-20250403032234-65de8f5d025b/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k= 128 | github.com/chromedp/chromedp v0.13.6 h1:xlNunMyzS5bu3r/QKrb3fzX6ow3WBQ6oao+J65PGZxk= 129 | github.com/chromedp/chromedp v0.13.6/go.mod h1:h8GPP6ZtLMLsU8zFbTcb7ZDGCvCy8j/vRoFmRltQx9A= 130 | github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM= 131 | github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8= 132 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 133 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 134 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 135 | github.com/ckaznocha/intrange v0.3.0 h1:VqnxtK32pxgkhJgYQEeOArVidIPg+ahLP7WBOXZd5ZY= 136 | github.com/ckaznocha/intrange v0.3.0/go.mod h1:+I/o2d2A1FBHgGELbGxzIcyd3/9l9DuwjM8FsbSS3Lo= 137 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 138 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 139 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 140 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 141 | github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= 142 | github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= 143 | github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs= 144 | github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88= 145 | github.com/daixiang0/gci v0.13.5 h1:kThgmH1yBmZSBCh1EJVxQ7JsHpm5Oms0AMed/0LaH4c= 146 | github.com/daixiang0/gci v0.13.5/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk= 147 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 148 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 149 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 150 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 151 | github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= 152 | github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= 153 | github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= 154 | github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= 155 | github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= 156 | github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 157 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 158 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 159 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 160 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 161 | github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= 162 | github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= 163 | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 164 | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= 165 | github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= 166 | github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= 167 | github.com/firefart/nonamedreturns v1.0.5 h1:tM+Me2ZaXs8tfdDw3X6DOX++wMCOqzYUho6tUTYIdRA= 168 | github.com/firefart/nonamedreturns v1.0.5/go.mod h1:gHJjDqhGM4WyPt639SOZs+G89Ko7QKH5R5BhnO6xJhw= 169 | github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= 170 | github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= 171 | github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= 172 | github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= 173 | github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= 174 | github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= 175 | github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= 176 | github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= 177 | github.com/ghostiam/protogetter v0.3.9 h1:j+zlLLWzqLay22Cz/aYwTHKQ88GE2DQ6GkWSYFOI4lQ= 178 | github.com/ghostiam/protogetter v0.3.9/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA= 179 | github.com/go-critic/go-critic v0.12.0 h1:iLosHZuye812wnkEz1Xu3aBwn5ocCPfc9yqmFG9pa6w= 180 | github.com/go-critic/go-critic v0.12.0/go.mod h1:DpE0P6OVc6JzVYzmM5gq5jMU31zLr4am5mB/VfFK64w= 181 | github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= 182 | github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= 183 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 184 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 185 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 186 | github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535 h1:yE7argOs92u+sSCRgqqe6eF+cDaVhSPlioy1UkA0p/w= 187 | github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s= 188 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 189 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 190 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 191 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 192 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 193 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 194 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 195 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 196 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= 197 | github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= 198 | github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= 199 | github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= 200 | github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= 201 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 202 | github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= 203 | github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= 204 | github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= 205 | github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= 206 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 207 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 208 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 209 | github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= 210 | github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU= 211 | github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s= 212 | github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw= 213 | github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= 214 | github.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ= 215 | github.com/go-toolsmith/astequal v1.2.0 h1:3Fs3CYZ1k9Vo4FzFhwwewC3CHISHDnVUPC4x0bI2+Cw= 216 | github.com/go-toolsmith/astequal v1.2.0/go.mod h1:c8NZ3+kSFtFY/8lPso4v8LuJjdJiUFVnSuU3s0qrrDY= 217 | github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco= 218 | github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4= 219 | github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA= 220 | github.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA= 221 | github.com/go-toolsmith/pkgload v1.2.2 h1:0CtmHq/02QhxcF7E9N5LIFcYFsMR5rdovfqTtRKkgIk= 222 | github.com/go-toolsmith/pkgload v1.2.2/go.mod h1:R2hxLNRKuAsiXCo2i5J6ZQPhnPMOVtU+f0arbFPWCus= 223 | github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= 224 | github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw= 225 | github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= 226 | github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= 227 | github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= 228 | github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= 229 | github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= 230 | github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY= 231 | github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= 232 | github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= 233 | github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= 234 | github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= 235 | github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= 236 | github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= 237 | github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= 238 | github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs= 239 | github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc= 240 | github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= 241 | github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= 242 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 243 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 244 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 245 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 246 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 247 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 248 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 249 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 250 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 251 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 252 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 253 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 254 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 255 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 256 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 257 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 258 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 259 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 260 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 261 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 262 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 263 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 264 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 265 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 266 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 267 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 268 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 269 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 270 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 271 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 272 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 273 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 274 | github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw= 275 | github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E= 276 | github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUPPyAKJuzv8pEJU= 277 | github.com/golangci/go-printf-func-name v0.1.0/go.mod h1:wqhWFH5mUdJQhweRnldEywnR5021wTdZSNgwYceV14s= 278 | github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE= 279 | github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY= 280 | github.com/golangci/golangci-lint v1.64.8 h1:y5TdeVidMtBGG32zgSC7ZXTFNHrsJkDnpO4ItB3Am+I= 281 | github.com/golangci/golangci-lint v1.64.8/go.mod h1:5cEsUQBSr6zi8XI8OjmcY2Xmliqc4iYL7YoPrL+zLJ4= 282 | github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= 283 | github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= 284 | github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= 285 | github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc= 286 | github.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s= 287 | github.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= 288 | github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed h1:IURFTjxeTfNFP0hTEi1YKjB/ub8zkpaOqFFMApi2EAs= 289 | github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed/go.mod h1:XLXN8bNw4CGRPaqgl3bv/lhz7bsGPh4/xSaMTbo2vkQ= 290 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 291 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 292 | github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= 293 | github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= 294 | github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= 295 | github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= 296 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 297 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 298 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 299 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 300 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 301 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 302 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 303 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 304 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 305 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 306 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 307 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 308 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 309 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 310 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 311 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 312 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 313 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 314 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 315 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 316 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 317 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 318 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 319 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 320 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 321 | github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= 322 | github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 323 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 324 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= 325 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= 326 | github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE= 327 | github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= 328 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 329 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 330 | github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI= 331 | github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA= 332 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 333 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 334 | github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s= 335 | github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= 336 | github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= 337 | github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= 338 | github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= 339 | github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= 340 | github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= 341 | github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= 342 | github.com/gostaticanalysis/comment v1.5.0 h1:X82FLl+TswsUMpMh17srGRuKaaXprTaytmEpgnKIDu8= 343 | github.com/gostaticanalysis/comment v1.5.0/go.mod h1:V6eb3gpCv9GNVqb6amXzEUX3jXLVK/AdA+IrAMSqvEc= 344 | github.com/gostaticanalysis/forcetypeassert v0.2.0 h1:uSnWrrUEYDr86OCxWa4/Tp2jeYDlogZiZHzGkWFefTk= 345 | github.com/gostaticanalysis/forcetypeassert v0.2.0/go.mod h1:M5iPavzE9pPqWyeiVXSFghQjljW1+l/Uke3PXHS6ILY= 346 | github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3Uqrmrcpk= 347 | github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= 348 | github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= 349 | github.com/gostaticanalysis/testutil v0.5.0 h1:Dq4wT1DdTwTGCQQv3rl3IvD5Ld0E6HiY+3Zh0sUGqw8= 350 | github.com/gostaticanalysis/testutil v0.5.0/go.mod h1:OLQSbuM6zw2EvCcXTz1lVq5unyoNft372msDY0nY5Hs= 351 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= 352 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 353 | github.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo= 354 | github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw= 355 | github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= 356 | github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 357 | github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 358 | github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= 359 | github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 360 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 361 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 362 | github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= 363 | github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 364 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 365 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 366 | github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= 367 | github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= 368 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 369 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 370 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 371 | github.com/int128/listener v1.3.0 h1:ZFePbpzFUt1i6hBSY15rzqo8tHZHJPPQkqCtgOAwS8g= 372 | github.com/int128/listener v1.3.0/go.mod h1:zF9mx2wn+2J/7Idmxi5kgqrGgERr6vr8fK8KqENrRZ0= 373 | github.com/jgautheron/goconst v1.7.1 h1:VpdAG7Ca7yvvJk5n8dMwQhfEZJh95kl/Hl9S1OI5Jkk= 374 | github.com/jgautheron/goconst v1.7.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= 375 | github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= 376 | github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= 377 | github.com/jjti/go-spancheck v0.6.4 h1:Tl7gQpYf4/TMU7AT84MN83/6PutY21Nb9fuQjFTpRRc= 378 | github.com/jjti/go-spancheck v0.6.4/go.mod h1:yAEYdKJ2lRkDA8g7X+oKUHXOWVAXSBJRv04OhF+QUjk= 379 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 380 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 381 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 382 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 383 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 384 | github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 385 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 386 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 387 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 388 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 389 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 390 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 391 | github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ= 392 | github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY= 393 | github.com/karamaru-alpha/copyloopvar v1.2.1 h1:wmZaZYIjnJ0b5UoKDjUHrikcV0zuPyyxI4SVplLd2CI= 394 | github.com/karamaru-alpha/copyloopvar v1.2.1/go.mod h1:nFmMlFNlClC2BPvNaHMdkirmTJxVCY0lhxBtlfOypMM= 395 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 396 | github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M= 397 | github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8= 398 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 399 | github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE= 400 | github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg= 401 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 402 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 403 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 404 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 405 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 406 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 407 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 408 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 409 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 410 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 411 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 412 | github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs= 413 | github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= 414 | github.com/kunwardeep/paralleltest v1.0.10 h1:wrodoaKYzS2mdNVnc4/w31YaXFtsc21PCTdvWJ/lDDs= 415 | github.com/kunwardeep/paralleltest v1.0.10/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= 416 | github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4= 417 | github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI= 418 | github.com/ldez/exptostd v0.4.2 h1:l5pOzHBz8mFOlbcifTxzfyYbgEmoUqjxLFHZkjlbHXs= 419 | github.com/ldez/exptostd v0.4.2/go.mod h1:iZBRYaUmcW5jwCR3KROEZ1KivQQp6PHXbDPk9hqJKCQ= 420 | github.com/ldez/gomoddirectives v0.6.1 h1:Z+PxGAY+217f/bSGjNZr/b2KTXcyYLgiWI6geMBN2Qc= 421 | github.com/ldez/gomoddirectives v0.6.1/go.mod h1:cVBiu3AHR9V31em9u2kwfMKD43ayN5/XDgr+cdaFaKs= 422 | github.com/ldez/grignotin v0.9.0 h1:MgOEmjZIVNn6p5wPaGp/0OKWyvq42KnzAt/DAb8O4Ow= 423 | github.com/ldez/grignotin v0.9.0/go.mod h1:uaVTr0SoZ1KBii33c47O1M8Jp3OP3YDwhZCmzT9GHEk= 424 | github.com/ldez/tagliatelle v0.7.1 h1:bTgKjjc2sQcsgPiT902+aadvMjCeMHrY7ly2XKFORIk= 425 | github.com/ldez/tagliatelle v0.7.1/go.mod h1:3zjxUpsNB2aEZScWiZTHrAXOl1x25t3cRmzfK1mlo2I= 426 | github.com/ldez/usetesting v0.4.2 h1:J2WwbrFGk3wx4cZwSMiCQQ00kjGR0+tuuyW0Lqm4lwA= 427 | github.com/ldez/usetesting v0.4.2/go.mod h1:eEs46T3PpQ+9RgN9VjpY6qWdiw2/QmfiDeWmdZdrjIQ= 428 | github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo= 429 | github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= 430 | github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= 431 | github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= 432 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= 433 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= 434 | github.com/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV1Mk= 435 | github.com/macabu/inamedparam v0.1.3/go.mod h1:93FLICAIk/quk7eaPPQvbzihUdn/QkGDwIZEoLtpH6I= 436 | github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= 437 | github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= 438 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 439 | github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= 440 | github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= 441 | github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= 442 | github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= 443 | github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04= 444 | github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc= 445 | github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4= 446 | github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs= 447 | github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= 448 | github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= 449 | github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= 450 | github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= 451 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 452 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 453 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 454 | github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= 455 | github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 456 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 457 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 458 | github.com/mgechev/revive v1.7.0 h1:JyeQ4yO5K8aZhIKf5rec56u0376h8AlKNQEmjfkjKlY= 459 | github.com/mgechev/revive v1.7.0/go.mod h1:qZnwcNhoguE58dfi96IJeSTPeZQejNeoMQLUZGi4SW4= 460 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 461 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 462 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 463 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 464 | github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= 465 | github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= 466 | github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= 467 | github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= 468 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 469 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 470 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 471 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 472 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 473 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 474 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 475 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= 476 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= 477 | github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= 478 | github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U= 479 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 480 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 481 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 482 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 483 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= 484 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= 485 | github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= 486 | github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= 487 | github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= 488 | github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= 489 | github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= 490 | github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= 491 | github.com/nunnatsa/ginkgolinter v0.19.1 h1:mjwbOlDQxZi9Cal+KfbEJTCz327OLNfwNvoZ70NJ+c4= 492 | github.com/nunnatsa/ginkgolinter v0.19.1/go.mod h1:jkQ3naZDmxaZMXPWaS9rblH+i+GWXQCaS/JFIWcOH2s= 493 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 494 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= 495 | github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= 496 | github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= 497 | github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= 498 | github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= 499 | github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw= 500 | github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= 501 | github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= 502 | github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= 503 | github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= 504 | github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= 505 | github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= 506 | github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= 507 | github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= 508 | github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= 509 | github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= 510 | github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= 511 | github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= 512 | github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= 513 | github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= 514 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= 515 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= 516 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 517 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 518 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 519 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 520 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 521 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 522 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 523 | github.com/polyfloyd/go-errorlint v1.7.1 h1:RyLVXIbosq1gBdk/pChWA8zWYLsq9UEw7a1L5TVMCnA= 524 | github.com/polyfloyd/go-errorlint v1.7.1/go.mod h1:aXjNb1x2TNhoLsk26iv1yl7a+zTnXPhwEMtEXukiLR8= 525 | github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= 526 | github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= 527 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 528 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 529 | github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= 530 | github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= 531 | github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= 532 | github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= 533 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 534 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 535 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 536 | github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= 537 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 538 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 539 | github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= 540 | github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= 541 | github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= 542 | github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= 543 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 544 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 545 | github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= 546 | github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= 547 | github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= 548 | github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= 549 | github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 h1:+Wl/0aFp0hpuHM3H//KMft64WQ1yX9LdJY64Qm/gFCo= 550 | github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1/go.mod h1:GJLgqsLeo4qgavUoL8JeGFNS7qcisx3awV/w9eWTmNI= 551 | github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= 552 | github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= 553 | github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= 554 | github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= 555 | github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU= 556 | github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= 557 | github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= 558 | github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= 559 | github.com/raeperd/recvcheck v0.2.0 h1:GnU+NsbiCqdC2XX5+vMZzP+jAJC5fht7rcVTAhX74UI= 560 | github.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU= 561 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 562 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 563 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 564 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 565 | github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= 566 | github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= 567 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 568 | github.com/ryancurrah/gomodguard v1.3.5 h1:cShyguSwUEeC0jS7ylOiG/idnd1TpJ1LfHGpV3oJmPU= 569 | github.com/ryancurrah/gomodguard v1.3.5/go.mod h1:MXlEPQRxgfPQa62O8wzK3Ozbkv9Rkqr+wKjSxTdsNJE= 570 | github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= 571 | github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= 572 | github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0= 573 | github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4= 574 | github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= 575 | github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= 576 | github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= 577 | github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= 578 | github.com/sashamelentyev/usestdlibvars v1.28.0 h1:jZnudE2zKCtYlGzLVreNp5pmCdOxXUzwsMDBkR21cyQ= 579 | github.com/sashamelentyev/usestdlibvars v1.28.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= 580 | github.com/securego/gosec/v2 v2.22.2 h1:IXbuI7cJninj0nRpZSLCUlotsj8jGusohfONMrHoF6g= 581 | github.com/securego/gosec/v2 v2.22.2/go.mod h1:UEBGA+dSKb+VqM6TdehR7lnQtIIMorYJ4/9CW1KVQBE= 582 | github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= 583 | github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 584 | github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= 585 | github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= 586 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 587 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 588 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 589 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 590 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 591 | github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= 592 | github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= 593 | github.com/sivchari/tenv v1.12.1 h1:+E0QzjktdnExv/wwsnnyk4oqZBUfuh89YMQT1cyuvSY= 594 | github.com/sivchari/tenv v1.12.1/go.mod h1:1LjSOUCc25snIr5n3DtGGrENhX3LuWefcplwVGC24mw= 595 | github.com/sonatard/noctx v0.1.0 h1:JjqOc2WN16ISWAjAk8M5ej0RfExEXtkEyExl2hLW+OM= 596 | github.com/sonatard/noctx v0.1.0/go.mod h1:0RvBxqY8D4j9cTTTWE8ylt2vqj2EPI8fHmrxHdsaZ2c= 597 | github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= 598 | github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= 599 | github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= 600 | github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= 601 | github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= 602 | github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= 603 | github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= 604 | github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= 605 | github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= 606 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 607 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 608 | github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= 609 | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 610 | github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= 611 | github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= 612 | github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= 613 | github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= 614 | github.com/stbenjam/no-sprintf-host-port v0.2.0 h1:i8pxvGrt1+4G0czLr/WnmyH7zbZ8Bg8etvARQ1rpyl4= 615 | github.com/stbenjam/no-sprintf-host-port v0.2.0/go.mod h1:eL0bQ9PasS0hsyTyfTjjG+E80QIyPnBVQbYZyv20Jfk= 616 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 617 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 618 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 619 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 620 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 621 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 622 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 623 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 624 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 625 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 626 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 627 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 628 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 629 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 630 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 631 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 632 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 633 | github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= 634 | github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= 635 | github.com/tdakkota/asciicheck v0.4.1 h1:bm0tbcmi0jezRA2b5kg4ozmMuGAFotKI3RZfrhfovg8= 636 | github.com/tdakkota/asciicheck v0.4.1/go.mod h1:0k7M3rCfRXb0Z6bwgvkEIMleKH3kXNz9UqJ9Xuqopr8= 637 | github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= 638 | github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= 639 | github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= 640 | github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= 641 | github.com/tetafro/godot v1.5.0 h1:aNwfVI4I3+gdxjMgYPus9eHmoBeJIbnajOyqZYStzuw= 642 | github.com/tetafro/godot v1.5.0/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= 643 | github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 h1:y4mJRFlM6fUyPhoXuFg/Yu02fg/nIPFMOY8tOqppoFg= 644 | github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460= 645 | github.com/timonwong/loggercheck v0.10.1 h1:uVZYClxQFpw55eh+PIoqM7uAOHMrhVcDoWDery9R8Lg= 646 | github.com/timonwong/loggercheck v0.10.1/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8= 647 | github.com/tomarrell/wrapcheck/v2 v2.10.0 h1:SzRCryzy4IrAH7bVGG4cK40tNUhmVmMDuJujy4XwYDg= 648 | github.com/tomarrell/wrapcheck/v2 v2.10.0/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo= 649 | github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= 650 | github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= 651 | github.com/ultraware/funlen v0.2.0 h1:gCHmCn+d2/1SemTdYMiKLAHFYxTYz7z9VIDRaTGyLkI= 652 | github.com/ultraware/funlen v0.2.0/go.mod h1:ZE0q4TsJ8T1SQcjmkhN/w+MceuatI6pBFSxxyteHIJA= 653 | github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSWoFa+g= 654 | github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= 655 | github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA= 656 | github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU= 657 | github.com/uudashr/iface v1.3.1 h1:bA51vmVx1UIhiIsQFSNq6GZ6VPTk3WNMZgRiCe9R29U= 658 | github.com/uudashr/iface v1.3.1/go.mod h1:4QvspiRd3JLPAEXBQ9AiZpLbJlrWWgRChOKDJEuQTdg= 659 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 660 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 661 | github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU= 662 | github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg= 663 | github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= 664 | github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= 665 | github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= 666 | github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= 667 | github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs= 668 | github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4= 669 | github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw= 670 | github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= 671 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 672 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 673 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 674 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 675 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 676 | github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 677 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 678 | gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo= 679 | gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8= 680 | go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ= 681 | go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= 682 | go-simpler.org/musttag v0.13.0 h1:Q/YAW0AHvaoaIbsPj3bvEI5/QFP7w696IMUpnKXQfCE= 683 | go-simpler.org/musttag v0.13.0/go.mod h1:FTzIGeK6OkKlUDVpj0iQUXZLUO1Js9+mvykDQy9C5yM= 684 | go-simpler.org/sloglint v0.9.0 h1:/40NQtjRx9txvsB/RN022KsUJU+zaaSb/9q9BSefSrE= 685 | go-simpler.org/sloglint v0.9.0/go.mod h1:G/OrAF6uxj48sHahCzrbarVMptL2kjWTaUeC8+fOGww= 686 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 687 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 688 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 689 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 690 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 691 | go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= 692 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 693 | go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= 694 | go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= 695 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 696 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 697 | go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= 698 | go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= 699 | go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= 700 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 701 | go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= 702 | go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= 703 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 704 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 705 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 706 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 707 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 708 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 709 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 710 | golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 711 | golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= 712 | golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= 713 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 714 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 715 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 716 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 717 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 718 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 719 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 720 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 721 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 722 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 723 | golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= 724 | golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= 725 | golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= 726 | golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= 727 | golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac h1:TSSpLIG4v+p0rPv1pNOQtl1I8knsO4S9trOxNMOLVP4= 728 | golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= 729 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 730 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 731 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 732 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 733 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 734 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 735 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 736 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 737 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 738 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 739 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 740 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 741 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 742 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 743 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 744 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 745 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 746 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 747 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 748 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 749 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 750 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 751 | golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= 752 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 753 | golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 754 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 755 | golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 756 | golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 757 | golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 758 | golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 759 | golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= 760 | golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 761 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 762 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 763 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 764 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 765 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 766 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 767 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 768 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 769 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 770 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 771 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 772 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 773 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 774 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 775 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 776 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 777 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 778 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 779 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 780 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 781 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 782 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 783 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 784 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 785 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 786 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 787 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 788 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 789 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 790 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 791 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 792 | golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 793 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 794 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 795 | golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= 796 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 797 | golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= 798 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 799 | golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 800 | golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 801 | golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= 802 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 803 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 804 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 805 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 806 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 807 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 808 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 809 | golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 810 | golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= 811 | golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= 812 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 813 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 814 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 815 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 816 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 817 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 818 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 819 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 820 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 821 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 822 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 823 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 824 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 825 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 826 | golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 827 | golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 828 | golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= 829 | golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 830 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 831 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 832 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 833 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 834 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 835 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 836 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 837 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 838 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 839 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 840 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 841 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 842 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 843 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 844 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 845 | golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 846 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 847 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 848 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 849 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 850 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 851 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 852 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 853 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 854 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 855 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 856 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 857 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 858 | golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 859 | golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 860 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 861 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 862 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 863 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 864 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 865 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 866 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 867 | golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 868 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 869 | golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 870 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 871 | golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 872 | golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 873 | golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 874 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 875 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 876 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 877 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 878 | golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 879 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 880 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 881 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 882 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 883 | golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 884 | golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 885 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 886 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 887 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 888 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 889 | golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= 890 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 891 | golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= 892 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 893 | golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= 894 | golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= 895 | golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= 896 | golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= 897 | golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= 898 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 899 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 900 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 901 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 902 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 903 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 904 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 905 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 906 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 907 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 908 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 909 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 910 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 911 | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= 912 | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 913 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 914 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 915 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 916 | golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= 917 | golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 918 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 919 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 920 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 921 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 922 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 923 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 924 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 925 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 926 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 927 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 928 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 929 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 930 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 931 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 932 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 933 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 934 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 935 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 936 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 937 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 938 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 939 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 940 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 941 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 942 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 943 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 944 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 945 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 946 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 947 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 948 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 949 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 950 | golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 951 | golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 952 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 953 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 954 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 955 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 956 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 957 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 958 | golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 959 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 960 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 961 | golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 962 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 963 | golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 964 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 965 | golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= 966 | golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= 967 | golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 968 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 969 | golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= 970 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 971 | golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= 972 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 973 | golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= 974 | golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 975 | golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= 976 | golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= 977 | golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= 978 | golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= 979 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 980 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 981 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 982 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 983 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 984 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 985 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 986 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 987 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 988 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 989 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 990 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 991 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 992 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 993 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 994 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 995 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 996 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 997 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 998 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 999 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 1000 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 1001 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 1002 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 1003 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 1004 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 1005 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 1006 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 1007 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 1008 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 1009 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 1010 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 1011 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 1012 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 1013 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 1014 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 1015 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 1016 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 1017 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 1018 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 1019 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 1020 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 1021 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 1022 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 1023 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 1024 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 1025 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 1026 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 1027 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 1028 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 1029 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 1030 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 1031 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 1032 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 1033 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 1034 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 1035 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 1036 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 1037 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 1038 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 1039 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 1040 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 1041 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 1042 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 1043 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 1044 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 1045 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 1046 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 1047 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 1048 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 1049 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 1050 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 1051 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 1052 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 1053 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 1054 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 1055 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 1056 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 1057 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 1058 | google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= 1059 | google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 1060 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 1061 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 1062 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 1063 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 1064 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 1065 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 1066 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 1067 | gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= 1068 | gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= 1069 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 1070 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 1071 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 1072 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 1073 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 1074 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 1075 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 1076 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 1077 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 1078 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 1079 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 1080 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 1081 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 1082 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 1083 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 1084 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 1085 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 1086 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 1087 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 1088 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 1089 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 1090 | honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI= 1091 | honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= 1092 | k8s.io/api v0.33.1 h1:tA6Cf3bHnLIrUK4IqEgb2v++/GYUtqiu9sRVk3iBXyw= 1093 | k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw= 1094 | k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4= 1095 | k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= 1096 | k8s.io/cli-runtime v0.33.1 h1:TvpjEtF71ViFmPeYMj1baZMJR4iWUEplklsUQ7D3quA= 1097 | k8s.io/cli-runtime v0.33.1/go.mod h1:9dz5Q4Uh8io4OWCLiEf/217DXwqNgiTS/IOuza99VZE= 1098 | k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4= 1099 | k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA= 1100 | k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= 1101 | k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 1102 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= 1103 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= 1104 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= 1105 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 1106 | mvdan.cc/gofumpt v0.7.0 h1:bg91ttqXmi9y2xawvkuMXyvAA/1ZGJqYAEGjXuP0JXU= 1107 | mvdan.cc/gofumpt v0.7.0/go.mod h1:txVFJy/Sc/mvaycET54pV8SW8gWxTlUuGHVEcncmNUo= 1108 | mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f h1:lMpcwN6GxNbWtbpI1+xzFLSW8XzX0u72NttUGVFjO3U= 1109 | mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f/go.mod h1:RSLa7mKKCNeTTMHBw5Hsy2rfJmd6O2ivt9Dw9ZqCQpQ= 1110 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 1111 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 1112 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 1113 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= 1114 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= 1115 | sigs.k8s.io/kustomize/api v0.19.0 h1:F+2HB2mU1MSiR9Hp1NEgoU2q9ItNOaBJl0I4Dlus5SQ= 1116 | sigs.k8s.io/kustomize/api v0.19.0/go.mod h1:/BbwnivGVcBh1r+8m3tH1VNxJmHSk1PzP5fkP6lbL1o= 1117 | sigs.k8s.io/kustomize/kyaml v0.19.0 h1:RFge5qsO1uHhwJsu3ipV7RNolC7Uozc0jUBC/61XSlA= 1118 | sigs.k8s.io/kustomize/kyaml v0.19.0/go.mod h1:FeKD5jEOH+FbZPpqUghBP8mrLjJ3+zD3/rf9NNu1cwY= 1119 | sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= 1120 | sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= 1121 | sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= 1122 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= 1123 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= 1124 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 1125 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 1126 | -------------------------------------------------------------------------------- /internal/authproxy/auth_proxy.go: -------------------------------------------------------------------------------- 1 | package authproxy 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "net/url" 8 | "strings" 9 | "sync" 10 | 11 | "github.com/cenkalti/backoff/v4" 12 | "github.com/google/wire" 13 | "github.com/int128/kauthproxy/internal/browser" 14 | "github.com/int128/kauthproxy/internal/env" 15 | "github.com/int128/kauthproxy/internal/logger" 16 | "github.com/int128/kauthproxy/internal/portforwarder" 17 | "github.com/int128/kauthproxy/internal/resolver" 18 | "github.com/int128/kauthproxy/internal/reverseproxy" 19 | "github.com/int128/kauthproxy/internal/transport" 20 | "golang.org/x/sync/errgroup" 21 | corev1 "k8s.io/api/core/v1" 22 | "k8s.io/client-go/rest" 23 | ) 24 | 25 | var Set = wire.NewSet( 26 | wire.Struct(new(AuthProxy), "*"), 27 | wire.Bind(new(Interface), new(*AuthProxy)), 28 | ) 29 | 30 | type Interface interface { 31 | Do(ctx context.Context, in Option) error 32 | } 33 | 34 | var portForwarderConnectionLostError = errors.New("connection lost") 35 | 36 | // AuthProxy provides a use-case of authentication proxy. 37 | type AuthProxy struct { 38 | ReverseProxy reverseproxy.Interface 39 | PortForwarder portforwarder.Interface 40 | ResolverFactory resolver.FactoryInterface 41 | NewTransport transport.NewFunc 42 | Env env.Interface 43 | Browser browser.Interface 44 | Logger logger.Interface 45 | } 46 | 47 | // Option represents an option of AuthProxy. 48 | type Option struct { 49 | Config *rest.Config 50 | Namespace string 51 | TargetURL *url.URL 52 | BindAddressCandidates []string 53 | SkipOpenBrowser bool 54 | } 55 | 56 | // Do runs the use-case. 57 | // This runs a port forwarder and reverse proxy. 58 | // 59 | // This never returns nil. 60 | // It returns an error which wraps context.Canceled if the context is canceled. 61 | func (u *AuthProxy) Do(ctx context.Context, o Option) error { 62 | rsv, err := u.ResolverFactory.New(o.Config) 63 | if err != nil { 64 | return fmt.Errorf("could not create a resolver: %w", err) 65 | } 66 | pod, containerPort, err := parseTargetURL(ctx, rsv, o.Namespace, o.TargetURL) 67 | if err != nil { 68 | return fmt.Errorf("could not find the pod and container port: %w", err) 69 | } 70 | u.Logger.V(1).Infof("found container port %d of pod %s", containerPort, pod.Name) 71 | transitPort, err := u.Env.AllocateLocalPort() 72 | if err != nil { 73 | return fmt.Errorf("could not allocate a local port: %w", err) 74 | } 75 | rpTransport, err := u.NewTransport(o.Config) 76 | if err != nil { 77 | return fmt.Errorf("could not create a transport for reverse proxy: %w", err) 78 | } 79 | u.Logger.V(1).Infof("client -> reverse_proxy -> port_forwarder:%d -> pod -> container:%d", transitPort, containerPort) 80 | 81 | var once sync.Once 82 | ro := runOption{ 83 | portForwarderOption: portforwarder.Option{ 84 | Config: o.Config, 85 | SourcePort: transitPort, 86 | TargetNamespace: pod.Namespace, 87 | TargetPodName: pod.Name, 88 | TargetContainerPort: containerPort, 89 | }, 90 | reverseProxyOption: reverseproxy.Option{ 91 | Transport: rpTransport, 92 | BindAddressCandidates: o.BindAddressCandidates, 93 | TargetScheme: o.TargetURL.Scheme, 94 | TargetHost: "localhost", 95 | TargetPort: transitPort, 96 | }, 97 | skipOpenBrowser: o.SkipOpenBrowser, 98 | onceOpenBrowser: &once, 99 | } 100 | b := backoff.NewExponentialBackOff() 101 | if err := backoff.Retry(func() error { 102 | if err := u.run(ctx, ro); err != nil { 103 | if errors.Is(err, portForwarderConnectionLostError) { 104 | u.Logger.Printf("retrying: %s", err) 105 | return err 106 | } 107 | return backoff.Permanent(err) 108 | } 109 | return nil 110 | }, b); err != nil { 111 | return fmt.Errorf("retry over: %w", err) 112 | } 113 | return nil 114 | } 115 | 116 | type runOption struct { 117 | portForwarderOption portforwarder.Option 118 | reverseProxyOption reverseproxy.Option 119 | skipOpenBrowser bool 120 | onceOpenBrowser *sync.Once 121 | } 122 | 123 | // run runs a port forwarder and reverse proxy, and waits for them, as follows: 124 | // 125 | // 1. Run a port forwarder. 126 | // 2. When the port forwarder is ready, run a reverse proxy. 127 | // 3. When the reverse proxy is ready, open the browser (only first time). 128 | // 129 | // When the context is canceled, 130 | // 131 | // - Shut down the port forwarder. 132 | // - Shut down the reverse proxy. 133 | // 134 | // This never returns nil. 135 | // It returns an error which wraps context.Canceled if the context is canceled. 136 | // It returns portForwarderConnectionLostError if a connection has lost. 137 | func (u *AuthProxy) run(ctx context.Context, o runOption) error { 138 | portForwarderIsReady := make(chan struct{}) 139 | reverseProxyIsReady := make(chan reverseproxy.Instance, 1) 140 | stopPortForwarder := make(chan struct{}) 141 | defer close(reverseProxyIsReady) 142 | 143 | eg, ctx := errgroup.WithContext(ctx) 144 | // start a port forwarder 145 | eg.Go(func() error { 146 | u.Logger.V(1).Infof("starting a port forwarder") 147 | if err := u.PortForwarder.Run(o.portForwarderOption, portForwarderIsReady, stopPortForwarder); err != nil { 148 | return fmt.Errorf("could not run a port forwarder: %w", err) 149 | } 150 | u.Logger.V(1).Infof("stopped the port forwarder") 151 | if ctx.Err() == nil { 152 | u.Logger.V(1).Infof("connection of the port forwarder has lost") 153 | return portForwarderConnectionLostError 154 | } 155 | return nil 156 | }) 157 | // stop the port forwarder when the context is done 158 | eg.Go(func() error { 159 | <-ctx.Done() 160 | u.Logger.V(1).Infof("stopping the port forwarder") 161 | close(stopPortForwarder) 162 | return fmt.Errorf("context canceled while running the port forwarder: %w", ctx.Err()) 163 | }) 164 | // start a reverse proxy when the port forwarder is ready 165 | eg.Go(func() error { 166 | select { 167 | case <-portForwarderIsReady: 168 | u.Logger.V(1).Infof("starting a reverse proxy") 169 | if err := u.ReverseProxy.Run(o.reverseProxyOption, reverseProxyIsReady); err != nil { 170 | return fmt.Errorf("could not run a reverse proxy: %w", err) 171 | } 172 | u.Logger.V(1).Infof("stopped the reverse proxy") 173 | return nil 174 | case <-ctx.Done(): 175 | u.Logger.V(1).Infof("context canceled before starting reverse proxy") 176 | return fmt.Errorf("context canceled before starting reverse proxy: %w", ctx.Err()) 177 | } 178 | }) 179 | // open the browser when the reverse proxy is ready 180 | eg.Go(func() error { 181 | u.Logger.V(1).Infof("waiting for the reverse proxy") 182 | select { 183 | case rp := <-reverseProxyIsReady: 184 | u.Logger.V(1).Infof("the reverse proxy is ready") 185 | rpURL := rp.URL().String() 186 | if o.skipOpenBrowser { 187 | u.Logger.Printf("Please open %s in the browser", rpURL) 188 | } else { 189 | o.onceOpenBrowser.Do(func() { 190 | u.Logger.V(1).Infof("opening %s in the browser", rpURL) 191 | if err := u.Browser.Open(rpURL); err != nil { 192 | u.Logger.Printf("Please open %s in the browser (could not open the browser: %s)", rpURL, err) 193 | } 194 | }) 195 | } 196 | // shutdown the reverse proxy when the context is done 197 | eg.Go(func() error { 198 | <-ctx.Done() 199 | u.Logger.V(1).Infof("shutting down the reverse proxy") 200 | if err := rp.Shutdown(context.Background()); err != nil { 201 | return fmt.Errorf("could not shutdown the reverse proxy: %w", err) 202 | } 203 | return fmt.Errorf("context canceled while running the reverse proxy: %w", ctx.Err()) 204 | }) 205 | return nil 206 | case <-ctx.Done(): 207 | u.Logger.V(1).Infof("context canceled before reverse proxy is ready") 208 | return fmt.Errorf("context canceled before reverse proxy is ready: %w", ctx.Err()) 209 | } 210 | }) 211 | if err := eg.Wait(); err != nil { 212 | return fmt.Errorf("error while running an authentication proxy: %w", err) 213 | } 214 | return nil 215 | } 216 | 217 | func parseTargetURL(ctx context.Context, r resolver.Interface, namespace string, u *url.URL) (*corev1.Pod, int, error) { 218 | h := u.Hostname() 219 | if strings.HasSuffix(h, ".svc") { 220 | serviceName := strings.TrimSuffix(h, ".svc") 221 | return r.FindPodByServiceName(ctx, namespace, serviceName) 222 | } 223 | return r.FindPodByName(ctx, namespace, h) 224 | } 225 | -------------------------------------------------------------------------------- /internal/authproxy/auth_proxy_test.go: -------------------------------------------------------------------------------- 1 | package authproxy 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "net/http" 7 | "net/url" 8 | "testing" 9 | "time" 10 | 11 | "github.com/int128/kauthproxy/internal/logger/mock_logger" 12 | "github.com/int128/kauthproxy/internal/mocks/mock_browser" 13 | "github.com/int128/kauthproxy/internal/mocks/mock_env" 14 | "github.com/int128/kauthproxy/internal/mocks/mock_portforwarder" 15 | "github.com/int128/kauthproxy/internal/mocks/mock_resolver" 16 | "github.com/int128/kauthproxy/internal/mocks/mock_reverseproxy" 17 | "github.com/int128/kauthproxy/internal/portforwarder" 18 | "github.com/int128/kauthproxy/internal/reverseproxy" 19 | "github.com/int128/kauthproxy/internal/transport" 20 | "go.uber.org/mock/gomock" 21 | corev1 "k8s.io/api/core/v1" 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | "k8s.io/client-go/rest" 24 | ) 25 | 26 | var notNil = gomock.Not(gomock.Nil()) 27 | 28 | var restConfig rest.Config 29 | var authProxyTransport http.Transport 30 | 31 | func newTransport(t *testing.T) transport.NewFunc { 32 | return func(got *rest.Config) (http.RoundTripper, error) { 33 | if got != &restConfig { 34 | t.Errorf("rest.Config mismatch, got %+v", got) 35 | } 36 | return &authProxyTransport, nil 37 | } 38 | } 39 | 40 | func TestAuthProxy_Do(t *testing.T) { 41 | const containerPort = 18888 42 | const transitPort = 28888 43 | pod := &corev1.Pod{ 44 | ObjectMeta: metav1.ObjectMeta{ 45 | Name: "kubernetes-dashboard-12345678-12345678", 46 | Namespace: "kubernetes-dashboard", 47 | }, 48 | } 49 | 50 | t.Run("ToPod", func(t *testing.T) { 51 | type mocks struct { 52 | resolverFactory *mock_resolver.MockFactoryInterface 53 | env *mock_env.MockInterface 54 | browser *mock_browser.MockInterface 55 | } 56 | newMocks := func(ctrl *gomock.Controller) mocks { 57 | m := mocks{ 58 | resolverFactory: mock_resolver.NewMockFactoryInterface(ctrl), 59 | env: mock_env.NewMockInterface(ctrl), 60 | browser: mock_browser.NewMockInterface(ctrl), 61 | } 62 | m.env.EXPECT(). 63 | AllocateLocalPort(). 64 | Return(transitPort, nil) 65 | mockResolver := mock_resolver.NewMockInterface(ctrl) 66 | mockResolver.EXPECT(). 67 | FindPodByName(gomock.Any(), "NAMESPACE", "podname"). 68 | Return(pod, containerPort, nil) 69 | m.resolverFactory.EXPECT(). 70 | New(&restConfig). 71 | Return(mockResolver, nil) 72 | return m 73 | } 74 | 75 | t.Run("Success", func(t *testing.T) { 76 | ctx, cancel := context.WithTimeout(context.TODO(), 500*time.Millisecond) 77 | defer cancel() 78 | ctrl := gomock.NewController(t) 79 | defer ctrl.Finish() 80 | 81 | portForwarder := mock_portforwarder.NewMockInterface(ctrl) 82 | portForwarder.EXPECT(). 83 | Run(portforwarder.Option{ 84 | Config: &restConfig, 85 | SourcePort: transitPort, 86 | TargetNamespace: "kubernetes-dashboard", 87 | TargetPodName: "kubernetes-dashboard-12345678-12345678", 88 | TargetContainerPort: containerPort, 89 | }, notNil, notNil). 90 | DoAndReturn(func(o portforwarder.Option, readyChan chan struct{}, stopChan <-chan struct{}) error { 91 | time.Sleep(100 * time.Millisecond) 92 | close(readyChan) 93 | <-stopChan 94 | return nil 95 | }) 96 | reverseProxy := mock_reverseproxy.NewMockInterface(ctrl) 97 | reverseProxy.EXPECT(). 98 | Run(reverseproxy.Option{ 99 | Transport: &authProxyTransport, 100 | BindAddressCandidates: []string{"127.0.0.1:8000"}, 101 | TargetScheme: "https", 102 | TargetHost: "localhost", 103 | TargetPort: transitPort, 104 | }, notNil). 105 | DoAndReturn(func(o reverseproxy.Option, readyChan chan<- reverseproxy.Instance) error { 106 | time.Sleep(100 * time.Millisecond) 107 | i := mock_reverseproxy.NewMockInstance(ctrl) 108 | i.EXPECT(). 109 | URL(). 110 | Return(&url.URL{Scheme: "http", Host: "localhost:8000"}) 111 | i.EXPECT(). 112 | Shutdown(notNil). 113 | Return(nil) 114 | readyChan <- i 115 | return nil 116 | }) 117 | m := newMocks(ctrl) 118 | m.browser.EXPECT().Open("http://localhost:8000") 119 | u := &AuthProxy{ 120 | ReverseProxy: reverseProxy, 121 | PortForwarder: portForwarder, 122 | ResolverFactory: m.resolverFactory, 123 | NewTransport: newTransport(t), 124 | Env: m.env, 125 | Browser: m.browser, 126 | Logger: mock_logger.New(t), 127 | } 128 | o := Option{ 129 | Config: &restConfig, 130 | Namespace: "NAMESPACE", 131 | TargetURL: parseURL(t, "https://podname"), 132 | BindAddressCandidates: []string{"127.0.0.1:8000"}, 133 | } 134 | err := u.Do(ctx, o) 135 | if !errors.Is(err, context.DeadlineExceeded) { 136 | t.Errorf("err wants context.DeadlineExceeded but was %+v", err) 137 | } 138 | }) 139 | 140 | t.Run("PortForwarderError", func(t *testing.T) { 141 | ctx := context.TODO() 142 | ctrl := gomock.NewController(t) 143 | defer ctrl.Finish() 144 | 145 | portForwarderError := errors.New("could not connect to pod") 146 | portForwarder := mock_portforwarder.NewMockInterface(ctrl) 147 | portForwarder.EXPECT(). 148 | Run(portforwarder.Option{ 149 | Config: &restConfig, 150 | SourcePort: transitPort, 151 | TargetNamespace: "kubernetes-dashboard", 152 | TargetPodName: "kubernetes-dashboard-12345678-12345678", 153 | TargetContainerPort: containerPort, 154 | }, notNil, notNil). 155 | DoAndReturn(func(o portforwarder.Option, readyChan chan struct{}, stopChan <-chan struct{}) error { 156 | return portForwarderError 157 | }) 158 | reverseProxy := mock_reverseproxy.NewMockInterface(ctrl) 159 | m := newMocks(ctrl) 160 | u := &AuthProxy{ 161 | ReverseProxy: reverseProxy, 162 | PortForwarder: portForwarder, 163 | ResolverFactory: m.resolverFactory, 164 | NewTransport: newTransport(t), 165 | Env: m.env, 166 | Browser: m.browser, 167 | Logger: mock_logger.New(t), 168 | } 169 | o := Option{ 170 | Config: &restConfig, 171 | Namespace: "NAMESPACE", 172 | TargetURL: parseURL(t, "https://podname"), 173 | BindAddressCandidates: []string{"127.0.0.1:8000"}, 174 | } 175 | err := u.Do(ctx, o) 176 | if !errors.Is(err, portForwarderError) { 177 | t.Errorf("err wants the port forwarder error but was %+v", err) 178 | } 179 | }) 180 | 181 | t.Run("ReverseProxyError", func(t *testing.T) { 182 | ctx := context.TODO() 183 | ctrl := gomock.NewController(t) 184 | defer ctrl.Finish() 185 | 186 | portForwarder := mock_portforwarder.NewMockInterface(ctrl) 187 | portForwarder.EXPECT(). 188 | Run(portforwarder.Option{ 189 | Config: &restConfig, 190 | SourcePort: transitPort, 191 | TargetNamespace: "kubernetes-dashboard", 192 | TargetPodName: "kubernetes-dashboard-12345678-12345678", 193 | TargetContainerPort: containerPort, 194 | }, notNil, notNil). 195 | DoAndReturn(func(o portforwarder.Option, readyChan chan struct{}, stopChan <-chan struct{}) error { 196 | time.Sleep(100 * time.Millisecond) 197 | close(readyChan) 198 | <-stopChan 199 | return nil 200 | }) 201 | reverseProxyError := errors.New("could not listen") 202 | reverseProxy := mock_reverseproxy.NewMockInterface(ctrl) 203 | reverseProxy.EXPECT(). 204 | Run(reverseproxy.Option{ 205 | Transport: &authProxyTransport, 206 | BindAddressCandidates: []string{"127.0.0.1:8000"}, 207 | TargetScheme: "https", 208 | TargetHost: "localhost", 209 | TargetPort: transitPort, 210 | }, notNil). 211 | DoAndReturn(func(o reverseproxy.Option, readyChan chan<- reverseproxy.Instance) error { 212 | return reverseProxyError 213 | }) 214 | m := newMocks(ctrl) 215 | u := &AuthProxy{ 216 | ReverseProxy: reverseProxy, 217 | PortForwarder: portForwarder, 218 | ResolverFactory: m.resolverFactory, 219 | NewTransport: newTransport(t), 220 | Env: m.env, 221 | Browser: m.browser, 222 | Logger: mock_logger.New(t), 223 | } 224 | o := Option{ 225 | Config: &restConfig, 226 | Namespace: "NAMESPACE", 227 | TargetURL: parseURL(t, "https://podname"), 228 | BindAddressCandidates: []string{"127.0.0.1:8000"}, 229 | } 230 | err := u.Do(ctx, o) 231 | if !errors.Is(err, reverseProxyError) { 232 | t.Errorf("err wants the port forwarder error but was %+v", err) 233 | } 234 | }) 235 | 236 | t.Run("PortForwarderConnectionLost", func(t *testing.T) { 237 | // 0ms: starting 238 | // 100ms: the port forwarder is ready 239 | // 200ms: the reverse proxy is ready 240 | // 400ms: lost connection 241 | // 900ms: retrying (after the backoff 500ms) 242 | // 1000ms: the port forwarder is ready 243 | // 1100ms: the reverse proxy is ready 244 | // 1200ms: cancel the context 245 | ctx, cancel := context.WithTimeout(context.TODO(), 1200*time.Millisecond) 246 | defer cancel() 247 | ctrl := gomock.NewController(t) 248 | defer ctrl.Finish() 249 | 250 | portForwarder := mock_portforwarder.NewMockInterface(ctrl) 251 | portForwarder.EXPECT(). 252 | Run(portforwarder.Option{ 253 | Config: &restConfig, 254 | SourcePort: transitPort, 255 | TargetNamespace: "kubernetes-dashboard", 256 | TargetPodName: "kubernetes-dashboard-12345678-12345678", 257 | TargetContainerPort: containerPort, 258 | }, notNil, notNil). 259 | DoAndReturn(func(o portforwarder.Option, readyChan chan struct{}, stopChan <-chan struct{}) error { 260 | time.Sleep(100 * time.Millisecond) 261 | close(readyChan) 262 | time.Sleep(300 * time.Millisecond) 263 | return nil // lost connection 264 | }). 265 | MinTimes(2) 266 | reverseProxy := mock_reverseproxy.NewMockInterface(ctrl) 267 | reverseProxy.EXPECT(). 268 | Run(reverseproxy.Option{ 269 | Transport: &authProxyTransport, 270 | BindAddressCandidates: []string{"127.0.0.1:8000"}, 271 | TargetScheme: "https", 272 | TargetHost: "localhost", 273 | TargetPort: transitPort, 274 | }, notNil). 275 | DoAndReturn(func(o reverseproxy.Option, readyChan chan<- reverseproxy.Instance) error { 276 | time.Sleep(100 * time.Millisecond) 277 | i := mock_reverseproxy.NewMockInstance(ctrl) 278 | i.EXPECT(). 279 | URL(). 280 | Return(&url.URL{Scheme: "http", Host: "localhost:8000"}) 281 | i.EXPECT(). 282 | Shutdown(notNil). 283 | Return(nil) 284 | readyChan <- i 285 | return nil 286 | }). 287 | MinTimes(2) 288 | m := newMocks(ctrl) 289 | m.browser.EXPECT().Open("http://localhost:8000") 290 | u := &AuthProxy{ 291 | ReverseProxy: reverseProxy, 292 | PortForwarder: portForwarder, 293 | ResolverFactory: m.resolverFactory, 294 | NewTransport: newTransport(t), 295 | Env: m.env, 296 | Browser: m.browser, 297 | Logger: mock_logger.New(t), 298 | } 299 | o := Option{ 300 | Config: &restConfig, 301 | Namespace: "NAMESPACE", 302 | TargetURL: parseURL(t, "https://podname"), 303 | BindAddressCandidates: []string{"127.0.0.1:8000"}, 304 | } 305 | err := u.Do(ctx, o) 306 | if !errors.Is(err, context.DeadlineExceeded) { 307 | t.Errorf("err wants context.DeadlineExceeded but was %+v", err) 308 | } 309 | }) 310 | }) 311 | 312 | t.Run("ToService", func(t *testing.T) { 313 | type mocks struct { 314 | resolverFactory *mock_resolver.MockFactoryInterface 315 | env *mock_env.MockInterface 316 | browser *mock_browser.MockInterface 317 | } 318 | newMocks := func(ctrl *gomock.Controller) mocks { 319 | m := mocks{ 320 | resolverFactory: mock_resolver.NewMockFactoryInterface(ctrl), 321 | env: mock_env.NewMockInterface(ctrl), 322 | browser: mock_browser.NewMockInterface(ctrl), 323 | } 324 | m.env.EXPECT(). 325 | AllocateLocalPort(). 326 | Return(transitPort, nil) 327 | mockResolver := mock_resolver.NewMockInterface(ctrl) 328 | mockResolver.EXPECT(). 329 | FindPodByServiceName(gomock.Any(), "NAMESPACE", "servicename"). 330 | Return(pod, containerPort, nil) 331 | m.resolverFactory.EXPECT(). 332 | New(&restConfig). 333 | Return(mockResolver, nil) 334 | return m 335 | } 336 | 337 | t.Run("Success", func(t *testing.T) { 338 | ctx, cancel := context.WithTimeout(context.TODO(), 500*time.Millisecond) 339 | defer cancel() 340 | ctrl := gomock.NewController(t) 341 | defer ctrl.Finish() 342 | portForwarder := mock_portforwarder.NewMockInterface(ctrl) 343 | portForwarder.EXPECT(). 344 | Run(portforwarder.Option{ 345 | Config: &restConfig, 346 | SourcePort: transitPort, 347 | TargetNamespace: "kubernetes-dashboard", 348 | TargetPodName: "kubernetes-dashboard-12345678-12345678", 349 | TargetContainerPort: containerPort, 350 | }, notNil, notNil). 351 | DoAndReturn(func(o portforwarder.Option, readyChan chan struct{}, stopChan <-chan struct{}) error { 352 | time.Sleep(100 * time.Millisecond) 353 | close(readyChan) 354 | <-stopChan 355 | return nil 356 | }) 357 | reverseProxyInstance := mock_reverseproxy.NewMockInstance(ctrl) 358 | reverseProxyInstance.EXPECT(). 359 | URL(). 360 | Return(&url.URL{Scheme: "http", Host: "localhost:8000"}) 361 | reverseProxyInstance.EXPECT(). 362 | Shutdown(notNil). 363 | Return(nil) 364 | reverseProxy := mock_reverseproxy.NewMockInterface(ctrl) 365 | reverseProxy.EXPECT(). 366 | Run(reverseproxy.Option{ 367 | Transport: &authProxyTransport, 368 | BindAddressCandidates: []string{"127.0.0.1:8000"}, 369 | TargetScheme: "https", 370 | TargetHost: "localhost", 371 | TargetPort: transitPort, 372 | }, notNil). 373 | DoAndReturn(func(o reverseproxy.Option, readyChan chan<- reverseproxy.Instance) error { 374 | time.Sleep(100 * time.Millisecond) 375 | readyChan <- reverseProxyInstance 376 | return nil 377 | }) 378 | m := newMocks(ctrl) 379 | m.browser.EXPECT().Open("http://localhost:8000") 380 | u := &AuthProxy{ 381 | ReverseProxy: reverseProxy, 382 | PortForwarder: portForwarder, 383 | ResolverFactory: m.resolverFactory, 384 | NewTransport: newTransport(t), 385 | Env: m.env, 386 | Browser: m.browser, 387 | Logger: mock_logger.New(t), 388 | } 389 | o := Option{ 390 | Config: &restConfig, 391 | Namespace: "NAMESPACE", 392 | TargetURL: parseURL(t, "https://servicename.svc"), 393 | BindAddressCandidates: []string{"127.0.0.1:8000"}, 394 | } 395 | err := u.Do(ctx, o) 396 | if !errors.Is(err, context.DeadlineExceeded) { 397 | t.Errorf("err wants context.DeadlineExceeded but was %+v", err) 398 | } 399 | }) 400 | }) 401 | } 402 | 403 | func parseURL(t *testing.T, s string) *url.URL { 404 | u, err := url.Parse(s) 405 | if err != nil { 406 | t.Errorf("could not parse URL: %s", err) 407 | } 408 | return u 409 | } 410 | -------------------------------------------------------------------------------- /internal/browser/browser.go: -------------------------------------------------------------------------------- 1 | package browser 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/google/wire" 7 | "github.com/pkg/browser" 8 | ) 9 | 10 | var Set = wire.NewSet( 11 | wire.Struct(new(Browser), "*"), 12 | wire.Bind(new(Interface), new(*Browser)), 13 | ) 14 | 15 | type Interface interface { 16 | Open(url string) error 17 | } 18 | 19 | type Browser struct{} 20 | 21 | // Open opens the default browser. 22 | func (*Browser) Open(url string) error { 23 | if err := browser.OpenURL(url); err != nil { 24 | return fmt.Errorf("could not open the browser: %w", err) 25 | } 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /internal/cmd/cmd.go: -------------------------------------------------------------------------------- 1 | // Package cmd provides command line interface. 2 | package cmd 3 | 4 | import ( 5 | "context" 6 | "errors" 7 | "fmt" 8 | "net/url" 9 | 10 | "github.com/google/wire" 11 | "github.com/int128/kauthproxy/internal/authproxy" 12 | "github.com/int128/kauthproxy/internal/logger" 13 | "github.com/spf13/cobra" 14 | "github.com/spf13/pflag" 15 | "k8s.io/cli-runtime/pkg/genericclioptions" 16 | ) 17 | 18 | var Set = wire.NewSet( 19 | wire.Struct(new(Cmd), "*"), 20 | wire.Bind(new(Interface), new(*Cmd)), 21 | ) 22 | 23 | type Interface interface { 24 | Run(ctx context.Context, osArgs []string, version string) int 25 | } 26 | 27 | var defaultAddress = []string{ 28 | "127.0.0.1:18000", 29 | "127.0.0.1:28000", 30 | } 31 | 32 | // Cmd provides command line interface. 33 | type Cmd struct { 34 | AuthProxy authproxy.Interface 35 | Logger logger.Interface 36 | } 37 | 38 | // Run parses the arguments and executes the corresponding use-case. 39 | func (cmd *Cmd) Run(ctx context.Context, osArgs []string, version string) int { 40 | rootCmd := cmd.newRootCmd() 41 | rootCmd.SilenceErrors = true 42 | rootCmd.SilenceUsage = true 43 | rootCmd.Version = version 44 | 45 | rootCmd.SetArgs(osArgs[1:]) 46 | if err := rootCmd.ExecuteContext(ctx); err != nil { 47 | if errors.Is(err, context.Canceled) { 48 | cmd.Logger.V(1).Infof("terminating: %s", err) 49 | return 0 50 | } 51 | cmd.Logger.Printf("error: %s", err) 52 | cmd.Logger.V(1).Infof("stacktrace: %+v", err) 53 | return 1 54 | } 55 | return 0 56 | } 57 | 58 | type rootCmdOptions struct { 59 | k8sOptions *genericclioptions.ConfigFlags 60 | addressCandidates []string 61 | skipOpenBrowser bool 62 | } 63 | 64 | func (o *rootCmdOptions) addFlags(f *pflag.FlagSet) { 65 | o.k8sOptions.AddFlags(f) 66 | f.StringArrayVar(&o.addressCandidates, "address", defaultAddress, "The address on which to run the proxy. If set multiple times, it will try binding the address in order") 67 | f.BoolVar(&o.skipOpenBrowser, "skip-open-browser", false, "If set, skip opening the browser") 68 | } 69 | 70 | func (cmd *Cmd) newRootCmd() *cobra.Command { 71 | var o rootCmdOptions 72 | o.k8sOptions = genericclioptions.NewConfigFlags(false) 73 | c := &cobra.Command{ 74 | Use: "kubectl auth-proxy POD_OR_SERVICE_URL", 75 | Short: "Forward a local port to a pod or service via the authentication proxy", 76 | Long: `Forward a local port to a pod or service via the authentication proxy. 77 | It gets a token from the current credential plugin (e.g. EKS, OpenID Connect). 78 | Then it appends the authorization header to HTTP requests, like "authorization: Bearer token". 79 | All traffic is routed by the authentication proxy and port forwarder as follows: 80 | [browser] -> [authentication proxy] -> [port forwarder] -> [pod]`, 81 | Example: ` # To access a service: 82 | kubectl auth-proxy https://kubernetes-dashboard.svc 83 | 84 | # To access a pod: 85 | kubectl auth-proxy https://kubernetes-dashboard-57fc4fcb74-jjg77`, 86 | Args: cobra.ExactArgs(1), 87 | RunE: func(c *cobra.Command, args []string) error { 88 | return cmd.runRootCmd(c.Context(), o, args) 89 | }, 90 | } 91 | o.addFlags(c.Flags()) 92 | cmd.Logger.AddFlags(c.PersistentFlags()) 93 | return c 94 | } 95 | 96 | func (cmd *Cmd) runRootCmd(ctx context.Context, o rootCmdOptions, args []string) error { 97 | remoteURL, err := url.Parse(args[0]) 98 | if err != nil { 99 | return fmt.Errorf("invalid remote URL: %w", err) 100 | } 101 | config, err := o.k8sOptions.ToRESTConfig() 102 | if err != nil { 103 | return fmt.Errorf("could not load the config: %w", err) 104 | } 105 | namespace, _, err := o.k8sOptions.ToRawKubeConfigLoader().Namespace() 106 | if err != nil { 107 | return fmt.Errorf("could not determine the namespace: %w", err) 108 | } 109 | authProxyOption := authproxy.Option{ 110 | Config: config, 111 | Namespace: namespace, 112 | TargetURL: remoteURL, 113 | BindAddressCandidates: o.addressCandidates, 114 | SkipOpenBrowser: o.skipOpenBrowser, 115 | } 116 | if err := cmd.AuthProxy.Do(ctx, authProxyOption); err != nil { 117 | return fmt.Errorf("could not run an authentication proxy: %w", err) 118 | } 119 | return nil 120 | } 121 | -------------------------------------------------------------------------------- /internal/di/di.go: -------------------------------------------------------------------------------- 1 | //go:build wireinject 2 | // +build wireinject 3 | 4 | // Package di provides dependency injection. 5 | package di 6 | 7 | import ( 8 | "github.com/google/wire" 9 | "github.com/int128/kauthproxy/internal/authproxy" 10 | "github.com/int128/kauthproxy/internal/browser" 11 | "github.com/int128/kauthproxy/internal/cmd" 12 | "github.com/int128/kauthproxy/internal/env" 13 | "github.com/int128/kauthproxy/internal/logger" 14 | "github.com/int128/kauthproxy/internal/portforwarder" 15 | "github.com/int128/kauthproxy/internal/resolver" 16 | "github.com/int128/kauthproxy/internal/reverseproxy" 17 | "github.com/int128/kauthproxy/internal/transport" 18 | ) 19 | 20 | func NewCmd() cmd.Interface { 21 | wire.Build( 22 | // adaptors 23 | cmd.Set, 24 | reverseproxy.Set, 25 | portforwarder.Set, 26 | resolver.Set, 27 | transport.Set, 28 | env.Set, 29 | browser.Set, 30 | logger.Set, 31 | 32 | // usecases 33 | authproxy.Set, 34 | ) 35 | return nil 36 | } 37 | -------------------------------------------------------------------------------- /internal/di/wire_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by Wire. DO NOT EDIT. 2 | 3 | //go:generate go run -mod=mod github.com/google/wire/cmd/wire 4 | //go:build !wireinject 5 | // +build !wireinject 6 | 7 | package di 8 | 9 | import ( 10 | "github.com/int128/kauthproxy/internal/authproxy" 11 | "github.com/int128/kauthproxy/internal/browser" 12 | "github.com/int128/kauthproxy/internal/cmd" 13 | "github.com/int128/kauthproxy/internal/env" 14 | "github.com/int128/kauthproxy/internal/logger" 15 | "github.com/int128/kauthproxy/internal/portforwarder" 16 | "github.com/int128/kauthproxy/internal/resolver" 17 | "github.com/int128/kauthproxy/internal/reverseproxy" 18 | "github.com/int128/kauthproxy/internal/transport" 19 | ) 20 | 21 | // Injectors from di.go: 22 | 23 | func NewCmd() cmd.Interface { 24 | reverseProxy := &reverseproxy.ReverseProxy{} 25 | portForwarder := &portforwarder.PortForwarder{} 26 | loggerLogger := &logger.Logger{} 27 | factory := &resolver.Factory{ 28 | Logger: loggerLogger, 29 | } 30 | newFunc := _wireNewFuncValue 31 | envEnv := &env.Env{} 32 | browserBrowser := &browser.Browser{} 33 | authProxy := &authproxy.AuthProxy{ 34 | ReverseProxy: reverseProxy, 35 | PortForwarder: portForwarder, 36 | ResolverFactory: factory, 37 | NewTransport: newFunc, 38 | Env: envEnv, 39 | Browser: browserBrowser, 40 | Logger: loggerLogger, 41 | } 42 | cmdCmd := &cmd.Cmd{ 43 | AuthProxy: authProxy, 44 | Logger: loggerLogger, 45 | } 46 | return cmdCmd 47 | } 48 | 49 | var ( 50 | _wireNewFuncValue = transport.NewFunc(transport.New) 51 | ) 52 | -------------------------------------------------------------------------------- /internal/env/env.go: -------------------------------------------------------------------------------- 1 | package env 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/google/wire" 8 | ) 9 | 10 | var Set = wire.NewSet( 11 | wire.Struct(new(Env), "*"), 12 | wire.Bind(new(Interface), new(*Env)), 13 | ) 14 | 15 | type Interface interface { 16 | AllocateLocalPort() (int, error) 17 | } 18 | 19 | type Env struct{} 20 | 21 | // AllocateLocalPort returns a free port on localhost. 22 | func (*Env) AllocateLocalPort() (int, error) { 23 | l, err := net.Listen("tcp", "localhost:0") 24 | if err != nil { 25 | return 0, fmt.Errorf("could not listen: %w", err) 26 | } 27 | defer l.Close() 28 | addr, ok := l.Addr().(*net.TCPAddr) 29 | if !ok { 30 | return 0, fmt.Errorf("internal error: unknown type %T", l.Addr()) 31 | } 32 | return addr.Port, nil 33 | } 34 | -------------------------------------------------------------------------------- /internal/logger/logger.go: -------------------------------------------------------------------------------- 1 | // Package logger provides logging facility. 2 | package logger 3 | 4 | import ( 5 | "flag" 6 | "fmt" 7 | "os" 8 | 9 | "github.com/google/wire" 10 | "github.com/spf13/pflag" 11 | "k8s.io/klog/v2" 12 | ) 13 | 14 | var Set = wire.NewSet( 15 | wire.Bind(new(Interface), new(*Logger)), 16 | wire.Struct(new(Logger)), 17 | ) 18 | 19 | type Interface interface { 20 | AddFlags(f *pflag.FlagSet) 21 | Printf(format string, args ...interface{}) 22 | V(level int) Verbose 23 | } 24 | 25 | type Verbose interface { 26 | Infof(format string, args ...interface{}) 27 | } 28 | 29 | // Logger provides logging facility using klog. 30 | type Logger struct{} 31 | 32 | // AddFlags adds the flags such as -v. 33 | func (*Logger) AddFlags(f *pflag.FlagSet) { 34 | gf := flag.NewFlagSet("", flag.ContinueOnError) 35 | klog.InitFlags(gf) 36 | f.AddGoFlagSet(gf) 37 | } 38 | 39 | // Printf writes the message to stderr. 40 | func (*Logger) Printf(format string, args ...interface{}) { 41 | _, _ = fmt.Fprintf(os.Stderr, format, args...) 42 | _, _ = fmt.Fprintln(os.Stderr, "") 43 | } 44 | 45 | // V returns a logger enabled only if the level is enabled. 46 | func (*Logger) V(level int) Verbose { 47 | return klog.V(klog.Level(level)) 48 | } 49 | -------------------------------------------------------------------------------- /internal/logger/mock_logger/mock_logger.go: -------------------------------------------------------------------------------- 1 | package mock_logger 2 | 3 | import ( 4 | "time" 5 | 6 | logger2 "github.com/int128/kauthproxy/internal/logger" 7 | "github.com/spf13/pflag" 8 | ) 9 | 10 | type testingLogf interface { 11 | Logf(format string, args ...interface{}) 12 | } 13 | 14 | func New(t testingLogf) *Logger { 15 | return &Logger{t} 16 | } 17 | 18 | type Logger struct { 19 | t testingLogf 20 | } 21 | 22 | func (l *Logger) AddFlags(f *pflag.FlagSet) { 23 | } 24 | 25 | func (l *Logger) Printf(format string, args ...interface{}) { 26 | logf(l.t, "", format, args) 27 | } 28 | 29 | func (l *Logger) V(level int) logger2.Verbose { 30 | return &Verbose{l.t} 31 | } 32 | 33 | type Verbose struct { 34 | t testingLogf 35 | } 36 | 37 | func (v *Verbose) Infof(format string, args ...interface{}) { 38 | logf(v.t, "I]", format, args) 39 | } 40 | 41 | func logf(t testingLogf, level, format string, args []interface{}) { 42 | t.Logf("%s %2s "+format, append([]interface{}{ 43 | time.Now().Format("15:04:05.000"), 44 | level, 45 | }, args...)...) 46 | } 47 | -------------------------------------------------------------------------------- /internal/mocks/mock_browser/mock.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/int128/kauthproxy/internal/browser (interfaces: Interface) 3 | // 4 | // Generated by this command: 5 | // 6 | // mockgen -destination internal/mocks/mock_browser/mock.go github.com/int128/kauthproxy/internal/browser Interface 7 | // 8 | 9 | // Package mock_browser is a generated GoMock package. 10 | package mock_browser 11 | 12 | import ( 13 | reflect "reflect" 14 | 15 | gomock "go.uber.org/mock/gomock" 16 | ) 17 | 18 | // MockInterface is a mock of Interface interface. 19 | type MockInterface struct { 20 | ctrl *gomock.Controller 21 | recorder *MockInterfaceMockRecorder 22 | isgomock struct{} 23 | } 24 | 25 | // MockInterfaceMockRecorder is the mock recorder for MockInterface. 26 | type MockInterfaceMockRecorder struct { 27 | mock *MockInterface 28 | } 29 | 30 | // NewMockInterface creates a new mock instance. 31 | func NewMockInterface(ctrl *gomock.Controller) *MockInterface { 32 | mock := &MockInterface{ctrl: ctrl} 33 | mock.recorder = &MockInterfaceMockRecorder{mock} 34 | return mock 35 | } 36 | 37 | // EXPECT returns an object that allows the caller to indicate expected use. 38 | func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder { 39 | return m.recorder 40 | } 41 | 42 | // Open mocks base method. 43 | func (m *MockInterface) Open(url string) error { 44 | m.ctrl.T.Helper() 45 | ret := m.ctrl.Call(m, "Open", url) 46 | ret0, _ := ret[0].(error) 47 | return ret0 48 | } 49 | 50 | // Open indicates an expected call of Open. 51 | func (mr *MockInterfaceMockRecorder) Open(url any) *gomock.Call { 52 | mr.mock.ctrl.T.Helper() 53 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Open", reflect.TypeOf((*MockInterface)(nil).Open), url) 54 | } 55 | -------------------------------------------------------------------------------- /internal/mocks/mock_env/mock.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/int128/kauthproxy/internal/env (interfaces: Interface) 3 | // 4 | // Generated by this command: 5 | // 6 | // mockgen -destination internal/mocks/mock_env/mock.go github.com/int128/kauthproxy/internal/env Interface 7 | // 8 | 9 | // Package mock_env is a generated GoMock package. 10 | package mock_env 11 | 12 | import ( 13 | reflect "reflect" 14 | 15 | gomock "go.uber.org/mock/gomock" 16 | ) 17 | 18 | // MockInterface is a mock of Interface interface. 19 | type MockInterface struct { 20 | ctrl *gomock.Controller 21 | recorder *MockInterfaceMockRecorder 22 | isgomock struct{} 23 | } 24 | 25 | // MockInterfaceMockRecorder is the mock recorder for MockInterface. 26 | type MockInterfaceMockRecorder struct { 27 | mock *MockInterface 28 | } 29 | 30 | // NewMockInterface creates a new mock instance. 31 | func NewMockInterface(ctrl *gomock.Controller) *MockInterface { 32 | mock := &MockInterface{ctrl: ctrl} 33 | mock.recorder = &MockInterfaceMockRecorder{mock} 34 | return mock 35 | } 36 | 37 | // EXPECT returns an object that allows the caller to indicate expected use. 38 | func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder { 39 | return m.recorder 40 | } 41 | 42 | // AllocateLocalPort mocks base method. 43 | func (m *MockInterface) AllocateLocalPort() (int, error) { 44 | m.ctrl.T.Helper() 45 | ret := m.ctrl.Call(m, "AllocateLocalPort") 46 | ret0, _ := ret[0].(int) 47 | ret1, _ := ret[1].(error) 48 | return ret0, ret1 49 | } 50 | 51 | // AllocateLocalPort indicates an expected call of AllocateLocalPort. 52 | func (mr *MockInterfaceMockRecorder) AllocateLocalPort() *gomock.Call { 53 | mr.mock.ctrl.T.Helper() 54 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AllocateLocalPort", reflect.TypeOf((*MockInterface)(nil).AllocateLocalPort)) 55 | } 56 | -------------------------------------------------------------------------------- /internal/mocks/mock_portforwarder/mock.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/int128/kauthproxy/internal/portforwarder (interfaces: Interface) 3 | // 4 | // Generated by this command: 5 | // 6 | // mockgen -destination internal/mocks/mock_portforwarder/mock.go github.com/int128/kauthproxy/internal/portforwarder Interface 7 | // 8 | 9 | // Package mock_portforwarder is a generated GoMock package. 10 | package mock_portforwarder 11 | 12 | import ( 13 | reflect "reflect" 14 | 15 | portforwarder "github.com/int128/kauthproxy/internal/portforwarder" 16 | gomock "go.uber.org/mock/gomock" 17 | ) 18 | 19 | // MockInterface is a mock of Interface interface. 20 | type MockInterface struct { 21 | ctrl *gomock.Controller 22 | recorder *MockInterfaceMockRecorder 23 | isgomock struct{} 24 | } 25 | 26 | // MockInterfaceMockRecorder is the mock recorder for MockInterface. 27 | type MockInterfaceMockRecorder struct { 28 | mock *MockInterface 29 | } 30 | 31 | // NewMockInterface creates a new mock instance. 32 | func NewMockInterface(ctrl *gomock.Controller) *MockInterface { 33 | mock := &MockInterface{ctrl: ctrl} 34 | mock.recorder = &MockInterfaceMockRecorder{mock} 35 | return mock 36 | } 37 | 38 | // EXPECT returns an object that allows the caller to indicate expected use. 39 | func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder { 40 | return m.recorder 41 | } 42 | 43 | // Run mocks base method. 44 | func (m *MockInterface) Run(o portforwarder.Option, readyChan chan struct{}, stopChan <-chan struct{}) error { 45 | m.ctrl.T.Helper() 46 | ret := m.ctrl.Call(m, "Run", o, readyChan, stopChan) 47 | ret0, _ := ret[0].(error) 48 | return ret0 49 | } 50 | 51 | // Run indicates an expected call of Run. 52 | func (mr *MockInterfaceMockRecorder) Run(o, readyChan, stopChan any) *gomock.Call { 53 | mr.mock.ctrl.T.Helper() 54 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockInterface)(nil).Run), o, readyChan, stopChan) 55 | } 56 | -------------------------------------------------------------------------------- /internal/mocks/mock_resolver/mock.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/int128/kauthproxy/internal/resolver (interfaces: FactoryInterface,Interface) 3 | // 4 | // Generated by this command: 5 | // 6 | // mockgen -destination internal/mocks/mock_resolver/mock.go github.com/int128/kauthproxy/internal/resolver FactoryInterface,Interface 7 | // 8 | 9 | // Package mock_resolver is a generated GoMock package. 10 | package mock_resolver 11 | 12 | import ( 13 | context "context" 14 | reflect "reflect" 15 | 16 | resolver "github.com/int128/kauthproxy/internal/resolver" 17 | gomock "go.uber.org/mock/gomock" 18 | v1 "k8s.io/api/core/v1" 19 | rest "k8s.io/client-go/rest" 20 | ) 21 | 22 | // MockFactoryInterface is a mock of FactoryInterface interface. 23 | type MockFactoryInterface struct { 24 | ctrl *gomock.Controller 25 | recorder *MockFactoryInterfaceMockRecorder 26 | isgomock struct{} 27 | } 28 | 29 | // MockFactoryInterfaceMockRecorder is the mock recorder for MockFactoryInterface. 30 | type MockFactoryInterfaceMockRecorder struct { 31 | mock *MockFactoryInterface 32 | } 33 | 34 | // NewMockFactoryInterface creates a new mock instance. 35 | func NewMockFactoryInterface(ctrl *gomock.Controller) *MockFactoryInterface { 36 | mock := &MockFactoryInterface{ctrl: ctrl} 37 | mock.recorder = &MockFactoryInterfaceMockRecorder{mock} 38 | return mock 39 | } 40 | 41 | // EXPECT returns an object that allows the caller to indicate expected use. 42 | func (m *MockFactoryInterface) EXPECT() *MockFactoryInterfaceMockRecorder { 43 | return m.recorder 44 | } 45 | 46 | // New mocks base method. 47 | func (m *MockFactoryInterface) New(config *rest.Config) (resolver.Interface, error) { 48 | m.ctrl.T.Helper() 49 | ret := m.ctrl.Call(m, "New", config) 50 | ret0, _ := ret[0].(resolver.Interface) 51 | ret1, _ := ret[1].(error) 52 | return ret0, ret1 53 | } 54 | 55 | // New indicates an expected call of New. 56 | func (mr *MockFactoryInterfaceMockRecorder) New(config any) *gomock.Call { 57 | mr.mock.ctrl.T.Helper() 58 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "New", reflect.TypeOf((*MockFactoryInterface)(nil).New), config) 59 | } 60 | 61 | // MockInterface is a mock of Interface interface. 62 | type MockInterface struct { 63 | ctrl *gomock.Controller 64 | recorder *MockInterfaceMockRecorder 65 | isgomock struct{} 66 | } 67 | 68 | // MockInterfaceMockRecorder is the mock recorder for MockInterface. 69 | type MockInterfaceMockRecorder struct { 70 | mock *MockInterface 71 | } 72 | 73 | // NewMockInterface creates a new mock instance. 74 | func NewMockInterface(ctrl *gomock.Controller) *MockInterface { 75 | mock := &MockInterface{ctrl: ctrl} 76 | mock.recorder = &MockInterfaceMockRecorder{mock} 77 | return mock 78 | } 79 | 80 | // EXPECT returns an object that allows the caller to indicate expected use. 81 | func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder { 82 | return m.recorder 83 | } 84 | 85 | // FindPodByName mocks base method. 86 | func (m *MockInterface) FindPodByName(ctx context.Context, namespace, podName string) (*v1.Pod, int, error) { 87 | m.ctrl.T.Helper() 88 | ret := m.ctrl.Call(m, "FindPodByName", ctx, namespace, podName) 89 | ret0, _ := ret[0].(*v1.Pod) 90 | ret1, _ := ret[1].(int) 91 | ret2, _ := ret[2].(error) 92 | return ret0, ret1, ret2 93 | } 94 | 95 | // FindPodByName indicates an expected call of FindPodByName. 96 | func (mr *MockInterfaceMockRecorder) FindPodByName(ctx, namespace, podName any) *gomock.Call { 97 | mr.mock.ctrl.T.Helper() 98 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindPodByName", reflect.TypeOf((*MockInterface)(nil).FindPodByName), ctx, namespace, podName) 99 | } 100 | 101 | // FindPodByServiceName mocks base method. 102 | func (m *MockInterface) FindPodByServiceName(ctx context.Context, namespace, serviceName string) (*v1.Pod, int, error) { 103 | m.ctrl.T.Helper() 104 | ret := m.ctrl.Call(m, "FindPodByServiceName", ctx, namespace, serviceName) 105 | ret0, _ := ret[0].(*v1.Pod) 106 | ret1, _ := ret[1].(int) 107 | ret2, _ := ret[2].(error) 108 | return ret0, ret1, ret2 109 | } 110 | 111 | // FindPodByServiceName indicates an expected call of FindPodByServiceName. 112 | func (mr *MockInterfaceMockRecorder) FindPodByServiceName(ctx, namespace, serviceName any) *gomock.Call { 113 | mr.mock.ctrl.T.Helper() 114 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindPodByServiceName", reflect.TypeOf((*MockInterface)(nil).FindPodByServiceName), ctx, namespace, serviceName) 115 | } 116 | -------------------------------------------------------------------------------- /internal/mocks/mock_reverseproxy/mock.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/int128/kauthproxy/internal/reverseproxy (interfaces: Interface,Instance) 3 | // 4 | // Generated by this command: 5 | // 6 | // mockgen -destination internal/mocks/mock_reverseproxy/mock.go github.com/int128/kauthproxy/internal/reverseproxy Interface,Instance 7 | // 8 | 9 | // Package mock_reverseproxy is a generated GoMock package. 10 | package mock_reverseproxy 11 | 12 | import ( 13 | context "context" 14 | url "net/url" 15 | reflect "reflect" 16 | 17 | reverseproxy "github.com/int128/kauthproxy/internal/reverseproxy" 18 | gomock "go.uber.org/mock/gomock" 19 | ) 20 | 21 | // MockInterface is a mock of Interface interface. 22 | type MockInterface struct { 23 | ctrl *gomock.Controller 24 | recorder *MockInterfaceMockRecorder 25 | isgomock struct{} 26 | } 27 | 28 | // MockInterfaceMockRecorder is the mock recorder for MockInterface. 29 | type MockInterfaceMockRecorder struct { 30 | mock *MockInterface 31 | } 32 | 33 | // NewMockInterface creates a new mock instance. 34 | func NewMockInterface(ctrl *gomock.Controller) *MockInterface { 35 | mock := &MockInterface{ctrl: ctrl} 36 | mock.recorder = &MockInterfaceMockRecorder{mock} 37 | return mock 38 | } 39 | 40 | // EXPECT returns an object that allows the caller to indicate expected use. 41 | func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder { 42 | return m.recorder 43 | } 44 | 45 | // Run mocks base method. 46 | func (m *MockInterface) Run(o reverseproxy.Option, readyChan chan<- reverseproxy.Instance) error { 47 | m.ctrl.T.Helper() 48 | ret := m.ctrl.Call(m, "Run", o, readyChan) 49 | ret0, _ := ret[0].(error) 50 | return ret0 51 | } 52 | 53 | // Run indicates an expected call of Run. 54 | func (mr *MockInterfaceMockRecorder) Run(o, readyChan any) *gomock.Call { 55 | mr.mock.ctrl.T.Helper() 56 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockInterface)(nil).Run), o, readyChan) 57 | } 58 | 59 | // MockInstance is a mock of Instance interface. 60 | type MockInstance struct { 61 | ctrl *gomock.Controller 62 | recorder *MockInstanceMockRecorder 63 | isgomock struct{} 64 | } 65 | 66 | // MockInstanceMockRecorder is the mock recorder for MockInstance. 67 | type MockInstanceMockRecorder struct { 68 | mock *MockInstance 69 | } 70 | 71 | // NewMockInstance creates a new mock instance. 72 | func NewMockInstance(ctrl *gomock.Controller) *MockInstance { 73 | mock := &MockInstance{ctrl: ctrl} 74 | mock.recorder = &MockInstanceMockRecorder{mock} 75 | return mock 76 | } 77 | 78 | // EXPECT returns an object that allows the caller to indicate expected use. 79 | func (m *MockInstance) EXPECT() *MockInstanceMockRecorder { 80 | return m.recorder 81 | } 82 | 83 | // Shutdown mocks base method. 84 | func (m *MockInstance) Shutdown(ctx context.Context) error { 85 | m.ctrl.T.Helper() 86 | ret := m.ctrl.Call(m, "Shutdown", ctx) 87 | ret0, _ := ret[0].(error) 88 | return ret0 89 | } 90 | 91 | // Shutdown indicates an expected call of Shutdown. 92 | func (mr *MockInstanceMockRecorder) Shutdown(ctx any) *gomock.Call { 93 | mr.mock.ctrl.T.Helper() 94 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Shutdown", reflect.TypeOf((*MockInstance)(nil).Shutdown), ctx) 95 | } 96 | 97 | // URL mocks base method. 98 | func (m *MockInstance) URL() *url.URL { 99 | m.ctrl.T.Helper() 100 | ret := m.ctrl.Call(m, "URL") 101 | ret0, _ := ret[0].(*url.URL) 102 | return ret0 103 | } 104 | 105 | // URL indicates an expected call of URL. 106 | func (mr *MockInstanceMockRecorder) URL() *gomock.Call { 107 | mr.mock.ctrl.T.Helper() 108 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "URL", reflect.TypeOf((*MockInstance)(nil).URL)) 109 | } 110 | -------------------------------------------------------------------------------- /internal/portforwarder/port_forwarder.go: -------------------------------------------------------------------------------- 1 | // Package portforwarder provides port forwarding between local and Kubernetes. 2 | package portforwarder 3 | 4 | import ( 5 | "fmt" 6 | "net/http" 7 | "net/url" 8 | "os" 9 | 10 | "github.com/google/wire" 11 | "k8s.io/client-go/rest" 12 | "k8s.io/client-go/tools/portforward" 13 | "k8s.io/client-go/transport/spdy" 14 | ) 15 | 16 | var Set = wire.NewSet( 17 | wire.Struct(new(PortForwarder), "*"), 18 | wire.Bind(new(Interface), new(*PortForwarder)), 19 | ) 20 | 21 | // Option represents an option of PortForwarder. 22 | type Option struct { 23 | Config *rest.Config 24 | SourcePort int 25 | TargetNamespace string 26 | TargetPodName string 27 | TargetContainerPort int 28 | } 29 | 30 | type Interface interface { 31 | Run(o Option, readyChan chan struct{}, stopChan <-chan struct{}) error 32 | } 33 | 34 | type PortForwarder struct { 35 | } 36 | 37 | // Run executes a port forwarder. 38 | // 39 | // It returns nil if stopChan has been closed or connection has lost. 40 | // It returns an error if it could not connect to the pod. 41 | // 42 | // It will close the readyChan when the port forwarder is ready. 43 | // Caller can stop the port forwarder by closing the stopChan. 44 | func (pf *PortForwarder) Run(o Option, readyChan chan struct{}, stopChan <-chan struct{}) error { 45 | pfURL, err := url.Parse(fmt.Sprintf("%s/api/v1/namespaces/%s/pods/%s/portforward", o.Config.Host, o.TargetNamespace, o.TargetPodName)) 46 | if err != nil { 47 | return fmt.Errorf("could not build URL for portforward: %w", err) 48 | } 49 | rt, upgrader, err := spdy.RoundTripperFor(o.Config) 50 | if err != nil { 51 | return fmt.Errorf("could not create a round tripper: %w", err) 52 | } 53 | dialer := spdy.NewDialer(upgrader, &http.Client{Transport: rt}, http.MethodPost, pfURL) 54 | portPair := fmt.Sprintf("%d:%d", o.SourcePort, o.TargetContainerPort) 55 | forwarder, err := portforward.NewOnAddresses(dialer, []string{"127.0.0.1"}, []string{portPair}, stopChan, readyChan, os.Stdout, os.Stderr) 56 | if err != nil { 57 | return fmt.Errorf("could not create a port forwarder: %w", err) 58 | } 59 | if err := forwarder.ForwardPorts(); err != nil { 60 | return fmt.Errorf("could not run the port forwarder at %s: %w", portPair, err) 61 | } 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /internal/resolver/resolver.go: -------------------------------------------------------------------------------- 1 | // Package resolver provides resolving a pod and container port. 2 | package resolver 3 | 4 | import ( 5 | "context" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/google/wire" 10 | "github.com/int128/kauthproxy/internal/logger" 11 | corev1 "k8s.io/api/core/v1" 12 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 | "k8s.io/client-go/kubernetes" 14 | typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1" 15 | "k8s.io/client-go/rest" 16 | ) 17 | 18 | var Set = wire.NewSet( 19 | wire.Struct(new(Factory), "*"), 20 | wire.Bind(new(FactoryInterface), new(*Factory)), 21 | ) 22 | 23 | type FactoryInterface interface { 24 | New(config *rest.Config) (Interface, error) 25 | } 26 | 27 | // Factory creates a Resolver. 28 | type Factory struct { 29 | Logger logger.Interface 30 | } 31 | 32 | // New returns a Resolver. 33 | func (f *Factory) New(config *rest.Config) (Interface, error) { 34 | clientset, err := kubernetes.NewForConfig(config) 35 | if err != nil { 36 | return nil, fmt.Errorf("could not create a client: %w", err) 37 | } 38 | return &Resolver{ 39 | Logger: f.Logger, 40 | CoreV1: clientset.CoreV1(), 41 | }, nil 42 | } 43 | 44 | type Interface interface { 45 | FindPodByServiceName(ctx context.Context, namespace, serviceName string) (*corev1.Pod, int, error) 46 | FindPodByName(ctx context.Context, namespace, podName string) (*corev1.Pod, int, error) 47 | } 48 | 49 | // Resolver provides resolving a pod and container port. 50 | type Resolver struct { 51 | Logger logger.Interface 52 | CoreV1 typedcorev1.CoreV1Interface 53 | } 54 | 55 | // FindPodByServiceName returns a pod and container port associated with the service. 56 | func (r *Resolver) FindPodByServiceName(ctx context.Context, namespace, serviceName string) (*corev1.Pod, int, error) { 57 | r.Logger.V(1).Infof("finding service %s in namespace %s", serviceName, namespace) 58 | service, err := r.CoreV1.Services(namespace).Get(ctx, serviceName, metav1.GetOptions{}) 59 | if err != nil { 60 | return nil, 0, fmt.Errorf("could not find the service: %w", err) 61 | } 62 | var selectors []string 63 | for k, v := range service.Spec.Selector { 64 | selectors = append(selectors, fmt.Sprintf("%s=%s", k, v)) 65 | } 66 | selector := strings.Join(selectors, ",") 67 | r.Logger.V(1).Infof("finding pods by selector %s", selectors) 68 | pods, err := r.CoreV1.Pods(namespace).List(ctx, metav1.ListOptions{LabelSelector: selector}) 69 | if err != nil { 70 | return nil, 0, fmt.Errorf("could not find pods by selector %s: %w", selector, err) 71 | } 72 | r.Logger.V(1).Infof("found %d pod(s)", len(pods.Items)) 73 | if len(pods.Items) == 0 { 74 | return nil, 0, fmt.Errorf("no pod matched to selector %s", selector) 75 | } 76 | pod := &pods.Items[0] 77 | r.Logger.V(1).Infof("first matched pod %s", pod.Name) 78 | for _, container := range pod.Spec.Containers { 79 | for _, port := range container.Ports { 80 | r.Logger.V(1).Infof("found container port %d in container %s of pod %s", 81 | port.ContainerPort, container.Name, pod.Name) 82 | return pod, int(port.ContainerPort), nil 83 | } 84 | } 85 | return nil, 0, fmt.Errorf("no container port in pod %s", pod.Name) 86 | } 87 | 88 | // FindPodByName finds a pod and container port by name. 89 | func (r *Resolver) FindPodByName(ctx context.Context, namespace, podName string) (*corev1.Pod, int, error) { 90 | r.Logger.V(1).Infof("finding pod %s in namespace %s", podName, namespace) 91 | pod, err := r.CoreV1.Pods(namespace).Get(ctx, podName, metav1.GetOptions{}) 92 | if err != nil { 93 | return nil, 0, fmt.Errorf("could not find the pod: %w", err) 94 | } 95 | for _, container := range pod.Spec.Containers { 96 | for _, port := range container.Ports { 97 | r.Logger.V(1).Infof("found container port %d in container %s of pod %s", 98 | port.ContainerPort, container.Name, pod.Name) 99 | return pod, int(port.ContainerPort), nil 100 | } 101 | } 102 | return nil, 0, fmt.Errorf("no container port in pod %s", pod.Name) 103 | } 104 | -------------------------------------------------------------------------------- /internal/reverseproxy/reverse_proxy.go: -------------------------------------------------------------------------------- 1 | // Package reverseproxy provides a reverse proxy. 2 | package reverseproxy 3 | 4 | import ( 5 | "context" 6 | "fmt" 7 | "net/http" 8 | "net/http/httputil" 9 | "net/url" 10 | 11 | "github.com/google/wire" 12 | "github.com/int128/listener" 13 | ) 14 | 15 | var Set = wire.NewSet( 16 | wire.Struct(new(ReverseProxy), "*"), 17 | wire.Bind(new(Interface), new(*ReverseProxy)), 18 | ) 19 | 20 | // Option represents an option of a reverse proxy. 21 | type Option struct { 22 | Transport http.RoundTripper 23 | BindAddressCandidates []string 24 | TargetScheme string 25 | TargetHost string 26 | TargetPort int 27 | } 28 | 29 | type Interface interface { 30 | Run(o Option, readyChan chan<- Instance) error 31 | } 32 | 33 | type Instance interface { 34 | URL() *url.URL 35 | Shutdown(ctx context.Context) error 36 | } 37 | 38 | type ReverseProxy struct { 39 | } 40 | 41 | // Run executes a reverse proxy server. 42 | // 43 | // It returns nil if the server has been closed. 44 | // It returns an error otherwise. 45 | // 46 | // It will send the Instance to the readyChan when the reverse proxy is ready. 47 | // Caller should close the readyChan. 48 | func (rp *ReverseProxy) Run(o Option, readyChan chan<- Instance) error { 49 | s := &http.Server{ 50 | Handler: &httputil.ReverseProxy{ 51 | Transport: o.Transport, 52 | Director: func(r *http.Request) { 53 | r.URL.Scheme = o.TargetScheme 54 | r.URL.Host = fmt.Sprintf("%s:%d", o.TargetHost, o.TargetPort) 55 | r.Host = "" 56 | }, 57 | }, 58 | } 59 | l, err := listener.New(o.BindAddressCandidates) 60 | if err != nil { 61 | return fmt.Errorf("could not listen: %w", err) 62 | } 63 | // l will be closed by s.Serve(l) 64 | 65 | if readyChan != nil { 66 | readyChan <- &instance{s: s, l: l} 67 | } 68 | if err := s.Serve(l); err != nil && err != http.ErrServerClosed { 69 | return fmt.Errorf("could not start a server: %w", err) 70 | } 71 | return nil 72 | } 73 | 74 | type instance struct { 75 | s *http.Server 76 | l *listener.Listener 77 | } 78 | 79 | func (i *instance) URL() *url.URL { 80 | return i.l.URL 81 | } 82 | 83 | func (i *instance) Shutdown(ctx context.Context) error { 84 | return i.s.Shutdown(ctx) 85 | } 86 | -------------------------------------------------------------------------------- /internal/transport/transport.go: -------------------------------------------------------------------------------- 1 | // Package transport provides a HTTP transport with a token got from the credential plugin of the cluster. 2 | package transport 3 | 4 | import ( 5 | "errors" 6 | "fmt" 7 | "net/http" 8 | 9 | "github.com/google/wire" 10 | "k8s.io/client-go/pkg/apis/clientauthentication" 11 | "k8s.io/client-go/plugin/pkg/client/auth/exec" 12 | "k8s.io/client-go/rest" 13 | "k8s.io/client-go/transport" 14 | ) 15 | 16 | var Set = wire.NewSet( 17 | wire.Value(NewFunc(New)), 18 | ) 19 | 20 | type NewFunc func(*rest.Config) (http.RoundTripper, error) 21 | 22 | // New returns a RoundTripper with token support. 23 | func New(c *rest.Config) (http.RoundTripper, error) { 24 | config := &transport.Config{ 25 | BearerToken: c.BearerToken, 26 | BearerTokenFile: c.BearerTokenFile, 27 | TLS: transport.TLSConfig{ 28 | Insecure: true, 29 | }, 30 | } 31 | // see rest.Config#TransportConfig 32 | if c.ExecProvider != nil && c.AuthProvider != nil { 33 | return nil, errors.New("execProvider and authProvider cannot be used in combination") 34 | } 35 | if c.ExecProvider != nil { 36 | var cluster *clientauthentication.Cluster 37 | if c.ExecProvider.ProvideClusterInfo { 38 | var err error 39 | cluster, err = rest.ConfigToExecCluster(c) 40 | if err != nil { 41 | return nil, err 42 | } 43 | } 44 | provider, err := exec.GetAuthenticator(c.ExecProvider, cluster) 45 | if err != nil { 46 | return nil, fmt.Errorf("could not get an authenticator: %w", err) 47 | } 48 | if err := provider.UpdateTransportConfig(config); err != nil { 49 | return nil, fmt.Errorf("could not update the transport config: %w", err) 50 | } 51 | } 52 | if c.AuthProvider != nil { 53 | provider, err := rest.GetAuthProvider(c.Host, c.AuthProvider, c.AuthConfigPersister) 54 | if err != nil { 55 | return nil, fmt.Errorf("could not get an auth-provider: %w", err) 56 | } 57 | config.Wrap(provider.WrapTransport) 58 | } 59 | t, err := transport.New(config) 60 | if err != nil { 61 | return nil, fmt.Errorf("could not create a transport: %w", err) 62 | } 63 | return t, nil 64 | } 65 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "os/signal" 7 | 8 | "github.com/int128/kauthproxy/internal/di" 9 | 10 | _ "k8s.io/client-go/plugin/pkg/client/auth" 11 | ) 12 | 13 | var version = "v0.0.0" 14 | 15 | func main() { 16 | ctx := context.Background() 17 | ctx, stop := signal.NotifyContext(ctx, os.Interrupt) 18 | defer stop() 19 | os.Exit(di.NewCmd().Run(ctx, os.Args, version)) 20 | } 21 | --------------------------------------------------------------------------------