├── .github └── workflows │ ├── go-ci.yml │ └── release.yml ├── .gitignore ├── .goreleaser.yml ├── Dockerfile ├── Dockerfile.goreleaser ├── LICENSE ├── Makefile ├── README.md ├── README.zh-cn.md ├── cmd └── mcp-k8s │ └── main.go ├── docs ├── create-deployment.png └── qrcode.png ├── go.mod ├── go.sum └── internal ├── config └── config.go ├── k8s ├── client.go └── helm.go └── tools ├── helm_tools.go └── tools.go /.github/workflows/go-ci.yml: -------------------------------------------------------------------------------- 1 | name: Go CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | test: 11 | name: Test and Lint 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v4 16 | 17 | - name: Set up Go 18 | uses: actions/setup-go@v5 19 | with: 20 | go-version: "1.24" 21 | check-latest: true 22 | cache: true 23 | 24 | - name: Verify dependencies 25 | run: go mod verify 26 | 27 | - name: Install golangci-lint 28 | uses: golangci/golangci-lint-action@v7 29 | with: 30 | version: latest 31 | args: --timeout=5m 32 | 33 | - name: Run tests 34 | run: go test -v -race -timeout 10m ./... 35 | 36 | - name: Build 37 | run: go build -v ./... 38 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | permissions: 9 | contents: write 10 | packages: write 11 | 12 | jobs: 13 | goreleaser: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v3 18 | with: 19 | fetch-depth: 0 20 | 21 | - name: Set up Go 22 | uses: actions/setup-go@v4 23 | with: 24 | go-version: "1.24" 25 | check-latest: true 26 | 27 | - name: Set up QEMU 28 | uses: docker/setup-qemu-action@v2 29 | 30 | - name: Set up Docker Buildx 31 | uses: docker/setup-buildx-action@v2 32 | 33 | - name: Login to GitHub Container Registry 34 | uses: docker/login-action@v2 35 | with: 36 | registry: ghcr.io 37 | username: ${{ github.actor }} 38 | password: ${{ secrets.GITHUB_TOKEN }} 39 | 40 | - name: Run GoReleaser 41 | uses: goreleaser/goreleaser-action@v4 42 | with: 43 | distribution: goreleaser 44 | version: latest 45 | args: release --clean 46 | env: 47 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 48 | GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }} 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | go.work.sum 23 | 24 | # env file 25 | .env 26 | /bin 27 | /.cursor 28 | /dist 29 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | project_name: mcp-k8s 3 | 4 | before: 5 | hooks: 6 | - go mod tidy 7 | 8 | builds: 9 | - id: mcp-k8s 10 | main: ./cmd/mcp-k8s 11 | binary: mcp-k8s 12 | env: 13 | - CGO_ENABLED=0 14 | goos: 15 | - linux 16 | - darwin 17 | - windows 18 | goarch: 19 | - amd64 20 | - arm64 21 | ignore: 22 | - goos: windows 23 | goarch: arm64 24 | 25 | archives: 26 | - id: default 27 | format: binary 28 | name_template: >- 29 | {{ .ProjectName }}_ 30 | {{- title .Os }}_ 31 | {{- if eq .Arch "amd64" }}x86_64 32 | {{- else if eq .Arch "386" }}i386 33 | {{- else }}{{ .Arch }}{{ end }} 34 | builds: 35 | - mcp-k8s 36 | files: 37 | - LICENSE 38 | - README.md 39 | format_overrides: 40 | - goos: linux 41 | format: tar.gz 42 | - goos: darwin 43 | format: tar.gz 44 | - goos: windows 45 | format: zip 46 | 47 | dockers: 48 | - image_templates: 49 | - "ghcr.io/silenceper/{{ .ProjectName }}:{{ .Version }}-amd64" 50 | dockerfile: Dockerfile.goreleaser 51 | use: buildx 52 | build_flag_templates: 53 | - "--platform=linux/amd64" 54 | - "--label=org.opencontainers.image.created={{.Date}}" 55 | - "--label=org.opencontainers.image.title={{.ProjectName}}" 56 | - "--label=org.opencontainers.image.revision={{.FullCommit}}" 57 | - "--label=org.opencontainers.image.version={{.Version}}" 58 | goarch: amd64 59 | skip_push: false 60 | - image_templates: 61 | - "ghcr.io/silenceper/{{ .ProjectName }}:{{ .Version }}-arm64" 62 | dockerfile: Dockerfile.goreleaser 63 | use: buildx 64 | build_flag_templates: 65 | - "--platform=linux/arm64" 66 | - "--label=org.opencontainers.image.created={{.Date}}" 67 | - "--label=org.opencontainers.image.title={{.ProjectName}}" 68 | - "--label=org.opencontainers.image.revision={{.FullCommit}}" 69 | - "--label=org.opencontainers.image.version={{.Version}}" 70 | goarch: arm64 71 | skip_push: false 72 | 73 | docker_manifests: 74 | - name_template: ghcr.io/silenceper/{{ .ProjectName }}:{{ .Version }} 75 | image_templates: 76 | - ghcr.io/silenceper/{{ .ProjectName }}:{{ .Version }}-amd64 77 | - ghcr.io/silenceper/{{ .ProjectName }}:{{ .Version }}-arm64 78 | - name_template: ghcr.io/silenceper/{{ .ProjectName }}:latest 79 | image_templates: 80 | - ghcr.io/silenceper/{{ .ProjectName }}:{{ .Version }}-amd64 81 | - ghcr.io/silenceper/{{ .ProjectName }}:{{ .Version }}-arm64 82 | 83 | release: 84 | github: 85 | owner: silenceper 86 | name: "{{ .ProjectName }}" 87 | prerelease: auto 88 | draft: false 89 | name_template: "{{ .Tag }}" 90 | 91 | changelog: 92 | sort: asc 93 | filters: 94 | exclude: 95 | - "^docs:" 96 | - "^test:" 97 | - "^ci:" 98 | - "^chore:" 99 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG BASEIMAGE=alpine:3.18.4 2 | ARG GOVERSION=1.24.1 3 | ARG LDFLAGS="" 4 | 5 | # Build the manager binary 6 | FROM golang:${GOVERSION} as builder 7 | # Copy in the go src 8 | WORKDIR /go/src/github.com/silenceper/mcp-k8s 9 | COPY internal internal/ 10 | COPY cmd cmd/ 11 | COPY go.mod go.mod 12 | COPY go.sum go.sum 13 | ARG LDFLAGS 14 | ARG TARGETOS 15 | ARG TARGETARCH 16 | 17 | # Build 18 | RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags="${LDFLAGS}" -a -o mcp-k8s /go/src/github.com/silenceper/mcp-k8s/cmd/mcp-k8s 19 | 20 | # Copy the cmd into a thin image 21 | FROM ${BASEIMAGE} 22 | WORKDIR /root 23 | RUN apk add gcompat 24 | COPY --from=builder /go/src/github.com/silenceper/mcp-k8s/mcp-k8s /usr/local/bin/mcp-k8s 25 | ENTRYPOINT ["/usr/local/bin/mcp-k8s"] 26 | -------------------------------------------------------------------------------- /Dockerfile.goreleaser: -------------------------------------------------------------------------------- 1 | FROM alpine:3.18.4 2 | 3 | WORKDIR /root 4 | RUN apk add --no-cache gcompat 5 | COPY mcp-k8s /usr/local/bin/mcp-k8s 6 | ENTRYPOINT ["/usr/local/bin/mcp-k8s"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: build 2 | 3 | # Version and application information 4 | VERSION := 1.0.0 5 | REPO := ghcr.io/silenceper/mcp-k8s 6 | APPNAME := mcp-k8s 7 | BUILDDIR := ./bin 8 | 9 | .PHONY: build 10 | build: 11 | go build -o ./bin/mcp-k8s cmd/mcp-k8s/main.go 12 | 13 | # Clean the project 14 | .PHONY: clean 15 | clean: 16 | rm -rf $(BUILDDIR) 17 | 18 | # Format the code 19 | .PHONY: fmt 20 | fmt: 21 | go fmt ./... 22 | 23 | # Create output directory 24 | .PHONY: init 25 | init: 26 | mkdir -p $(BUILDDIR) 27 | # Cross-platform build - Windows 28 | .PHONY: build-windows-amd64 29 | build-windows-amd64: init 30 | GOOS=windows GOARCH=amd64 go build -o $(BUILDDIR)/$(APPNAME)_windows_amd64.exe cmd/mcp-k8s/main.go 31 | 32 | # Cross-platform build - macOS (Intel) 33 | .PHONY: build-darwin-amd64 34 | build-darwin-amd64: init 35 | GOOS=darwin GOARCH=amd64 go build -o $(BUILDDIR)/$(APPNAME)_darwin_amd64 cmd/mcp-k8s/main.go 36 | 37 | # Cross-platform build - macOS (Apple Silicon) 38 | .PHONY: build-darwin-arm64 39 | build-darwin-arm64: init 40 | GOOS=darwin GOARCH=arm64 go build -o $(BUILDDIR)/$(APPNAME)_darwin_arm64 cmd/mcp-k8s/main.go 41 | 42 | # Cross-platform build - Linux 43 | .PHONY: build-linux-amd64 44 | build-linux-amd64: init 45 | GOOS=linux GOARCH=amd64 go build -o $(BUILDDIR)/$(APPNAME)_linux_amd64 cmd/mcp-k8s/main.go 46 | 47 | # Cross-platform build - Linux 48 | .PHONY: build-linux-arm64 49 | build-linux-arm64: init 50 | GOOS=linux GOARCH=arm64 go build -o $(BUILDDIR)/$(APPNAME)_linux_arm64 cmd/mcp-k8s/main.go 51 | 52 | # Cross-platform build - All platforms 53 | .PHONY: build-all 54 | build-all: build-windows-amd64 build-darwin-amd64 build-darwin-arm64 build-linux-amd64 build-linux-arm64 55 | @echo "All platforms built successfully" 56 | @ls -la $(BUILDDIR) 57 | 58 | .PHONY: docker-build 59 | docker-build: 60 | docker build -t $(REPO):$(VERSION) -f Dockerfile . 61 | 62 | .PHONY: docker-push 63 | docker-push: 64 | docker push $(REPO):$(VERSION) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mcp-k8s 2 | 3 | [![Go Version](https://img.shields.io/github/go-mod/go-version/silenceper/mcp-k8s)](https://github.com/silenceper/mcp-k8s/blob/main/go.mod) 4 | [![License](https://img.shields.io/github/license/silenceper/mcp-k8s)](https://github.com/silenceper/mcp-k8s/blob/main/LICENSE) 5 | [![Latest Release](https://img.shields.io/github/v/release/silenceper/mcp-k8s)](https://github.com/silenceper/mcp-k8s/releases) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/silenceper/mcp-k8s)](https://goreportcard.com/report/github.com/silenceper/mcp-k8s) 7 | [![Go CI](https://github.com/silenceper/mcp-k8s/actions/workflows/go-ci.yml/badge.svg)](https://github.com/silenceper/mcp-k8s/actions/workflows/go-ci.yml) 8 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/silenceper/mcp-k8s/pulls) 9 | 10 | A Kubernetes MCP (Model Control Protocol) server that enables interaction with Kubernetes clusters through MCP tools. 11 | 12 | ## Features 13 | 14 | - Query supported Kubernetes resource types (built-in resources and CRDs) 15 | - Kubernetes resource operations with fine-grained control 16 | - Read operations: get resource details, list resources by type with filtering options 17 | - Write operations: create, update, and delete resources (each can be independently enabled/disabled) 18 | - Support for all Kubernetes resource types, including custom resources 19 | - Connects to Kubernetes cluster using kubeconfig 20 | - Helm support with fine-grained control 21 | - Helm releases management (list, get, install, upgrade, uninstall) 22 | - Helm repositories management (list, add, remove) 23 | - Each operation can be independently enabled/disabled 24 | 25 | ## Preview 26 | > Interaction through cursor 27 | 28 | ![](./docs/create-deployment.png) 29 | 30 | ## Use Cases 31 | 32 | ### 1. Kubernetes Resource Management via LLM 33 | 34 | - **Interactive Resource Management**: Manage Kubernetes resources through natural language interaction with LLM, eliminating the need to memorize complex kubectl commands 35 | - **Batch Operations**: Describe complex batch operation requirements in natural language, letting LLM translate them into specific resource operations 36 | - **Resource Status Queries**: Query cluster resource status using natural language and receive easy-to-understand responses 37 | 38 | ### 2. Automated Operations Scenarios 39 | 40 | - **Intelligent Operations Assistant**: Serve as an intelligent assistant for operators in daily cluster management tasks 41 | - **Problem Diagnosis**: Assist in cluster problem diagnosis through natural language problem descriptions 42 | - **Configuration Review**: Leverage LLM's understanding capabilities to help review and optimize Kubernetes resource configurations 43 | 44 | ### 3. Development and Testing Support 45 | 46 | - **Quick Prototype Validation**: Developers can quickly create and validate resource configurations through natural language 47 | - **Environment Management**: Simplify test environment resource management, quickly create, modify, and clean up test resources 48 | - **Configuration Generation**: Automatically generate resource configurations that follow best practices based on requirement descriptions 49 | 50 | ### 4. Education and Training Scenarios 51 | 52 | - **Interactive Learning**: Newcomers can learn Kubernetes concepts and operations through natural language interaction 53 | - **Best Practice Guidance**: LLM provides best practice suggestions during resource operations 54 | - **Error Explanation**: Provide easy-to-understand error explanations and correction suggestions when operations fail 55 | 56 | ## Architecture 57 | 58 | ### 1. Project Overview 59 | 60 | An stdio-based MCP server that connects to Kubernetes clusters and provides the following capabilities: 61 | - Query Kubernetes resource types (including built-in resources and CRDs) 62 | - CRUD operations on Kubernetes resources (with configurable write operations) 63 | - Helm operations for release and repository management 64 | 65 | ### 2. Technical Stack 66 | 67 | - Go 68 | - [mcp-go](https://github.com/mark3labs/mcp-go) SDK 69 | - Kubernetes client-go library 70 | - Helm v3 client library 71 | - Stdio for communication 72 | 73 | ### 3. Core Components 74 | 75 | 1. **MCP Server**: Uses mcp-go's `mcp-k8s` package to create an stdio-based MCP server 76 | 2. **K8s Client**: Uses client-go to connect to Kubernetes clusters 77 | 3. **Helm Client**: Uses Helm v3 library for Helm operations 78 | 4. **Tool Implementations**: Implements various MCP tools for different Kubernetes operations 79 | 80 | ### 4. Available Tools 81 | 82 | #### Resource Type Query Tools 83 | - `get_api_resources`: Get all supported API resource types in the cluster 84 | 85 | #### Resource Operation Tools 86 | - `get_resource`: Get detailed information about a specific resource 87 | - `list_resources`: List all instances of a resource type 88 | - `create_resource`: Create new resources (can be disabled) 89 | - `update_resource`: Update existing resources (can be disabled) 90 | - `delete_resource`: Delete resources (can be disabled) 91 | 92 | #### Helm Operation Tools 93 | - `list_helm_releases`: List all Helm releases in the cluster 94 | - `get_helm_release`: Get detailed information about a specific Helm release 95 | - `install_helm_chart`: Install a Helm chart (can be disabled) 96 | - `upgrade_helm_chart`: Upgrade a Helm release (can be disabled) 97 | - `uninstall_helm_chart`: Uninstall a Helm release (can be disabled) 98 | - `list_helm_repositories`: List configured Helm repositories 99 | - `add_helm_repository`: Add a new Helm repository (can be disabled) 100 | - `remove_helm_repository`: Remove a Helm repository (can be disabled) 101 | 102 | ## Usage 103 | 104 | mcp-k8s supports two communication modes: 105 | 106 | ### 1. Stdio Mode (Default) 107 | 108 | In stdio mode, mcp-k8s communicates with the client through standard input/output streams. This is the default mode and is suitable for most use cases. 109 | 110 | ```bash 111 | # Run in stdio mode (default) 112 | { 113 | "mcpServers": 114 | { 115 | "mcp-k8s": 116 | { 117 | "command": "/path/to/mcp-k8s", 118 | "args": 119 | [ 120 | "-kubeconfig", 121 | "/path/to/kubeconfig", 122 | "-enable-create", 123 | "-enable-delete", 124 | "-enable-update", 125 | "-enable-list", 126 | "-enable-helm-install", 127 | "-enable-helm-upgrade" 128 | ] 129 | } 130 | } 131 | } 132 | ``` 133 | 134 | ### 2. SSE Mode 135 | 136 | In SSE (Server-Sent Events) mode, mcp-k8s exposes an HTTP endpoint to mcp client. 137 | You can deploy the service on a remote server (but you need to pay attention to security) 138 | 139 | ```bash 140 | # Run in SSE mode 141 | ./bin/mcp-k8s -kubeconfig=/path/to/kubeconfig -transport=sse -port=8080 -host=localhost -enable-create -enable-delete -enable-list -enable-update -enable-helm-install 142 | # This command will open all operations 143 | ``` 144 | 145 | mcp config 146 | ```json 147 | { 148 | "mcpServers": { 149 | "mcp-k8s": { 150 | "url": "http://localhost:8080/sse", 151 | "args": [] 152 | } 153 | } 154 | } 155 | ``` 156 | 157 | SSE mode configuration: 158 | - `-transport`: Set to "sse" to enable SSE mode 159 | - `-port`: HTTP server port (default: 8080) 160 | - `--host`: HTTP server host (default: "localhost") 161 | 162 | ### 3. Docker environment 163 | #### SSE Mode 164 | 165 | 1. Complete Example 166 | Assuming your image name is mcp-k8s and you need to map ports and set environment parameters, you can run: 167 | ```bash 168 | docker run --rm -p 8080:8080 -i -v ~/.kube/config:/root/.kube/config ghcr.io/silenceper/mcp-k8s:latest -transport=sse 169 | ``` 170 | #### stdio Mode 171 | 172 | ```json 173 | { 174 | "mcpServers": { 175 | "mcp-k8s": { 176 | "command": "docker", 177 | "args": [ 178 | "run", 179 | "-i", 180 | "-v", 181 | "~/.kube/config:/root/.kube/config", 182 | "--rm", 183 | "ghcr.io/silenceper/mcp-k8s:latest" 184 | ] 185 | } 186 | } 187 | } 188 | ``` 189 | 190 | 191 | ## Getting Started 192 | 193 | ### Direct Usage 194 | You can directly download the binary for your platform from the [releases page](https://github.com/silenceper/mcp-k8s/releases) and use it immediately. 195 | 196 | ### Go Install 197 | 198 | ```bash 199 | go install github.com/silenceper/mcp-k8s/cmd/mcp-k8s@latest 200 | ``` 201 | 202 | ### Build 203 | 204 | ```bash 205 | git clone https://github.com/silenceper/mcp-k8s.git 206 | cd mcp-k8s 207 | go build -o bin/mcp-k8s cmd/mcp-k8s/main.go 208 | ``` 209 | 210 | ### Command Line Arguments 211 | 212 | #### Kubernetes Resource Operations 213 | - `-kubeconfig`: Path to Kubernetes configuration file (uses default config if not specified) 214 | - `-enable-create`: Enable resource creation operations (default: false) 215 | - `-enable-update`: Enable resource update operations (default: false) 216 | - `-enable-delete`: Enable resource deletion operations (default: false) 217 | - `-enable-list`: Enable resource list operations (default: true) 218 | 219 | #### Helm Operations 220 | - `-enable-helm-release-list`: Enable Helm release list operations (default: true) 221 | - `-enable-helm-release-get`: Enable Helm release get operations (default: true) 222 | - `-enable-helm-install`: Enable Helm chart installation (default: false) 223 | - `-enable-helm-upgrade`: Enable Helm chart upgrade (default: false) 224 | - `-enable-helm-uninstall`: Enable Helm chart uninstallation (default: false) 225 | - `-enable-helm-repo-list`: Enable Helm repository list operations (default: true) 226 | - `-enable-helm-repo-add`: Enable Helm repository add operations (default: false) 227 | - `-enable-helm-repo-remove`: Enable Helm repository remove operations (default: false) 228 | 229 | #### Transport Configuration 230 | - `-transport`: Transport type (stdio or sse) (default: "stdio") 231 | - `-host`: Host for SSE transport (default "localhost") 232 | - `-port`: TCP port for SSE transport (default 8080) 233 | 234 | ### Integration with MCP Clients 235 | 236 | mcp-k8s is an stdio-based MCP server that can be integrated with any MCP-compatible LLM client. Refer to your MCP client's documentation for integration instructions. 237 | 238 | ## Security Considerations 239 | 240 | - Write operations are strictly controlled through independent configuration switches 241 | - Uses RBAC to ensure K8s client has only necessary permissions 242 | - Validates all user inputs to prevent injection attacks 243 | - Helm operations follow the same security principles with read operations enabled by default and write operations disabled by default 244 | 245 | ## Follow WeChat Official Account 246 | ![AI技术小林](./docs/qrcode.png) 247 | -------------------------------------------------------------------------------- /README.zh-cn.md: -------------------------------------------------------------------------------- 1 | # mcp-k8s 2 | 3 | [![Go Version](https://img.shields.io/github/go-mod/go-version/silenceper/mcp-k8s)](https://github.com/silenceper/mcp-k8s/blob/main/go.mod) 4 | [![License](https://img.shields.io/github/license/silenceper/mcp-k8s)](https://github.com/silenceper/mcp-k8s/blob/main/LICENSE) 5 | [![Latest Release](https://img.shields.io/github/v/release/silenceper/mcp-k8s)](https://github.com/silenceper/mcp-k8s/releases) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/silenceper/mcp-k8s)](https://goreportcard.com/report/github.com/silenceper/mcp-k8s) 7 | [![Go CI](https://github.com/silenceper/mcp-k8s/actions/workflows/go-ci.yml/badge.svg)](https://github.com/silenceper/mcp-k8s/actions/workflows/go-ci.yml) 8 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/silenceper/mcp-k8s/pulls) 9 | 10 | 一个基于 MCP(Model Control Protocol)的 Kubernetes 服务器,支持通过 MCP 工具与 Kubernetes 集群进行交互。 11 | 12 | ## 特性 13 | 14 | - 查询支持的 Kubernetes 资源类型(内置资源和 CRD) 15 | - Kubernetes 资源操作,具有细粒度控制 16 | - 读操作:获取资源详情,按类型列出资源并支持过滤选项 17 | - 写操作:创建、更新和删除资源(每种操作可独立启用/禁用) 18 | - 支持所有 Kubernetes 资源类型,包括自定义资源 19 | - 使用 kubeconfig 连接到 Kubernetes 集群 20 | - Helm 支持,具有细粒度控制 21 | - Helm 发布版管理(列表、查询、安装、升级、卸载) 22 | - Helm 仓库管理(列表、添加、删除) 23 | - 每个操作可以独立启用/禁用 24 | 25 | ## 预览 26 | > 通过 cursor 进行交互 27 | 28 | ![](./docs/create-deployment.png) 29 | 30 | ## 使用场景 31 | 32 | ### 1. 通过 LLM 管理 Kubernetes 资源 33 | 34 | - **交互式资源管理**:通过自然语言与 LLM 交互来管理 Kubernetes 资源,无需记忆复杂的 kubectl 命令 35 | - **批量操作**:用自然语言描述复杂的批量操作需求,让 LLM 将其转换为具体的资源操作 36 | - **资源状态查询**:使用自然语言查询集群资源状态,获得易于理解的响应 37 | 38 | ### 2. 自动化运维场景 39 | 40 | - **智能运维助手**:作为运维人员在日常集群管理任务中的智能助手 41 | - **问题诊断**:通过自然语言问题描述协助集群问题诊断 42 | - **配置审查**:利用 LLM 的理解能力帮助审查和优化 Kubernetes 资源配置 43 | 44 | ### 3. 开发和测试支持 45 | 46 | - **快速原型验证**:开发者可以通过自然语言快速创建和验证资源配置 47 | - **环境管理**:简化测试环境资源管理,快速创建、修改和清理测试资源 48 | - **配置生成**:根据需求描述自动生成遵循最佳实践的资源配置 49 | 50 | ### 4. 教育和培训场景 51 | 52 | - **交互式学习**:新手可以通过自然语言交互学习 Kubernetes 概念和操作 53 | - **最佳实践指导**:在资源操作过程中,LLM 提供最佳实践建议 54 | - **错误解释**:在操作失败时提供易于理解的错误解释和修正建议 55 | 56 | ## 架构 57 | 58 | ### 1. 项目概述 59 | 60 | 一个基于 stdio 的 MCP 服务器,连接到 Kubernetes 集群并提供以下功能: 61 | - 查询 Kubernetes 资源类型(包括内置资源和 CRD) 62 | - 对 Kubernetes 资源进行 CRUD 操作(可配置写操作) 63 | - Helm 操作,用于发布版和仓库管理 64 | 65 | ### 2. 技术栈 66 | 67 | - Go 68 | - [mcp-go](https://github.com/mark3labs/mcp-go) SDK 69 | - Kubernetes client-go 库 70 | - Helm v3 客户端库 71 | - Stdio 用于通信 72 | 73 | ### 3. 核心组件 74 | 75 | 1. **MCP 服务器**:使用 mcp-go 的 `mcp-k8s` 包创建基于 stdio 的 MCP 服务器 76 | 2. **K8s 客户端**:使用 client-go 连接到 Kubernetes 集群 77 | 3. **Helm 客户端**:使用 Helm v3 库进行 Helm 操作 78 | 4. **工具实现**:实现各种 MCP 工具用于不同的 Kubernetes 操作 79 | 80 | ### 4. 可用工具 81 | 82 | #### 资源类型查询工具 83 | - `get_api_resources`:获取集群中所有支持的 API 资源类型 84 | 85 | #### 资源操作工具 86 | - `get_resource`:获取特定资源的详细信息 87 | - `list_resources`:列出资源类型的所有实例 88 | - `create_resource`:创建新资源(可禁用) 89 | - `update_resource`:更新现有资源(可禁用) 90 | - `delete_resource`:删除资源(可禁用) 91 | 92 | #### Helm 操作工具 93 | - `list_helm_releases`:列出集群中所有 Helm 发布版 94 | - `get_helm_release`:获取特定 Helm 发布版的详细信息 95 | - `install_helm_chart`:安装 Helm 图表(可禁用) 96 | - `upgrade_helm_chart`:升级 Helm 发布版(可禁用) 97 | - `uninstall_helm_chart`:卸载 Helm 发布版(可禁用) 98 | - `list_helm_repositories`:列出已配置的 Helm 仓库 99 | - `add_helm_repository`:添加新的 Helm 仓库(可禁用) 100 | - `remove_helm_repository`:删除 Helm 仓库(可禁用) 101 | 102 | ## 使用方式 103 | 104 | mcp-k8s 支持两种通信模式: 105 | 106 | ### 1. Stdio 模式(默认) 107 | 108 | 在 stdio 模式下,mcp-k8s 通过标准输入/输出流与客户端通信。这是默认模式,适合大多数使用场景。 109 | 110 | ```bash 111 | # 以 stdio 模式运行(默认) 112 | { 113 | "mcpServers": 114 | { 115 | "mcp-k8s": 116 | { 117 | "command": "/path/to/mcp-k8s", 118 | "args": 119 | [ 120 | "-kubeconfig", 121 | "/path/to/kubeconfig", 122 | "-enable-create", 123 | "-enable-delete", 124 | "-enable-update", 125 | "-enable-list", 126 | "-enable-helm-install", 127 | "-enable-helm-upgrade" 128 | ] 129 | } 130 | } 131 | } 132 | ``` 133 | 134 | ### 2. SSE 模式 135 | 136 | 在 SSE(Server-Sent Events)模式下,mcp-k8s 向 mcp 客户端暴露 HTTP 端点。 137 | 您可以将服务部署在远程服务器上(但需要注意安全性) 138 | 139 | ```bash 140 | # 以 SSE 模式运行 141 | ./bin/mcp-k8s -kubeconfig=/path/to/kubeconfig -transport=sse -port=8080 -host=localhost -enable-create -enable-delete -enable-list -enable-update -enable-helm-install 142 | # 此命令将开启所有操作 143 | ``` 144 | 145 | mcp 配置 146 | ```json 147 | { 148 | "mcpServers": { 149 | "mcp-k8s": { 150 | "url": "http://localhost:8080/sse", 151 | "args": [] 152 | } 153 | } 154 | } 155 | ``` 156 | 157 | SSE 模式配置: 158 | - `-transport`:设置为 "sse" 以启用 SSE 模式 159 | - `-port`:HTTP 服务器端口(默认:8080) 160 | - `-host`:HTTP 服务器主机(默认:"localhost") 161 | 162 | ### 3. Docker 环境 163 | 164 | #### SSE 模式配置 165 | 166 | 1. 完整示例 167 | 假设你的镜像名为 mcp-k8s,并且需要映射端口和设置环境参数,可以运行: 168 | ```bash 169 | docker run --rm -p 8080:8080 -i -v ~/.kube/config:/root/.kube/config ghcr.io/silenceper/mcp-k8s:latest -transport=sse 170 | ``` 171 | #### stdio 模式配置 172 | 173 | ```json 174 | { 175 | "mcpServers": { 176 | "mcp-k8s": { 177 | "command": "docker", 178 | "args": [ 179 | "run", 180 | "-i", 181 | "-v", 182 | "~/.kube/config:/root/.kube/config", 183 | "--rm", 184 | "ghcr.io/silenceper/mcp-k8s:latest" 185 | ] 186 | } 187 | } 188 | } 189 | ``` 190 | 191 | ## 快速开始 192 | 193 | ### 直接使用 194 | 您可以直接从 [releases 页面](https://github.com/silenceper/mcp-k8s/releases) 下载适合您平台的二进制文件并立即使用。 195 | 196 | ### 使用 Go Install 197 | 198 | ```bash 199 | go install github.com/silenceper/mcp-k8s/cmd/mcp-k8s@latest 200 | ``` 201 | 202 | ### 构建 203 | 204 | ```bash 205 | git clone https://github.com/silenceper/mcp-k8s.git 206 | cd mcp-k8s 207 | go build -o bin/mcp-k8s cmd/mcp-k8s/main.go 208 | ``` 209 | 210 | ### 命令行参数 211 | 212 | #### Kubernetes 资源操作 213 | - `-kubeconfig`:Kubernetes 配置文件路径(如果未指定则使用默认配置) 214 | - `-enable-create`:启用资源创建操作(默认:false) 215 | - `-enable-update`:启用资源更新操作(默认:false) 216 | - `-enable-delete`:启用资源删除操作(默认:false) 217 | - `-enable-list`:启用资源列表操作(默认:true) 218 | 219 | #### Helm 操作 220 | - `-enable-helm-release-list`:启用 Helm 发布版列表操作(默认:true) 221 | - `-enable-helm-release-get`:启用 Helm 发布版获取操作(默认:true) 222 | - `-enable-helm-install`:启用 Helm 图表安装(默认:false) 223 | - `-enable-helm-upgrade`:启用 Helm 图表升级(默认:false) 224 | - `-enable-helm-uninstall`:启用 Helm 图表卸载(默认:false) 225 | - `-enable-helm-repo-list`:启用 Helm 仓库列表操作(默认:true) 226 | - `-enable-helm-repo-add`:启用 Helm 仓库添加操作(默认:false) 227 | - `-enable-helm-repo-remove`:启用 Helm 仓库删除操作(默认:false) 228 | 229 | #### 传输配置 230 | - `-transport`:传输类型(stdio 或 sse)(默认:"stdio") 231 | - `-host`:SSE 传输的主机(默认 "localhost") 232 | - `-port`:SSE 传输的 TCP 端口(默认 8080) 233 | 234 | ### 与 MCP 客户端集成 235 | 236 | mcp-k8s 是一个基于 stdio 的 MCP 服务器,可以与任何兼容 MCP 的 LLM 客户端集成。请参考您的 MCP 客户端的文档了解集成说明。 237 | 238 | ## 安全考虑 239 | 240 | - 通过独立的配置开关严格控制写操作 241 | - 使用 RBAC 确保 K8s 客户端仅具有必要的权限 242 | - 验证所有用户输入以防止注入攻击 243 | - Helm 操作遵循相同的安全原则,读操作默认启用,写操作默认禁用 244 | 245 | ## 关注微信公众号 246 | ![AI技术小林](./docs/qrcode.png) 247 | -------------------------------------------------------------------------------- /cmd/mcp-k8s/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | 9 | "github.com/mark3labs/mcp-go/server" 10 | "github.com/silenceper/mcp-k8s/internal/config" 11 | "github.com/silenceper/mcp-k8s/internal/k8s" 12 | "github.com/silenceper/mcp-k8s/internal/tools" 13 | ) 14 | 15 | func main() { 16 | // Parse command line arguments 17 | kubeconfigPath := flag.String("kubeconfig", "", "Path to Kubernetes configuration file (uses default config if not specified)") 18 | enableCreate := flag.Bool("enable-create", false, "Enable resource creation operations") 19 | enableUpdate := flag.Bool("enable-update", false, "Enable resource update operations") 20 | enableDelete := flag.Bool("enable-delete", false, "Enable resource deletion operations") 21 | enableList := flag.Bool("enable-list", true, "Enable resource list operations") 22 | 23 | // Helm 操作的细粒度控制 24 | enableHelmInstall := flag.Bool("enable-helm-install", false, "Enable Helm install operations") 25 | enableHelmUpgrade := flag.Bool("enable-helm-upgrade", false, "Enable Helm upgrade operations") 26 | enableHelmUninstall := flag.Bool("enable-helm-uninstall", false, "Enable Helm uninstall operations") 27 | enableHelmRepoAdd := flag.Bool("enable-helm-repo-add", false, "Enable Helm repository add operations") 28 | enableHelmRepoRemove := flag.Bool("enable-helm-repo-remove", false, "Enable Helm repository remove operations") 29 | enableHelmReleaseList := flag.Bool("enable-helm-release-list", true, "Enable Helm release list operations") 30 | enableHelmReleaseGet := flag.Bool("enable-helm-release-get", true, "Enable Helm release get operations") 31 | enableHelmRepoList := flag.Bool("enable-helm-repo-list", true, "Enable Helm repository list operations") 32 | 33 | transport := flag.String("transport", "stdio", "Transport type (stdio or sse)") 34 | host := flag.String("host", "localhost", "Host for SSE transport") 35 | port := flag.Int("port", 8080, "TCP port for SSE transport") 36 | flag.Parse() 37 | 38 | // Create configuration 39 | cfg := config.NewConfig(*kubeconfigPath, *enableCreate, *enableUpdate, *enableDelete, *enableList) 40 | 41 | // 设置Helm相关配置 42 | // 初始化Helm默认配置 43 | cfg.InitHelmDefaults() 44 | 45 | // 使用命令行参数覆盖默认配置 46 | cfg.EnableHelmInstall = *enableHelmInstall 47 | cfg.EnableHelmUpgrade = *enableHelmUpgrade 48 | cfg.EnableHelmUninstall = *enableHelmUninstall 49 | cfg.EnableHelmRepoAdd = *enableHelmRepoAdd 50 | cfg.EnableHelmRepoRemove = *enableHelmRepoRemove 51 | cfg.EnableHelmReleaseList = *enableHelmReleaseList 52 | cfg.EnableHelmReleaseGet = *enableHelmReleaseGet 53 | cfg.EnableHelmRepoList = *enableHelmRepoList 54 | 55 | if err := cfg.Validate(); err != nil { 56 | fmt.Fprintf(os.Stderr, "Configuration validation failed: %v\n", err) 57 | os.Exit(1) 58 | } 59 | 60 | // Create Kubernetes client 61 | client, err := k8s.NewClient(cfg.KubeconfigPath) 62 | if err != nil { 63 | fmt.Fprintf(os.Stderr, "Failed to create Kubernetes client: %v\n", err) 64 | os.Exit(1) 65 | } 66 | 67 | // Create MCP server 68 | s := server.NewMCPServer( 69 | "Kubernetes MCP Server", 70 | "1.0.0", 71 | ) 72 | 73 | // Add basic tools 74 | fmt.Println("Registering basic tools...") 75 | s.AddTool(tools.CreateGetAPIResourcesTool(), tools.HandleGetAPIResources(client)) 76 | s.AddTool(tools.CreateGetResourceTool(), tools.HandleGetResource(client)) 77 | if cfg.EnableList { 78 | s.AddTool(tools.CreateListResourcesTool(), tools.HandleListResources(client)) 79 | } 80 | 81 | // Add write operation tools (if enabled) 82 | if cfg.EnableCreate { 83 | fmt.Println("Registering resource creation tool...") 84 | s.AddTool(tools.CreateCreateResourceTool(), tools.HandleCreateResource(client)) 85 | } 86 | 87 | if cfg.EnableUpdate { 88 | fmt.Println("Registering resource update tool...") 89 | s.AddTool(tools.CreateUpdateResourceTool(), tools.HandleUpdateResource(client)) 90 | } 91 | 92 | if cfg.EnableDelete { 93 | fmt.Println("Registering resource deletion tool...") 94 | s.AddTool(tools.CreateDeleteResourceTool(), tools.HandleDeleteResource(client)) 95 | } 96 | 97 | // Add Helm tools (if enabled) 98 | fmt.Println("Registering Helm tools...") 99 | 100 | // Helm Release 管理 - 读操作 101 | if cfg.EnableHelmReleaseList { 102 | fmt.Println("Registering Helm release list tool...") 103 | s.AddTool(tools.CreateListHelmReleasesTool(), tools.HandleListHelmReleases(client)) 104 | } 105 | 106 | if cfg.EnableHelmReleaseGet { 107 | fmt.Println("Registering Helm release get tool...") 108 | s.AddTool(tools.CreateGetHelmReleaseTool(), tools.HandleGetHelmRelease(client)) 109 | } 110 | 111 | // Helm Release 管理 - 写操作 112 | if cfg.EnableHelmInstall { 113 | fmt.Println("Registering Helm chart install tool...") 114 | s.AddTool(tools.CreateInstallHelmChartTool(), tools.HandleInstallHelmChart(client)) 115 | } 116 | 117 | if cfg.EnableHelmUpgrade { 118 | fmt.Println("Registering Helm chart upgrade tool...") 119 | s.AddTool(tools.CreateUpgradeHelmChartTool(), tools.HandleUpgradeHelmChart(client)) 120 | } 121 | 122 | if cfg.EnableHelmUninstall { 123 | fmt.Println("Registering Helm chart uninstall tool...") 124 | s.AddTool(tools.CreateUninstallHelmChartTool(), tools.HandleUninstallHelmChart(client)) 125 | } 126 | 127 | // Helm 仓库管理 - 读操作 128 | if cfg.EnableHelmRepoList { 129 | fmt.Println("Registering Helm repository list tool...") 130 | s.AddTool(tools.CreateListHelmRepositoriesTool(), tools.HandleListHelmRepositories(client)) 131 | } 132 | 133 | // Helm 仓库管理 - 写操作 134 | if cfg.EnableHelmRepoAdd { 135 | fmt.Println("Registering Helm repository add tool...") 136 | s.AddTool(tools.CreateAddHelmRepositoryTool(), tools.HandleAddHelmRepository(client)) 137 | } 138 | 139 | if cfg.EnableHelmRepoRemove { 140 | fmt.Println("Registering Helm repository remove tool...") 141 | s.AddTool(tools.CreateRemoveHelmRepositoryTool(), tools.HandleRemoveHelmRepository(client)) 142 | } 143 | 144 | // Output functionality status 145 | fmt.Printf("\nStarting Kubernetes MCP Server with %s transport on %s:%d\n", *transport, *host, *port) 146 | fmt.Printf("Create operations: %v\n", cfg.EnableCreate) 147 | fmt.Printf("Update operations: %v\n", cfg.EnableUpdate) 148 | fmt.Printf("Delete operations: %v\n", cfg.EnableDelete) 149 | fmt.Printf("List operations: %v\n", cfg.EnableList) 150 | 151 | fmt.Println("\nHelm operations details:") 152 | fmt.Printf(" Helm release list: %v\n", cfg.EnableHelmReleaseList) 153 | fmt.Printf(" Helm release get: %v\n", cfg.EnableHelmReleaseGet) 154 | fmt.Printf(" Helm install: %v\n", cfg.EnableHelmInstall) 155 | fmt.Printf(" Helm upgrade: %v\n", cfg.EnableHelmUpgrade) 156 | fmt.Printf(" Helm uninstall: %v\n", cfg.EnableHelmUninstall) 157 | fmt.Printf(" Helm repository list: %v\n", cfg.EnableHelmRepoList) 158 | fmt.Printf(" Helm repository add: %v\n", cfg.EnableHelmRepoAdd) 159 | fmt.Printf(" Helm repository remove: %v\n", cfg.EnableHelmRepoRemove) 160 | 161 | // Start stdio server 162 | fmt.Println("\nServer started, waiting for MCP client connections...") 163 | switch *transport { 164 | case "stdio": 165 | if err := server.ServeStdio(s); err != nil { 166 | fmt.Fprintf(os.Stderr, "Server error: %v\n", err) 167 | os.Exit(1) 168 | } 169 | case "sse": 170 | sseUrl := fmt.Sprintf("http://%s:%d", *host, *port) 171 | sseServer := server.NewSSEServer(s, server.WithBaseURL(sseUrl)) 172 | if err := sseServer.Start(fmt.Sprintf(":%d", *port)); err != nil { 173 | log.Fatalf("Server error: %v", err) 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /docs/create-deployment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silenceper/mcp-k8s/a447f7c3d92e1a9b1353bb36a0f9279c4e5c1fe8/docs/create-deployment.png -------------------------------------------------------------------------------- /docs/qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silenceper/mcp-k8s/a447f7c3d92e1a9b1353bb36a0f9279c4e5c1fe8/docs/qrcode.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/silenceper/mcp-k8s 2 | 3 | go 1.24.1 4 | 5 | require ( 6 | github.com/mark3labs/mcp-go v0.17.0 7 | helm.sh/helm/v3 v3.17.3 8 | k8s.io/apimachinery v0.32.2 9 | k8s.io/client-go v0.32.2 10 | sigs.k8s.io/yaml v1.4.0 11 | ) 12 | 13 | require ( 14 | dario.cat/mergo v1.0.1 // indirect 15 | github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect 16 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect 17 | github.com/BurntSushi/toml v1.4.0 // indirect 18 | github.com/MakeNowJust/heredoc v1.0.0 // indirect 19 | github.com/Masterminds/goutils v1.1.1 // indirect 20 | github.com/Masterminds/semver/v3 v3.3.0 // indirect 21 | github.com/Masterminds/sprig/v3 v3.3.0 // indirect 22 | github.com/Masterminds/squirrel v1.5.4 // indirect 23 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect 24 | github.com/beorn7/perks v1.0.1 // indirect 25 | github.com/blang/semver/v4 v4.0.0 // indirect 26 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 27 | github.com/chai2010/gettext-go v1.0.2 // indirect 28 | github.com/containerd/containerd v1.7.27 // indirect 29 | github.com/containerd/errdefs v0.3.0 // indirect 30 | github.com/containerd/log v0.1.0 // indirect 31 | github.com/containerd/platforms v0.2.1 // indirect 32 | github.com/cyphar/filepath-securejoin v0.3.6 // indirect 33 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 34 | github.com/distribution/reference v0.6.0 // indirect 35 | github.com/docker/cli v25.0.1+incompatible // indirect 36 | github.com/docker/distribution v2.8.3+incompatible // indirect 37 | github.com/docker/docker v25.0.6+incompatible // indirect 38 | github.com/docker/docker-credential-helpers v0.7.0 // indirect 39 | github.com/docker/go-connections v0.5.0 // indirect 40 | github.com/docker/go-metrics v0.0.1 // indirect 41 | github.com/emicklei/go-restful/v3 v3.11.3 // indirect 42 | github.com/evanphx/json-patch v5.9.0+incompatible // indirect 43 | github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect 44 | github.com/fatih/color v1.13.0 // indirect 45 | github.com/felixge/httpsnoop v1.0.4 // indirect 46 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 47 | github.com/go-errors/errors v1.4.2 // indirect 48 | github.com/go-gorp/gorp/v3 v3.1.0 // indirect 49 | github.com/go-logr/logr v1.4.2 // indirect 50 | github.com/go-logr/stdr v1.2.2 // indirect 51 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 52 | github.com/go-openapi/jsonreference v0.20.4 // indirect 53 | github.com/go-openapi/swag v0.23.0 // indirect 54 | github.com/gobwas/glob v0.2.3 // indirect 55 | github.com/gogo/protobuf v1.3.2 // indirect 56 | github.com/golang/protobuf v1.5.4 // indirect 57 | github.com/google/btree v1.0.1 // indirect 58 | github.com/google/gnostic-models v0.6.8 // indirect 59 | github.com/google/go-cmp v0.6.0 // indirect 60 | github.com/google/gofuzz v1.2.0 // indirect 61 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 62 | github.com/google/uuid v1.6.0 // indirect 63 | github.com/gorilla/mux v1.8.0 // indirect 64 | github.com/gorilla/websocket v1.5.0 // indirect 65 | github.com/gosuri/uitable v0.0.4 // indirect 66 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect 67 | github.com/hashicorp/errwrap v1.1.0 // indirect 68 | github.com/hashicorp/go-multierror v1.1.1 // indirect 69 | github.com/huandu/xstrings v1.5.0 // indirect 70 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 71 | github.com/jmoiron/sqlx v1.4.0 // indirect 72 | github.com/josharian/intern v1.0.0 // indirect 73 | github.com/json-iterator/go v1.1.12 // indirect 74 | github.com/klauspost/compress v1.16.7 // indirect 75 | github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect 76 | github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect 77 | github.com/lib/pq v1.10.9 // indirect 78 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect 79 | github.com/mailru/easyjson v0.7.7 // indirect 80 | github.com/mattn/go-colorable v0.1.13 // indirect 81 | github.com/mattn/go-isatty v0.0.17 // indirect 82 | github.com/mattn/go-runewidth v0.0.9 // indirect 83 | github.com/mitchellh/copystructure v1.2.0 // indirect 84 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect 85 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 86 | github.com/moby/locker v1.0.1 // indirect 87 | github.com/moby/spdystream v0.5.0 // indirect 88 | github.com/moby/term v0.5.0 // indirect 89 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 90 | github.com/modern-go/reflect2 v1.0.2 // indirect 91 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect 92 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 93 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect 94 | github.com/opencontainers/go-digest v1.0.0 // indirect 95 | github.com/opencontainers/image-spec v1.1.0 // indirect 96 | github.com/peterbourgon/diskv v2.0.1+incompatible // indirect 97 | github.com/pkg/errors v0.9.1 // indirect 98 | github.com/prometheus/client_golang v1.19.1 // indirect 99 | github.com/prometheus/client_model v0.6.1 // indirect 100 | github.com/prometheus/common v0.55.0 // indirect 101 | github.com/prometheus/procfs v0.15.1 // indirect 102 | github.com/rubenv/sql-migrate v1.7.1 // indirect 103 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 104 | github.com/shopspring/decimal v1.4.0 // indirect 105 | github.com/sirupsen/logrus v1.9.3 // indirect 106 | github.com/spf13/cast v1.7.0 // indirect 107 | github.com/spf13/cobra v1.8.1 // indirect 108 | github.com/spf13/pflag v1.0.5 // indirect 109 | github.com/x448/float16 v0.8.4 // indirect 110 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect 111 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect 112 | github.com/xeipuuv/gojsonschema v1.2.0 // indirect 113 | github.com/xlab/treeprint v1.2.0 // indirect 114 | github.com/yosida95/uritemplate/v3 v3.0.2 // indirect 115 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect 116 | go.opentelemetry.io/otel v1.28.0 // indirect 117 | go.opentelemetry.io/otel/metric v1.28.0 // indirect 118 | go.opentelemetry.io/otel/trace v1.28.0 // indirect 119 | golang.org/x/crypto v0.36.0 // indirect 120 | golang.org/x/net v0.38.0 // indirect 121 | golang.org/x/oauth2 v0.23.0 // indirect 122 | golang.org/x/sync v0.12.0 // indirect 123 | golang.org/x/sys v0.31.0 // indirect 124 | golang.org/x/term v0.30.0 // indirect 125 | golang.org/x/text v0.23.0 // indirect 126 | golang.org/x/time v0.7.0 // indirect 127 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 // indirect 128 | google.golang.org/grpc v1.65.0 // indirect 129 | google.golang.org/protobuf v1.35.2 // indirect 130 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 131 | gopkg.in/inf.v0 v0.9.1 // indirect 132 | gopkg.in/yaml.v3 v3.0.1 // indirect 133 | k8s.io/api v0.32.2 // indirect 134 | k8s.io/apiextensions-apiserver v0.32.2 // indirect 135 | k8s.io/apiserver v0.32.2 // indirect 136 | k8s.io/cli-runtime v0.32.2 // indirect 137 | k8s.io/component-base v0.32.2 // indirect 138 | k8s.io/klog/v2 v2.130.1 // indirect 139 | k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect 140 | k8s.io/kubectl v0.32.2 // indirect 141 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect 142 | oras.land/oras-go v1.2.5 // indirect 143 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 144 | sigs.k8s.io/kustomize/api v0.18.0 // indirect 145 | sigs.k8s.io/kustomize/kyaml v0.18.1 // indirect 146 | sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect 147 | ) 148 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= 2 | dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= 3 | filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= 4 | filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= 5 | github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= 6 | github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= 7 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= 8 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 9 | github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= 10 | github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 11 | github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= 12 | github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= 13 | github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= 14 | github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= 15 | github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= 16 | github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= 17 | github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= 18 | github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= 19 | github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= 20 | github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= 21 | github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= 22 | github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= 23 | github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= 24 | github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 25 | github.com/Microsoft/hcsshim v0.11.7 h1:vl/nj3Bar/CvJSYo7gIQPyRWc9f3c6IeSNavBTSZNZQ= 26 | github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU= 27 | github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= 28 | github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= 29 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 30 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 31 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= 32 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 33 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= 34 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= 35 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 36 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 37 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 38 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 39 | github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= 40 | github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= 41 | github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= 42 | github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= 43 | github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= 44 | github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= 45 | github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= 46 | github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= 47 | github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= 48 | github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= 49 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 50 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 51 | github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= 52 | github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= 53 | github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= 54 | github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= 55 | github.com/containerd/containerd v1.7.27 h1:yFyEyojddO3MIGVER2xJLWoCIn+Up4GaHFquP7hsFII= 56 | github.com/containerd/containerd v1.7.27/go.mod h1:xZmPnl75Vc+BLGt4MIfu6bp+fy03gdHAn9bz+FreFR0= 57 | github.com/containerd/continuity v0.4.4 h1:/fNVfTJ7wIl/YPMHjf+5H32uFhl63JucB34PlCpMKII= 58 | github.com/containerd/continuity v0.4.4/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= 59 | github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= 60 | github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= 61 | github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= 62 | github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= 63 | github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= 64 | github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= 65 | github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 66 | github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= 67 | github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= 68 | github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= 69 | github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= 70 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 71 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 72 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 73 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 74 | github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc= 75 | github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI= 76 | github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= 77 | github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= 78 | github.com/docker/cli v25.0.1+incompatible h1:mFpqnrS6Hsm3v1k7Wa/BO23oz0k121MTbTO1lpcGSkU= 79 | github.com/docker/cli v25.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= 80 | github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= 81 | github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= 82 | github.com/docker/docker v25.0.6+incompatible h1:5cPwbwriIcsua2REJe8HqQV+6WlWc1byg2QSXzBxBGg= 83 | github.com/docker/docker v25.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 84 | github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= 85 | github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= 86 | github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= 87 | github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= 88 | github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= 89 | github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= 90 | github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= 91 | github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= 92 | github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= 93 | github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= 94 | github.com/emicklei/go-restful/v3 v3.11.3 h1:yagOQz/38xJmcNeZJtrUcKjkHRltIaIFXKWeG1SkWGE= 95 | github.com/emicklei/go-restful/v3 v3.11.3/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 96 | github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= 97 | github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 98 | github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= 99 | github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= 100 | github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= 101 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= 102 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 103 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 104 | github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= 105 | github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= 106 | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= 107 | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 108 | github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= 109 | github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= 110 | github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= 111 | github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= 112 | github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= 113 | github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= 114 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 115 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 116 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 117 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 118 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 119 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 120 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 121 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 122 | github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= 123 | github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= 124 | github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= 125 | github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= 126 | github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= 127 | github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= 128 | github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= 129 | github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= 130 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 131 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 132 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 133 | github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= 134 | github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= 135 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 136 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 137 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 138 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 139 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 140 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 141 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 142 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 143 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 144 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 145 | github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= 146 | github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= 147 | github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= 148 | github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= 149 | github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= 150 | github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= 151 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 152 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 153 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 154 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 155 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 156 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 157 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 158 | github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= 159 | github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 160 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= 161 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= 162 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 163 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 164 | github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= 165 | github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= 166 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= 167 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 168 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 169 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 170 | github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= 171 | github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= 172 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= 173 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 174 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 175 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= 176 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 177 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 178 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 179 | github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= 180 | github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 181 | github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= 182 | github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 183 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 184 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 185 | github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= 186 | github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= 187 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 188 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 189 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 190 | github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 191 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 192 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 193 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 194 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 195 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 196 | github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= 197 | github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= 198 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 199 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 200 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 201 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 202 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 203 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 204 | github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= 205 | github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= 206 | github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= 207 | github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= 208 | github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= 209 | github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 210 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= 211 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= 212 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 213 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 214 | github.com/mark3labs/mcp-go v0.17.0 h1:5Ps6T7qXr7De/2QTqs9h6BKeZ/qdeUeGrgM5lPzi930= 215 | github.com/mark3labs/mcp-go v0.17.0/go.mod h1:KmJndYv7GIgcPVwEKJjNcbhVQ+hJGJhrCCB/9xITzpE= 216 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 217 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 218 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 219 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 220 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 221 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 222 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= 223 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 224 | github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= 225 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 226 | github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= 227 | github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 228 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 229 | github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= 230 | github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= 231 | github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= 232 | github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= 233 | github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= 234 | github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= 235 | github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= 236 | github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 237 | github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= 238 | github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= 239 | github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= 240 | github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= 241 | github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= 242 | github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= 243 | github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= 244 | github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= 245 | github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= 246 | github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= 247 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 248 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 249 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 250 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 251 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 252 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 253 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 254 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= 255 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= 256 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 257 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 258 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 259 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= 260 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= 261 | github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= 262 | github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= 263 | github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= 264 | github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= 265 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 266 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 267 | github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= 268 | github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= 269 | github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= 270 | github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= 271 | github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= 272 | github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= 273 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 274 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 275 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 276 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 277 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 278 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 279 | github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= 280 | github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= 281 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 282 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 283 | github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= 284 | github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= 285 | github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= 286 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 287 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 288 | github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 289 | github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 290 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 291 | github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= 292 | github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= 293 | github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= 294 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 295 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 296 | github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= 297 | github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= 298 | github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 299 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 300 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 301 | github.com/rubenv/sql-migrate v1.7.1 h1:f/o0WgfO/GqNuVg+6801K/KW3WdDSupzSjDYODmiUq4= 302 | github.com/rubenv/sql-migrate v1.7.1/go.mod h1:Ob2Psprc0/3ggbM6wCzyYVFFuc6FyZrb2AS+ezLDFb4= 303 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 304 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 305 | github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= 306 | github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 307 | github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= 308 | github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= 309 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 310 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 311 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 312 | github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= 313 | github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= 314 | github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= 315 | github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= 316 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 317 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 318 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 319 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 320 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 321 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 322 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 323 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 324 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 325 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 326 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 327 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 328 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 329 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 330 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 331 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= 332 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 333 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= 334 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= 335 | github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= 336 | github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= 337 | github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= 338 | github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= 339 | github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= 340 | github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= 341 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 342 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 343 | github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= 344 | github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= 345 | github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= 346 | github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= 347 | github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= 348 | github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= 349 | go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= 350 | go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= 351 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= 352 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= 353 | go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= 354 | go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= 355 | go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= 356 | go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= 357 | go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= 358 | go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= 359 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 360 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 361 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 362 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 363 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 364 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 365 | golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= 366 | golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= 367 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 368 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 369 | golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= 370 | golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 371 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 372 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 373 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 374 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 375 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 376 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 377 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 378 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 379 | golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= 380 | golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 381 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 382 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 383 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 384 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 385 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 386 | golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= 387 | golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 388 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 389 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 390 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 391 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 392 | golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 393 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 394 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 395 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 396 | golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 397 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 398 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 399 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 400 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 401 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 402 | golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= 403 | golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= 404 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 405 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 406 | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= 407 | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 408 | golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= 409 | golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 410 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 411 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 412 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 413 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 414 | golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= 415 | golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= 416 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 417 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 418 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 419 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 420 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 h1:2035KHhUv+EpyB+hWgJnaWKJOdX1E95w2S8Rr4uWKTs= 421 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= 422 | google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= 423 | google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= 424 | google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= 425 | google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 426 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 427 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 428 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 429 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 430 | gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= 431 | gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= 432 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 433 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 434 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 435 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 436 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 437 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 438 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 439 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 440 | gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= 441 | gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= 442 | helm.sh/helm/v3 v3.17.3 h1:3n5rW3D0ArjFl0p4/oWO8IbY/HKaNNwJtOQFdH2AZHg= 443 | helm.sh/helm/v3 v3.17.3/go.mod h1:+uJKMH/UiMzZQOALR3XUf3BLIoczI2RKKD6bMhPh4G8= 444 | k8s.io/api v0.32.2 h1:bZrMLEkgizC24G9eViHGOPbW+aRo9duEISRIJKfdJuw= 445 | k8s.io/api v0.32.2/go.mod h1:hKlhk4x1sJyYnHENsrdCWw31FEmCijNGPJO5WzHiJ6Y= 446 | k8s.io/apiextensions-apiserver v0.32.2 h1:2YMk285jWMk2188V2AERy5yDwBYrjgWYggscghPCvV4= 447 | k8s.io/apiextensions-apiserver v0.32.2/go.mod h1:GPwf8sph7YlJT3H6aKUWtd0E+oyShk/YHWQHf/OOgCA= 448 | k8s.io/apimachinery v0.32.2 h1:yoQBR9ZGkA6Rgmhbp/yuT9/g+4lxtsGYwW6dR6BDPLQ= 449 | k8s.io/apimachinery v0.32.2/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= 450 | k8s.io/apiserver v0.32.2 h1:WzyxAu4mvLkQxwD9hGa4ZfExo3yZZaYzoYvvVDlM6vw= 451 | k8s.io/apiserver v0.32.2/go.mod h1:PEwREHiHNU2oFdte7BjzA1ZyjWjuckORLIK/wLV5goM= 452 | k8s.io/cli-runtime v0.32.2 h1:aKQR4foh9qeyckKRkNXUccP9moxzffyndZAvr+IXMks= 453 | k8s.io/cli-runtime v0.32.2/go.mod h1:a/JpeMztz3xDa7GCyyShcwe55p8pbcCVQxvqZnIwXN8= 454 | k8s.io/client-go v0.32.2 h1:4dYCD4Nz+9RApM2b/3BtVvBHw54QjMFUl1OLcJG5yOA= 455 | k8s.io/client-go v0.32.2/go.mod h1:fpZ4oJXclZ3r2nDOv+Ux3XcJutfrwjKTCHz2H3sww94= 456 | k8s.io/component-base v0.32.2 h1:1aUL5Vdmu7qNo4ZsE+569PV5zFatM9hl+lb3dEea2zU= 457 | k8s.io/component-base v0.32.2/go.mod h1:PXJ61Vx9Lg+P5mS8TLd7bCIr+eMJRQTyXe8KvkrvJq0= 458 | k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= 459 | k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 460 | k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= 461 | k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= 462 | k8s.io/kubectl v0.32.2 h1:TAkag6+XfSBgkqK9I7ZvwtF0WVtUAvK8ZqTt+5zi1Us= 463 | k8s.io/kubectl v0.32.2/go.mod h1:+h/NQFSPxiDZYX/WZaWw9fwYezGLISP0ud8nQKg+3g8= 464 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= 465 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 466 | oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo= 467 | oras.land/oras-go v1.2.5/go.mod h1:PuAwRShRZCsZb7g8Ar3jKKQR/2A/qN+pkYxIOd/FAoo= 468 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= 469 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= 470 | sigs.k8s.io/kustomize/api v0.18.0 h1:hTzp67k+3NEVInwz5BHyzc9rGxIauoXferXyjv5lWPo= 471 | sigs.k8s.io/kustomize/api v0.18.0/go.mod h1:f8isXnX+8b+SGLHQ6yO4JG1rdkZlvhaCf/uZbLVMb0U= 472 | sigs.k8s.io/kustomize/kyaml v0.18.1 h1:WvBo56Wzw3fjS+7vBjN6TeivvpbW9GmRaWZ9CIVmt4E= 473 | sigs.k8s.io/kustomize/kyaml v0.18.1/go.mod h1:C3L2BFVU1jgcddNBE1TxuVLgS46TjObMwW5FT9FcjYo= 474 | sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= 475 | sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= 476 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 477 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 478 | -------------------------------------------------------------------------------- /internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | // Config 表示应用程序配置 9 | type Config struct { 10 | // Kubeconfig 文件路径 11 | KubeconfigPath string 12 | // 是否启用资源创建操作 13 | EnableCreate bool 14 | // 是否启用资源更新操作 15 | EnableUpdate bool 16 | // 是否启用资源删除操作 17 | EnableDelete bool 18 | // 是否启用资源列表操作 19 | EnableList bool 20 | // 是否启用 Helm 安装操作 21 | EnableHelmInstall bool 22 | // 是否启用 Helm 升级操作 23 | EnableHelmUpgrade bool 24 | // 是否启用 Helm 卸载操作 25 | EnableHelmUninstall bool 26 | // 是否启用 Helm 仓库添加操作 27 | EnableHelmRepoAdd bool 28 | // 是否启用 Helm 仓库删除操作 29 | EnableHelmRepoRemove bool 30 | // 是否启用 Helm 发布版列表操作 31 | EnableHelmReleaseList bool 32 | // 是否启用 Helm 发布版查询操作 33 | EnableHelmReleaseGet bool 34 | // 是否启用 Helm 仓库列表操作 35 | EnableHelmRepoList bool 36 | } 37 | 38 | // NewConfig 从命令行参数创建配置 39 | func NewConfig(kubeconfigPath string, enableCreate, enableUpdate, enableDelete, enableList bool) *Config { 40 | return &Config{ 41 | KubeconfigPath: kubeconfigPath, 42 | EnableCreate: enableCreate, 43 | EnableUpdate: enableUpdate, 44 | EnableDelete: enableDelete, 45 | EnableList: enableList, 46 | } 47 | } 48 | 49 | // Validate 验证配置是否有效 50 | func (c *Config) Validate() error { 51 | // 检查 kubeconfig 是否可访问 52 | if c.KubeconfigPath != "" { 53 | _, err := os.Stat(c.KubeconfigPath) 54 | if err != nil { 55 | return fmt.Errorf("无法访问 kubeconfig 文件: %w", err) 56 | } 57 | } 58 | return nil 59 | } 60 | 61 | // InitHelmDefaults 初始化Helm相关的默认配置 62 | func (c *Config) InitHelmDefaults() { 63 | // 读操作默认开启 64 | c.EnableHelmReleaseList = true 65 | c.EnableHelmReleaseGet = true 66 | c.EnableHelmRepoList = true 67 | 68 | // 写操作默认关闭 69 | c.EnableHelmInstall = false 70 | c.EnableHelmUpgrade = false 71 | c.EnableHelmUninstall = false 72 | c.EnableHelmRepoAdd = false 73 | c.EnableHelmRepoRemove = false 74 | } 75 | -------------------------------------------------------------------------------- /internal/k8s/client.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "path/filepath" 8 | 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 11 | "k8s.io/apimachinery/pkg/runtime/schema" 12 | "k8s.io/client-go/discovery" 13 | "k8s.io/client-go/dynamic" 14 | "k8s.io/client-go/kubernetes" 15 | "k8s.io/client-go/rest" 16 | "k8s.io/client-go/tools/clientcmd" 17 | "k8s.io/client-go/util/homedir" 18 | ) 19 | 20 | // Client 封装了 Kubernetes 客户端功能 21 | type Client struct { 22 | // 标准 clientset 23 | clientset *kubernetes.Clientset 24 | // 动态客户端 25 | dynamicClient dynamic.Interface 26 | // 发现客户端 27 | discoveryClient *discovery.DiscoveryClient 28 | // REST 配置 29 | restConfig *rest.Config 30 | // kubeconfig 路径 31 | kubeconfigPath string 32 | } 33 | 34 | // NewClient 创建一个新的 Kubernetes 客户端 35 | func NewClient(kubeconfigPath string) (*Client, error) { 36 | var kubeconfig string 37 | var config *rest.Config 38 | var err error 39 | 40 | // 如果提供了 kubeconfig 路径,使用它 41 | if kubeconfigPath != "" { 42 | kubeconfig = kubeconfigPath 43 | } else if home := homedir.HomeDir(); home != "" { 44 | // 否则尝试使用默认路径 45 | kubeconfig = filepath.Join(home, ".kube", "config") 46 | } 47 | 48 | // 使用提供的 kubeconfig 或尝试集群内配置 49 | if kubeconfig != "" { 50 | config, err = clientcmd.BuildConfigFromFlags("", kubeconfig) 51 | } else { 52 | config, err = rest.InClusterConfig() 53 | } 54 | if err != nil { 55 | return nil, fmt.Errorf("创建 Kubernetes 配置失败: %w", err) 56 | } 57 | 58 | // 创建 clientset 59 | clientset, err := kubernetes.NewForConfig(config) 60 | if err != nil { 61 | return nil, fmt.Errorf("创建 Kubernetes 客户端失败: %w", err) 62 | } 63 | 64 | // 创建动态客户端 65 | dynamicClient, err := dynamic.NewForConfig(config) 66 | if err != nil { 67 | return nil, fmt.Errorf("创建动态客户端失败: %w", err) 68 | } 69 | 70 | // 创建发现客户端 71 | discoveryClient, err := discovery.NewDiscoveryClientForConfig(config) 72 | if err != nil { 73 | return nil, fmt.Errorf("创建发现客户端失败: %w", err) 74 | } 75 | 76 | return &Client{ 77 | clientset: clientset, 78 | dynamicClient: dynamicClient, 79 | discoveryClient: discoveryClient, 80 | restConfig: config, 81 | kubeconfigPath: kubeconfig, 82 | }, nil 83 | } 84 | 85 | // GetAPIResources 获取集群中的所有 API 资源类型 86 | func (c *Client) GetAPIResources(ctx context.Context, includeNamespaceScoped, includeClusterScoped bool) ([]map[string]interface{}, error) { 87 | // 获取集群中所有 API Groups 和 Resources 88 | resourceLists, err := c.discoveryClient.ServerPreferredResources() 89 | if err != nil { 90 | // 处理部分错误,有些资源可能无法访问 91 | if !discovery.IsGroupDiscoveryFailedError(err) { 92 | return nil, fmt.Errorf("获取 API 资源失败: %w", err) 93 | } 94 | } 95 | 96 | var resources []map[string]interface{} 97 | 98 | // 处理每个API组中的资源 99 | for _, resourceList := range resourceLists { 100 | groupVersion := resourceList.GroupVersion 101 | for _, resource := range resourceList.APIResources { 102 | // 忽略子资源 103 | if len(resource.Group) == 0 { 104 | resource.Group = resourceList.GroupVersion 105 | } 106 | if len(resource.Version) == 0 { 107 | gv, err := schema.ParseGroupVersion(groupVersion) 108 | if err != nil { 109 | continue 110 | } 111 | resource.Version = gv.Version 112 | } 113 | 114 | // 根据命名空间范围过滤 115 | if (resource.Namespaced && !includeNamespaceScoped) || (!resource.Namespaced && !includeClusterScoped) { 116 | continue 117 | } 118 | 119 | resources = append(resources, map[string]interface{}{ 120 | "name": resource.Name, 121 | "singularName": resource.SingularName, 122 | "namespaced": resource.Namespaced, 123 | "kind": resource.Kind, 124 | "group": resource.Group, 125 | "version": resource.Version, 126 | "verbs": resource.Verbs, 127 | }) 128 | } 129 | } 130 | 131 | return resources, nil 132 | } 133 | 134 | // GetResource 获取特定资源的详细信息 135 | func (c *Client) GetResource(ctx context.Context, kind, name, namespace string) (map[string]interface{}, error) { 136 | // 获取资源的 GVR 137 | gvr, err := c.findGroupVersionResource(kind) 138 | if err != nil { 139 | return nil, err 140 | } 141 | 142 | var obj *unstructured.Unstructured 143 | if namespace != "" { 144 | obj, err = c.dynamicClient.Resource(*gvr).Namespace(namespace).Get(ctx, name, metav1.GetOptions{}) 145 | } else { 146 | obj, err = c.dynamicClient.Resource(*gvr).Get(ctx, name, metav1.GetOptions{}) 147 | } 148 | 149 | if err != nil { 150 | return nil, fmt.Errorf("获取资源失败: %w", err) 151 | } 152 | 153 | return obj.UnstructuredContent(), nil 154 | } 155 | 156 | // ListResources 列出某类资源的所有实例 157 | func (c *Client) ListResources(ctx context.Context, kind, namespace string, labelSelector, fieldSelector string) ([]map[string]interface{}, error) { 158 | // 获取资源的 GVR 159 | gvr, err := c.findGroupVersionResource(kind) 160 | if err != nil { 161 | return nil, err 162 | } 163 | 164 | options := metav1.ListOptions{} 165 | if labelSelector != "" { 166 | options.LabelSelector = labelSelector 167 | } 168 | if fieldSelector != "" { 169 | options.FieldSelector = fieldSelector 170 | } 171 | 172 | var list *unstructured.UnstructuredList 173 | if namespace != "" { 174 | list, err = c.dynamicClient.Resource(*gvr).Namespace(namespace).List(ctx, options) 175 | } else { 176 | list, err = c.dynamicClient.Resource(*gvr).List(ctx, options) 177 | } 178 | 179 | if err != nil { 180 | return nil, fmt.Errorf("列出资源失败: %w", err) 181 | } 182 | 183 | var resources []map[string]interface{} 184 | for _, item := range list.Items { 185 | resources = append(resources, item.UnstructuredContent()) 186 | } 187 | 188 | return resources, nil 189 | } 190 | 191 | // CreateResource 创建一个新的资源 192 | func (c *Client) CreateResource(ctx context.Context, kind, namespace string, manifest string) (map[string]interface{}, error) { 193 | obj := &unstructured.Unstructured{} 194 | if err := json.Unmarshal([]byte(manifest), &obj.Object); err != nil { 195 | return nil, fmt.Errorf("解析资源清单失败: %w", err) 196 | } 197 | 198 | // 获取资源的 GVR 199 | gvr, err := c.findGroupVersionResource(kind) 200 | if err != nil { 201 | return nil, err 202 | } 203 | 204 | var result *unstructured.Unstructured 205 | if namespace != "" || obj.GetNamespace() != "" { 206 | targetNamespace := namespace 207 | if targetNamespace == "" { 208 | targetNamespace = obj.GetNamespace() 209 | } 210 | result, err = c.dynamicClient.Resource(*gvr).Namespace(targetNamespace).Create(ctx, obj, metav1.CreateOptions{}) 211 | } else { 212 | result, err = c.dynamicClient.Resource(*gvr).Create(ctx, obj, metav1.CreateOptions{}) 213 | } 214 | 215 | if err != nil { 216 | return nil, fmt.Errorf("创建资源失败: %w", err) 217 | } 218 | 219 | return result.UnstructuredContent(), nil 220 | } 221 | 222 | // UpdateResource 更新现有资源 223 | func (c *Client) UpdateResource(ctx context.Context, kind, name, namespace string, manifest string) (map[string]interface{}, error) { 224 | obj := &unstructured.Unstructured{} 225 | if err := json.Unmarshal([]byte(manifest), &obj.Object); err != nil { 226 | return nil, fmt.Errorf("解析资源清单失败: %w", err) 227 | } 228 | 229 | // 检查名称是否匹配 230 | if obj.GetName() != name { 231 | return nil, fmt.Errorf("资源清单中的名称 (%s) 与请求的名称 (%s) 不匹配", obj.GetName(), name) 232 | } 233 | 234 | // 获取资源的 GVR 235 | gvr, err := c.findGroupVersionResource(kind) 236 | if err != nil { 237 | return nil, err 238 | } 239 | 240 | var result *unstructured.Unstructured 241 | if namespace != "" { 242 | result, err = c.dynamicClient.Resource(*gvr).Namespace(namespace).Update(ctx, obj, metav1.UpdateOptions{}) 243 | } else { 244 | result, err = c.dynamicClient.Resource(*gvr).Update(ctx, obj, metav1.UpdateOptions{}) 245 | } 246 | 247 | if err != nil { 248 | return nil, fmt.Errorf("更新资源失败: %w", err) 249 | } 250 | 251 | return result.UnstructuredContent(), nil 252 | } 253 | 254 | // DeleteResource 删除资源 255 | func (c *Client) DeleteResource(ctx context.Context, kind, name, namespace string) error { 256 | // 获取资源的 GVR 257 | gvr, err := c.findGroupVersionResource(kind) 258 | if err != nil { 259 | return err 260 | } 261 | 262 | var deleteErr error 263 | if namespace != "" { 264 | deleteErr = c.dynamicClient.Resource(*gvr).Namespace(namespace).Delete(ctx, name, metav1.DeleteOptions{}) 265 | } else { 266 | deleteErr = c.dynamicClient.Resource(*gvr).Delete(ctx, name, metav1.DeleteOptions{}) 267 | } 268 | 269 | if deleteErr != nil { 270 | return fmt.Errorf("删除资源失败: %w", deleteErr) 271 | } 272 | 273 | return nil 274 | } 275 | 276 | // findGroupVersionResource 根据 Kind 查找对应的 GroupVersionResource 277 | func (c *Client) findGroupVersionResource(kind string) (*schema.GroupVersionResource, error) { 278 | // 获取集群中所有 API Groups 和 Resources 279 | resourceLists, err := c.discoveryClient.ServerPreferredResources() 280 | if err != nil { 281 | // 处理部分错误,有些资源可能无法访问 282 | if !discovery.IsGroupDiscoveryFailedError(err) { 283 | return nil, fmt.Errorf("获取 API 资源失败: %w", err) 284 | } 285 | } 286 | 287 | // 遍历所有 API 组和资源,查找指定的 Kind 288 | for _, resourceList := range resourceLists { 289 | gv, err := schema.ParseGroupVersion(resourceList.GroupVersion) 290 | if err != nil { 291 | continue 292 | } 293 | 294 | for _, resource := range resourceList.APIResources { 295 | if resource.Kind == kind { 296 | return &schema.GroupVersionResource{ 297 | Group: gv.Group, 298 | Version: gv.Version, 299 | Resource: resource.Name, 300 | }, nil 301 | } 302 | } 303 | } 304 | 305 | return nil, fmt.Errorf("找不到资源类型 %s", kind) 306 | } 307 | 308 | // GetKubeconfigPath 返回客户端使用的 kubeconfig 路径 309 | func (c *Client) GetKubeconfigPath() string { 310 | return c.kubeconfigPath 311 | } 312 | -------------------------------------------------------------------------------- /internal/k8s/helm.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "time" 8 | 9 | "helm.sh/helm/v3/pkg/action" 10 | "helm.sh/helm/v3/pkg/chart" 11 | "helm.sh/helm/v3/pkg/chart/loader" 12 | "helm.sh/helm/v3/pkg/cli" 13 | "helm.sh/helm/v3/pkg/repo" 14 | "sigs.k8s.io/yaml" 15 | ) 16 | 17 | // HelmClient 提供 Helm 操作功能 18 | type HelmClient struct { 19 | settings *cli.EnvSettings 20 | config *action.Configuration 21 | namespace string 22 | } 23 | 24 | // HelmRelease 表示 Helm 部署的信息 25 | type HelmRelease struct { 26 | Name string `json:"name"` 27 | Namespace string `json:"namespace"` 28 | Revision int `json:"revision"` 29 | Status string `json:"status"` 30 | Chart string `json:"chart"` 31 | ChartVersion string `json:"chartVersion"` 32 | AppVersion string `json:"appVersion,omitempty"` 33 | Updated time.Time `json:"updated"` 34 | Values map[string]interface{} `json:"values,omitempty"` 35 | } 36 | 37 | // HelmRepository 表示一个 Helm 仓库 38 | type HelmRepository struct { 39 | Name string `json:"name"` 40 | URL string `json:"url"` 41 | Username string `json:"username,omitempty"` 42 | Password string `json:"password,omitempty"` 43 | } 44 | 45 | // NewHelmClient 创建一个 Helm 客户端 46 | func NewHelmClient(namespace string, kubeconfigPath string) (*HelmClient, error) { 47 | settings := cli.New() 48 | 49 | // 如果提供了 kubeconfig 路径,设置它 50 | if kubeconfigPath != "" { 51 | settings.KubeConfig = kubeconfigPath 52 | } 53 | 54 | // 设置默认命名空间 55 | if namespace != "" { 56 | settings.SetNamespace(namespace) 57 | } else { 58 | settings.SetNamespace("default") 59 | } 60 | 61 | actionConfig := new(action.Configuration) 62 | if err := actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), os.Getenv("HELM_DRIVER"), func(format string, v ...interface{}) { 63 | fmt.Printf(format, v...) 64 | }); err != nil { 65 | return nil, fmt.Errorf("初始化 Helm 配置失败: %w", err) 66 | } 67 | 68 | return &HelmClient{ 69 | settings: settings, 70 | config: actionConfig, 71 | namespace: settings.Namespace(), 72 | }, nil 73 | } 74 | 75 | // SetNamespace 设置 Helm 客户端操作的命名空间 76 | func (c *HelmClient) SetNamespace(namespace string) error { 77 | c.settings.SetNamespace(namespace) 78 | c.namespace = namespace 79 | 80 | // 重新初始化配置 81 | if err := c.config.Init(c.settings.RESTClientGetter(), namespace, os.Getenv("HELM_DRIVER"), func(format string, v ...interface{}) { 82 | fmt.Printf(format, v...) 83 | }); err != nil { 84 | return fmt.Errorf("更新 Helm 配置命名空间失败: %w", err) 85 | } 86 | 87 | return nil 88 | } 89 | 90 | // ListReleases 列出所有部署的 Helm charts 91 | func (c *HelmClient) ListReleases(allNamespaces bool) ([]HelmRelease, error) { 92 | client := action.NewList(c.config) 93 | 94 | // 是否列出所有命名空间的 releases 95 | if allNamespaces { 96 | client.AllNamespaces = true 97 | } 98 | 99 | results, err := client.Run() 100 | if err != nil { 101 | return nil, fmt.Errorf("列出 Helm releases 失败: %w", err) 102 | } 103 | 104 | releases := []HelmRelease{} 105 | for _, r := range results { 106 | releases = append(releases, HelmRelease{ 107 | Name: r.Name, 108 | Namespace: r.Namespace, 109 | Revision: r.Version, 110 | Status: r.Info.Status.String(), 111 | Chart: r.Chart.Metadata.Name, 112 | ChartVersion: r.Chart.Metadata.Version, 113 | AppVersion: r.Chart.Metadata.AppVersion, 114 | Updated: r.Info.LastDeployed.Time, 115 | }) 116 | } 117 | 118 | return releases, nil 119 | } 120 | 121 | // GetRelease 获取特定 Helm release 的详细信息 122 | func (c *HelmClient) GetRelease(name string) (*HelmRelease, error) { 123 | client := action.NewGet(c.config) 124 | 125 | release, err := client.Run(name) 126 | if err != nil { 127 | return nil, fmt.Errorf("获取 Helm release 失败: %w", err) 128 | } 129 | 130 | // 获取值 131 | var values map[string]interface{} 132 | // 合并用户提供的值和计算的值 133 | if release.Config != nil && release.Chart.Values != nil { 134 | values = release.Config 135 | } 136 | 137 | return &HelmRelease{ 138 | Name: release.Name, 139 | Namespace: release.Namespace, 140 | Revision: release.Version, 141 | Status: release.Info.Status.String(), 142 | Chart: release.Chart.Metadata.Name, 143 | ChartVersion: release.Chart.Metadata.Version, 144 | AppVersion: release.Chart.Metadata.AppVersion, 145 | Updated: release.Info.LastDeployed.Time, 146 | Values: values, 147 | }, nil 148 | } 149 | 150 | // InstallChart 安装 Helm chart 151 | func (c *HelmClient) InstallChart(name, chartName string, values map[string]interface{}, version string, repo string) (*HelmRelease, error) { 152 | client := action.NewInstall(c.config) 153 | client.ReleaseName = name 154 | client.Namespace = c.namespace 155 | 156 | // 如果指定了版本,设置版本 157 | if version != "" { 158 | client.Version = version 159 | } 160 | 161 | // 解析 chart 名称和仓库 162 | var chartPath string 163 | if repo != "" { 164 | // 使用指定的仓库 165 | chartPath = fmt.Sprintf("%s/%s", repo, chartName) 166 | } else { 167 | // 默认使用本地或远程 chart 168 | chartPath = chartName 169 | } 170 | 171 | // 定位 chart 172 | chartPathOptions := client.ChartPathOptions 173 | cp, err := chartPathOptions.LocateChart(chartPath, c.settings) 174 | if err != nil { 175 | return nil, fmt.Errorf("查找 chart '%s' 失败: %w", chartPath, err) 176 | } 177 | 178 | // 加载 chart 179 | chartRequested, err := loader.Load(cp) 180 | if err != nil { 181 | return nil, fmt.Errorf("加载 chart 失败: %w", err) 182 | } 183 | 184 | // 安装 chart 185 | release, err := client.Run(chartRequested, values) 186 | if err != nil { 187 | return nil, fmt.Errorf("安装 chart 失败: %w", err) 188 | } 189 | 190 | return &HelmRelease{ 191 | Name: release.Name, 192 | Namespace: release.Namespace, 193 | Revision: release.Version, 194 | Status: release.Info.Status.String(), 195 | Chart: release.Chart.Metadata.Name, 196 | ChartVersion: release.Chart.Metadata.Version, 197 | AppVersion: release.Chart.Metadata.AppVersion, 198 | Updated: release.Info.LastDeployed.Time, 199 | Values: values, 200 | }, nil 201 | } 202 | 203 | // UpgradeChart 升级 Helm chart 204 | func (c *HelmClient) UpgradeChart(name, chartName string, values map[string]interface{}, version string, repo string) (*HelmRelease, error) { 205 | client := action.NewUpgrade(c.config) 206 | 207 | // 如果指定了版本,设置版本 208 | if version != "" { 209 | client.Version = version 210 | } 211 | 212 | // 解析 chart 名称和仓库 213 | var chartPath string 214 | if repo != "" { 215 | // 使用指定的仓库 216 | chartPath = fmt.Sprintf("%s/%s", repo, chartName) 217 | } else { 218 | // 默认使用本地或远程 chart 219 | chartPath = chartName 220 | } 221 | 222 | // 定位 chart 223 | chartPathOptions := client.ChartPathOptions 224 | cp, err := chartPathOptions.LocateChart(chartPath, c.settings) 225 | if err != nil { 226 | return nil, fmt.Errorf("查找 chart '%s' 失败: %w", chartPath, err) 227 | } 228 | 229 | // 加载 chart 230 | chartRequested, err := loader.Load(cp) 231 | if err != nil { 232 | return nil, fmt.Errorf("加载 chart 失败: %w", err) 233 | } 234 | 235 | // 升级 chart 236 | release, err := client.Run(name, chartRequested, values) 237 | if err != nil { 238 | return nil, fmt.Errorf("升级 chart 失败: %w", err) 239 | } 240 | 241 | return &HelmRelease{ 242 | Name: release.Name, 243 | Namespace: release.Namespace, 244 | Revision: release.Version, 245 | Status: release.Info.Status.String(), 246 | Chart: release.Chart.Metadata.Name, 247 | ChartVersion: release.Chart.Metadata.Version, 248 | AppVersion: release.Chart.Metadata.AppVersion, 249 | Updated: release.Info.LastDeployed.Time, 250 | Values: values, 251 | }, nil 252 | } 253 | 254 | // UninstallChart 卸载 Helm chart 255 | func (c *HelmClient) UninstallChart(name string) error { 256 | client := action.NewUninstall(c.config) 257 | 258 | _, err := client.Run(name) 259 | if err != nil { 260 | return fmt.Errorf("卸载 chart 失败: %w", err) 261 | } 262 | 263 | return nil 264 | } 265 | 266 | // RollbackRelease 回滚 Helm release 到指定版本 267 | func (c *HelmClient) RollbackRelease(name string, revision int) error { 268 | client := action.NewRollback(c.config) 269 | client.Version = revision 270 | 271 | return client.Run(name) 272 | } 273 | 274 | // GetReleaseHistory 获取 Helm release 的历史记录 275 | func (c *HelmClient) GetReleaseHistory(name string) ([]HelmRelease, error) { 276 | client := action.NewHistory(c.config) 277 | 278 | hist, err := client.Run(name) 279 | if err != nil { 280 | return nil, fmt.Errorf("获取 release 历史记录失败: %w", err) 281 | } 282 | 283 | releases := []HelmRelease{} 284 | for _, r := range hist { 285 | releases = append(releases, HelmRelease{ 286 | Name: r.Name, 287 | Namespace: r.Namespace, 288 | Revision: r.Version, 289 | Status: r.Info.Status.String(), 290 | Chart: r.Chart.Metadata.Name, 291 | ChartVersion: r.Chart.Metadata.Version, 292 | AppVersion: r.Chart.Metadata.AppVersion, 293 | Updated: r.Info.LastDeployed.Time, 294 | }) 295 | } 296 | 297 | return releases, nil 298 | } 299 | 300 | // AddRepository 添加 Helm 仓库 301 | func (c *HelmClient) AddRepository(repository *HelmRepository) error { 302 | // 创建存储库条目 303 | entry := &repo.Entry{ 304 | Name: repository.Name, 305 | URL: repository.URL, 306 | Username: repository.Username, 307 | Password: repository.Password, 308 | } 309 | 310 | // 获取存储库文件路径 311 | repoFile := c.settings.RepositoryConfig 312 | 313 | // 确保存储库目录存在 314 | err := os.MkdirAll(filepath.Dir(repoFile), os.ModePerm) 315 | if err != nil && !os.IsExist(err) { 316 | return fmt.Errorf("创建 repository 配置目录失败: %w", err) 317 | } 318 | 319 | // 加载存储库文件 320 | f, err := repo.LoadFile(repoFile) 321 | if err != nil { 322 | // 如果文件不存在,创建一个新的文件 323 | if os.IsNotExist(err) { 324 | f = repo.NewFile() 325 | } else { 326 | return err 327 | } 328 | } 329 | 330 | // 检查是否已存在同名存储库 331 | if f.Has(entry.Name) { 332 | // 更新存在的条目 333 | f.Update(entry) 334 | } else { 335 | // 添加新条目 336 | f.Add(entry) 337 | } 338 | 339 | // 保存存储库文件 340 | if err := f.WriteFile(repoFile, 0644); err != nil { 341 | return fmt.Errorf("保存 repository 配置失败: %w", err) 342 | } 343 | 344 | return nil 345 | } 346 | 347 | // RemoveRepository 移除 Helm 仓库 348 | func (c *HelmClient) RemoveRepository(name string) error { 349 | // 获取存储库文件路径 350 | repoFile := c.settings.RepositoryConfig 351 | 352 | // 加载存储库文件 353 | f, err := repo.LoadFile(repoFile) 354 | if err != nil { 355 | return fmt.Errorf("加载 repository 配置失败: %w", err) 356 | } 357 | 358 | // 检查是否存在该存储库 359 | if !f.Has(name) { 360 | return fmt.Errorf("repository %s 不存在", name) 361 | } 362 | 363 | // 移除存储库 364 | f.Remove(name) 365 | 366 | // 保存存储库文件 367 | if err := f.WriteFile(repoFile, 0644); err != nil { 368 | return fmt.Errorf("保存 repository 配置失败: %w", err) 369 | } 370 | 371 | return nil 372 | } 373 | 374 | // ListRepositories 列出所有 Helm 仓库 375 | func (c *HelmClient) ListRepositories() ([]*HelmRepository, error) { 376 | // 获取存储库文件路径 377 | repoFile := c.settings.RepositoryConfig 378 | 379 | // 加载存储库文件 380 | f, err := repo.LoadFile(repoFile) 381 | if err != nil { 382 | if os.IsNotExist(err) { 383 | return []*HelmRepository{}, nil 384 | } 385 | return nil, fmt.Errorf("加载 repository 配置失败: %w", err) 386 | } 387 | 388 | repos := []*HelmRepository{} 389 | for _, entry := range f.Repositories { 390 | repos = append(repos, &HelmRepository{ 391 | Name: entry.Name, 392 | URL: entry.URL, 393 | }) 394 | } 395 | 396 | return repos, nil 397 | } 398 | 399 | // ParseYamlValues 解析 YAML 格式的值为 map 400 | func ParseYamlValues(valuesYaml string) (map[string]interface{}, error) { 401 | if valuesYaml == "" { 402 | return map[string]interface{}{}, nil 403 | } 404 | 405 | values := map[string]interface{}{} 406 | err := yaml.Unmarshal([]byte(valuesYaml), &values) 407 | if err != nil { 408 | return nil, fmt.Errorf("解析 YAML 值失败: %w", err) 409 | } 410 | 411 | return values, nil 412 | } 413 | 414 | // GetChartInfo 获取 chart 的详细信息 415 | func (c *HelmClient) GetChartInfo(name string, version string, repo string) (*chart.Metadata, error) { 416 | client := action.NewShowWithConfig(action.ShowChart, c.config) 417 | chartPathOptions := client.ChartPathOptions 418 | chartPathOptions.Version = version 419 | 420 | // 解析 chart 名称和仓库 421 | var chartPath string 422 | if repo != "" { 423 | // 使用指定的仓库 424 | chartPath = fmt.Sprintf("%s/%s", repo, name) 425 | } else { 426 | // 默认使用本地或远程 chart 427 | chartPath = name 428 | } 429 | 430 | // 定位 chart 431 | cp, err := chartPathOptions.LocateChart(chartPath, c.settings) 432 | if err != nil { 433 | return nil, fmt.Errorf("查找 chart '%s' 失败: %w", chartPath, err) 434 | } 435 | 436 | // 加载 chart 437 | chartRequested, err := loader.Load(cp) 438 | if err != nil { 439 | return nil, fmt.Errorf("加载 chart 失败: %w", err) 440 | } 441 | 442 | return chartRequested.Metadata, nil 443 | } 444 | 445 | // SearchCharts 搜索 Helm 仓库中的 charts 446 | func (c *HelmClient) SearchCharts(keyword string, repoName string) ([]*HelmRepository, error) { 447 | // 这里需要实现搜索逻辑 448 | // 由于 Helm v3 API 中搜索有些复杂,暂时只返回所有仓库列表 449 | return c.ListRepositories() 450 | } -------------------------------------------------------------------------------- /internal/tools/helm_tools.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | 8 | "github.com/mark3labs/mcp-go/mcp" 9 | "github.com/silenceper/mcp-k8s/internal/k8s" 10 | ) 11 | 12 | // CreateListHelmReleasesTool 创建列出所有 Helm Releases 的工具 13 | func CreateListHelmReleasesTool() mcp.Tool { 14 | return mcp.NewTool("list_helm_releases", 15 | mcp.WithDescription("列出所有已安装的 Helm charts"), 16 | mcp.WithBoolean("all_namespaces", 17 | mcp.Description("是否列出所有命名空间的 releases"), 18 | mcp.DefaultBool(false), 19 | ), 20 | ) 21 | } 22 | 23 | // CreateGetHelmReleaseTool 创建获取单个 Helm Release 的工具 24 | func CreateGetHelmReleaseTool() mcp.Tool { 25 | return mcp.NewTool("get_helm_release", 26 | mcp.WithDescription("获取指定 Helm release 的详细信息"), 27 | mcp.WithString("name", 28 | mcp.Required(), 29 | mcp.Description("Release 的名称"), 30 | ), 31 | ) 32 | } 33 | 34 | // CreateInstallHelmChartTool 创建安装 Helm Chart 的工具 35 | func CreateInstallHelmChartTool() mcp.Tool { 36 | return mcp.NewTool("install_helm_chart", 37 | mcp.WithDescription("安装 Helm chart"), 38 | mcp.WithString("name", 39 | mcp.Required(), 40 | mcp.Description("要安装的 Release 名称"), 41 | ), 42 | mcp.WithString("chart", 43 | mcp.Required(), 44 | mcp.Description("Chart 名称"), 45 | ), 46 | mcp.WithString("namespace", 47 | mcp.Description("目标命名空间(如果不指定,使用当前命名空间)"), 48 | ), 49 | mcp.WithString("version", 50 | mcp.Description("Chart 版本"), 51 | ), 52 | mcp.WithString("repo", 53 | mcp.Description("仓库名称(如果不指定,使用本地或远程 chart)"), 54 | ), 55 | mcp.WithString("values", 56 | mcp.Description("YAML 格式的值,将覆盖默认值"), 57 | ), 58 | ) 59 | } 60 | 61 | // CreateUpgradeHelmChartTool 创建升级 Helm Chart 的工具 62 | func CreateUpgradeHelmChartTool() mcp.Tool { 63 | return mcp.NewTool("upgrade_helm_chart", 64 | mcp.WithDescription("升级 Helm chart"), 65 | mcp.WithString("name", 66 | mcp.Required(), 67 | mcp.Description("要升级的 Release 名称"), 68 | ), 69 | mcp.WithString("chart", 70 | mcp.Required(), 71 | mcp.Description("Chart 名称"), 72 | ), 73 | mcp.WithString("namespace", 74 | mcp.Description("Release 所在的命名空间(如果不指定,使用当前命名空间)"), 75 | ), 76 | mcp.WithString("version", 77 | mcp.Description("Chart 版本"), 78 | ), 79 | mcp.WithString("repo", 80 | mcp.Description("仓库名称(如果不指定,使用本地或远程 chart)"), 81 | ), 82 | mcp.WithString("values", 83 | mcp.Description("YAML 格式的值,将覆盖默认值"), 84 | ), 85 | ) 86 | } 87 | 88 | // CreateUninstallHelmChartTool 创建卸载 Helm Chart 的工具 89 | func CreateUninstallHelmChartTool() mcp.Tool { 90 | return mcp.NewTool("uninstall_helm_chart", 91 | mcp.WithDescription("卸载 Helm chart"), 92 | mcp.WithString("name", 93 | mcp.Required(), 94 | mcp.Description("要卸载的 Release 名称"), 95 | ), 96 | mcp.WithString("namespace", 97 | mcp.Description("Release 所在的命名空间(如果不指定,使用当前命名空间)"), 98 | ), 99 | ) 100 | } 101 | 102 | // CreateListHelmRepositoriesTool 创建列出 Helm 仓库的工具 103 | func CreateListHelmRepositoriesTool() mcp.Tool { 104 | return mcp.NewTool("list_helm_repos", 105 | mcp.WithDescription("列出所有配置的 Helm 仓库"), 106 | ) 107 | } 108 | 109 | // CreateAddHelmRepositoryTool 创建添加 Helm 仓库的工具 110 | func CreateAddHelmRepositoryTool() mcp.Tool { 111 | return mcp.NewTool("add_helm_repo", 112 | mcp.WithDescription("添加 Helm 仓库"), 113 | mcp.WithString("name", 114 | mcp.Required(), 115 | mcp.Description("仓库名称"), 116 | ), 117 | mcp.WithString("url", 118 | mcp.Required(), 119 | mcp.Description("仓库 URL"), 120 | ), 121 | mcp.WithString("username", 122 | mcp.Description("访问仓库的用户名(如果需要)"), 123 | ), 124 | mcp.WithString("password", 125 | mcp.Description("访问仓库的密码(如果需要)"), 126 | ), 127 | ) 128 | } 129 | 130 | // CreateRemoveHelmRepositoryTool 创建移除 Helm 仓库的工具 131 | func CreateRemoveHelmRepositoryTool() mcp.Tool { 132 | return mcp.NewTool("remove_helm_repo", 133 | mcp.WithDescription("移除 Helm 仓库"), 134 | mcp.WithString("name", 135 | mcp.Required(), 136 | mcp.Description("仓库名称"), 137 | ), 138 | ) 139 | } 140 | 141 | // GetHelmClient 获取共享的 Helm 客户端 142 | func GetHelmClient(client *k8s.Client, namespace string) (*k8s.HelmClient, error) { 143 | return k8s.NewHelmClient(namespace, client.GetKubeconfigPath()) 144 | } 145 | 146 | // HandleListHelmReleases 处理列出 Helm Releases 的请求 147 | func HandleListHelmReleases(client *k8s.Client) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 148 | return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 149 | allNamespaces := false 150 | if val, ok := request.Params.Arguments["all_namespaces"].(bool); ok { 151 | allNamespaces = val 152 | } 153 | 154 | // 获取 Helm 客户端 155 | helmClient, err := GetHelmClient(client, "") 156 | if err != nil { 157 | return nil, fmt.Errorf("创建 Helm 客户端失败: %w", err) 158 | } 159 | 160 | // 列出所有 releases 161 | releases, err := helmClient.ListReleases(allNamespaces) 162 | if err != nil { 163 | return nil, err 164 | } 165 | 166 | jsonResponse, err := json.Marshal(releases) 167 | if err != nil { 168 | return nil, fmt.Errorf("序列化响应失败: %w", err) 169 | } 170 | 171 | return mcp.NewToolResultText(string(jsonResponse)), nil 172 | } 173 | } 174 | 175 | // HandleGetHelmRelease 处理获取单个 Helm Release 的请求 176 | func HandleGetHelmRelease(client *k8s.Client) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 177 | return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 178 | name, ok := request.Params.Arguments["name"].(string) 179 | if !ok || name == "" { 180 | return nil, fmt.Errorf("缺少必需的参数: name") 181 | } 182 | 183 | // 获取 Helm 客户端 184 | helmClient, err := GetHelmClient(client, "") 185 | if err != nil { 186 | return nil, fmt.Errorf("创建 Helm 客户端失败: %w", err) 187 | } 188 | 189 | // 获取 release 详情 190 | release, err := helmClient.GetRelease(name) 191 | if err != nil { 192 | return nil, err 193 | } 194 | 195 | jsonResponse, err := json.Marshal(release) 196 | if err != nil { 197 | return nil, fmt.Errorf("序列化响应失败: %w", err) 198 | } 199 | 200 | return mcp.NewToolResultText(string(jsonResponse)), nil 201 | } 202 | } 203 | 204 | // HandleInstallHelmChart 处理安装 Helm Chart 的请求 205 | func HandleInstallHelmChart(client *k8s.Client) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 206 | return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 207 | name, ok := request.Params.Arguments["name"].(string) 208 | if !ok || name == "" { 209 | return nil, fmt.Errorf("缺少必需的参数: name") 210 | } 211 | 212 | chart, ok := request.Params.Arguments["chart"].(string) 213 | if !ok || chart == "" { 214 | return nil, fmt.Errorf("缺少必需的参数: chart") 215 | } 216 | 217 | namespace, _ := request.Params.Arguments["namespace"].(string) 218 | version, _ := request.Params.Arguments["version"].(string) 219 | repo, _ := request.Params.Arguments["repo"].(string) 220 | valuesYaml, _ := request.Params.Arguments["values"].(string) 221 | 222 | // 解析 YAML 格式的值 223 | values, err := k8s.ParseYamlValues(valuesYaml) 224 | if err != nil { 225 | return nil, err 226 | } 227 | 228 | // 获取 Helm 客户端 229 | helmClient, err := GetHelmClient(client, namespace) 230 | if err != nil { 231 | return nil, fmt.Errorf("创建 Helm 客户端失败: %w", err) 232 | } 233 | 234 | // 安装 chart 235 | release, err := helmClient.InstallChart(name, chart, values, version, repo) 236 | if err != nil { 237 | return nil, err 238 | } 239 | 240 | jsonResponse, err := json.Marshal(release) 241 | if err != nil { 242 | return nil, fmt.Errorf("序列化响应失败: %w", err) 243 | } 244 | 245 | return mcp.NewToolResultText(string(jsonResponse)), nil 246 | } 247 | } 248 | 249 | // HandleUpgradeHelmChart 处理升级 Helm Chart 的请求 250 | func HandleUpgradeHelmChart(client *k8s.Client) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 251 | return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 252 | name, ok := request.Params.Arguments["name"].(string) 253 | if !ok || name == "" { 254 | return nil, fmt.Errorf("缺少必需的参数: name") 255 | } 256 | 257 | chart, ok := request.Params.Arguments["chart"].(string) 258 | if !ok || chart == "" { 259 | return nil, fmt.Errorf("缺少必需的参数: chart") 260 | } 261 | 262 | namespace, _ := request.Params.Arguments["namespace"].(string) 263 | version, _ := request.Params.Arguments["version"].(string) 264 | repo, _ := request.Params.Arguments["repo"].(string) 265 | valuesYaml, _ := request.Params.Arguments["values"].(string) 266 | 267 | // 解析 YAML 格式的值 268 | values, err := k8s.ParseYamlValues(valuesYaml) 269 | if err != nil { 270 | return nil, err 271 | } 272 | 273 | // 获取 Helm 客户端 274 | helmClient, err := GetHelmClient(client, namespace) 275 | if err != nil { 276 | return nil, fmt.Errorf("创建 Helm 客户端失败: %w", err) 277 | } 278 | 279 | // 升级 chart 280 | release, err := helmClient.UpgradeChart(name, chart, values, version, repo) 281 | if err != nil { 282 | return nil, err 283 | } 284 | 285 | jsonResponse, err := json.Marshal(release) 286 | if err != nil { 287 | return nil, fmt.Errorf("序列化响应失败: %w", err) 288 | } 289 | 290 | return mcp.NewToolResultText(string(jsonResponse)), nil 291 | } 292 | } 293 | 294 | // HandleUninstallHelmChart 处理卸载 Helm Chart 的请求 295 | func HandleUninstallHelmChart(client *k8s.Client) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 296 | return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 297 | name, ok := request.Params.Arguments["name"].(string) 298 | if !ok || name == "" { 299 | return nil, fmt.Errorf("缺少必需的参数: name") 300 | } 301 | 302 | namespace, _ := request.Params.Arguments["namespace"].(string) 303 | 304 | // 获取 Helm 客户端 305 | helmClient, err := GetHelmClient(client, namespace) 306 | if err != nil { 307 | return nil, fmt.Errorf("创建 Helm 客户端失败: %w", err) 308 | } 309 | 310 | // 卸载 chart 311 | err = helmClient.UninstallChart(name) 312 | if err != nil { 313 | return nil, err 314 | } 315 | 316 | return mcp.NewToolResultText(fmt.Sprintf("成功卸载 Helm release: %s", name)), nil 317 | } 318 | } 319 | 320 | // HandleListHelmRepositories 处理列出 Helm 仓库的请求 321 | func HandleListHelmRepositories(client *k8s.Client) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 322 | return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 323 | // 获取 Helm 客户端 324 | helmClient, err := GetHelmClient(client, "") 325 | if err != nil { 326 | return nil, fmt.Errorf("创建 Helm 客户端失败: %w", err) 327 | } 328 | 329 | // 列出所有仓库 330 | repos, err := helmClient.ListRepositories() 331 | if err != nil { 332 | return nil, err 333 | } 334 | 335 | jsonResponse, err := json.Marshal(repos) 336 | if err != nil { 337 | return nil, fmt.Errorf("序列化响应失败: %w", err) 338 | } 339 | 340 | return mcp.NewToolResultText(string(jsonResponse)), nil 341 | } 342 | } 343 | 344 | // HandleAddHelmRepository 处理添加 Helm 仓库的请求 345 | func HandleAddHelmRepository(client *k8s.Client) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 346 | return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 347 | name, ok := request.Params.Arguments["name"].(string) 348 | if !ok || name == "" { 349 | return nil, fmt.Errorf("缺少必需的参数: name") 350 | } 351 | 352 | url, ok := request.Params.Arguments["url"].(string) 353 | if !ok || url == "" { 354 | return nil, fmt.Errorf("缺少必需的参数: url") 355 | } 356 | 357 | username, _ := request.Params.Arguments["username"].(string) 358 | password, _ := request.Params.Arguments["password"].(string) 359 | 360 | // 获取 Helm 客户端 361 | helmClient, err := GetHelmClient(client, "") 362 | if err != nil { 363 | return nil, fmt.Errorf("创建 Helm 客户端失败: %w", err) 364 | } 365 | 366 | // 添加仓库 367 | repo := &k8s.HelmRepository{ 368 | Name: name, 369 | URL: url, 370 | Username: username, 371 | Password: password, 372 | } 373 | 374 | err = helmClient.AddRepository(repo) 375 | if err != nil { 376 | return nil, err 377 | } 378 | 379 | return mcp.NewToolResultText(fmt.Sprintf("成功添加 Helm 仓库: %s", name)), nil 380 | } 381 | } 382 | 383 | // HandleRemoveHelmRepository 处理移除 Helm 仓库的请求 384 | func HandleRemoveHelmRepository(client *k8s.Client) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 385 | return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 386 | name, ok := request.Params.Arguments["name"].(string) 387 | if !ok || name == "" { 388 | return nil, fmt.Errorf("缺少必需的参数: name") 389 | } 390 | 391 | // 获取 Helm 客户端 392 | helmClient, err := GetHelmClient(client, "") 393 | if err != nil { 394 | return nil, fmt.Errorf("创建 Helm 客户端失败: %w", err) 395 | } 396 | 397 | // 移除仓库 398 | err = helmClient.RemoveRepository(name) 399 | if err != nil { 400 | return nil, err 401 | } 402 | 403 | return mcp.NewToolResultText(fmt.Sprintf("成功移除 Helm 仓库: %s", name)), nil 404 | } 405 | } -------------------------------------------------------------------------------- /internal/tools/tools.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | 8 | "github.com/mark3labs/mcp-go/mcp" 9 | "github.com/silenceper/mcp-k8s/internal/k8s" 10 | ) 11 | 12 | // CreateGetAPIResourcesTool creates a tool for getting API resources 13 | func CreateGetAPIResourcesTool() mcp.Tool { 14 | return mcp.NewTool("get_api_resources", 15 | mcp.WithDescription("Get all supported API resource types in the cluster, including built-in resources and CRDs"), 16 | mcp.WithBoolean("includeNamespaceScoped", 17 | mcp.Description("Include namespace-scoped resources"), 18 | mcp.DefaultBool(true), 19 | ), 20 | mcp.WithBoolean("includeClusterScoped", 21 | mcp.Description("Include cluster-scoped resources"), 22 | mcp.DefaultBool(true), 23 | ), 24 | ) 25 | } 26 | 27 | // CreateGetResourceTool creates a tool for getting a specific resource 28 | func CreateGetResourceTool() mcp.Tool { 29 | return mcp.NewTool("get_resource", 30 | mcp.WithDescription("Get detailed information about a specific resource"), 31 | mcp.WithString("kind", 32 | mcp.Required(), 33 | mcp.Description("Resource type"), 34 | ), 35 | mcp.WithString("name", 36 | mcp.Required(), 37 | mcp.Description("Resource name"), 38 | ), 39 | mcp.WithString("namespace", 40 | mcp.Description("Namespace (required for namespace-scoped resources)"), 41 | ), 42 | ) 43 | } 44 | 45 | // CreateListResourcesTool creates a tool for listing resources 46 | func CreateListResourcesTool() mcp.Tool { 47 | return mcp.NewTool("list_resources", 48 | mcp.WithDescription("List all instances of a resource type"), 49 | mcp.WithString("kind", 50 | mcp.Required(), 51 | mcp.Description("Resource type"), 52 | ), 53 | mcp.WithString("namespace", 54 | mcp.Description("Namespace (only list resources in this namespace)"), 55 | ), 56 | mcp.WithString("labelSelector", 57 | mcp.Description("Label selector (format: key1=value1,key2=value2)"), 58 | ), 59 | mcp.WithString("fieldSelector", 60 | mcp.Description("Field selector (format: key1=value1,key2=value2)"), 61 | ), 62 | ) 63 | } 64 | 65 | // CreateCreateResourceTool creates a tool for creating resources 66 | func CreateCreateResourceTool() mcp.Tool { 67 | return mcp.NewTool("create_resource", 68 | mcp.WithDescription("Create a new resource"), 69 | mcp.WithString("kind", 70 | mcp.Required(), 71 | mcp.Description("Resource type"), 72 | ), 73 | mcp.WithString("namespace", 74 | mcp.Description("Namespace (required for namespace-scoped resources)"), 75 | ), 76 | mcp.WithString("manifest", 77 | mcp.Required(), 78 | mcp.Description("Resource manifest (Only JSON is supported)"), 79 | ), 80 | ) 81 | } 82 | 83 | // CreateUpdateResourceTool creates a tool for updating resources 84 | func CreateUpdateResourceTool() mcp.Tool { 85 | return mcp.NewTool("update_resource", 86 | mcp.WithDescription("Update an existing resource"), 87 | mcp.WithString("kind", 88 | mcp.Required(), 89 | mcp.Description("Resource type"), 90 | ), 91 | mcp.WithString("name", 92 | mcp.Required(), 93 | mcp.Description("Resource name"), 94 | ), 95 | mcp.WithString("namespace", 96 | mcp.Description("Namespace (required for namespace-scoped resources)"), 97 | ), 98 | mcp.WithString("manifest", 99 | mcp.Required(), 100 | mcp.Description("Resource manifest (Only JSON is supported)"), 101 | ), 102 | ) 103 | } 104 | 105 | // CreateDeleteResourceTool creates a tool for deleting resources 106 | func CreateDeleteResourceTool() mcp.Tool { 107 | return mcp.NewTool("delete_resource", 108 | mcp.WithDescription("Delete a resource"), 109 | mcp.WithString("kind", 110 | mcp.Required(), 111 | mcp.Description("Resource type"), 112 | ), 113 | mcp.WithString("name", 114 | mcp.Required(), 115 | mcp.Description("Resource name"), 116 | ), 117 | mcp.WithString("namespace", 118 | mcp.Description("Namespace (required for namespace-scoped resources)"), 119 | ), 120 | ) 121 | } 122 | 123 | // HandleGetAPIResources handles the get API resources tool 124 | func HandleGetAPIResources(client *k8s.Client) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 125 | return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 126 | includeNamespaceScoped := true 127 | includeClusterScoped := true 128 | 129 | if val, ok := request.Params.Arguments["includeNamespaceScoped"].(bool); ok { 130 | includeNamespaceScoped = val 131 | } 132 | 133 | if val, ok := request.Params.Arguments["includeClusterScoped"].(bool); ok { 134 | includeClusterScoped = val 135 | } 136 | 137 | resources, err := client.GetAPIResources(ctx, includeNamespaceScoped, includeClusterScoped) 138 | if err != nil { 139 | return nil, err 140 | } 141 | 142 | jsonResponse, err := json.Marshal(resources) 143 | if err != nil { 144 | return nil, fmt.Errorf("failed to serialize response: %w", err) 145 | } 146 | 147 | return mcp.NewToolResultText(string(jsonResponse)), nil 148 | } 149 | } 150 | 151 | // HandleGetResource handles the get resource tool 152 | func HandleGetResource(client *k8s.Client) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 153 | return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 154 | kind, ok := request.Params.Arguments["kind"].(string) 155 | if !ok || kind == "" { 156 | return nil, fmt.Errorf("missing required parameter: kind") 157 | } 158 | 159 | name, ok := request.Params.Arguments["name"].(string) 160 | if !ok || name == "" { 161 | return nil, fmt.Errorf("missing required parameter: name") 162 | } 163 | 164 | namespace, _ := request.Params.Arguments["namespace"].(string) 165 | 166 | resource, err := client.GetResource(ctx, kind, name, namespace) 167 | if err != nil { 168 | return nil, err 169 | } 170 | 171 | jsonResponse, err := json.Marshal(resource) 172 | if err != nil { 173 | return nil, fmt.Errorf("failed to serialize response: %w", err) 174 | } 175 | 176 | return mcp.NewToolResultText(string(jsonResponse)), nil 177 | } 178 | } 179 | 180 | // HandleListResources handles the list resources tool 181 | func HandleListResources(client *k8s.Client) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 182 | return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 183 | kind, ok := request.Params.Arguments["kind"].(string) 184 | if !ok || kind == "" { 185 | return nil, fmt.Errorf("missing required parameter: kind") 186 | } 187 | 188 | namespace, _ := request.Params.Arguments["namespace"].(string) 189 | labelSelector, _ := request.Params.Arguments["labelSelector"].(string) 190 | fieldSelector, _ := request.Params.Arguments["fieldSelector"].(string) 191 | 192 | resources, err := client.ListResources(ctx, kind, namespace, labelSelector, fieldSelector) 193 | if err != nil { 194 | return nil, err 195 | } 196 | 197 | jsonResponse, err := json.Marshal(resources) 198 | if err != nil { 199 | return nil, fmt.Errorf("failed to serialize response: %w", err) 200 | } 201 | 202 | return mcp.NewToolResultText(string(jsonResponse)), nil 203 | } 204 | } 205 | 206 | // HandleCreateResource handles the create resource tool 207 | func HandleCreateResource(client *k8s.Client) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 208 | return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 209 | kind, ok := request.Params.Arguments["kind"].(string) 210 | if !ok || kind == "" { 211 | return nil, fmt.Errorf("missing required parameter: kind") 212 | } 213 | 214 | manifest, ok := request.Params.Arguments["manifest"].(string) 215 | if !ok || manifest == "" { 216 | return nil, fmt.Errorf("missing required parameter: manifest") 217 | } 218 | 219 | namespace, _ := request.Params.Arguments["namespace"].(string) 220 | 221 | resource, err := client.CreateResource(ctx, kind, namespace, manifest) 222 | if err != nil { 223 | return nil, err 224 | } 225 | 226 | jsonResponse, err := json.Marshal(resource) 227 | if err != nil { 228 | return nil, fmt.Errorf("failed to serialize response: %w", err) 229 | } 230 | 231 | return mcp.NewToolResultText(string(jsonResponse)), nil 232 | } 233 | } 234 | 235 | // HandleUpdateResource handles the update resource tool 236 | func HandleUpdateResource(client *k8s.Client) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 237 | return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 238 | kind, ok := request.Params.Arguments["kind"].(string) 239 | if !ok || kind == "" { 240 | return nil, fmt.Errorf("missing required parameter: kind") 241 | } 242 | 243 | name, ok := request.Params.Arguments["name"].(string) 244 | if !ok || name == "" { 245 | return nil, fmt.Errorf("missing required parameter: name") 246 | } 247 | 248 | manifest, ok := request.Params.Arguments["manifest"].(string) 249 | if !ok || manifest == "" { 250 | return nil, fmt.Errorf("missing required parameter: manifest") 251 | } 252 | 253 | namespace, _ := request.Params.Arguments["namespace"].(string) 254 | 255 | resource, err := client.UpdateResource(ctx, kind, name, namespace, manifest) 256 | if err != nil { 257 | return nil, err 258 | } 259 | 260 | jsonResponse, err := json.Marshal(resource) 261 | if err != nil { 262 | return nil, fmt.Errorf("failed to serialize response: %w", err) 263 | } 264 | 265 | return mcp.NewToolResultText(string(jsonResponse)), nil 266 | } 267 | } 268 | 269 | // HandleDeleteResource handles the delete resource tool 270 | func HandleDeleteResource(client *k8s.Client) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 271 | return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 272 | kind, ok := request.Params.Arguments["kind"].(string) 273 | if !ok || kind == "" { 274 | return nil, fmt.Errorf("missing required parameter: kind") 275 | } 276 | 277 | name, ok := request.Params.Arguments["name"].(string) 278 | if !ok || name == "" { 279 | return nil, fmt.Errorf("missing required parameter: name") 280 | } 281 | 282 | namespace, _ := request.Params.Arguments["namespace"].(string) 283 | 284 | err := client.DeleteResource(ctx, kind, name, namespace) 285 | if err != nil { 286 | return nil, err 287 | } 288 | 289 | return mcp.NewToolResultText(fmt.Sprintf("Successfully deleted resource %s/%s", kind, name)), nil 290 | } 291 | } 292 | --------------------------------------------------------------------------------