├── .github └── workflows │ ├── build-image.yaml │ ├── go-cross-build.yml │ ├── test-darwin.yml │ ├── test-windows.yml │ └── test.yml ├── LICENSE ├── README.md ├── cmd └── fake-k8s │ └── main.go ├── fake-k8s.benchmark.test.sh ├── fake-k8s.mock.test.sh ├── fake-k8s.sh ├── fake-k8s.test.sh ├── go.mod ├── go.sum ├── images ├── cluster │ ├── Dockerfile │ ├── build.sh │ └── entrypoint.sh └── fake-k8s │ ├── Dockerfile │ └── build.sh ├── pkg ├── cmd │ └── fake-k8s │ │ ├── create │ │ ├── cluster │ │ │ └── cluster.go │ │ └── create.go │ │ ├── delete │ │ ├── cluster │ │ │ └── cluster.go │ │ └── delete.go │ │ ├── get │ │ ├── binaries │ │ │ └── binaries.go │ │ ├── clusters │ │ │ └── clusters.go │ │ ├── get.go │ │ ├── images │ │ │ └── images.go │ │ └── kubeconfig │ │ │ └── kubeconfig.go │ │ ├── kubectl │ │ └── kubectl.go │ │ ├── load │ │ ├── load.go │ │ └── resource │ │ │ └── resource.go │ │ ├── logs │ │ └── logs.go │ │ └── root.go ├── k8s │ ├── etcd.go │ ├── feature_gates.go │ ├── feature_gates_data.go │ ├── feature_gates_data.sh │ ├── feature_gates_data_test.go │ ├── kubeconfig.go │ ├── kubeconfig.yaml.tpl │ └── runtime_config.go ├── log │ └── logger.go ├── pki │ ├── admin.crt │ ├── admin.csr │ ├── admin.key │ ├── ca.crt │ ├── ca.key │ ├── openssl.cnf │ └── pki.go ├── resource │ └── load │ │ ├── load.go │ │ └── load_test.go ├── runtime │ ├── binary │ │ ├── cluster.go │ │ ├── init.go │ │ ├── node.tpl │ │ ├── promehteus.go │ │ └── prometheus.yaml.tpl │ ├── cluster.go │ ├── compose │ │ ├── cluster.go │ │ ├── compose.go │ │ ├── compose.yaml.tpl │ │ ├── init.go │ │ ├── prometheus.go │ │ └── prometheus.yaml.tpl │ ├── config.go │ ├── kind │ │ ├── cluster.go │ │ ├── fake-kubelet-deploy.go │ │ ├── fake-kubelet-deploy.yaml.tpl │ │ ├── init.go │ │ ├── kind.go │ │ ├── kind.yaml.tpl │ │ ├── prometheus-deploy.go │ │ └── prometheus-deploy.yaml.tpl │ ├── list.go │ └── registry.go ├── utils │ ├── cmd.go │ ├── cmd_unix.go │ ├── cmd_windows.go │ ├── download.go │ ├── filepath_unix.go │ ├── filepath_windows.go │ ├── progress_bar.go │ ├── temp.go │ └── untar.go └── vars │ └── vars.go └── supported_releases.txt /.github/workflows/build-image.yaml: -------------------------------------------------------------------------------- 1 | name: Build Image 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | 7 | jobs: 8 | build-cluster-image: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Set up QEMU 13 | uses: docker/setup-qemu-action@v2 14 | - name: Set up Docker Buildx 15 | uses: docker/setup-buildx-action@v2 16 | - name: Log into registry 17 | run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin 18 | - name: Build fake-k8s image 19 | run: | 20 | REF=${{ github.ref }} 21 | TAG="${REF##*/}" 22 | ./images/fake-k8s/build.sh "ghcr.io/wzshiming/fake-k8s/fake-k8s:${TAG}" 23 | - name: Build cluster image 24 | run: | 25 | REF=${{ github.ref }} 26 | TAG="${REF##*/}" 27 | ./images/cluster/build.sh "ghcr.io/wzshiming/fake-k8s/fake-k8s:${TAG}" "ghcr.io/wzshiming/fake-k8s/cluster" 28 | -------------------------------------------------------------------------------- /.github/workflows/go-cross-build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Set up Go 13 | uses: actions/setup-go@v2 14 | with: 15 | go-version: 1.18 16 | - name: Build Cross Platform 17 | uses: wzshiming/action-go-build-cross-plantform@v1 18 | - name: Upload Release Assets 19 | uses: wzshiming/action-upload-release-assets@v1 20 | env: 21 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | -------------------------------------------------------------------------------- /.github/workflows/test-darwin.yml: -------------------------------------------------------------------------------- 1 | name: Test Darwin 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | workflow_dispatch: 10 | 11 | jobs: 12 | build-k8s: 13 | runs-on: macos-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | with: 17 | repository: kubernetes/kubernetes 18 | ref: release-1.24 19 | - name: Set up Go 20 | uses: actions/setup-go@v2 21 | with: 22 | go-version: 1.18 23 | - name: Build k8s 24 | run: | 25 | go build -o kube-apiserver ./cmd/kube-apiserver 26 | go build -o kube-controller-manager ./cmd/kube-controller-manager 27 | go build -o kube-scheduler ./cmd/kube-scheduler 28 | - name: Upload kube binary 29 | uses: actions/upload-artifact@v2 30 | with: 31 | name: binary 32 | path: | 33 | kube-apiserver 34 | kube-controller-manager 35 | kube-scheduler 36 | 37 | build-fake-k8s: 38 | runs-on: macos-latest 39 | steps: 40 | - uses: actions/checkout@v2 41 | with: 42 | repository: wzshiming/fake-k8s 43 | - name: Set up Go 44 | uses: actions/setup-go@v2 45 | with: 46 | go-version: 1.18 47 | - name: Build fake-k8s 48 | run: | 49 | go build -o fake-k8s ./cmd/fake-k8s 50 | - name: Upload fake-k8s 51 | uses: actions/upload-artifact@v2 52 | with: 53 | name: binary 54 | path: fake-k8s 55 | 56 | e2e-test-binary: 57 | runs-on: macos-latest 58 | needs: 59 | - build-fake-k8s 60 | - build-k8s 61 | steps: 62 | - uses: actions/checkout@v2 63 | - name: Download for binary 64 | uses: actions/download-artifact@v2 65 | with: 66 | name: binary 67 | - name: Run test 68 | run: | 69 | chmod +x fake-k8s 70 | chmod +x kube-apiserver 71 | chmod +x kube-controller-manager 72 | chmod +x kube-scheduler 73 | KUBE_BINARY_PREFIX=$(pwd) RUNTIME=binary ./fake-k8s.test.sh $(cat supported_releases.txt | head -n 1) 74 | 75 | e2e-mock-test-binary: 76 | runs-on: macos-latest 77 | needs: 78 | - build-fake-k8s 79 | - build-k8s 80 | steps: 81 | - uses: actions/checkout@v2 82 | - name: Download for binary 83 | uses: actions/download-artifact@v2 84 | with: 85 | name: binary 86 | - name: Run test 87 | run: | 88 | chmod +x fake-k8s 89 | chmod +x kube-apiserver 90 | chmod +x kube-controller-manager 91 | chmod +x kube-scheduler 92 | KUBE_BINARY_PREFIX=$(pwd) RUNTIME=binary ./fake-k8s.mock.test.sh $(cat supported_releases.txt | head -n 1) 93 | -------------------------------------------------------------------------------- /.github/workflows/test-windows.yml: -------------------------------------------------------------------------------- 1 | name: Test Windows 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | workflow_dispatch: 10 | 11 | jobs: 12 | build-k8s: 13 | runs-on: windows-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | with: 17 | repository: kubernetes/kubernetes 18 | ref: release-1.24 19 | - name: Set up Go 20 | uses: actions/setup-go@v2 21 | with: 22 | go-version: 1.18 23 | - name: Build k8s 24 | shell: bash 25 | run: | 26 | go build -o kube-apiserver.exe ./cmd/kube-apiserver 27 | go build -o kube-controller-manager.exe ./cmd/kube-controller-manager 28 | go build -o kube-scheduler.exe ./cmd/kube-scheduler 29 | - name: Upload kube binary 30 | uses: actions/upload-artifact@v2 31 | with: 32 | name: binary 33 | path: | 34 | kube-apiserver.exe 35 | kube-controller-manager.exe 36 | kube-scheduler.exe 37 | 38 | build-fake-k8s: 39 | runs-on: windows-latest 40 | steps: 41 | - uses: actions/checkout@v2 42 | with: 43 | repository: wzshiming/fake-k8s 44 | - name: Set up Go 45 | uses: actions/setup-go@v2 46 | with: 47 | go-version: 1.18 48 | - name: Build fake-k8s 49 | shell: bash 50 | run: | 51 | go build -o fake-k8s.exe ./cmd/fake-k8s 52 | - name: Upload fake-k8s 53 | uses: actions/upload-artifact@v2 54 | with: 55 | name: binary 56 | path: fake-k8s.exe 57 | 58 | e2e-test-binary: 59 | runs-on: windows-latest 60 | needs: 61 | - build-fake-k8s 62 | - build-k8s 63 | steps: 64 | - uses: actions/checkout@v2 65 | - name: Download for binary 66 | uses: actions/download-artifact@v2 67 | with: 68 | name: binary 69 | - name: Link binary 70 | shell: cmd 71 | run: | 72 | mklink fake-k8s fake-k8s.exe 73 | mklink kube-apiserver kube-apiserver.exe 74 | mklink kube-controller-manager kube-controller-manager.exe 75 | mklink kube-scheduler scheduler.exe 76 | - name: Run test 77 | shell: bash 78 | run: | 79 | KUBE_BINARY_PREFIX=$(pwd) RUNTIME=binary ./fake-k8s.test.sh $(cat supported_releases.txt | head -n 1) 80 | 81 | e2e-mock-test-binary: 82 | runs-on: windows-latest 83 | needs: 84 | - build-fake-k8s 85 | - build-k8s 86 | steps: 87 | - uses: actions/checkout@v2 88 | - name: Download for binary 89 | uses: actions/download-artifact@v2 90 | with: 91 | name: binary 92 | - name: Link binary 93 | shell: cmd 94 | run: | 95 | mklink fake-k8s fake-k8s.exe 96 | mklink kube-apiserver kube-apiserver.exe 97 | mklink kube-controller-manager kube-controller-manager.exe 98 | mklink kube-scheduler scheduler.exe 99 | - name: Run test 100 | shell: bash 101 | run: | 102 | KUBE_BINARY_PREFIX=$(pwd) RUNTIME=binary ./fake-k8s.mock.test.sh $(cat supported_releases.txt | head -n 1) 103 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | workflow_dispatch: 10 | 11 | env: 12 | FAKE_KUBELET_IMAGE: fake-kubelet:test 13 | 14 | jobs: 15 | build-fake-k8s: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v2 19 | with: 20 | repository: wzshiming/fake-k8s 21 | - name: Set up Go 22 | uses: actions/setup-go@v2 23 | with: 24 | go-version: 1.18 25 | - name: Build fake-k8s 26 | run: | 27 | go build -o fake-k8s ./cmd/fake-k8s 28 | - name: Upload fake-k8s 29 | uses: actions/upload-artifact@v2 30 | with: 31 | name: binary 32 | path: fake-k8s 33 | 34 | build-fake-kubelet: 35 | runs-on: ubuntu-latest 36 | steps: 37 | - uses: actions/checkout@v2 38 | with: 39 | repository: wzshiming/fake-kubelet 40 | - name: Build image 41 | run: | 42 | docker build -t "${FAKE_KUBELET_IMAGE}" . 43 | docker save "${FAKE_KUBELET_IMAGE}" -o image.tar 44 | - name: Upload image tar 45 | uses: actions/upload-artifact@v2 46 | with: 47 | name: image 48 | path: image.tar 49 | 50 | e2e-test-kind: 51 | runs-on: ubuntu-latest 52 | needs: 53 | - build-fake-k8s 54 | - build-fake-kubelet 55 | steps: 56 | - uses: actions/checkout@v2 57 | - name: Download for image tar 58 | uses: actions/download-artifact@v2 59 | with: 60 | name: image 61 | - name: Load image tar 62 | run: | 63 | docker load -i image.tar 64 | - name: Download for binary 65 | uses: actions/download-artifact@v2 66 | with: 67 | name: binary 68 | - name: Run test 69 | run: | 70 | chmod +x fake-k8s 71 | RUNTIME=kind ./fake-k8s.test.sh $(cat supported_releases.txt | head -n -6) 72 | 73 | e2e-mock-test-kind: 74 | runs-on: ubuntu-latest 75 | needs: 76 | - build-fake-k8s 77 | - build-fake-kubelet 78 | steps: 79 | - uses: actions/checkout@v2 80 | - name: Download for image tar 81 | uses: actions/download-artifact@v2 82 | with: 83 | name: image 84 | - name: Load image tar 85 | run: | 86 | docker load -i image.tar 87 | - name: Download for binary 88 | uses: actions/download-artifact@v2 89 | with: 90 | name: binary 91 | - name: Run test 92 | run: | 93 | chmod +x fake-k8s 94 | RUNTIME=kind ./fake-k8s.mock.test.sh $(cat supported_releases.txt | head -n 1) 95 | 96 | e2e-test-binary: 97 | runs-on: ubuntu-latest 98 | needs: 99 | - build-fake-k8s 100 | steps: 101 | - uses: actions/checkout@v2 102 | - name: Download for binary 103 | uses: actions/download-artifact@v2 104 | with: 105 | name: binary 106 | - name: Run test 107 | run: | 108 | chmod +x fake-k8s 109 | RUNTIME=binary ./fake-k8s.test.sh $(cat supported_releases.txt | head -n -3) 110 | 111 | e2e-mock-test-binary: 112 | runs-on: ubuntu-latest 113 | needs: 114 | - build-fake-k8s 115 | steps: 116 | - uses: actions/checkout@v2 117 | - name: Download for binary 118 | uses: actions/download-artifact@v2 119 | with: 120 | name: binary 121 | - name: Run test 122 | run: | 123 | chmod +x fake-k8s 124 | RUNTIME=binary ./fake-k8s.mock.test.sh $(cat supported_releases.txt | head -n 1) 125 | 126 | e2e-benchmark-test-binary: 127 | runs-on: ubuntu-latest 128 | needs: 129 | - build-fake-k8s 130 | steps: 131 | - uses: actions/checkout@v2 132 | - name: Download for binary 133 | uses: actions/download-artifact@v2 134 | with: 135 | name: binary 136 | - name: Run benchmark test 137 | run: | 138 | chmod +x fake-k8s 139 | RUNTIME=binary ./fake-k8s.benchmark.test.sh $(cat supported_releases.txt | head -n 1) 140 | 141 | e2e-test-compose: 142 | runs-on: ubuntu-latest 143 | needs: 144 | - build-fake-k8s 145 | - build-fake-kubelet 146 | steps: 147 | - uses: actions/checkout@v2 148 | - name: Download for image tar 149 | uses: actions/download-artifact@v2 150 | with: 151 | name: image 152 | - name: Load image tar 153 | run: | 154 | docker load -i image.tar 155 | - name: Download for binary 156 | uses: actions/download-artifact@v2 157 | with: 158 | name: binary 159 | - name: Run test 160 | run: | 161 | chmod +x fake-k8s 162 | RUNTIME=docker ./fake-k8s.test.sh $(cat supported_releases.txt) 163 | 164 | e2e-mock-test-compose: 165 | runs-on: ubuntu-latest 166 | needs: 167 | - build-fake-k8s 168 | - build-fake-kubelet 169 | steps: 170 | - uses: actions/checkout@v2 171 | - name: Download for image tar 172 | uses: actions/download-artifact@v2 173 | with: 174 | name: image 175 | - name: Load image tar 176 | run: | 177 | docker load -i image.tar 178 | - name: Download for binary 179 | uses: actions/download-artifact@v2 180 | with: 181 | name: binary 182 | - name: Run test 183 | run: | 184 | chmod +x fake-k8s 185 | RUNTIME=docker ./fake-k8s.mock.test.sh $(cat supported_releases.txt | head -n 1) 186 | 187 | e2e-benchmark-test-compose: 188 | runs-on: ubuntu-latest 189 | needs: 190 | - build-fake-k8s 191 | - build-fake-kubelet 192 | steps: 193 | - uses: actions/checkout@v2 194 | - name: Download for image tar 195 | uses: actions/download-artifact@v2 196 | with: 197 | name: image 198 | - name: Load image tar 199 | run: | 200 | docker load -i image.tar 201 | - name: Download for binary 202 | uses: actions/download-artifact@v2 203 | with: 204 | name: binary 205 | - name: Run benchmark test 206 | run: | 207 | chmod +x fake-k8s 208 | RUNTIME=docker ./fake-k8s.benchmark.test.sh $(cat supported_releases.txt | head -n 1) 209 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 wzshiming 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fake-k8s 2 | 3 | [![Build](https://github.com/wzshiming/fake-k8s/actions/workflows/go-cross-build.yml/badge.svg)](https://github.com/wzshiming/fake-k8s/actions/workflows/go-cross-build.yml) 4 | [![GitHub license](https://img.shields.io/github/license/wzshiming/fake-k8s.svg)](https://github.com/wzshiming/fake-k8s/blob/master/LICENSE) 5 | 6 | fake-k8s is a tool for running Fake Kubernetes clusters 7 | 8 | [The old version](https://github.com/wzshiming/fake-k8s/blob/v0.1.1/fake-k8s.sh) uses shell implementation and is no longer maintained 9 | 10 | ## Usage 11 | 12 | ### Support runtime 13 | 14 | - Binary (the default of Linux) 15 | Download and start the fake cluster using the binaries, 16 | Since Kubernetes officially only provides binaries for Linux, it is only the default for Linux. 17 | For other than Linux you will need to compile Kubernetes binaries yourself. 18 | - Docker (compose v2) (the default of other than Linux) 19 | Start the fake cluster using the container. 20 | [Compose v2](https://docs.docker.com/compose/install/compose-plugin/) needs to be install on Linux 21 | - Nerdctl 22 | ditto. 23 | - Kind 24 | Start the fake cluster using the kind container. 25 | 26 | ### Cteate cluster 27 | 28 | ``` console 29 | fake-k8s create cluster --name c1 30 | fake-k8s create cluster --name c2 31 | ``` 32 | 33 | ### Simulates the specified cluster 34 | 35 | ``` console 36 | kubectl get ns,node,statefulset,daemonset,deployment,replicaset,pod -A -o yaml > mock.yaml 37 | fake-k8s create cluster --name m1 --generate-replicas 0 38 | fake-k8s load resource --name m1 -f mock.yaml 39 | ``` 40 | 41 | ### Get node of cluster 42 | 43 | ``` console 44 | kubectl --context=fake-k8s-c1 get node 45 | NAME STATUS ROLES AGE VERSION 46 | fake-0 Ready agent 1s fake 47 | fake-1 Ready agent 1s fake 48 | fake-2 Ready agent 1s fake 49 | fake-3 Ready agent 1s fake 50 | fake-4 Ready agent 1s fake 51 | ``` 52 | 53 | ### List cluster 54 | 55 | ``` console 56 | fake-k8s get clusters 57 | c1 58 | c2 59 | ``` 60 | 61 | ### Delete cluster 62 | 63 | ``` console 64 | fake-k8s delete cluster --name c1 65 | fake-k8s delete cluster --name c2 66 | ``` 67 | 68 | ## Examples 69 | 70 | ``` console 71 | $ time fake-k8s create cluster 72 | [+] Running 5/5 73 | ⠿ Container fake-k8s-default-etcd Started 1.7s 74 | ⠿ Container fake-k8s-default-kube-apiserver Started 1.7s 75 | ⠿ Container fake-k8s-default-kube-scheduler Started 1.6s 76 | ⠿ Container fake-k8s-default-kube-controller Started 1.5s 77 | ⠿ Container fake-k8s-default-fake-kubelet Started 1.7s 78 | 79 | real 0m2.587s 80 | user 0m0.418s 81 | sys 0m0.283s 82 | 83 | $ kubectl --context=fake-k8s-default get node -o wide 84 | NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME 85 | fake-0 Ready agent 2s fake 196.168.0.1 86 | fake-1 Ready agent 2s fake 196.168.0.1 87 | fake-2 Ready agent 2s fake 196.168.0.1 88 | fake-3 Ready agent 2s fake 196.168.0.1 89 | fake-4 Ready agent 2s fake 196.168.0.1 90 | 91 | $ kubectl --context=fake-k8s-default apply -f - < 116 | fake-pod-794f9d7464-2cvdk 1/1 Running 0 1s 10.0.0.19 fake-4 117 | fake-pod-794f9d7464-447d5 1/1 Running 0 1s 10.0.0.5 fake-3 118 | fake-pod-794f9d7464-5n5hv 1/1 Running 0 1s 10.0.0.13 fake-2 119 | fake-pod-794f9d7464-8zdqp 1/1 Running 0 1s 10.0.0.17 fake-0 120 | fake-pod-794f9d7464-bmt9q 1/1 Running 0 1s 10.0.0.7 fake-0 121 | fake-pod-794f9d7464-lg4x8 1/1 Running 0 1s 10.0.0.9 fake-4 122 | fake-pod-794f9d7464-ln2mg 1/1 Running 0 1s 10.0.0.3 fake-2 123 | fake-pod-794f9d7464-mz92q 1/1 Running 0 1s 10.0.0.11 fake-1 124 | fake-pod-794f9d7464-sgtdv 1/1 Running 0 1s 10.0.0.21 fake-1 125 | 126 | ``` 127 | 128 | See more: [fake-kubelet](https://github.com/wzshiming/fake-kubelet) 129 | 130 | ## License 131 | 132 | Licensed under the MIT License. See [LICENSE](https://github.com/wzshiming/fake-k8s/blob/master/LICENSE) for the full license text. 133 | -------------------------------------------------------------------------------- /cmd/fake-k8s/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | _ "github.com/wzshiming/fake-k8s/pkg/runtime/binary" 8 | _ "github.com/wzshiming/fake-k8s/pkg/runtime/compose" 9 | _ "github.com/wzshiming/fake-k8s/pkg/runtime/kind" 10 | 11 | fakek8s "github.com/wzshiming/fake-k8s/pkg/cmd/fake-k8s" 12 | ) 13 | 14 | func main() { 15 | logger := log.New(os.Stdout, "", 0) 16 | cmd := fakek8s.NewCommand(logger) 17 | err := cmd.Execute() 18 | if err != nil { 19 | os.Exit(1) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /fake-k8s.benchmark.test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | kube_version="$1" 4 | 5 | function child_timeout() { 6 | local to="${1}" 7 | shift 8 | "${@}" & 9 | local wp=$! 10 | local start=0 11 | while kill -0 "${wp}" 2>/dev/null; do 12 | if [[ "${start}" -ge "${to}" ]]; then 13 | kill "${wp}" 14 | echo "Timeout ${to}s" >&2 15 | return 1 16 | fi 17 | ((start++)) 18 | sleep 1 19 | done 20 | echo "Took ${start}s" >&2 21 | } 22 | 23 | function wait_resource() { 24 | local name="${1}" 25 | local reason="${2}" 26 | local want="${3}" 27 | local raw 28 | local got 29 | local all 30 | while true; do 31 | raw="$(./fake-k8s kubectl get --no-headers "${name}" 2>/dev/null)" 32 | got=$(echo "${raw}" | grep -c "${reason}") 33 | if [ "${got}" -eq "${want}" ]; then 34 | echo "${name} ${got} done" 35 | break 36 | else 37 | all=$(echo "${raw}" | wc -l) 38 | echo "${name} ${got}/${all} => ${want}" 39 | fi 40 | sleep 1 41 | done 42 | } 43 | 44 | function gen_pods() { 45 | local size="${1}" 46 | local node_name="${2}" 47 | for ((i = 0; i < "${size}"; i++)); do 48 | cat </dev/null 2>&1 & 71 | wait_resource Pod Running "${size}" 72 | } 73 | 74 | function test_delete_pod() { 75 | local size="${1}" 76 | ./fake-k8s kubectl delete pod -l app=fake-pod --grace-period 1 >/dev/null 2>&1 & 77 | wait_resource Pod fake-pod- "${size}" 78 | } 79 | 80 | function test_create_node() { 81 | local size="${1}" 82 | wait_resource Node Ready "${size}" 83 | } 84 | 85 | failed=() 86 | 87 | KUBE_VERSION="${kube_version}" ./fake-k8s create cluster --quiet-pull --generate-replicas 1 88 | 89 | echo "=== Test create pod ===" 90 | child_timeout 120 test_create_pod 10000 || failed+=("test_create_pod_timeout") 91 | 92 | echo "=== Test delete pod ===" 93 | child_timeout 120 test_delete_pod 0 || failed+=("test_delete_pod_timeout") 94 | 95 | ./fake-k8s delete cluster 96 | 97 | KUBE_VERSION="${kube_version}" ./fake-k8s create cluster --quiet-pull --generate-replicas 10000 98 | 99 | echo "=== Test create node ===" 100 | child_timeout 120 test_create_node 10000 || failed+=("test_create_node_timeout") 101 | 102 | ./fake-k8s delete cluster 103 | 104 | if [[ "${#failed[*]}" -eq 0 ]]; then 105 | echo "=== All tests are passed ===" 106 | else 107 | echo "=== Failed tests: ${failed[*]} ===" 108 | exit 1 109 | fi 110 | -------------------------------------------------------------------------------- /fake-k8s.mock.test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | resource="deployment,replicaset,pod" 4 | kube_version="$1" 5 | 6 | KUBE_VERSION="${kube_version}" ./fake-k8s create cluster --quiet-pull --generate-replicas 1 7 | sleep 10 8 | ./fake-k8s kubectl create -f - < tmp.yaml 31 | old_fake_k8s_content="$(./fake-k8s kubectl get "${resource}" -n default -o name)" 32 | ./fake-k8s delete cluster 33 | 34 | 35 | KUBE_VERSION="${kube_version}" ./fake-k8s create cluster --quiet-pull --generate-replicas 1 36 | sleep 10 37 | cat tmp.yaml | ./fake-k8s load resource -f - 38 | sleep 10 39 | 40 | fake_k8s_content="$(./fake-k8s kubectl get "${resource}" -n default -o name)" 41 | 42 | ./fake-k8s delete cluster 43 | 44 | echo "=== old fale-k8s content ===" 45 | echo "${old_fake_k8s_content}" 46 | 47 | echo "=== fake-k8s content ===" 48 | echo "${fake_k8s_content}" 49 | 50 | echo "=== diff ===" 51 | diff <(echo "${old_fake_k8s_content}") <(echo "${fake_k8s_content}") 52 | -------------------------------------------------------------------------------- /fake-k8s.test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Gets the release from the argument 4 | releases=() 5 | while [[ $# -gt 0 ]]; do 6 | releases+=("${1}") 7 | shift 8 | done 9 | 10 | function test_release() { 11 | local release="${1}" 12 | local name="cluster-${release//./-}" 13 | local targets 14 | local i 15 | 16 | KUBE_VERSION="${release}" ./fake-k8s create cluster --name "${name}" --quiet-pull --prometheus-port 9090 17 | 18 | for ((i = 0; i < 30; i++)); do 19 | ./fake-k8s --name "${name}" kubectl apply -f - </dev/null | grep Running >/dev/null 2>&1; then 40 | break 41 | fi 42 | sleep 1 43 | done 44 | 45 | echo kubectl --context="fake-k8s-${name}" config view --minify 46 | ./fake-k8s --name="${name}" kubectl config view --minify 47 | 48 | echo kubectl --context="fake-k8s-${name}" get pod 49 | ./fake-k8s --name="${name}" kubectl get pod 50 | 51 | if ! ./fake-k8s --name="${name}" kubectl get pod | grep Running >/dev/null 2>&1; then 52 | echo "=== release ${release} is not works ===" 53 | failed+=("${release}_not_works") 54 | 55 | ./fake-k8s --name="${name}" logs kube-apiserver 56 | fi 57 | 58 | for ((i = 0; i < 30; i++)); do 59 | targets="$(curl -s http://127.0.0.1:9090/api/v1/targets | jq -r '.data.activeTargets[] | "\(.health) \(.scrapePool)"')" 60 | if [[ "$(echo "${targets}" | grep "^up " | wc -l)" -eq "6" ]]; then 61 | break 62 | fi 63 | sleep 1 64 | done 65 | echo "${targets}" 66 | 67 | if ! [[ "$(echo "${targets}" | grep "^up " | wc -l)" -eq "6" ]]; then 68 | echo "=== prometheus of release ${release} is not works ===" 69 | failed+=("${release}_prometheus_not_works") 70 | fi 71 | 72 | ./fake-k8s delete cluster --name "${name}" >/dev/null 2>&1 & 73 | } 74 | 75 | failed=() 76 | for release in "${releases[@]}"; do 77 | time test_release "${release}" 78 | done 79 | 80 | if [ "${#failed[*]}" -eq 0 ]; then 81 | echo "=== All releases are works ===" 82 | else 83 | echo "=== Failed releases: ${failed[*]} ===" 84 | exit 1 85 | fi 86 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wzshiming/fake-k8s 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/google/go-cmp v0.5.8 7 | github.com/nxadm/tail v1.4.8 8 | github.com/spf13/cobra v1.4.0 9 | golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 10 | gopkg.in/yaml.v2 v2.4.0 11 | k8s.io/api v0.24.1 12 | k8s.io/apimachinery v0.24.1 13 | sigs.k8s.io/yaml v1.3.0 14 | ) 15 | 16 | require ( 17 | github.com/fsnotify/fsnotify v1.4.9 // indirect 18 | github.com/go-logr/logr v1.2.3 // indirect 19 | github.com/gogo/protobuf v1.3.2 // indirect 20 | github.com/google/gofuzz v1.1.0 // indirect 21 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 22 | github.com/json-iterator/go v1.1.12 // indirect 23 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 24 | github.com/modern-go/reflect2 v1.0.2 // indirect 25 | github.com/spf13/pflag v1.0.5 // indirect 26 | github.com/stretchr/testify v1.7.1 // indirect 27 | golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect 28 | golang.org/x/text v0.3.7 // indirect 29 | gopkg.in/inf.v0 v0.9.1 // indirect 30 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect 31 | k8s.io/klog/v2 v2.60.1 // indirect 32 | k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect 33 | sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect 34 | sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect 35 | ) 36 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= 4 | github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 5 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 6 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 7 | github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= 8 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 9 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 10 | github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 11 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 12 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 14 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= 16 | github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= 17 | github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= 18 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 19 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 20 | github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 21 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 22 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 23 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 24 | github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= 25 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 26 | github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= 27 | github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= 28 | github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 29 | github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= 30 | github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 31 | github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 32 | github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 33 | github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= 34 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 35 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 36 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 37 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 38 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 39 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 40 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 41 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 42 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 43 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 44 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 45 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 46 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 47 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 48 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 49 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 50 | github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= 51 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 52 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 53 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 54 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 55 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 56 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= 57 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 58 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 59 | github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= 60 | github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 61 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 62 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 63 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 64 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 65 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 66 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 67 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 68 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 69 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 70 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 71 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 72 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 73 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 74 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 75 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 76 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 77 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 78 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 79 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 80 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 81 | github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= 82 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 83 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 84 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 85 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 86 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 87 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 88 | github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 89 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= 90 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 91 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 92 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 93 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= 94 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 95 | github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 96 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 97 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 98 | github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= 99 | github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= 100 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 101 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 102 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 103 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 104 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 105 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 106 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 107 | github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 108 | github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= 109 | github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= 110 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 111 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 112 | github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= 113 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 114 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 115 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 116 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 117 | github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= 118 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 119 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 120 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 121 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 122 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 123 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 124 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 125 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 126 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 127 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 128 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 129 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 130 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 131 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 132 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 133 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 134 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 135 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 136 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 137 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 138 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 139 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 140 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 141 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 142 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 143 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 144 | golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= 145 | golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 146 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 147 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 148 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 149 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 150 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 151 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 152 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 153 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 154 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 155 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 156 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 157 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 158 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 159 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 160 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 161 | golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 162 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 163 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 164 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 165 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 166 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 167 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 168 | golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 169 | golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc= 170 | golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 171 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 172 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 173 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 174 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 175 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 176 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 177 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 178 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 179 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 180 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 181 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 182 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 183 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 184 | golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 185 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 186 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 187 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 188 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 189 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 190 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 191 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 192 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 193 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 194 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 195 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 196 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 197 | google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 198 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 199 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 200 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 201 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 202 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 203 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 204 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 205 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 206 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 207 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 208 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 209 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 210 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 211 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 212 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 213 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 214 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 215 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 216 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 217 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 218 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 219 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 220 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 221 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 222 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 223 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 224 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 225 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 226 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 227 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 228 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 229 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 230 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 231 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 232 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 233 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 234 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 235 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 236 | k8s.io/api v0.24.1 h1:BjCMRDcyEYz03joa3K1+rbshwh1Ay6oB53+iUx2H8UY= 237 | k8s.io/api v0.24.1/go.mod h1:JhoOvNiLXKTPQ60zh2g0ewpA+bnEYf5q44Flhquh4vQ= 238 | k8s.io/apimachinery v0.24.1 h1:ShD4aDxTQKN5zNf8K1RQ2u98ELLdIW7jEnlO9uAMX/I= 239 | k8s.io/apimachinery v0.24.1/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= 240 | k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= 241 | k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= 242 | k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= 243 | k8s.io/klog/v2 v2.60.1 h1:VW25q3bZx9uE3vvdL6M8ezOX79vA2Aq1nEWLqNQclHc= 244 | k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= 245 | k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= 246 | k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= 247 | k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc= 248 | k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= 249 | sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87JJaPNLh5tIzYHgStQ9Y= 250 | sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= 251 | sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= 252 | sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= 253 | sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= 254 | sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= 255 | sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= 256 | sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= 257 | -------------------------------------------------------------------------------- /images/cluster/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG base_image 2 | FROM $base_image 3 | 4 | ARG kube_version 5 | ENV KUBE_VERSION $kube_version 6 | 7 | RUN fake-k8s create cluster --quiet-pull && \ 8 | fake-k8s kubectl version && \ 9 | fake-k8s delete cluster 10 | 11 | COPY entrypoint.sh /entrypoint.sh 12 | 13 | # Used in entrypoint.sh not in fake-k8s 14 | ENV APISERVER_PORT 8080 15 | ENTRYPOINT [ "/entrypoint.sh" ] 16 | -------------------------------------------------------------------------------- /images/cluster/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | DIR="$(dirname "${BASH_SOURCE[@]}")" 4 | 5 | base_image="${1}" 6 | cluster_image_prefix="${2}" 7 | 8 | if [[ -z "${base_image}" ]] || [[ -z "${cluster_image_prefix}" ]]; then 9 | echo "Usage: ${0} &2 6 | cat <&2 22 | echo "kubectl -s :${APISERVER_PORT} get node" 23 | echo "==============================" >&2 24 | fake-k8s kubectl version 25 | echo "==============================" >&2 26 | fake-k8s kubectl proxy --port="${APISERVER_PORT}" --accept-hosts='^*$' --address="0.0.0.0" 27 | -------------------------------------------------------------------------------- /images/fake-k8s/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine AS builder 2 | WORKDIR /go/src/github.com/wzshiming/fake-k8s/ 3 | COPY . . 4 | ENV CGO_ENABLED=0 5 | RUN go install ./cmd/fake-k8s 6 | 7 | FROM alpine 8 | COPY --from=builder /go/bin/fake-k8s /usr/local/bin/ 9 | ENTRYPOINT [ "/usr/local/bin/fake-k8s" ] 10 | -------------------------------------------------------------------------------- /images/fake-k8s/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | DIR="$(dirname "${BASH_SOURCE[@]}")" 4 | 5 | image="${1}" 6 | if [[ -z "${image}" ]]; then 7 | echo "Usage: ${0} " 8 | exit 1 9 | fi 10 | 11 | docker buildx build \ 12 | --push \ 13 | --platform linux/amd64,linux/arm64 \ 14 | -t "${image}" \ 15 | -f "${DIR}/Dockerfile" \ 16 | "${DIR}/../.." 17 | -------------------------------------------------------------------------------- /pkg/cmd/fake-k8s/create/cluster/cluster.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "strings" 8 | "time" 9 | 10 | "github.com/spf13/cobra" 11 | "github.com/wzshiming/fake-k8s/pkg/log" 12 | "github.com/wzshiming/fake-k8s/pkg/runtime" 13 | "github.com/wzshiming/fake-k8s/pkg/utils" 14 | "github.com/wzshiming/fake-k8s/pkg/vars" 15 | ) 16 | 17 | type flagpole struct { 18 | Name string 19 | ApiserverPort uint32 20 | PrometheusPort uint32 21 | SecurePort bool 22 | QuietPull bool 23 | EtcdImage string 24 | KubeApiserverImage string 25 | KubeControllerManagerImage string 26 | KubeSchedulerImage string 27 | FakeKubeletImage string 28 | PrometheusImage string 29 | KindNodeImage string 30 | KubeApiserverBinary string 31 | KubeControllerManagerBinary string 32 | KubeSchedulerBinary string 33 | FakeKubeletBinary string 34 | EtcdBinaryTar string 35 | PrometheusBinaryTar string 36 | GenerateReplicas uint32 37 | GenerateNodeName string 38 | NodeName []string 39 | Runtime string 40 | FeatureGates string 41 | RuntimeConfig string 42 | } 43 | 44 | // NewCommand returns a new cobra.Command for cluster creation 45 | func NewCommand(logger log.Logger) *cobra.Command { 46 | flags := &flagpole{} 47 | cmd := &cobra.Command{ 48 | Args: cobra.NoArgs, 49 | Use: "cluster", 50 | Short: "Creates a fake Kubernetes cluster", 51 | Long: "Creates a fake Kubernetes cluster", 52 | RunE: func(cmd *cobra.Command, args []string) error { 53 | flags.Name = vars.DefaultCluster 54 | return runE(cmd.Context(), logger, flags) 55 | }, 56 | } 57 | cmd.Flags().Uint32Var(&flags.ApiserverPort, "apiserver-port", uint32(vars.ApiserverPort), "port of the apiserver, default is random") 58 | cmd.Flags().Uint32Var(&flags.PrometheusPort, "prometheus-port", uint32(vars.PrometheusPort), `port to expose Prometheus metrics`) 59 | cmd.Flags().BoolVar(&flags.SecurePort, "secure-port", vars.SecurePort, `apiserver use TLS`) 60 | cmd.Flags().BoolVar(&flags.QuietPull, "quiet-pull", vars.QuietPull, `pull without printing progress information`) 61 | cmd.Flags().StringVar(&flags.EtcdImage, "etcd-image", vars.EtcdImage, `image of etcd, only for docker/nerdctl runtime 62 | '${KUBE_IMAGE_PREFIX}/etcd:${ETCD_VERSION}' 63 | `) 64 | cmd.Flags().StringVar(&flags.KubeApiserverImage, "kube-apiserver-image", vars.KubeApiserverImage, `image of kube-apiserver, only for docker/nerdctl runtime 65 | '${KUBE_IMAGE_PREFIX}/kube-apiserver:${KUBE_VERSION}' 66 | `) 67 | cmd.Flags().StringVar(&flags.KubeControllerManagerImage, "kube-controller-manager-image", vars.KubeControllerManagerImage, `image of kube-controller-manager, only for docker/nerdctl runtime 68 | '${KUBE_IMAGE_PREFIX}/kube-controller-manager:${KUBE_VERSION}' 69 | `) 70 | cmd.Flags().StringVar(&flags.KubeSchedulerImage, "kube-scheduler-image", vars.KubeSchedulerImage, `image of kube-scheduler, only for docker/nerdctl runtime 71 | '${KUBE_IMAGE_PREFIX}/kube-scheduler:${KUBE_VERSION}' 72 | `) 73 | cmd.Flags().StringVar(&flags.FakeKubeletImage, "fake-kubelet-image", vars.FakeKubeletImage, `image of fake-kubelet, only for docker/nerdctl/kind runtime 74 | '${FAKE_IMAGE_PREFIX}/fake-kubelet:${FAKE_VERSION}' 75 | `) 76 | cmd.Flags().StringVar(&flags.PrometheusImage, "prometheus-image", vars.PrometheusImage, `image of Prometheus, only for docker/nerdctl/kind runtime 77 | '${PROMETHEUS_IMAGE_PREFIX}/prometheus:${PROMETHEUS_VERSION}' 78 | `) 79 | cmd.Flags().StringVar(&flags.KindNodeImage, "kind-node-image", vars.KindNodeImage, `image of kind node, only for kind runtime 80 | '${KIND_NODE_IMAGE_PREFIX}/node:${KUBE_VERSION}' 81 | `) 82 | cmd.Flags().StringVar(&flags.KubeApiserverBinary, "kube-apiserver-binary", vars.KubeApiserverBinary, `binary of kube-apiserver, only for binary runtime 83 | '${KUBE_BINARY_PREFIX}/kube-apiserver' 84 | `) 85 | cmd.Flags().StringVar(&flags.KubeControllerManagerBinary, "kube-controller-manager-binary", vars.KubeControllerManagerBinary, `binary of kube-controller-manager, only for binary runtime 86 | '${KUBE_BINARY_PREFIX}/kube-controller-manager' 87 | `) 88 | cmd.Flags().StringVar(&flags.KubeSchedulerBinary, "kube-scheduler-binary", vars.KubeSchedulerBinary, `binary of kube-scheduler, only for binary runtime 89 | '${KUBE_BINARY_PREFIX}/kube-scheduler' 90 | `) 91 | cmd.Flags().StringVar(&flags.FakeKubeletBinary, "fake-kubelet-binary", vars.FakeKubeletBinary, `binary of fake-kubelet, only for binary runtime 92 | `) 93 | cmd.Flags().StringVar(&flags.EtcdBinaryTar, "etcd-binary-tar", vars.EtcdBinaryTar, `tar of etcd, only for binary runtime 94 | `) 95 | cmd.Flags().StringVar(&flags.PrometheusBinaryTar, "prometheus-binary-tar", vars.PrometheusBinaryTar, `tar of Prometheus, only for binary runtime 96 | `) 97 | cmd.Flags().Uint32Var(&flags.GenerateReplicas, "generate-replicas", uint32(vars.GenerateReplicas), `replicas of the fake node`) 98 | cmd.Flags().StringVar(&flags.GenerateNodeName, "generate-node-name", vars.GenerateNodeName, `node name of the fake node`) 99 | cmd.Flags().StringArrayVar(&flags.NodeName, "node-name", vars.NodeName, `node name of the fake node`) 100 | cmd.Flags().StringVar(&flags.Runtime, "runtime", vars.Runtime, "runtime of the fake cluster ("+strings.Join(runtime.List(), " or ")+")") 101 | cmd.Flags().StringVar(&flags.FeatureGates, "feature-gates", vars.FeatureGates, "a set of key=value pairs that describe feature gates for alpha/experimental features of Kubernetes") 102 | cmd.Flags().StringVar(&flags.RuntimeConfig, "runtime-config", vars.RuntimeConfig, "a set of key=value pairs that enable or disable built-in APIs") 103 | return cmd 104 | } 105 | 106 | func runE(ctx context.Context, logger log.Logger, flags *flagpole) error { 107 | name := vars.ProjectName + "-" + flags.Name 108 | workdir := utils.PathJoin(vars.TempDir, flags.Name) 109 | 110 | newRuntime, ok := runtime.Get(flags.Runtime) 111 | if !ok { 112 | return fmt.Errorf("runtime %q not found", flags.Runtime) 113 | } 114 | 115 | dc, err := newRuntime(name, workdir, logger) 116 | if err != nil { 117 | return err 118 | } 119 | conf, err := dc.Config() 120 | if err == nil { 121 | logger.Printf("Cluster %q already exists", name) 122 | 123 | ready, err := dc.Ready(ctx) 124 | if err == nil && ready { 125 | logger.Printf("Cluster %q is already ready", name) 126 | return nil 127 | } 128 | 129 | logger.Printf("Cluster %q is not ready yet, will be restarted", name) 130 | err = dc.Install(ctx, *conf) 131 | if err != nil { 132 | logger.Printf("Failed to continue install cluster %q: %v", name, err) 133 | return err 134 | } 135 | 136 | err = dc.Down(ctx) 137 | if err != nil { 138 | logger.Printf("Failed to down cluster %q: %v", name, err) 139 | } 140 | } else { 141 | logger.Printf("Creating cluster %q", name) 142 | err = dc.Install(ctx, runtime.Config{ 143 | Name: name, 144 | ApiserverPort: flags.ApiserverPort, 145 | Workdir: workdir, 146 | Runtime: flags.Runtime, 147 | PrometheusImage: flags.PrometheusImage, 148 | EtcdImage: flags.EtcdImage, 149 | KubeApiserverImage: flags.KubeApiserverImage, 150 | KubeControllerManagerImage: flags.KubeControllerManagerImage, 151 | KubeSchedulerImage: flags.KubeSchedulerImage, 152 | FakeKubeletImage: flags.FakeKubeletImage, 153 | KindNodeImage: flags.KindNodeImage, 154 | KubeApiserverBinary: flags.KubeApiserverBinary, 155 | KubeControllerManagerBinary: flags.KubeControllerManagerBinary, 156 | KubeSchedulerBinary: flags.KubeSchedulerBinary, 157 | FakeKubeletBinary: flags.FakeKubeletBinary, 158 | EtcdBinaryTar: flags.EtcdBinaryTar, 159 | PrometheusBinaryTar: flags.PrometheusBinaryTar, 160 | CacheDir: vars.CacheDir, 161 | SecretPort: flags.SecurePort, 162 | QuietPull: flags.QuietPull, 163 | PrometheusPort: flags.PrometheusPort, 164 | GenerateNodeName: flags.GenerateNodeName, 165 | GenerateReplicas: flags.GenerateReplicas, 166 | NodeName: strings.Join(flags.NodeName, ","), 167 | FeatureGates: flags.FeatureGates, 168 | RuntimeConfig: flags.RuntimeConfig, 169 | }) 170 | if err != nil { 171 | return fmt.Errorf("failed install cluster %q: %w", name, err) 172 | } 173 | } 174 | 175 | logger.Printf("Starting cluster %q", name) 176 | err = dc.Up(ctx) 177 | if err != nil { 178 | return fmt.Errorf("failed start cluster %q: %w", name, err) 179 | } 180 | 181 | logger.Printf("Wait for cluster %q to be ready", name) 182 | err = dc.WaitReady(ctx, 30*time.Second) 183 | if err != nil { 184 | return fmt.Errorf("failed wait for cluster %q be ready: %w", name, err) 185 | } 186 | 187 | logger.Printf("Cluster %q is ready", name) 188 | 189 | fmt.Fprintf(os.Stderr, "> kubectl --context %s get node\n", name) 190 | err = dc.Kubectl(ctx, utils.IOStreams{ 191 | Out: os.Stderr, 192 | ErrOut: os.Stderr, 193 | }, "--context", name, "get", "node") 194 | if err != nil { 195 | return err 196 | } 197 | return nil 198 | } 199 | -------------------------------------------------------------------------------- /pkg/cmd/fake-k8s/create/create.go: -------------------------------------------------------------------------------- 1 | package create 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | "github.com/wzshiming/fake-k8s/pkg/cmd/fake-k8s/create/cluster" 8 | "github.com/wzshiming/fake-k8s/pkg/log" 9 | ) 10 | 11 | // NewCommand returns a new cobra.Command for cluster creation 12 | func NewCommand(logger log.Logger) *cobra.Command { 13 | cmd := &cobra.Command{ 14 | Args: cobra.NoArgs, 15 | Use: "create", 16 | Short: "Creates one of [cluster]", 17 | Long: "Creates one of [cluster]", 18 | RunE: func(cmd *cobra.Command, args []string) error { 19 | return fmt.Errorf("subcommand is required") 20 | }, 21 | } 22 | cmd.AddCommand(cluster.NewCommand(logger)) 23 | return cmd 24 | } 25 | -------------------------------------------------------------------------------- /pkg/cmd/fake-k8s/delete/cluster/cluster.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/spf13/cobra" 7 | "github.com/wzshiming/fake-k8s/pkg/log" 8 | "github.com/wzshiming/fake-k8s/pkg/runtime" 9 | "github.com/wzshiming/fake-k8s/pkg/utils" 10 | "github.com/wzshiming/fake-k8s/pkg/vars" 11 | ) 12 | 13 | type flagpole struct { 14 | Name string 15 | } 16 | 17 | // NewCommand returns a new cobra.Command for cluster creation 18 | func NewCommand(logger log.Logger) *cobra.Command { 19 | flags := &flagpole{} 20 | cmd := &cobra.Command{ 21 | Args: cobra.NoArgs, 22 | Use: "cluster", 23 | Short: "Deletes a cluster", 24 | Long: "Deletes a cluster", 25 | RunE: func(cmd *cobra.Command, args []string) error { 26 | flags.Name = vars.DefaultCluster 27 | return runE(cmd.Context(), logger, flags) 28 | }, 29 | } 30 | return cmd 31 | } 32 | 33 | func runE(ctx context.Context, logger log.Logger, flags *flagpole) error { 34 | name := vars.ProjectName + "-" + flags.Name 35 | workdir := utils.PathJoin(vars.TempDir, flags.Name) 36 | 37 | dc, err := runtime.Load(name, workdir, logger) 38 | if err != nil { 39 | return err 40 | } 41 | logger.Printf("Stopping cluster %q", name) 42 | err = dc.Down(ctx) 43 | if err != nil { 44 | logger.Printf("Error stopping cluster %q: %v", name, err) 45 | } 46 | 47 | logger.Printf("Deleting cluster %q", name) 48 | err = dc.Uninstall(ctx) 49 | if err != nil { 50 | return err 51 | } 52 | logger.Printf("Cluster %q deleted", name) 53 | return nil 54 | } 55 | -------------------------------------------------------------------------------- /pkg/cmd/fake-k8s/delete/delete.go: -------------------------------------------------------------------------------- 1 | package delete 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | "github.com/wzshiming/fake-k8s/pkg/cmd/fake-k8s/delete/cluster" 8 | "github.com/wzshiming/fake-k8s/pkg/log" 9 | ) 10 | 11 | // NewCommand returns a new cobra.Command for cluster creation 12 | func NewCommand(logger log.Logger) *cobra.Command { 13 | cmd := &cobra.Command{ 14 | Args: cobra.NoArgs, 15 | Use: "delete", 16 | Short: "Deletes one of [cluster]", 17 | Long: "Deletes one of [cluster]", 18 | RunE: func(cmd *cobra.Command, args []string) error { 19 | return fmt.Errorf("subcommand is required") 20 | }, 21 | } 22 | cmd.AddCommand(cluster.NewCommand(logger)) 23 | return cmd 24 | } 25 | -------------------------------------------------------------------------------- /pkg/cmd/fake-k8s/get/binaries/binaries.go: -------------------------------------------------------------------------------- 1 | package binaries 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/spf13/cobra" 8 | "github.com/wzshiming/fake-k8s/pkg/log" 9 | "github.com/wzshiming/fake-k8s/pkg/runtime" 10 | "github.com/wzshiming/fake-k8s/pkg/vars" 11 | ) 12 | 13 | type flagpole struct { 14 | Runtime string 15 | } 16 | 17 | // NewCommand returns a new cobra.Command for getting the list of clusters 18 | func NewCommand(logger log.Logger) *cobra.Command { 19 | flags := &flagpole{} 20 | cmd := &cobra.Command{ 21 | Args: cobra.NoArgs, 22 | Use: "binaries", 23 | Short: "Lists binaries used by fake cluster, only for binary runtime", 24 | Long: "Lists binaries used by fake cluster, only for binary runtime", 25 | RunE: func(cmd *cobra.Command, args []string) error { 26 | return runE(logger, flags) 27 | }, 28 | } 29 | cmd.Flags().StringVar(&flags.Runtime, "runtime", vars.Runtime, "runtime of the fake cluster ("+strings.Join(runtime.List(), " or ")+")") 30 | return cmd 31 | } 32 | 33 | func runE(logger log.Logger, flags *flagpole) error { 34 | var images []string 35 | var err error 36 | switch flags.Runtime { 37 | case "docker", "nerdctl", "kind": 38 | logger.Printf("no binaries need to be installed for %s", flags.Runtime) 39 | return nil 40 | case "binary": 41 | images, err = runtime.ListBinaries() 42 | default: 43 | return fmt.Errorf("unknown runtime: %s", flags.Runtime) 44 | } 45 | if err != nil { 46 | return err 47 | } 48 | 49 | for _, image := range images { 50 | fmt.Println(image) 51 | } 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /pkg/cmd/fake-k8s/get/clusters/clusters.go: -------------------------------------------------------------------------------- 1 | package clusters 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | "github.com/wzshiming/fake-k8s/pkg/log" 8 | "github.com/wzshiming/fake-k8s/pkg/runtime" 9 | "github.com/wzshiming/fake-k8s/pkg/vars" 10 | ) 11 | 12 | // NewCommand returns a new cobra.Command for getting the list of clusters 13 | func NewCommand(logger log.Logger) *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Args: cobra.NoArgs, 16 | Use: "clusters", 17 | Short: "Lists existing fake clusters by their name", 18 | Long: "Lists existing fake clusters by their name", 19 | RunE: func(cmd *cobra.Command, args []string) error { 20 | return runE(logger) 21 | }, 22 | } 23 | return cmd 24 | } 25 | 26 | func runE(logger log.Logger) error { 27 | clusters, err := runtime.ListClusters(vars.TempDir) 28 | if err != nil { 29 | return err 30 | } 31 | if len(clusters) == 0 { 32 | logger.Printf("no clusters found") 33 | } else { 34 | for _, cluster := range clusters { 35 | fmt.Println(cluster) 36 | } 37 | } 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /pkg/cmd/fake-k8s/get/get.go: -------------------------------------------------------------------------------- 1 | package get 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | "github.com/wzshiming/fake-k8s/pkg/cmd/fake-k8s/get/binaries" 8 | "github.com/wzshiming/fake-k8s/pkg/cmd/fake-k8s/get/clusters" 9 | "github.com/wzshiming/fake-k8s/pkg/cmd/fake-k8s/get/images" 10 | "github.com/wzshiming/fake-k8s/pkg/cmd/fake-k8s/get/kubeconfig" 11 | "github.com/wzshiming/fake-k8s/pkg/log" 12 | ) 13 | 14 | // NewCommand returns a new cobra.Command for get 15 | func NewCommand(logger log.Logger) *cobra.Command { 16 | cmd := &cobra.Command{ 17 | Args: cobra.NoArgs, 18 | Use: "get", 19 | Short: "Gets one of [clusters, images, binaries, kubeconfig]", 20 | Long: "Gets one of [clusters, images, binaries, kubeconfig]", 21 | RunE: func(cmd *cobra.Command, args []string) error { 22 | return fmt.Errorf("subcommand is required") 23 | }, 24 | } 25 | // add subcommands 26 | cmd.AddCommand(clusters.NewCommand(logger)) 27 | cmd.AddCommand(images.NewCommand(logger)) 28 | cmd.AddCommand(binaries.NewCommand(logger)) 29 | cmd.AddCommand(kubeconfig.NewCommand(logger)) 30 | return cmd 31 | } 32 | -------------------------------------------------------------------------------- /pkg/cmd/fake-k8s/get/images/images.go: -------------------------------------------------------------------------------- 1 | package images 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/spf13/cobra" 8 | "github.com/wzshiming/fake-k8s/pkg/log" 9 | "github.com/wzshiming/fake-k8s/pkg/runtime" 10 | "github.com/wzshiming/fake-k8s/pkg/vars" 11 | ) 12 | 13 | type flagpole struct { 14 | Runtime string 15 | } 16 | 17 | // NewCommand returns a new cobra.Command for getting the list of clusters 18 | func NewCommand(logger log.Logger) *cobra.Command { 19 | flags := &flagpole{} 20 | cmd := &cobra.Command{ 21 | Args: cobra.NoArgs, 22 | Use: "images", 23 | Short: "Lists images used by fake cluster, only for docker/nerdctl/kind runtime", 24 | Long: "Lists images used by fake cluster, only for docker/nerdctl/kind runtime", 25 | RunE: func(cmd *cobra.Command, args []string) error { 26 | return runE(logger, flags) 27 | }, 28 | } 29 | cmd.Flags().StringVar(&flags.Runtime, "runtime", vars.Runtime, "runtime of the fake cluster ("+strings.Join(runtime.List(), " or ")+")") 30 | return cmd 31 | } 32 | 33 | func runE(logger log.Logger, flags *flagpole) error { 34 | var images []string 35 | var err error 36 | switch flags.Runtime { 37 | case "docker", "nerdctl": 38 | images, err = runtime.ListImagesCompose() 39 | case "kind": 40 | images, err = runtime.ListImagesKind() 41 | case "binary": 42 | logger.Printf("no images need to be pull for %s", flags.Runtime) 43 | return nil 44 | default: 45 | return fmt.Errorf("unknown runtime: %s", flags.Runtime) 46 | } 47 | if err != nil { 48 | return err 49 | } 50 | 51 | for _, image := range images { 52 | fmt.Println(image) 53 | } 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /pkg/cmd/fake-k8s/get/kubeconfig/kubeconfig.go: -------------------------------------------------------------------------------- 1 | package kubeconfig 2 | 3 | import ( 4 | "context" 5 | "os" 6 | 7 | "github.com/spf13/cobra" 8 | "github.com/wzshiming/fake-k8s/pkg/log" 9 | "github.com/wzshiming/fake-k8s/pkg/runtime" 10 | "github.com/wzshiming/fake-k8s/pkg/utils" 11 | "github.com/wzshiming/fake-k8s/pkg/vars" 12 | ) 13 | 14 | type flagpole struct { 15 | Name string 16 | } 17 | 18 | // NewCommand returns a new cobra.Command for getting the list of clusters 19 | func NewCommand(logger log.Logger) *cobra.Command { 20 | flags := &flagpole{} 21 | cmd := &cobra.Command{ 22 | Args: cobra.NoArgs, 23 | Use: "kubeconfig", 24 | Short: "Prints cluster kubeconfig", 25 | Long: "Prints cluster kubeconfig", 26 | RunE: func(cmd *cobra.Command, args []string) error { 27 | flags.Name = vars.DefaultCluster 28 | return runE(cmd.Context(), logger, flags) 29 | }, 30 | } 31 | return cmd 32 | } 33 | 34 | func runE(ctx context.Context, logger log.Logger, flags *flagpole) error { 35 | name := vars.ProjectName + "-" + flags.Name 36 | workdir := utils.PathJoin(vars.TempDir, flags.Name) 37 | 38 | dc, err := runtime.Load(name, workdir, logger) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | err = dc.KubectlInCluster(ctx, utils.IOStreams{ 44 | Out: os.Stdout, 45 | ErrOut: os.Stderr, 46 | }, "config", "view", "--minify", "--flatten", "--raw") 47 | 48 | if err != nil { 49 | return err 50 | } 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /pkg/cmd/fake-k8s/kubectl/kubectl.go: -------------------------------------------------------------------------------- 1 | package kubectl 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/spf13/cobra" 9 | "github.com/wzshiming/fake-k8s/pkg/log" 10 | "github.com/wzshiming/fake-k8s/pkg/runtime" 11 | "github.com/wzshiming/fake-k8s/pkg/utils" 12 | "github.com/wzshiming/fake-k8s/pkg/vars" 13 | ) 14 | 15 | type flagpole struct { 16 | Name string 17 | } 18 | 19 | // NewCommand returns a new cobra.Command for getting the list of clusters 20 | func NewCommand(logger log.Logger) *cobra.Command { 21 | flags := &flagpole{} 22 | cmd := &cobra.Command{ 23 | Use: "kubectl", 24 | Short: "kubectl in cluster", 25 | Long: "kubectl in cluster", 26 | RunE: func(cmd *cobra.Command, args []string) error { 27 | flags.Name = vars.DefaultCluster 28 | err := runE(cmd.Context(), logger, flags, args) 29 | if err != nil { 30 | return fmt.Errorf("%v: %w", args, err) 31 | } 32 | return nil 33 | }, 34 | } 35 | cmd.DisableFlagParsing = true 36 | return cmd 37 | } 38 | 39 | func runE(ctx context.Context, logger log.Logger, flags *flagpole, args []string) error { 40 | name := vars.ProjectName + "-" + flags.Name 41 | workdir := utils.PathJoin(vars.TempDir, flags.Name) 42 | 43 | dc, err := runtime.Load(name, workdir, logger) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | err = dc.KubectlInCluster(ctx, utils.IOStreams{ 49 | In: os.Stdin, 50 | Out: os.Stdout, 51 | ErrOut: os.Stderr, 52 | }, args...) 53 | 54 | if err != nil { 55 | return err 56 | } 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /pkg/cmd/fake-k8s/load/load.go: -------------------------------------------------------------------------------- 1 | package load 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | "github.com/wzshiming/fake-k8s/pkg/cmd/fake-k8s/load/resource" 8 | "github.com/wzshiming/fake-k8s/pkg/log" 9 | ) 10 | 11 | // NewCommand returns a new cobra.Command for load 12 | func NewCommand(logger log.Logger) *cobra.Command { 13 | cmd := &cobra.Command{ 14 | Args: cobra.NoArgs, 15 | Use: "load", 16 | Short: "Loads one of [resource]", 17 | Long: "Loads one of [resource]", 18 | RunE: func(cmd *cobra.Command, args []string) error { 19 | return fmt.Errorf("subcommand is required") 20 | }, 21 | } 22 | cmd.AddCommand(resource.NewCommand(logger)) 23 | return cmd 24 | } 25 | -------------------------------------------------------------------------------- /pkg/cmd/fake-k8s/load/resource/resource.go: -------------------------------------------------------------------------------- 1 | package resource 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/spf13/cobra" 7 | "github.com/wzshiming/fake-k8s/pkg/log" 8 | "github.com/wzshiming/fake-k8s/pkg/resource/load" 9 | "github.com/wzshiming/fake-k8s/pkg/runtime" 10 | "github.com/wzshiming/fake-k8s/pkg/utils" 11 | "github.com/wzshiming/fake-k8s/pkg/vars" 12 | ) 13 | 14 | type flagpole struct { 15 | Name string 16 | File string 17 | } 18 | 19 | // NewCommand returns a new cobra.Command for cluster creation 20 | func NewCommand(logger log.Logger) *cobra.Command { 21 | flags := &flagpole{} 22 | cmd := &cobra.Command{ 23 | Args: cobra.NoArgs, 24 | Use: "resource", 25 | Short: "Loads a resource", 26 | Long: "Loads a resource", 27 | RunE: func(cmd *cobra.Command, args []string) error { 28 | flags.Name = vars.DefaultCluster 29 | return runE(cmd.Context(), logger, flags) 30 | }, 31 | } 32 | cmd.Flags().StringVarP(&flags.File, "file", "f", "", "resource file") 33 | return cmd 34 | } 35 | 36 | func runE(ctx context.Context, logger log.Logger, flags *flagpole) error { 37 | controllerName := "kube-controller-manager" 38 | name := vars.ProjectName + "-" + flags.Name 39 | workdir := utils.PathJoin(vars.TempDir, flags.Name) 40 | 41 | dc, err := runtime.Load(name, workdir, logger) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | logger.Printf("Stopping controller %q on %q", controllerName, name) 47 | err = dc.Stop(ctx, controllerName) 48 | if err != nil { 49 | return err 50 | } 51 | defer func() { 52 | logger.Printf("Starting controller %q on %q", controllerName, name) 53 | err = dc.Start(ctx, controllerName) 54 | if err != nil { 55 | logger.Printf("Error starting controller %q on %q: %v", controllerName, name, err) 56 | } 57 | }() 58 | 59 | file := flags.File 60 | if file == "-" { 61 | file = "STDIN" 62 | } 63 | logger.Printf("Loading resource %q on %q", file, name) 64 | err = load.Load(ctx, dc, flags.File) 65 | if err != nil { 66 | return err 67 | } 68 | 69 | return nil 70 | } 71 | -------------------------------------------------------------------------------- /pkg/cmd/fake-k8s/logs/logs.go: -------------------------------------------------------------------------------- 1 | package logs 2 | 3 | import ( 4 | "context" 5 | "os" 6 | 7 | "github.com/spf13/cobra" 8 | "github.com/wzshiming/fake-k8s/pkg/log" 9 | "github.com/wzshiming/fake-k8s/pkg/runtime" 10 | "github.com/wzshiming/fake-k8s/pkg/utils" 11 | "github.com/wzshiming/fake-k8s/pkg/vars" 12 | ) 13 | 14 | type flagpole struct { 15 | Name string 16 | Follow bool 17 | } 18 | 19 | // NewCommand returns a new cobra.Command for getting the list of clusters 20 | func NewCommand(logger log.Logger) *cobra.Command { 21 | flags := &flagpole{} 22 | cmd := &cobra.Command{ 23 | Args: cobra.ExactArgs(1), 24 | Use: "logs", 25 | Short: "Logs one of [etcd, kube-apiserver, kube-controller-manager, kube-scheduler, fake-kubelet, prometheus]", 26 | Long: "Logs one of [etcd, kube-apiserver, kube-controller-manager, kube-scheduler, fake-kubelet, prometheus]", 27 | RunE: func(cmd *cobra.Command, args []string) error { 28 | flags.Name = vars.DefaultCluster 29 | return runE(cmd.Context(), logger, flags, args) 30 | }, 31 | } 32 | cmd.Flags().BoolVarP(&flags.Follow, "follow", "f", false, "follow the log") 33 | return cmd 34 | } 35 | 36 | func runE(ctx context.Context, logger log.Logger, flags *flagpole, args []string) error { 37 | name := vars.ProjectName + "-" + flags.Name 38 | workdir := utils.PathJoin(vars.TempDir, flags.Name) 39 | 40 | dc, err := runtime.Load(name, workdir, logger) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | if flags.Follow { 46 | err = dc.LogsFollow(ctx, args[0], os.Stdout) 47 | } else { 48 | err = dc.Logs(ctx, args[0], os.Stdout) 49 | } 50 | if err != nil { 51 | return err 52 | } 53 | return nil 54 | } 55 | -------------------------------------------------------------------------------- /pkg/cmd/fake-k8s/root.go: -------------------------------------------------------------------------------- 1 | package fakek8s 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | "github.com/wzshiming/fake-k8s/pkg/cmd/fake-k8s/create" 8 | del "github.com/wzshiming/fake-k8s/pkg/cmd/fake-k8s/delete" 9 | "github.com/wzshiming/fake-k8s/pkg/cmd/fake-k8s/get" 10 | "github.com/wzshiming/fake-k8s/pkg/cmd/fake-k8s/kubectl" 11 | "github.com/wzshiming/fake-k8s/pkg/cmd/fake-k8s/load" 12 | "github.com/wzshiming/fake-k8s/pkg/cmd/fake-k8s/logs" 13 | "github.com/wzshiming/fake-k8s/pkg/log" 14 | "github.com/wzshiming/fake-k8s/pkg/vars" 15 | ) 16 | 17 | // NewCommand returns a new cobra.Command for root 18 | func NewCommand(logger log.Logger) *cobra.Command { 19 | cmd := &cobra.Command{ 20 | Args: cobra.NoArgs, 21 | Use: "fake-k8s [command]", 22 | Short: "fake-k8s is a fake Kubernetes cluster manager tools", 23 | Long: `fake-k8s is a fake Kubernetes cluster manager tools`, 24 | RunE: func(cmd *cobra.Command, args []string) error { 25 | return fmt.Errorf("subcommand is required") 26 | }, 27 | } 28 | 29 | cmd.PersistentFlags().StringVar(&vars.DefaultCluster, "name", "default", "cluster name") 30 | cmd.TraverseChildren = true 31 | 32 | cmd.AddCommand( 33 | create.NewCommand(logger), 34 | del.NewCommand(logger), 35 | get.NewCommand(logger), 36 | load.NewCommand(logger), 37 | kubectl.NewCommand(logger), 38 | logs.NewCommand(logger), 39 | ) 40 | return cmd 41 | } 42 | -------------------------------------------------------------------------------- /pkg/k8s/etcd.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "os" 5 | "runtime" 6 | ) 7 | 8 | // lists from https://github.com/kubernetes/kubernetes/blob/2d7dcf928c3e0e8dd4c29c421893a299e1a1b857/cmd/kubeadm/app/constants/constants.go#L491 9 | var etcdVersions = map[int]string{ 10 | 8: "3.0.17", 11 | 9: "3.1.12", 12 | 10: "3.1.12", 13 | 11: "3.2.18", 14 | 12: "3.2.24", 15 | 13: "3.2.24", 16 | 14: "3.3.10", 17 | 15: "3.3.10", 18 | 16: "3.3.17-0", 19 | 17: "3.4.3-0", 20 | 18: "3.4.3-0", 21 | 19: "3.4.13-0", 22 | 20: "3.4.13-0", 23 | 21: "3.4.13-0", 24 | 22: "3.5.4-0", 25 | 23: "3.5.4-0", 26 | 24: "3.5.4-0", 27 | 25: "3.5.4-0", 28 | } 29 | 30 | func GetEtcdVersion(version int) string { 31 | if version < 8 { 32 | version = 8 33 | } 34 | if version > 25 { 35 | version = 25 36 | } 37 | return etcdVersions[version] 38 | } 39 | 40 | func init() { 41 | os.Setenv("ETCD_UNSUPPORTED_ARCH", runtime.GOARCH) 42 | } 43 | -------------------------------------------------------------------------------- /pkg/k8s/feature_gates.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | //go:generate ./feature_gates_data.sh 24 11 | 12 | var lockEnabled = map[string]bool{} 13 | 14 | func GetFeatureGates(version int) string { 15 | // Enable only the beta feature of the final GA 16 | isGA := map[string]bool{} 17 | for _, raw := range rawData { 18 | if raw.Stage == GA { 19 | _, ok := isGA[raw.Name] 20 | if !ok { 21 | isGA[raw.Name] = true 22 | } 23 | } else if raw.Stage == Deprecated { 24 | isGA[raw.Name] = false 25 | } 26 | } 27 | 28 | enables := map[string]bool{} 29 | for _, raw := range rawData { 30 | if raw.Contain(version) { 31 | if raw.Stage == Beta { 32 | enables[raw.Name] = isGA[raw.Name] || lockEnabled[raw.Name] 33 | } 34 | } 35 | } 36 | 37 | gates := make([]string, 0, len(enables)) 38 | for name, enable := range enables { 39 | gates = append(gates, name+"="+strconv.FormatBool(enable)) 40 | } 41 | sort.Strings(gates) 42 | return strings.Join(gates, ",") 43 | } 44 | 45 | type FeatureSpec struct { 46 | Name string 47 | Stage Stage 48 | Since int 49 | Until int 50 | } 51 | 52 | func (f *FeatureSpec) Contain(v int) bool { 53 | return f.Since <= v && 54 | (f.Until < 0 || v <= f.Until) 55 | } 56 | 57 | func (f *FeatureSpec) Verification() error { 58 | if f.Since < 0 { 59 | return fmt.Errorf("invalid since: %d", f.Since) 60 | } 61 | if f.Until >= 0 && f.Until < f.Since { 62 | return fmt.Errorf("invalid until: %d < since: %d", f.Until, f.Since) 63 | } 64 | return nil 65 | } 66 | 67 | type Stage string 68 | 69 | const ( 70 | Alpha = Stage("ALPHA") 71 | Beta = Stage("BETA") 72 | GA = Stage("GA") 73 | 74 | // Deprecated 75 | Deprecated = Stage("DEPRECATED") 76 | ) 77 | -------------------------------------------------------------------------------- /pkg/k8s/feature_gates_data.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # https://github.com/kubernetes/website/blob/main/content/en/docs/reference/command-line-tools-reference/feature-gates.md 4 | # Some details of the feature gate on the official website are not synchronized so here we use a script to get the real details in the code 5 | 6 | latest_release="${1:-24}" 7 | 8 | function features() { 9 | for i in $(seq 6 "${latest_release}"); do 10 | curl -sSL "https://github.com/kubernetes/kubernetes/raw/release-1.${i}/pkg/features/kube_features.go" | 11 | grep "{Default: " | 12 | sed -e 's/\w\+\.//g' | 13 | sed -e 's/[:,}]//g' | 14 | awk "{print \$1, \$5, $i}" 15 | done 16 | } 17 | 18 | function gen() { 19 | echo "package k8s" 20 | echo 21 | echo "// Don't edit this file directly. It is generated by feature_gates_data.sh." 22 | echo "var rawData = []FeatureSpec{" 23 | raw="$(features | sort)" 24 | 25 | raw="${raw//ExperimentalHostUserNamespaceDefaultingGate/ExperimentalHostUserNamespaceDefaulting}" 26 | 27 | gates="$(echo "${raw}" | awk '{print $1}' | sort -u)" 28 | 29 | for gate in ${gates}; do 30 | stage="$(echo "${raw}" | grep -e "^${gate} " | awk '{print $2}' | sort -u)" 31 | since_release="" 32 | until_release="" 33 | 34 | echo 35 | echo " // ${gate}" 36 | for s in ${stage}; do 37 | release="$(echo "${raw}" | grep -e "^${gate} ${s} " | awk '{print $3}' | sort -n)" 38 | since_release="$(echo "${release}" | head -n 1)" 39 | until_release="$(echo "${release}" | tail -n 1)" 40 | if [[ "${until_release}" == "${latest_release}" ]]; then 41 | until_release="-1" 42 | fi 43 | echo " {\"${gate}\", ${s}, ${since_release}, ${until_release}}," 44 | done 45 | done 46 | echo "}" 47 | } 48 | 49 | function gen_file() { 50 | gen >feature_gates_data.go 51 | } 52 | 53 | gen_file 54 | -------------------------------------------------------------------------------- /pkg/k8s/feature_gates_data_test.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestRawData(t *testing.T) { 9 | for i, data := range rawData { 10 | if err := data.Verification(); err != nil { 11 | t.Error(data, err) 12 | } 13 | 14 | if data.Until >= 0 && i+1 < len(rawData) { 15 | nextData := rawData[i+1] 16 | if data.Name == nextData.Name { 17 | if data.Until+1 != nextData.Since { 18 | t.Error(data, fmt.Errorf("invalid until: %d + 1 != next since: %d", data.Until, data.Since)) 19 | } 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pkg/k8s/kubeconfig.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "fmt" 7 | "html/template" 8 | ) 9 | 10 | //go:embed kubeconfig.yaml.tpl 11 | var kubeconfigYamlTpl string 12 | 13 | var kubeconfigYamlTemplate = template.Must(template.New("_").Delims("${{", "}}").Parse(kubeconfigYamlTpl)) 14 | 15 | func BuildKubeconfig(conf BuildKubeconfigConfig) (string, error) { 16 | buf := bytes.NewBuffer(nil) 17 | err := kubeconfigYamlTemplate.Execute(buf, conf) 18 | if err != nil { 19 | return "", fmt.Errorf("build kubeconfig error: %s", err) 20 | } 21 | return buf.String(), nil 22 | } 23 | 24 | type BuildKubeconfigConfig struct { 25 | ProjectName string 26 | SecretPort bool 27 | Address string 28 | AdminCrtPath string 29 | AdminKeyPath string 30 | } 31 | -------------------------------------------------------------------------------- /pkg/k8s/kubeconfig.yaml.tpl: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Config 3 | preferences: {} 4 | current-context: ${{ .ProjectName }} 5 | clusters: 6 | - name: ${{ .ProjectName }} 7 | cluster: 8 | server: ${{ .Address }} 9 | ${{ if .SecretPort }} 10 | insecure-skip-tls-verify: true 11 | ${{ end}} 12 | contexts: 13 | - name: ${{ .ProjectName }} 14 | context: 15 | cluster: ${{ .ProjectName }} 16 | 17 | ${{ if .SecretPort }} 18 | user: ${{ .ProjectName }} 19 | users: 20 | - name: ${{ .ProjectName }} 21 | user: 22 | client-certificate: ${{ .AdminCrtPath }} 23 | client-key: ${{ .AdminKeyPath }} 24 | ${{ end}} 25 | -------------------------------------------------------------------------------- /pkg/k8s/runtime_config.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | func GetRuntimeConfig(version int) string { 4 | if version < 17 { 5 | return "" 6 | } 7 | return "api/legacy=false,api/alpha=false" 8 | } 9 | -------------------------------------------------------------------------------- /pkg/log/logger.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | type Logger interface { 4 | Printf(format string, args ...interface{}) 5 | } 6 | -------------------------------------------------------------------------------- /pkg/pki/admin.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDgjCCAmqgAwIBAgIUcu7ukVo4FVcpdrEr28OmXAIBXxQwDQYJKoZIhvcNAQEL 3 | BQAwEjEQMA4GA1UEAwwHZmFrZS1jYTAgFw0yMjA1MjMxMTIxNDlaGA8zMDIxMDky 4 | MzExMjE0OVowFTETMBEGA1UEAwwKZmFrZS1hZG1pbjCCASIwDQYJKoZIhvcNAQEB 5 | BQADggEPADCCAQoCggEBALiEPxawI7oN2NyiVVn71MGYNhtt6mPABjmEqv2Vvc6z 6 | iWtxfLlg5gmfFKNqaZgleCO6uuz6sPJAQLEspLwm43v6mNZfWYwJTh/X4jkmkcNY 7 | BScWsTnWudWtwOzegIYGFbnQ4dPzwwePXIwNpBFA6gbRDH4XOfitAPkcVkkrLJ2/ 8 | zooPA+2MWtNhT+KJEzZCLPWCP/7L3sBNzO6I9wAlsmMhoW2psF5OukqNppabmu52 9 | r1GE5CWsvHH4bhuAMQ8epupxLRgptGg7NUNXgnGdQ9vGdUu8pfysojV2rS3OBya/ 10 | 08O0xjychLkBgUZ8T/YgKbcrm56KRq2SuLtUhDCJoMUCAwEAAaOByjCBxzAJBgNV 11 | HRMEAjAAMAsGA1UdDwQEAwIF4DBtBgNVHREEZjBkggprdWJlcm5ldGVzghJrdWJl 12 | cm5ldGVzLmRlZmF1bHSCFmt1YmVybmV0ZXMuZGVmYXVsdC5zdmOCJGt1YmVybmV0 13 | ZXMuZGVmYXVsdC5zdmMuY2x1c3Rlci5sb2NhbIcEfwAAATAdBgNVHQ4EFgQUXp6x 14 | BUnfA+nYJTjPQU1yDzub+B4wHwYDVR0jBBgwFoAUDf/aR0FirsAramSn56u5zcpj 15 | c0UwDQYJKoZIhvcNAQELBQADggEBAFaRYYW1vWMDsBfo4M5xcHKutCC+F3nnSW5w 16 | 4qEnDbKR2Mc01GizhNNrMYPrDukNxOhNgGM8n/Y1hptKM8UCOM2HBbSjakCeBYm3 17 | jyRhFqpkOkkXvvhcgE4WNWxKqnWZXscdVG4pY+tYCfrI7ot+gE4Blt2VCSaYLgKc 18 | TwGERS924HIYdcpViUTgSTySR8QbFKesIpA2dZi7vPFXrrpSTJbXVvpbKVydbksR 19 | jqPHfB8JY//moiPNUmMSnl5yliGxsa4UEV07RcyldWd1mRSV7p6CQycvpXIG17aq 20 | qtj/hvRMiDQhrWovvoKZpunA5s7KAkn94wKP2oZkSUMmwV6Fnkg= 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /pkg/pki/admin.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIC9jCCAd4CAQAwFTETMBEGA1UEAwwKZmFrZS1hZG1pbjCCASIwDQYJKoZIhvcN 3 | AQEBBQADggEPADCCAQoCggEBALiEPxawI7oN2NyiVVn71MGYNhtt6mPABjmEqv2V 4 | vc6ziWtxfLlg5gmfFKNqaZgleCO6uuz6sPJAQLEspLwm43v6mNZfWYwJTh/X4jkm 5 | kcNYBScWsTnWudWtwOzegIYGFbnQ4dPzwwePXIwNpBFA6gbRDH4XOfitAPkcVkkr 6 | LJ2/zooPA+2MWtNhT+KJEzZCLPWCP/7L3sBNzO6I9wAlsmMhoW2psF5OukqNppab 7 | mu52r1GE5CWsvHH4bhuAMQ8epupxLRgptGg7NUNXgnGdQ9vGdUu8pfysojV2rS3O 8 | Bya/08O0xjychLkBgUZ8T/YgKbcrm56KRq2SuLtUhDCJoMUCAwEAAaCBmzCBmAYJ 9 | KoZIhvcNAQkOMYGKMIGHMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMG0GA1UdEQRm 10 | MGSCCmt1YmVybmV0ZXOCEmt1YmVybmV0ZXMuZGVmYXVsdIIWa3ViZXJuZXRlcy5k 11 | ZWZhdWx0LnN2Y4Ika3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2Fs 12 | hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQCgueLeth/fa0QUhD+TxPKAkmqSKVj3 13 | Q2i3FHBGU2B31aBVN6kdC9ybjBEk5VrH42uLIowi2GCo/mjHzi1I0P3l+I3uGAME 14 | ZClgmVbAItpCHfcEAwz/O4tRnCU7mJ3JyQrWglPN+G8zS3s4U9mXtBfbp/qjmFx6 15 | qQTG5keTPb1hC/KBMVVM6CA9il1ooZXgVJwG9MkMPAWhfCcr+TT6Ujk7VOD+sR+C 16 | J9LHAi02Fr6UoLCTv91qHfK5Nak5LBJXhpEMfEisVB5kKxZtq+r+dR1bc6bqwWNJ 17 | 4fiN3DQ4XVfH3RFWYGZ9zM/RzL+HyHU+X1qhL87/3xiobt8yL6ZsrZXt 18 | -----END CERTIFICATE REQUEST----- 19 | -------------------------------------------------------------------------------- /pkg/pki/admin.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC4hD8WsCO6Ddjc 3 | olVZ+9TBmDYbbepjwAY5hKr9lb3Os4lrcXy5YOYJnxSjammYJXgjurrs+rDyQECx 4 | LKS8JuN7+pjWX1mMCU4f1+I5JpHDWAUnFrE51rnVrcDs3oCGBhW50OHT88MHj1yM 5 | DaQRQOoG0Qx+Fzn4rQD5HFZJKyydv86KDwPtjFrTYU/iiRM2Qiz1gj/+y97ATczu 6 | iPcAJbJjIaFtqbBeTrpKjaaWm5rudq9RhOQlrLxx+G4bgDEPHqbqcS0YKbRoOzVD 7 | V4JxnUPbxnVLvKX8rKI1dq0tzgcmv9PDtMY8nIS5AYFGfE/2ICm3K5ueikatkri7 8 | VIQwiaDFAgMBAAECggEAAnv85wYFi1hAt59TsPn3p2xM8jKZKbE258vn4Fva1IvN 9 | EJVw3YSMMpNMlQEg8rndF0rzgFmo2elCHNXpNa41B1Zce+NPi2X4c+wVW/KhKTxS 10 | p6jpDoNb//WR/ukedf23+3/+bRBN3VGVGiJN/rVAwyzYKJqtV6P3yRu+9Bx3+yYs 11 | 8+ICxRTfE+HOLy6dbKKVmNF5zCL1q8iHGZdhb7M6Nj0cZKsxqQtc6NNlNXtFxW70 12 | Wdak6gOu9AOJq7ikfwh391S0h/67qQ/cVmr6IiXHMnl5iCjZ9fzzVV0uN5S13h4E 13 | 25ByvpMUBLy3SmbhyhAYiQrzxYet2GO7FdKIPTu6oQKBgQDiM5uVmW7ZQLqnZU+C 14 | 5scl38dtDvdCzKvqiJIISGDWFtajnpayX6lM/+2JtWB38Gyis9WIVzRj4bsgH32z 15 | eqgk+OfisokC9zCRPwdtcA+OxeIFx86U6eUiMkJbDtPt75HI8w6Zayn33ASuv7z3 16 | PLYHUBrNKfCxTgmEhFxC9RJvVQKBgQDQ0tvxeAyiBnGDshnxGzUzxpYKrAt3Oqur 17 | Zt9w1H3rTCajveIj4hQfJU4DtQqMcKshGOxvvyVdTzC+XRPJUK5v0hroOr3zRy1n 18 | YZehZ6M0jYigXKLfyHxMKrSH7Vrec5L6xQ1eZ/3MNi4mrP5P2/0NbN8nH/7XAFaB 19 | JV4BT/oLsQKBgHvGS4tHou1ESx6QBDsXtr9VvRVDDC/EOQoj4uyfVkWvx5FOlR1T 20 | 1RsqK7ufwQLAT2GUT/LD7Qn+mdtRAAJ2yo04mgJlOS2jPzMrLxBGMm0ogK4zECpR 21 | 9iyJHeAfs/3NipNP1JxElHlpLjLE/ky3Ls+/mHrNSURr9C9MEgQdgBHlAoGARHpQ 22 | +78lTWCVit01mpYUfTUK8Nb4L5ICx4NFlGdqH2XqadmTtbamaIxAKAEPQ3yS8ZiN 23 | uBtTcmTMlDBLk8GsYijg//R7Y7KclgEvyxMENvCOtB+MnZvN/uAmMUjJepclxKwn 24 | tSSe2n+N5Mrn3mJCekuQZfEdDX4j39fUGtQSKBECgYEAowtnP/D+EuW3+6/I5HDb 25 | Sz4NtdO6nyuL1lGFC0qPrpE3YKD3A6rJmL4NNqOUQ441o5JaEwe1g6REofzJHMHE 26 | yxAJe4KHjSNXCBK7qIe9uW/Bbx0yOc4a6lBnLGKVyFIZpzbvpSNO6JISPAoMq0H0 27 | 7n+UKR5K+rJoolc3ajtjLMY= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /pkg/pki/ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDBzCCAe+gAwIBAgIUA5hhQGY4FK0QUwYBhiZyYN5XJPgwDQYJKoZIhvcNAQEL 3 | BQAwEjEQMA4GA1UEAwwHZmFrZS1jYTAgFw0yMjA1MjMxMTIxNDhaGA8zMDIxMDky 4 | MzExMjE0OFowEjEQMA4GA1UEAwwHZmFrZS1jYTCCASIwDQYJKoZIhvcNAQEBBQAD 5 | ggEPADCCAQoCggEBAKfUYkTNetxhEfB74Y3ISHht9v18cRRKME0Fg7VlNpLxJUgl 6 | hIkK9uN3umuJnyC9QvmvnwJKO7+rNircW1WlwFind2b1eDWML2nQJ7+HB/3Y+oYx 7 | 0HSYUbiJfp1O7XZCf4tGaQrdJ+Iz8h4Zm9nqS8/TRTo7vmufo1k5qv+onPH1Fu3V 8 | 6PGi0NKxdZpNIgIMAl143pxgwxO/82LEUz0CtN0WRsXeC9F8/aMM5kxqhQVDei7n 9 | 1t7d+AfU1ISShu3hk68c+XNqDlC8N9N4Jfr8F6401ckRhD15qa3uJYrKUSNFts2V 10 | iIIWHZfNGIijPE8PRbl28Qrb8qjToIF+4UKUG60CAwEAAaNTMFEwHQYDVR0OBBYE 11 | FA3/2kdBYq7AK2pkp+eruc3KY3NFMB8GA1UdIwQYMBaAFA3/2kdBYq7AK2pkp+er 12 | uc3KY3NFMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAGWMQ3nQ 13 | ASm5nY3kMdn3vzqdtEx5LYhQouPIrg4kUqyzLMjJl7hpb64sWMT9HrxJOl30l7n6 14 | l0/8flQmuKj/mdPi9KoDMXya9xhdxppH86JdzS0Adj56fb8fvhwB7rqKoVwMyYEo 15 | nTmGtKwGHJvKcw2o8wr3z/rBlEoUIrqIAdV0fyShK4dgnclUm4biS3DuiTwXlcqR 16 | DRUWTtDZQBPDGP1oPe9lGWucUs08qmeJ6w9IVrPAT9z68x6Mi/sqYOw9v8wlLHJo 17 | sPdD+XaliN3AZ0M8bA/YJ8q1VGSZUe+wBxD4u43zoo3CIZzoqg0O8KIPq81hUv29 18 | JewBhZlH4PYXJHI= 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /pkg/pki/ca.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCn1GJEzXrcYRHw 3 | e+GNyEh4bfb9fHEUSjBNBYO1ZTaS8SVIJYSJCvbjd7priZ8gvUL5r58CSju/qzYq 4 | 3FtVpcBYp3dm9Xg1jC9p0Ce/hwf92PqGMdB0mFG4iX6dTu12Qn+LRmkK3SfiM/Ie 5 | GZvZ6kvP00U6O75rn6NZOar/qJzx9Rbt1ejxotDSsXWaTSICDAJdeN6cYMMTv/Ni 6 | xFM9ArTdFkbF3gvRfP2jDOZMaoUFQ3ou59be3fgH1NSEkobt4ZOvHPlzag5QvDfT 7 | eCX6/BeuNNXJEYQ9eamt7iWKylEjRbbNlYiCFh2XzRiIozxPD0W5dvEK2/Ko06CB 8 | fuFClButAgMBAAECggEAAVqij1UMEnabBzJsq7DrusMCXOGx5NzDXn+2pBJEujNF 9 | XntlAhkLTGMFJ0R1OvWj8nFDCmjwafe26GoeN4bukhiJy1QcK+xtXoBuLaqoexkS 10 | zl3dBW/MKv26/ohqPIvFTSmcg9isb3QFVk5VFlFAWtUkyBc+zk37qqbfNRBWcdJJ 11 | zEemCqKO/59YmG3g5v/o5MQ4pvlR0fbPcGfDsXCDfJhBs1csy3RypjLxh/L70BVB 12 | 91QjFM2yjVRoNuEqs81de+11UNd0C8MZgEFGUa1TXub+9oZkp2vZB2hc+0gUQ8pn 13 | uNdQBa5kQ0AG0RJIZ1glY9hRr2CiMGnLJAYkpaAmWQKBgQDJ2RIHY22Y+Qh3NE+7 14 | wEMGkJU/QcrsFb+QalMvH0aMfY2gJlpSs9Q+CHK1vosMbIk1pBDZaIkeOgDMbGfH 15 | dbPHaoKA4f3ZhIBHGjpm1qO6iJuwxgX1xp2wjqPbn3wndzOjoc8lXP3tqZ8NVSRt 16 | e8wo4VlFQuiw1L/kkksDBbOeSQKBgQDU2u8Ozup4wtvYnyIn+vJPWpJSpBIBdgKc 17 | uSkTEmDO3zwCsAlJNjviB35cLj57WSW6RcwE391VQHkb7WBUPir/FYvzCV1AbzRt 18 | 8xYCdrQW1UrGlyWdUi7o5nQZ6sqw0OfTYnfl/gpLdXvXGz03V6fyXQmvUS00Ada0 19 | J7ZEQ/7iRQKBgHVHohkW5XRPpmKUy4s8r/UdhwplMQGxtUe077ZehsBUpziZcDvD 20 | bxvIDtaY6vQCEEtRk9Suz5T3gMVGa5pSQm4o7cDzDmsXeO1XSP290w4sF8JDnOzs 21 | RW1zw7N1XE8WDLQT7Wc5O/Wea+L0SKEwoDRUk2kR8l/kjcbCVcFgy7e5AoGBAMHT 22 | c2kKppy85DEIB7RcYZY9JY03dmX6LjrEqVCCpcD7qsvPvUT7q+ARbtFGEwODlEVh 23 | FX1C1WuyQBZcPCG6UrDh0ATJ/C6WlSmh9+LacPdx1pusO0Zc9faDFCodkeEc+l2V 24 | BxVaFcnM+65PUZk5queaLR2GUCFRo0998g2L1Wf5AoGAEE+ZVzrt8asqXsA+FyEn 25 | i7np75aHV25vmngBq3Ny1+OZKMtVHPYJBXO8+Fh5PU3IGCXURZGQgUoDdpoTVyQA 26 | 2f+jVzucpPgOg0txfTlJUZ55L0nZz2V/jao47IRvhnx47Uh981lVtHb8n2D9gf/4 27 | 59U/BopdGUGQacqRU4VRj9E= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /pkg/pki/openssl.cnf: -------------------------------------------------------------------------------- 1 | [req] 2 | req_extensions = v3_req 3 | distinguished_name = req_distinguished_name 4 | [req_distinguished_name] 5 | [ v3_req ] 6 | basicConstraints = CA:FALSE 7 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment 8 | subjectAltName = @alt_names 9 | [alt_names] 10 | DNS.1 = kubernetes 11 | DNS.2 = kubernetes.default 12 | DNS.3 = kubernetes.default.svc 13 | DNS.4 = kubernetes.default.svc.cluster.local 14 | IP.1 = 127.0.0.1 -------------------------------------------------------------------------------- /pkg/pki/pki.go: -------------------------------------------------------------------------------- 1 | package pki 2 | 3 | import ( 4 | _ "embed" 5 | "os" 6 | 7 | "github.com/wzshiming/fake-k8s/pkg/utils" 8 | ) 9 | 10 | // This is just a local key, it doesn't matter if it is leaked. 11 | 12 | //go:generate openssl genrsa -out ca.key 2048 13 | //go:generate openssl req -sha256 -x509 -new -nodes -key ca.key -subj "/CN=fake-ca" -out ca.crt -days 365000 14 | //go:generate openssl genrsa -out admin.key 2048 15 | //go:generate openssl req -new -key admin.key -subj "/CN=fake-admin" -out admin.csr -config openssl.cnf 16 | //go:generate openssl x509 -sha256 -req -in admin.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out admin.crt -days 365000 -extensions v3_req -extfile openssl.cnf 17 | 18 | var ( 19 | //go:embed ca.crt 20 | CACrt []byte 21 | //go:embed admin.key 22 | AdminKey []byte 23 | //go:embed admin.crt 24 | AdminCrt []byte 25 | ) 26 | 27 | // DumpPki generates a pki directory. 28 | func DumpPki(dir string) error { 29 | err := os.MkdirAll(dir, 0755) 30 | if err != nil { 31 | return err 32 | } 33 | err = os.WriteFile(utils.PathJoin(dir, "ca.crt"), CACrt, 0644) 34 | if err != nil { 35 | return err 36 | } 37 | err = os.WriteFile(utils.PathJoin(dir, "admin.key"), AdminKey, 0644) 38 | if err != nil { 39 | return err 40 | } 41 | err = os.WriteFile(utils.PathJoin(dir, "admin.crt"), AdminCrt, 0644) 42 | if err != nil { 43 | return err 44 | } 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /pkg/resource/load/load.go: -------------------------------------------------------------------------------- 1 | package load 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "k8s.io/apimachinery/pkg/runtime" 10 | "os" 11 | "strings" 12 | 13 | fakeruntime "github.com/wzshiming/fake-k8s/pkg/runtime" 14 | "github.com/wzshiming/fake-k8s/pkg/utils" 15 | oyaml "gopkg.in/yaml.v2" 16 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 17 | "sigs.k8s.io/yaml" 18 | ) 19 | 20 | func Load(ctx context.Context, rt fakeruntime.Runtime, src string) error { 21 | file, err := openFile(src) 22 | if err != nil { 23 | return err 24 | } 25 | defer file.Close() 26 | 27 | objs, err := decodeObjects(file) 28 | if err != nil { 29 | return err 30 | } 31 | inputRaw := bytes.NewBuffer(nil) 32 | outputRaw := bytes.NewBuffer(nil) 33 | otherResource, err := load(objs, func(objs []*unstructured.Unstructured) ([]*unstructured.Unstructured, error) { 34 | inputRaw.Reset() 35 | outputRaw.Reset() 36 | 37 | encoder := json.NewEncoder(inputRaw) 38 | for _, obj := range objs { 39 | err = encoder.Encode(obj) 40 | if err != nil { 41 | return nil, err 42 | } 43 | } 44 | 45 | err = rt.KubectlInCluster(ctx, utils.IOStreams{ 46 | In: inputRaw, 47 | Out: outputRaw, 48 | ErrOut: os.Stderr, 49 | }, "create", "--validate=false", "-o", "json", "-f", "-") 50 | if err != nil { 51 | for _, obj := range objs { 52 | fmt.Fprintf(os.Stderr, "%s/%s failed\n", strings.ToLower(obj.GetObjectKind().GroupVersionKind().Kind), obj.GetName()) 53 | } 54 | } 55 | newObj, err := decodeObjects(outputRaw) 56 | if err != nil { 57 | return nil, err 58 | } 59 | for _, obj := range newObj { 60 | fmt.Fprintf(os.Stderr, "%s/%s succeed\n", strings.ToLower(obj.GetObjectKind().GroupVersionKind().Kind), obj.GetName()) 61 | } 62 | return newObj, nil 63 | }) 64 | if err != nil { 65 | return err 66 | } 67 | for _, obj := range otherResource { 68 | fmt.Fprintf(os.Stderr, "%s/%s skipped\n", strings.ToLower(obj.GetObjectKind().GroupVersionKind().Kind), obj.GetName()) 69 | } 70 | return nil 71 | } 72 | 73 | func openFile(path string) (io.ReadCloser, error) { 74 | if path == "-" { 75 | return io.NopCloser(os.Stdin), nil 76 | } 77 | return os.Open(path) 78 | } 79 | 80 | func decodeObjects(data io.Reader) ([]*unstructured.Unstructured, error) { 81 | out := []*unstructured.Unstructured{} 82 | tmp := map[string]interface{}{} 83 | decoder := oyaml.NewDecoder(data) 84 | for { 85 | err := decoder.Decode(&tmp) 86 | if err != nil { 87 | if err == io.EOF { 88 | break 89 | } 90 | return nil, err 91 | } 92 | data, err := oyaml.Marshal(tmp) 93 | if err != nil { 94 | return nil, err 95 | } 96 | data, err = yaml.YAMLToJSON(data) 97 | if err != nil { 98 | return nil, err 99 | } 100 | obj := &unstructured.Unstructured{} 101 | err = obj.UnmarshalJSON(data) 102 | if err != nil { 103 | return nil, err 104 | } 105 | 106 | if obj.IsList() { 107 | err = obj.EachListItem(func(object runtime.Object) error { 108 | out = append(out, object.DeepCopyObject().(*unstructured.Unstructured)) 109 | return nil 110 | }) 111 | if err != nil { 112 | return nil, err 113 | } 114 | } else { 115 | out = append(out, obj.DeepCopyObject().(*unstructured.Unstructured)) 116 | } 117 | } 118 | return out, nil 119 | } 120 | 121 | func filter(input []*unstructured.Unstructured, fun func(*unstructured.Unstructured) bool) []*unstructured.Unstructured { 122 | ret := []*unstructured.Unstructured{} 123 | for _, i := range input { 124 | if fun(i) { 125 | ret = append(ret, i) 126 | } 127 | } 128 | return ret 129 | } 130 | 131 | func load(input []*unstructured.Unstructured, apply func([]*unstructured.Unstructured) ([]*unstructured.Unstructured, error)) ([]*unstructured.Unstructured, error) { 132 | applyResource := []*unstructured.Unstructured{} 133 | otherResource := []*unstructured.Unstructured{} 134 | 135 | for _, obj := range input { 136 | // These are built-in resources that do not need to be created 137 | if obj.GetObjectKind().GroupVersionKind().Kind == "Namespace" && 138 | (obj.GetName() == "kube-public" || 139 | obj.GetName() == "kube-node-lease" || 140 | obj.GetName() == "kube-system" || 141 | obj.GetName() == "default") { 142 | continue 143 | } 144 | 145 | refs := obj.GetOwnerReferences() 146 | if len(refs) != 0 && refs[0].Controller != nil && *refs[0].Controller { 147 | otherResource = append(otherResource, obj) 148 | } else { 149 | applyResource = append(applyResource, obj) 150 | } 151 | } 152 | 153 | for len(applyResource) != 0 { 154 | nextApplyResource := []*unstructured.Unstructured{} 155 | newResource, err := apply(applyResource) 156 | if err != nil { 157 | return nil, err 158 | } 159 | if len(otherResource) == 0 { 160 | break 161 | } 162 | for i, newObj := range newResource { 163 | oldUid := applyResource[i].GetUID() 164 | newUid := newObj.GetUID() 165 | 166 | remove := map[*unstructured.Unstructured]struct{}{} 167 | nextResource := filter(otherResource, func(otherObj *unstructured.Unstructured) bool { 168 | otherRefs := otherObj.GetOwnerReferences() 169 | otherRef := &otherRefs[0] 170 | if otherRef.UID != oldUid { 171 | return false 172 | } 173 | otherRef.UID = newUid 174 | otherObj.SetOwnerReferences(otherRefs) 175 | remove[otherObj] = struct{}{} 176 | return true 177 | }) 178 | if len(remove) != 0 { 179 | otherResource = filter(otherResource, func(otherObj *unstructured.Unstructured) bool { 180 | _, ok := remove[otherObj] 181 | return !ok 182 | }) 183 | nextApplyResource = append(nextApplyResource, nextResource...) 184 | } 185 | } 186 | applyResource = nextApplyResource 187 | } 188 | return otherResource, nil 189 | } 190 | -------------------------------------------------------------------------------- /pkg/resource/load/load_test.go: -------------------------------------------------------------------------------- 1 | package load 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "io" 7 | "testing" 8 | 9 | "github.com/google/go-cmp/cmp" 10 | appsv1 "k8s.io/api/apps/v1" 11 | corev1 "k8s.io/api/core/v1" 12 | "k8s.io/apimachinery/pkg/api/equality" 13 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 15 | "k8s.io/apimachinery/pkg/runtime" 16 | ) 17 | 18 | func Test_load(t *testing.T) { 19 | controller := true 20 | type args struct { 21 | input []runtime.Object 22 | } 23 | tests := []struct { 24 | name string 25 | args args 26 | want []runtime.Object 27 | wantErr bool 28 | wantUpdated []runtime.Object 29 | }{ 30 | { 31 | args: args{ 32 | input: []runtime.Object{ 33 | &appsv1.DaemonSet{ 34 | ObjectMeta: metav1.ObjectMeta{ 35 | UID: "1", 36 | }, 37 | }, 38 | &corev1.Pod{ 39 | ObjectMeta: metav1.ObjectMeta{ 40 | UID: "2", 41 | OwnerReferences: []metav1.OwnerReference{ 42 | { 43 | Controller: &controller, 44 | UID: "1", 45 | }, 46 | }, 47 | }, 48 | }, 49 | }, 50 | }, 51 | wantUpdated: []runtime.Object{ 52 | &appsv1.DaemonSet{ 53 | ObjectMeta: metav1.ObjectMeta{ 54 | UID: "10", 55 | }, 56 | }, 57 | &corev1.Pod{ 58 | ObjectMeta: metav1.ObjectMeta{ 59 | UID: "20", 60 | OwnerReferences: []metav1.OwnerReference{ 61 | { 62 | Controller: &controller, 63 | UID: "10", 64 | }, 65 | }, 66 | }, 67 | }, 68 | }, 69 | }, 70 | } 71 | for _, tt := range tests { 72 | t.Run(tt.name, func(t *testing.T) { 73 | updated := []*unstructured.Unstructured{} 74 | apply := func(objs []*unstructured.Unstructured) ([]*unstructured.Unstructured, error) { 75 | ret := []*unstructured.Unstructured{} 76 | for _, obj := range objs { 77 | o := obj.DeepCopyObject().(*unstructured.Unstructured) 78 | o.SetUID(obj.GetUID() + "0") 79 | ret = append(ret, o) 80 | updated = append(updated, o) 81 | } 82 | return ret, nil 83 | } 84 | input := []*unstructured.Unstructured{} 85 | for _, i := range tt.args.input { 86 | tmp, _ := json.Marshal(i) 87 | u := &unstructured.Unstructured{} 88 | u.UnmarshalJSON(tmp) 89 | input = append(input, u) 90 | } 91 | 92 | got, err := load(input, apply) 93 | if (err != nil) != tt.wantErr { 94 | t.Errorf("load() error = %v, wantErr %v", err, tt.wantErr) 95 | return 96 | } 97 | 98 | want := ObjectListToUnstructuredList(tt.want) 99 | if !equality.Semantic.DeepEqual(got, want) { 100 | t.Errorf("expected vs got:\n%s", cmp.Diff(want, got)) 101 | } 102 | 103 | wantUpdated := ObjectListToUnstructuredList(tt.wantUpdated) 104 | if !equality.Semantic.DeepEqual(updated, wantUpdated) { 105 | t.Errorf("expected vs got:\n%s", cmp.Diff(updated, wantUpdated)) 106 | } 107 | }) 108 | } 109 | } 110 | 111 | func Test_decodeObjects(t *testing.T) { 112 | type args struct { 113 | data io.Reader 114 | } 115 | tests := []struct { 116 | name string 117 | args args 118 | want []runtime.Object 119 | wantErr bool 120 | }{ 121 | { 122 | args: args{ 123 | data: bytes.NewBufferString(` 124 | apiVersion: v1 125 | kind: Pod 126 | metadata: 127 | name: test 128 | namespace: test 129 | spec: 130 | containers: [] 131 | --- 132 | apiVersion: v1 133 | kind: Pod 134 | metadata: 135 | name: test-2 136 | namespace: test 137 | spec: 138 | containers: [] 139 | `), 140 | }, 141 | want: []runtime.Object{ 142 | &unstructured.Unstructured{ 143 | Object: map[string]interface{}{ 144 | "apiVersion": "v1", 145 | "kind": "Pod", 146 | "metadata": map[string]interface{}{ 147 | "name": "test", 148 | "namespace": "test", 149 | }, 150 | "spec": map[string]interface{}{ 151 | "containers": []interface{}{}, 152 | }, 153 | }, 154 | }, 155 | &unstructured.Unstructured{ 156 | Object: map[string]interface{}{ 157 | "apiVersion": "v1", 158 | "kind": "Pod", 159 | "metadata": map[string]interface{}{ 160 | "name": "test-2", 161 | "namespace": "test", 162 | }, 163 | "spec": map[string]interface{}{ 164 | "containers": []interface{}{}, 165 | }, 166 | }, 167 | }, 168 | }, 169 | }, 170 | } 171 | for _, tt := range tests { 172 | t.Run(tt.name, func(t *testing.T) { 173 | got, err := decodeObjects(tt.args.data) 174 | if (err != nil) != tt.wantErr { 175 | t.Errorf("decodeObjects() error = %v, wantErr %v", err, tt.wantErr) 176 | return 177 | } 178 | 179 | want := ObjectListToUnstructuredList(tt.want) 180 | if !equality.Semantic.DeepEqual(got, want) { 181 | t.Errorf("expected vs got:\n%s", cmp.Diff(want, got)) 182 | } 183 | }) 184 | } 185 | } 186 | 187 | func ObjectListToUnstructuredList(objects []runtime.Object) []*unstructured.Unstructured { 188 | out := []*unstructured.Unstructured{} 189 | for _, obj := range objects { 190 | out = append(out, ObjectToUnstructured(obj)) 191 | } 192 | return out 193 | } 194 | 195 | func ObjectToUnstructured(object runtime.Object) *unstructured.Unstructured { 196 | data, _ := json.Marshal(object) 197 | u := &unstructured.Unstructured{} 198 | u.UnmarshalJSON(data) 199 | return u 200 | } 201 | -------------------------------------------------------------------------------- /pkg/runtime/binary/cluster.go: -------------------------------------------------------------------------------- 1 | package binary 2 | 3 | import ( 4 | "context" 5 | _ "embed" 6 | "fmt" 7 | "io" 8 | "os" 9 | "path/filepath" 10 | "strconv" 11 | "time" 12 | 13 | "github.com/nxadm/tail" 14 | "github.com/wzshiming/fake-k8s/pkg/k8s" 15 | "github.com/wzshiming/fake-k8s/pkg/log" 16 | "github.com/wzshiming/fake-k8s/pkg/pki" 17 | "github.com/wzshiming/fake-k8s/pkg/runtime" 18 | "github.com/wzshiming/fake-k8s/pkg/utils" 19 | "github.com/wzshiming/fake-k8s/pkg/vars" 20 | ) 21 | 22 | type Cluster struct { 23 | *runtime.Cluster 24 | } 25 | 26 | func NewCluster(name, workdir string, logger log.Logger) (runtime.Runtime, error) { 27 | return &Cluster{ 28 | Cluster: runtime.NewCluster(name, workdir, logger), 29 | }, nil 30 | } 31 | 32 | func (c *Cluster) Install(ctx context.Context, conf runtime.Config) error { 33 | err := c.Cluster.Install(ctx, conf) 34 | if err != nil { 35 | return err 36 | } 37 | 38 | bin := utils.PathJoin(conf.Workdir, "bin") 39 | 40 | kubeApiserverPath := utils.PathJoin(bin, "kube-apiserver"+vars.BinSuffix) 41 | err = utils.DownloadWithCache(ctx, conf.CacheDir, conf.KubeApiserverBinary, kubeApiserverPath, 0755, conf.QuietPull) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | kubeControllerManagerPath := utils.PathJoin(bin, "kube-controller-manager"+vars.BinSuffix) 47 | err = utils.DownloadWithCache(ctx, conf.CacheDir, conf.KubeControllerManagerBinary, kubeControllerManagerPath, 0755, conf.QuietPull) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | kubeSchedulerPath := utils.PathJoin(bin, "kube-scheduler"+vars.BinSuffix) 53 | err = utils.DownloadWithCache(ctx, conf.CacheDir, conf.KubeSchedulerBinary, kubeSchedulerPath, 0755, conf.QuietPull) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | fakeKubeletPath := utils.PathJoin(bin, "fake-kubelet"+vars.BinSuffix) 59 | err = utils.DownloadWithCache(ctx, conf.CacheDir, conf.FakeKubeletBinary, fakeKubeletPath, 0755, conf.QuietPull) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | etcdPath := utils.PathJoin(bin, "etcd"+vars.BinSuffix) 65 | err = utils.DownloadWithCacheAndExtract(ctx, conf.CacheDir, conf.EtcdBinaryTar, etcdPath, "etcd"+vars.BinSuffix, 0755, conf.QuietPull, true) 66 | if err != nil { 67 | return err 68 | } 69 | 70 | if conf.PrometheusPort != 0 { 71 | prometheusPath := utils.PathJoin(bin, "prometheus"+vars.BinSuffix) 72 | err = utils.DownloadWithCacheAndExtract(ctx, conf.CacheDir, conf.PrometheusBinaryTar, prometheusPath, "prometheus"+vars.BinSuffix, 0755, conf.QuietPull, true) 73 | if err != nil { 74 | return err 75 | } 76 | } 77 | 78 | etcdDataPath := utils.PathJoin(conf.Workdir, runtime.EtcdDataDirName) 79 | os.MkdirAll(etcdDataPath, 0755) 80 | 81 | if conf.SecretPort { 82 | pkiPath := utils.PathJoin(conf.Workdir, runtime.PkiName) 83 | err = pki.DumpPki(pkiPath) 84 | if err != nil { 85 | return fmt.Errorf("failed to generate pki: %s", err) 86 | } 87 | } 88 | 89 | return nil 90 | } 91 | 92 | func (c *Cluster) Up(ctx context.Context) error { 93 | conf, err := c.Config() 94 | if err != nil { 95 | return err 96 | } 97 | scheme := "http" 98 | if conf.SecretPort { 99 | scheme = "https" 100 | } 101 | bin := utils.PathJoin(conf.Workdir, "bin") 102 | 103 | localAddress := "127.0.0.1" 104 | serveAddress := "0.0.0.0" 105 | 106 | kubeApiserverPath := utils.PathJoin(bin, "kube-apiserver") 107 | kubeControllerManagerPath := utils.PathJoin(bin, "kube-controller-manager") 108 | kubeSchedulerPath := utils.PathJoin(bin, "kube-scheduler") 109 | fakeKubeletPath := utils.PathJoin(bin, "fake-kubelet") 110 | etcdPath := utils.PathJoin(bin, "etcd") 111 | etcdDataPath := utils.PathJoin(conf.Workdir, runtime.EtcdDataDirName) 112 | pkiPath := utils.PathJoin(conf.Workdir, runtime.PkiName) 113 | caCertPath := utils.PathJoin(pkiPath, "ca.crt") 114 | adminKeyPath := utils.PathJoin(pkiPath, "admin.key") 115 | adminCertPath := utils.PathJoin(pkiPath, "admin.crt") 116 | 117 | etcdPeerPort, err := utils.GetUnusedPort() 118 | if err != nil { 119 | return err 120 | } 121 | etcdPeerPortStr := strconv.Itoa(etcdPeerPort) 122 | 123 | etcdClientPort, err := utils.GetUnusedPort() 124 | if err != nil { 125 | return err 126 | } 127 | etcdClientPortStr := strconv.Itoa(etcdClientPort) 128 | 129 | etcdArgs := []string{ 130 | "--data-dir", 131 | etcdDataPath, 132 | "--name", 133 | "node0", 134 | "--initial-advertise-peer-urls", 135 | "http://" + localAddress + ":" + etcdPeerPortStr, 136 | "--listen-peer-urls", 137 | "http://" + localAddress + ":" + etcdPeerPortStr, 138 | "--advertise-client-urls", 139 | "http://" + localAddress + ":" + etcdClientPortStr, 140 | "--listen-client-urls", 141 | "http://" + localAddress + ":" + etcdClientPortStr, 142 | "--initial-cluster", 143 | "node0=http://" + localAddress + ":" + etcdPeerPortStr, 144 | "--auto-compaction-retention", 145 | "1", 146 | "--quota-backend-bytes", 147 | "8589934592", 148 | } 149 | err = utils.ForkExec(ctx, conf.Workdir, etcdPath, etcdArgs...) 150 | if err != nil { 151 | return err 152 | } 153 | 154 | apiserverPort := int(conf.ApiserverPort) 155 | if apiserverPort == 0 { 156 | apiserverPort, err = utils.GetUnusedPort() 157 | if err != nil { 158 | return err 159 | } 160 | } 161 | apiserverPortStr := strconv.Itoa(apiserverPort) 162 | 163 | kubeApiserverArgs := []string{ 164 | "--admission-control", 165 | "", 166 | "--etcd-servers", 167 | "http://" + localAddress + ":" + etcdClientPortStr, 168 | "--etcd-prefix", 169 | "/prefix/registry", 170 | "--allow-privileged", 171 | } 172 | if conf.RuntimeConfig != "" { 173 | kubeApiserverArgs = append(kubeApiserverArgs, 174 | "--runtime-config", 175 | conf.RuntimeConfig, 176 | ) 177 | } 178 | if conf.FeatureGates != "" { 179 | kubeApiserverArgs = append(kubeApiserverArgs, 180 | "--feature-gates", 181 | conf.FeatureGates, 182 | ) 183 | } 184 | 185 | if conf.SecretPort { 186 | kubeApiserverArgs = append(kubeApiserverArgs, 187 | "--bind-address", 188 | serveAddress, 189 | "--secure-port", 190 | apiserverPortStr, 191 | "--tls-cert-file", 192 | adminCertPath, 193 | "--tls-private-key-file", 194 | adminKeyPath, 195 | "--client-ca-file", 196 | caCertPath, 197 | "--service-account-key-file", 198 | adminKeyPath, 199 | "--service-account-signing-key-file", 200 | adminKeyPath, 201 | "--service-account-issuer", 202 | "https://kubernetes.default.svc.cluster.local", 203 | ) 204 | } else { 205 | kubeApiserverArgs = append(kubeApiserverArgs, 206 | "--insecure-bind-address", 207 | serveAddress, 208 | "--insecure-port", 209 | apiserverPortStr, 210 | "--cert-dir", 211 | utils.PathJoin(conf.Workdir, "cert"), 212 | ) 213 | } 214 | err = utils.ForkExec(ctx, conf.Workdir, kubeApiserverPath, kubeApiserverArgs...) 215 | if err != nil { 216 | return err 217 | } 218 | 219 | kubeconfigData, err := k8s.BuildKubeconfig(k8s.BuildKubeconfigConfig{ 220 | ProjectName: conf.Name, 221 | SecretPort: conf.SecretPort, 222 | Address: scheme + "://" + localAddress + ":" + apiserverPortStr, 223 | AdminCrtPath: adminCertPath, 224 | AdminKeyPath: adminKeyPath, 225 | }) 226 | if err != nil { 227 | return err 228 | } 229 | 230 | kubeconfigPath := utils.PathJoin(conf.Workdir, runtime.InHostKubeconfigName) 231 | err = os.WriteFile(kubeconfigPath, []byte(kubeconfigData), 0644) 232 | if err != nil { 233 | return err 234 | } 235 | 236 | err = c.WaitReady(ctx, 30*time.Second) 237 | if err != nil { 238 | return fmt.Errorf("failed to wait for kube-apiserver ready: %v", err) 239 | } 240 | 241 | kubeControllerManagerArgs := []string{ 242 | "--kubeconfig", 243 | kubeconfigPath, 244 | } 245 | if conf.FeatureGates != "" { 246 | kubeControllerManagerArgs = append(kubeControllerManagerArgs, 247 | "--feature-gates", 248 | conf.FeatureGates, 249 | ) 250 | } 251 | 252 | kubeControllerManagerPort, err := utils.GetUnusedPort() 253 | if err != nil { 254 | return err 255 | } 256 | if conf.SecretPort { 257 | kubeControllerManagerArgs = append(kubeControllerManagerArgs, 258 | "--bind-address", 259 | localAddress, 260 | "--secure-port", 261 | strconv.Itoa(kubeControllerManagerPort), 262 | "--authorization-always-allow-paths", 263 | "/healthz,/metrics", 264 | ) 265 | } else { 266 | kubeControllerManagerArgs = append(kubeControllerManagerArgs, 267 | "--address", 268 | localAddress, 269 | "--port", 270 | strconv.Itoa(kubeControllerManagerPort), 271 | "--secure-port", 272 | "0", 273 | ) 274 | } 275 | 276 | err = utils.ForkExec(ctx, conf.Workdir, kubeControllerManagerPath, kubeControllerManagerArgs...) 277 | if err != nil { 278 | return err 279 | } 280 | 281 | kubeSchedulerArgs := []string{ 282 | "--kubeconfig", 283 | kubeconfigPath, 284 | } 285 | if conf.FeatureGates != "" { 286 | kubeSchedulerArgs = append(kubeSchedulerArgs, 287 | "--feature-gates", 288 | conf.FeatureGates, 289 | ) 290 | } 291 | 292 | kubeSchedulerPort, err := utils.GetUnusedPort() 293 | if err != nil { 294 | return err 295 | } 296 | if conf.SecretPort { 297 | kubeSchedulerArgs = append(kubeSchedulerArgs, 298 | "--bind-address", 299 | localAddress, 300 | "--secure-port", 301 | strconv.Itoa(kubeSchedulerPort), 302 | "--authorization-always-allow-paths", 303 | "/healthz,/metrics", 304 | ) 305 | } else { 306 | kubeSchedulerArgs = append(kubeSchedulerArgs, 307 | "--address", 308 | localAddress, 309 | "--port", 310 | strconv.Itoa(kubeSchedulerPort), 311 | "--secure-port", 312 | "0", 313 | ) 314 | } 315 | err = utils.ForkExec(ctx, conf.Workdir, kubeSchedulerPath, kubeSchedulerArgs...) 316 | if err != nil { 317 | return err 318 | } 319 | 320 | nodeTplPath := utils.PathJoin(conf.Workdir, "node.tpl") 321 | err = os.WriteFile(nodeTplPath, nodeTpl, 0644) 322 | if err != nil { 323 | return err 324 | } 325 | 326 | fakeKubeletArgs := []string{ 327 | "--kubeconfig", 328 | kubeconfigPath, 329 | "--take-over-all", 330 | "--node-name", 331 | conf.NodeName, 332 | "--generate-node-name", 333 | conf.GenerateNodeName, 334 | "--generate-replicas", 335 | strconv.Itoa(int(conf.GenerateReplicas)), 336 | "--node-template-file", 337 | nodeTplPath, 338 | } 339 | var fakeKubeletPort int 340 | if conf.PrometheusPort != 0 { 341 | fakeKubeletPort, err = utils.GetUnusedPort() 342 | if err != nil { 343 | return err 344 | } 345 | fakeKubeletArgs = append(fakeKubeletArgs, 346 | "--server-address", 347 | localAddress+":"+strconv.Itoa(fakeKubeletPort), 348 | ) 349 | } 350 | err = utils.ForkExec(ctx, conf.Workdir, fakeKubeletPath, fakeKubeletArgs...) 351 | if err != nil { 352 | return err 353 | } 354 | 355 | if conf.PrometheusPort != 0 { 356 | prometheusPortStr := strconv.Itoa(int(conf.PrometheusPort)) 357 | 358 | prometheusData, err := BuildPrometheus(BuildPrometheusConfig{ 359 | ProjectName: conf.Name, 360 | SecretPort: conf.SecretPort, 361 | AdminCrtPath: adminCertPath, 362 | AdminKeyPath: adminKeyPath, 363 | PrometheusPort: int(conf.PrometheusPort), 364 | EtcdPort: etcdClientPort, 365 | KubeApiserverPort: apiserverPort, 366 | KubeControllerManagerPort: kubeControllerManagerPort, 367 | KubeSchedulerPort: kubeSchedulerPort, 368 | FakeKubeletPort: fakeKubeletPort, 369 | }) 370 | if err != nil { 371 | return fmt.Errorf("failed to generate prometheus yaml: %s", err) 372 | } 373 | prometheusConfigPath := utils.PathJoin(conf.Workdir, runtime.Prometheus) 374 | err = os.WriteFile(prometheusConfigPath, []byte(prometheusData), 0644) 375 | if err != nil { 376 | return fmt.Errorf("failed to write prometheus yaml: %s", err) 377 | } 378 | 379 | prometheusPath := utils.PathJoin(bin, "prometheus") 380 | prometheusArgs := []string{ 381 | "--config.file", 382 | prometheusConfigPath, 383 | "--web.listen-address", 384 | serveAddress + ":" + prometheusPortStr, 385 | } 386 | err = utils.ForkExec(ctx, conf.Workdir, prometheusPath, prometheusArgs...) 387 | if err != nil { 388 | return err 389 | } 390 | } 391 | 392 | // set the context in default kubeconfig 393 | c.Kubectl(ctx, utils.IOStreams{}, "config", "set", "clusters."+conf.Name+".server", scheme+"://"+localAddress+":"+apiserverPortStr) 394 | c.Kubectl(ctx, utils.IOStreams{}, "config", "set", "contexts."+conf.Name+".cluster", conf.Name) 395 | if conf.SecretPort { 396 | c.Kubectl(ctx, utils.IOStreams{}, "config", "set", "clusters."+conf.Name+".insecure-skip-tls-verify", "true") 397 | c.Kubectl(ctx, utils.IOStreams{}, "config", "set", "contexts."+conf.Name+".user", conf.Name) 398 | c.Kubectl(ctx, utils.IOStreams{}, "config", "set", "users."+conf.Name+".client-certificate", adminCertPath) 399 | c.Kubectl(ctx, utils.IOStreams{}, "config", "set", "users."+conf.Name+".client-key", adminKeyPath) 400 | } 401 | return nil 402 | } 403 | 404 | //go:embed node.tpl 405 | var nodeTpl []byte 406 | 407 | func (c *Cluster) Down(ctx context.Context) error { 408 | conf, err := c.Config() 409 | if err != nil { 410 | return err 411 | } 412 | 413 | c.Kubectl(ctx, utils.IOStreams{}, "config", "unset", "clusters."+conf.Name) 414 | c.Kubectl(ctx, utils.IOStreams{}, "config", "unset", "users."+conf.Name) 415 | c.Kubectl(ctx, utils.IOStreams{}, "config", "unset", "contexts."+conf.Name) 416 | 417 | bin := utils.PathJoin(conf.Workdir, "bin") 418 | kubeApiserverPath := utils.PathJoin(bin, "kube-apiserver") 419 | kubeControllerManagerPath := utils.PathJoin(bin, "kube-controller-manager") 420 | kubeSchedulerPath := utils.PathJoin(bin, "kube-scheduler") 421 | fakeKubeletPath := utils.PathJoin(bin, "fake-kubelet") 422 | etcdPath := utils.PathJoin(bin, "etcd") 423 | prometheusPath := utils.PathJoin(bin, "prometheus") 424 | 425 | err = utils.ForkExecKill(ctx, conf.Workdir, fakeKubeletPath) 426 | if err != nil { 427 | c.Logger().Printf("failed to kill fake-kubelet: %s", err) 428 | } 429 | 430 | err = utils.ForkExecKill(ctx, conf.Workdir, kubeSchedulerPath) 431 | if err != nil { 432 | c.Logger().Printf("failed to kill kube-scheduler: %s", err) 433 | } 434 | 435 | err = utils.ForkExecKill(ctx, conf.Workdir, kubeControllerManagerPath) 436 | if err != nil { 437 | c.Logger().Printf("failed to kill kube-controller-manager: %s", err) 438 | } 439 | 440 | err = utils.ForkExecKill(ctx, conf.Workdir, kubeApiserverPath) 441 | if err != nil { 442 | c.Logger().Printf("failed to kill kube-apiserver: %s", err) 443 | } 444 | 445 | err = utils.ForkExecKill(ctx, conf.Workdir, etcdPath) 446 | if err != nil { 447 | c.Logger().Printf("failed to kill etcd: %s", err) 448 | } 449 | 450 | if conf.PrometheusPort != 0 { 451 | err = utils.ForkExecKill(ctx, conf.Workdir, prometheusPath) 452 | if err != nil { 453 | c.Logger().Printf("failed to kill prometheus: %s", err) 454 | } 455 | } 456 | 457 | return nil 458 | } 459 | 460 | func (c *Cluster) Start(ctx context.Context, name string) error { 461 | conf, err := c.Config() 462 | if err != nil { 463 | return err 464 | } 465 | 466 | bin := utils.PathJoin(conf.Workdir, "bin") 467 | svc := utils.PathJoin(bin, name) 468 | 469 | err = utils.ForkExecRestart(ctx, conf.Workdir, svc) 470 | if err != nil { 471 | return fmt.Errorf("failed to restart %s: %w", name, err) 472 | } 473 | return nil 474 | } 475 | 476 | func (c *Cluster) Stop(ctx context.Context, name string) error { 477 | conf, err := c.Config() 478 | if err != nil { 479 | return err 480 | } 481 | 482 | bin := utils.PathJoin(conf.Workdir, "bin") 483 | svc := utils.PathJoin(bin, name) 484 | 485 | err = utils.ForkExecKill(ctx, conf.Workdir, svc) 486 | if err != nil { 487 | return fmt.Errorf("failed to kill %s: %w", name, err) 488 | } 489 | return nil 490 | } 491 | 492 | func (c *Cluster) Logs(ctx context.Context, name string, out io.Writer) error { 493 | conf, err := c.Config() 494 | if err != nil { 495 | return err 496 | } 497 | 498 | logs := utils.PathJoin(conf.Workdir, "logs", filepath.Base(name)+".log") 499 | 500 | f, err := os.OpenFile(logs, os.O_RDONLY, 0644) 501 | if err != nil { 502 | return err 503 | } 504 | defer f.Close() 505 | 506 | io.Copy(out, f) 507 | return nil 508 | } 509 | 510 | func (c *Cluster) LogsFollow(ctx context.Context, name string, out io.Writer) error { 511 | conf, err := c.Config() 512 | if err != nil { 513 | return err 514 | } 515 | 516 | logs := utils.PathJoin(conf.Workdir, "logs", filepath.Base(name)+".log") 517 | 518 | t, err := tail.TailFile(logs, tail.Config{ReOpen: true, Follow: true}) 519 | if err != nil { 520 | return err 521 | } 522 | defer t.Stop() 523 | 524 | go func() { 525 | for line := range t.Lines { 526 | out.Write([]byte(line.Text + "\n")) 527 | } 528 | }() 529 | <-ctx.Done() 530 | return nil 531 | } 532 | -------------------------------------------------------------------------------- /pkg/runtime/binary/init.go: -------------------------------------------------------------------------------- 1 | package binary 2 | 3 | import ( 4 | "github.com/wzshiming/fake-k8s/pkg/runtime" 5 | ) 6 | 7 | func init() { 8 | runtime.Register("binary", NewCluster) 9 | } 10 | -------------------------------------------------------------------------------- /pkg/runtime/binary/node.tpl: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Node 3 | metadata: 4 | annotations: 5 | node.alpha.kubernetes.io/ttl: "0" 6 | labels: 7 | app: fake-kubelet 8 | beta.kubernetes.io/arch: amd64 9 | beta.kubernetes.io/os: linux 10 | kubernetes.io/arch: amd64 11 | kubernetes.io/hostname: {{ .metadata.name }} 12 | kubernetes.io/os: linux 13 | kubernetes.io/role: agent 14 | node-role.kubernetes.io/agent: "" 15 | type: fake-kubelet 16 | name: {{ .metadata.name }} -------------------------------------------------------------------------------- /pkg/runtime/binary/promehteus.go: -------------------------------------------------------------------------------- 1 | package binary 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "fmt" 7 | "text/template" 8 | ) 9 | 10 | //go:embed prometheus.yaml.tpl 11 | var prometheusYamlTpl string 12 | 13 | var prometheusYamlTemplate = template.Must(template.New("_").Delims("${{", "}}").Parse(prometheusYamlTpl)) 14 | 15 | func BuildPrometheus(conf BuildPrometheusConfig) (string, error) { 16 | buf := bytes.NewBuffer(nil) 17 | err := prometheusYamlTemplate.Execute(buf, conf) 18 | if err != nil { 19 | return "", fmt.Errorf("build prometheus error: %s", err) 20 | } 21 | return buf.String(), nil 22 | } 23 | 24 | type BuildPrometheusConfig struct { 25 | ProjectName string 26 | SecretPort bool 27 | AdminCrtPath string 28 | AdminKeyPath string 29 | PrometheusPort int 30 | EtcdPort int 31 | KubeApiserverPort int 32 | KubeControllerManagerPort int 33 | KubeSchedulerPort int 34 | FakeKubeletPort int 35 | } 36 | -------------------------------------------------------------------------------- /pkg/runtime/binary/prometheus.yaml.tpl: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 15s 3 | scrape_timeout: 10s 4 | evaluation_interval: 15s 5 | alerting: 6 | alertmanagers: 7 | - follow_redirects: true 8 | enable_http2: true 9 | scheme: http 10 | timeout: 10s 11 | api_version: v2 12 | static_configs: 13 | - targets: [] 14 | scrape_configs: 15 | - job_name: "prometheus" 16 | scheme: http 17 | honor_timestamps: true 18 | metrics_path: /metrics 19 | follow_redirects: true 20 | enable_http2: true 21 | static_configs: 22 | - targets: 23 | - localhost:${{ .PrometheusPort }} 24 | - job_name: "etcd" 25 | scheme: http 26 | honor_timestamps: true 27 | metrics_path: /metrics 28 | follow_redirects: true 29 | enable_http2: true 30 | static_configs: 31 | - targets: 32 | - localhost:${{ .EtcdPort }} 33 | - job_name: "fake-kubelet" 34 | scheme: http 35 | honor_timestamps: true 36 | metrics_path: /metrics 37 | follow_redirects: true 38 | enable_http2: true 39 | static_configs: 40 | - targets: 41 | - localhost:${{ .FakeKubeletPort }} 42 | 43 | ${{ if .SecretPort }} 44 | - job_name: "kube-apiserver" 45 | scheme: https 46 | honor_timestamps: true 47 | metrics_path: /metrics 48 | follow_redirects: true 49 | enable_http2: true 50 | tls_config: 51 | cert_file: "${{ .AdminCrtPath }}" 52 | key_file: "${{ .AdminKeyPath }}" 53 | insecure_skip_verify: true 54 | static_configs: 55 | - targets: 56 | - localhost:${{ .KubeApiserverPort }} 57 | - job_name: "kube-controller-manager" 58 | scheme: https 59 | honor_timestamps: true 60 | metrics_path: /metrics 61 | follow_redirects: true 62 | enable_http2: true 63 | tls_config: 64 | cert_file: "${{ .AdminCrtPath }}" 65 | key_file: "${{ .AdminKeyPath }}" 66 | insecure_skip_verify: true 67 | static_configs: 68 | - targets: 69 | - localhost:${{ .KubeControllerManagerPort }} 70 | - job_name: "kube-scheduler" 71 | scheme: https 72 | honor_timestamps: true 73 | metrics_path: /metrics 74 | follow_redirects: true 75 | enable_http2: true 76 | tls_config: 77 | cert_file: "${{ .AdminCrtPath }}" 78 | key_file: "${{ .AdminKeyPath }}" 79 | insecure_skip_verify: true 80 | static_configs: 81 | - targets: 82 | - localhost:${{ .KubeSchedulerPort }} 83 | ${{ else }} 84 | - job_name: "kube-apiserver" 85 | scheme: http 86 | honor_timestamps: true 87 | metrics_path: /metrics 88 | follow_redirects: true 89 | enable_http2: true 90 | static_configs: 91 | - targets: 92 | - localhost:${{ .KubeApiserverPort }} 93 | - job_name: "kube-controller-manager" 94 | scheme: http 95 | honor_timestamps: true 96 | metrics_path: /metrics 97 | follow_redirects: true 98 | enable_http2: true 99 | static_configs: 100 | - targets: 101 | - localhost:${{ .KubeControllerManagerPort }} 102 | - job_name: "kube-scheduler" 103 | scheme: http 104 | honor_timestamps: true 105 | metrics_path: /metrics 106 | follow_redirects: true 107 | enable_http2: true 108 | static_configs: 109 | - targets: 110 | - localhost:${{ .KubeSchedulerPort }} 111 | ${{ end }} -------------------------------------------------------------------------------- /pkg/runtime/cluster.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "os" 7 | "os/exec" 8 | "time" 9 | 10 | "github.com/wzshiming/fake-k8s/pkg/log" 11 | "github.com/wzshiming/fake-k8s/pkg/utils" 12 | "github.com/wzshiming/fake-k8s/pkg/vars" 13 | "sigs.k8s.io/yaml" 14 | ) 15 | 16 | var ( 17 | RawClusterConfigName = "fake-k8s.yaml" 18 | InHostKubeconfigName = "kubeconfig.yaml" 19 | InClusterKubeconfigName = "kubeconfig" 20 | EtcdDataDirName = "etcd" 21 | PkiName = "pki" 22 | ComposeName = "docker-compose.yaml" 23 | Prometheus = "prometheus.yaml" 24 | KindName = "kind.yaml" 25 | FakeKubeletDeploy = "fake-kubelet-deploy.yaml" 26 | PrometheusDeploy = "prometheus-deploy.yaml" 27 | ) 28 | 29 | type Cluster struct { 30 | workdir string 31 | name string 32 | conf *Config 33 | logger log.Logger 34 | } 35 | 36 | func NewCluster(name, workdir string, logger log.Logger) *Cluster { 37 | return &Cluster{ 38 | name: name, 39 | workdir: workdir, 40 | logger: logger, 41 | } 42 | } 43 | 44 | func (c *Cluster) Logger() log.Logger { 45 | return c.logger 46 | } 47 | 48 | func (c *Cluster) Config() (*Config, error) { 49 | if c.conf != nil { 50 | return c.conf, nil 51 | } 52 | config, err := os.ReadFile(utils.PathJoin(c.workdir, RawClusterConfigName)) 53 | if err != nil { 54 | return nil, err 55 | } 56 | conf := Config{} 57 | err = yaml.Unmarshal(config, &conf) 58 | if err != nil { 59 | return nil, err 60 | } 61 | c.conf = &conf 62 | return c.conf, nil 63 | } 64 | 65 | func (c *Cluster) InHostKubeconfig() (string, error) { 66 | conf, err := c.Config() 67 | if err != nil { 68 | return "", err 69 | } 70 | 71 | return utils.PathJoin(conf.Workdir, InHostKubeconfigName), nil 72 | } 73 | 74 | func (c *Cluster) Load(ctx context.Context) (conf Config, err error) { 75 | file, err := os.ReadFile(utils.PathJoin(c.workdir, RawClusterConfigName)) 76 | if err != nil { 77 | return Config{}, err 78 | } 79 | err = yaml.Unmarshal(file, &conf) 80 | if err != nil { 81 | return Config{}, err 82 | } 83 | return conf, nil 84 | } 85 | 86 | func (c *Cluster) Install(ctx context.Context, conf Config) error { 87 | config, err := yaml.Marshal(conf) 88 | if err != nil { 89 | return err 90 | } 91 | err = os.MkdirAll(c.workdir, 0755) 92 | if err != nil { 93 | return err 94 | } 95 | 96 | bin := utils.PathJoin(conf.Workdir, "bin") 97 | 98 | kubectlPath := utils.PathJoin(bin, "kubectl"+vars.BinSuffix) 99 | err = utils.DownloadWithCache(ctx, conf.CacheDir, vars.MustKubectlBinary, kubectlPath, 0755, conf.QuietPull) 100 | if err != nil { 101 | return err 102 | } 103 | 104 | if err != nil { 105 | return err 106 | } 107 | err = os.WriteFile(utils.PathJoin(c.workdir, RawClusterConfigName), config, 0644) 108 | if err != nil { 109 | return err 110 | } 111 | return nil 112 | } 113 | 114 | func (c *Cluster) Uninstall(ctx context.Context) error { 115 | conf, err := c.Config() 116 | if err != nil { 117 | return err 118 | } 119 | 120 | // cleanup workdir 121 | os.RemoveAll(conf.Workdir) 122 | return nil 123 | } 124 | 125 | func (c *Cluster) Ready(ctx context.Context) (bool, error) { 126 | out := bytes.NewBuffer(nil) 127 | err := c.KubectlInCluster(ctx, utils.IOStreams{ 128 | Out: out, 129 | ErrOut: out, 130 | }, "get", "node") 131 | if err != nil { 132 | return false, err 133 | } 134 | 135 | ready := !bytes.Contains(out.Bytes(), []byte("NotReady")) 136 | return ready, nil 137 | } 138 | 139 | func (c *Cluster) WaitReady(ctx context.Context, timeout time.Duration) error { 140 | var err error 141 | var ready bool 142 | for i := 0; i < int(timeout/time.Second); i++ { 143 | ready, err = c.Ready(ctx) 144 | if ready { 145 | return nil 146 | } 147 | time.Sleep(time.Second) 148 | } 149 | return err 150 | } 151 | 152 | func (c *Cluster) Kubectl(ctx context.Context, stm utils.IOStreams, args ...string) error { 153 | conf, err := c.Config() 154 | if err != nil { 155 | return err 156 | } 157 | 158 | kubectlPath, err := exec.LookPath("kubectl") 159 | if err != nil { 160 | bin := utils.PathJoin(conf.Workdir, "bin") 161 | kubectlPath = utils.PathJoin(bin, "kubectl"+vars.BinSuffix) 162 | err = utils.DownloadWithCache(ctx, conf.CacheDir, vars.MustKubectlBinary, kubectlPath, 0755, conf.QuietPull) 163 | if err != nil { 164 | return err 165 | } 166 | } 167 | return utils.Exec(ctx, "", stm, kubectlPath, args...) 168 | } 169 | 170 | func (c *Cluster) KubectlInCluster(ctx context.Context, stm utils.IOStreams, args ...string) error { 171 | conf, err := c.Config() 172 | if err != nil { 173 | return err 174 | } 175 | 176 | bin := utils.PathJoin(conf.Workdir, "bin") 177 | kubectlPath := utils.PathJoin(bin, "kubectl"+vars.BinSuffix) 178 | err = utils.DownloadWithCache(ctx, conf.CacheDir, vars.MustKubectlBinary, kubectlPath, 0755, conf.QuietPull) 179 | if err != nil { 180 | return err 181 | } 182 | return utils.Exec(ctx, "", stm, kubectlPath, 183 | append([]string{"--kubeconfig", utils.PathJoin(conf.Workdir, InHostKubeconfigName)}, args...)...) 184 | } 185 | -------------------------------------------------------------------------------- /pkg/runtime/compose/cluster.go: -------------------------------------------------------------------------------- 1 | package compose 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "os" 8 | "strconv" 9 | 10 | "github.com/wzshiming/fake-k8s/pkg/k8s" 11 | "github.com/wzshiming/fake-k8s/pkg/log" 12 | "github.com/wzshiming/fake-k8s/pkg/pki" 13 | "github.com/wzshiming/fake-k8s/pkg/runtime" 14 | "github.com/wzshiming/fake-k8s/pkg/utils" 15 | ) 16 | 17 | type Cluster struct { 18 | *runtime.Cluster 19 | } 20 | 21 | func NewCluster(name, workdir string, logger log.Logger) (runtime.Runtime, error) { 22 | return &Cluster{ 23 | Cluster: runtime.NewCluster(name, workdir, logger), 24 | }, nil 25 | } 26 | 27 | func (c *Cluster) Install(ctx context.Context, conf runtime.Config) error { 28 | err := c.Cluster.Install(ctx, conf) 29 | if err != nil { 30 | return err 31 | } 32 | 33 | kubeconfigPath := utils.PathJoin(conf.Workdir, runtime.InHostKubeconfigName) 34 | prometheusPath := "" 35 | inClusterOnHostKubeconfigPath := utils.PathJoin(conf.Workdir, runtime.InClusterKubeconfigName) 36 | pkiPath := utils.PathJoin(conf.Workdir, runtime.PkiName) 37 | composePath := utils.PathJoin(conf.Workdir, runtime.ComposeName) 38 | 39 | caCertPath := "" 40 | adminKeyPath := "" 41 | adminCertPath := "" 42 | inClusterKubeconfigPath := "/root/.kube/config" 43 | inClusterEtcdDataPath := "/etcd-data" 44 | InClusterPrometheusPath := "/etc/prometheus/prometheus.yml" 45 | inClusterAdminKeyPath := "" 46 | inClusterAdminCertPath := "" 47 | inClusterCACertPath := "" 48 | inClusterPort := 8080 49 | scheme := "http" 50 | 51 | // generate ca cert 52 | if conf.SecretPort { 53 | err := pki.DumpPki(pkiPath) 54 | if err != nil { 55 | return fmt.Errorf("failed to generate pki: %s", err) 56 | } 57 | caCertPath = utils.PathJoin(pkiPath, "ca.crt") 58 | adminKeyPath = utils.PathJoin(pkiPath, "admin.key") 59 | adminCertPath = utils.PathJoin(pkiPath, "admin.crt") 60 | inClusterPkiPath := "/etc/kubernetes/pki/" 61 | inClusterCACertPath = utils.PathJoin(inClusterPkiPath, "ca.crt") 62 | inClusterAdminKeyPath = utils.PathJoin(inClusterPkiPath, "admin.key") 63 | inClusterAdminCertPath = utils.PathJoin(inClusterPkiPath, "admin.crt") 64 | inClusterPort = 6443 65 | scheme = "https" 66 | } 67 | 68 | // Setup prometheus 69 | if conf.PrometheusPort != 0 { 70 | prometheusPath = utils.PathJoin(conf.Workdir, runtime.Prometheus) 71 | prometheusData, err := BuildPrometheus(BuildPrometheusConfig{ 72 | ProjectName: conf.Name, 73 | SecretPort: conf.SecretPort, 74 | AdminCrtPath: inClusterAdminCertPath, 75 | AdminKeyPath: inClusterAdminKeyPath, 76 | }) 77 | if err != nil { 78 | return fmt.Errorf("failed to generate prometheus yaml: %s", err) 79 | } 80 | err = os.WriteFile(prometheusPath, []byte(prometheusData), 0644) 81 | if err != nil { 82 | return fmt.Errorf("failed to write prometheus yaml: %s", err) 83 | } 84 | } 85 | 86 | apiserverPort := int(conf.ApiserverPort) 87 | if apiserverPort == 0 { 88 | apiserverPort, err = utils.GetUnusedPort() 89 | if err != nil { 90 | return err 91 | } 92 | } 93 | 94 | // Setup compose 95 | dockercompose, err := BuildCompose(BuildComposeConfig{ 96 | ProjectName: conf.Name, 97 | ApiserverPort: uint32(apiserverPort), 98 | KubeconfigPath: inClusterOnHostKubeconfigPath, 99 | AdminCertPath: adminCertPath, 100 | AdminKeyPath: adminKeyPath, 101 | CACertPath: caCertPath, 102 | InClusterKubeconfigPath: inClusterKubeconfigPath, 103 | InClusterAdminCertPath: inClusterAdminCertPath, 104 | InClusterAdminKeyPath: inClusterAdminKeyPath, 105 | InClusterCACertPath: inClusterCACertPath, 106 | InClusterEtcdDataPath: inClusterEtcdDataPath, 107 | InClusterPrometheusPath: InClusterPrometheusPath, 108 | PrometheusPath: prometheusPath, 109 | EtcdImage: conf.EtcdImage, 110 | KubeApiserverImage: conf.KubeApiserverImage, 111 | KubeControllerManagerImage: conf.KubeControllerManagerImage, 112 | KubeSchedulerImage: conf.KubeSchedulerImage, 113 | FakeKubeletImage: conf.FakeKubeletImage, 114 | PrometheusImage: conf.PrometheusImage, 115 | SecretPort: conf.SecretPort, 116 | QuietPull: conf.QuietPull, 117 | PrometheusPort: conf.PrometheusPort, 118 | GenerateNodeName: conf.GenerateNodeName, 119 | GenerateReplicas: conf.GenerateReplicas, 120 | NodeName: conf.NodeName, 121 | RuntimeConfig: conf.RuntimeConfig, 122 | FeatureGates: conf.FeatureGates, 123 | }) 124 | if err != nil { 125 | return err 126 | } 127 | 128 | // Setup kubeconfig 129 | kubeconfigData, err := k8s.BuildKubeconfig(k8s.BuildKubeconfigConfig{ 130 | ProjectName: conf.Name, 131 | SecretPort: conf.SecretPort, 132 | Address: scheme + "://127.0.0.1:" + strconv.Itoa(apiserverPort), 133 | AdminCrtPath: adminCertPath, 134 | AdminKeyPath: adminKeyPath, 135 | }) 136 | if err != nil { 137 | return err 138 | } 139 | inClusterKubeconfigData, err := k8s.BuildKubeconfig(k8s.BuildKubeconfigConfig{ 140 | ProjectName: conf.Name, 141 | SecretPort: conf.SecretPort, 142 | Address: scheme + "://" + conf.Name + "-kube-apiserver:" + strconv.Itoa(inClusterPort), 143 | AdminCrtPath: inClusterAdminCertPath, 144 | AdminKeyPath: inClusterAdminKeyPath, 145 | }) 146 | if err != nil { 147 | return err 148 | } 149 | 150 | // Save config 151 | err = os.WriteFile(kubeconfigPath, []byte(kubeconfigData), 0644) 152 | if err != nil { 153 | return err 154 | } 155 | 156 | err = os.WriteFile(inClusterOnHostKubeconfigPath, []byte(inClusterKubeconfigData), 0644) 157 | if err != nil { 158 | return err 159 | } 160 | 161 | err = os.WriteFile(composePath, []byte(dockercompose), 0644) 162 | if err != nil { 163 | return err 164 | } 165 | 166 | // set the context in default kubeconfig 167 | c.Kubectl(ctx, utils.IOStreams{}, "config", "set", "clusters."+conf.Name+".server", scheme+"://127.0.0.1:"+strconv.Itoa(apiserverPort)) 168 | c.Kubectl(ctx, utils.IOStreams{}, "config", "set", "contexts."+conf.Name+".cluster", conf.Name) 169 | if conf.SecretPort { 170 | c.Kubectl(ctx, utils.IOStreams{}, "config", "set", "clusters."+conf.Name+".insecure-skip-tls-verify", "true") 171 | c.Kubectl(ctx, utils.IOStreams{}, "config", "set", "contexts."+conf.Name+".user", conf.Name) 172 | c.Kubectl(ctx, utils.IOStreams{}, "config", "set", "users."+conf.Name+".client-certificate", adminCertPath) 173 | c.Kubectl(ctx, utils.IOStreams{}, "config", "set", "users."+conf.Name+".client-key", adminKeyPath) 174 | } 175 | 176 | var out io.Writer = os.Stderr 177 | if conf.QuietPull { 178 | out = nil 179 | } 180 | images := []string{ 181 | conf.EtcdImage, 182 | conf.KubeApiserverImage, 183 | conf.KubeControllerManagerImage, 184 | conf.KubeSchedulerImage, 185 | conf.FakeKubeletImage, 186 | } 187 | if conf.PrometheusPort != 0 { 188 | images = append(images, conf.PrometheusImage) 189 | } 190 | for _, image := range images { 191 | err = utils.Exec(ctx, "", utils.IOStreams{}, conf.Runtime, "inspect", 192 | image, 193 | ) 194 | if err != nil { 195 | err = utils.Exec(ctx, "", utils.IOStreams{ 196 | Out: out, 197 | ErrOut: out, 198 | }, conf.Runtime, "pull", 199 | image, 200 | ) 201 | if err != nil { 202 | return err 203 | } 204 | } 205 | } 206 | return nil 207 | } 208 | 209 | func (c *Cluster) Uninstall(ctx context.Context) error { 210 | conf, err := c.Config() 211 | if err != nil { 212 | return err 213 | } 214 | 215 | // unset the context in default kubeconfig 216 | c.Kubectl(ctx, utils.IOStreams{}, "config", "unset", "clusters."+conf.Name) 217 | c.Kubectl(ctx, utils.IOStreams{}, "config", "unset", "users."+conf.Name) 218 | c.Kubectl(ctx, utils.IOStreams{}, "config", "unset", "contexts."+conf.Name) 219 | 220 | err = c.Cluster.Uninstall(ctx) 221 | if err != nil { 222 | return err 223 | } 224 | return nil 225 | } 226 | 227 | func (c *Cluster) Up(ctx context.Context) error { 228 | conf, err := c.Config() 229 | if err != nil { 230 | return err 231 | } 232 | args := []string{"compose", "up", "-d"} 233 | if conf.QuietPull { 234 | args = append(args, "--quiet-pull") 235 | } 236 | err = utils.Exec(ctx, conf.Workdir, utils.IOStreams{ 237 | ErrOut: os.Stderr, 238 | }, conf.Runtime, args...) 239 | if err != nil { 240 | return err 241 | } 242 | 243 | return nil 244 | } 245 | 246 | func (c *Cluster) Down(ctx context.Context) error { 247 | conf, err := c.Config() 248 | if err != nil { 249 | return err 250 | } 251 | args := []string{"compose", "down"} 252 | err = utils.Exec(ctx, conf.Workdir, utils.IOStreams{ 253 | ErrOut: os.Stderr, 254 | }, conf.Runtime, args...) 255 | if err != nil { 256 | c.Logger().Printf("Failed to down cluster: %v", err) 257 | } 258 | return nil 259 | } 260 | 261 | func (c *Cluster) Start(ctx context.Context, name string) error { 262 | conf, err := c.Config() 263 | if err != nil { 264 | return err 265 | } 266 | err = utils.Exec(ctx, conf.Workdir, utils.IOStreams{}, conf.Runtime, "start", conf.Name+"-"+name) 267 | if err != nil { 268 | return err 269 | } 270 | return nil 271 | } 272 | 273 | func (c *Cluster) Stop(ctx context.Context, name string) error { 274 | conf, err := c.Config() 275 | if err != nil { 276 | return err 277 | } 278 | err = utils.Exec(ctx, conf.Workdir, utils.IOStreams{}, conf.Runtime, "stop", conf.Name+"-"+name) 279 | if err != nil { 280 | return err 281 | } 282 | return nil 283 | } 284 | 285 | func (c *Cluster) logs(ctx context.Context, name string, out io.Writer, follow bool) error { 286 | conf, err := c.Config() 287 | if err != nil { 288 | return err 289 | } 290 | args := []string{"logs"} 291 | if follow { 292 | args = append(args, "-f") 293 | } 294 | args = append(args, conf.Name+"-"+name) 295 | err = utils.Exec(ctx, conf.Workdir, utils.IOStreams{ 296 | ErrOut: out, 297 | Out: out, 298 | }, conf.Runtime, args...) 299 | if err != nil { 300 | return err 301 | } 302 | return nil 303 | } 304 | 305 | func (c *Cluster) Logs(ctx context.Context, name string, out io.Writer) error { 306 | return c.logs(ctx, name, out, false) 307 | } 308 | 309 | func (c *Cluster) LogsFollow(ctx context.Context, name string, out io.Writer) error { 310 | return c.logs(ctx, name, out, true) 311 | } 312 | -------------------------------------------------------------------------------- /pkg/runtime/compose/compose.go: -------------------------------------------------------------------------------- 1 | package compose 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "fmt" 7 | "text/template" 8 | ) 9 | 10 | //go:embed compose.yaml.tpl 11 | var composeYamlTpl string 12 | 13 | var composeYamlTemplate = template.Must(template.New("_").Delims("${{", "}}").Parse(composeYamlTpl)) 14 | 15 | func BuildCompose(conf BuildComposeConfig) (string, error) { 16 | buf := bytes.NewBuffer(nil) 17 | err := composeYamlTemplate.Execute(buf, conf) 18 | if err != nil { 19 | return "", fmt.Errorf("failed to execute compose yaml template: %w", err) 20 | } 21 | return buf.String(), nil 22 | } 23 | 24 | type BuildComposeConfig struct { 25 | ProjectName string 26 | 27 | PrometheusImage string 28 | EtcdImage string 29 | KubeApiserverImage string 30 | KubeControllerManagerImage string 31 | KubeSchedulerImage string 32 | FakeKubeletImage string 33 | 34 | PrometheusPath string 35 | AdminKeyPath string 36 | AdminCertPath string 37 | CACertPath string 38 | KubeconfigPath string 39 | InClusterAdminKeyPath string 40 | InClusterAdminCertPath string 41 | InClusterCACertPath string 42 | InClusterKubeconfigPath string 43 | InClusterEtcdDataPath string 44 | InClusterPrometheusPath string 45 | 46 | SecretPort bool 47 | QuietPull bool 48 | 49 | ApiserverPort uint32 50 | PrometheusPort uint32 51 | 52 | NodeName string 53 | GenerateNodeName string 54 | GenerateReplicas uint32 55 | 56 | RuntimeConfig string 57 | FeatureGates string 58 | } 59 | -------------------------------------------------------------------------------- /pkg/runtime/compose/compose.yaml.tpl: -------------------------------------------------------------------------------- 1 | version: "3.1" 2 | services: 3 | 4 | # Etcd 5 | etcd: 6 | container_name: "${{ .ProjectName }}-etcd" 7 | image: ${{ .EtcdImage }} 8 | restart: always 9 | command: 10 | - etcd 11 | - --data-dir 12 | - ${{ .InClusterEtcdDataPath }} 13 | - --name 14 | - node0 15 | - --initial-advertise-peer-urls 16 | - http://0.0.0.0:2380 17 | - --listen-peer-urls 18 | - http://0.0.0.0:2380 19 | - --advertise-client-urls 20 | - http://0.0.0.0:2379 21 | - --listen-client-urls 22 | - http://0.0.0.0:2379 23 | - --initial-cluster 24 | - node0=http://0.0.0.0:2380 25 | - --auto-compaction-retention 26 | - "1" 27 | - --quota-backend-bytes 28 | - "8589934592" 29 | 30 | # Kube-apiserver 31 | kube_apiserver: 32 | container_name: "${{ .ProjectName }}-kube-apiserver" 33 | image: ${{ .KubeApiserverImage }} 34 | restart: always 35 | links: 36 | - etcd 37 | ports: 38 | ${{ if .SecretPort }} 39 | - ${{ .ApiserverPort }}:6443 40 | ${{ else }} 41 | - ${{ .ApiserverPort }}:8080 42 | ${{ end }} 43 | command: 44 | - kube-apiserver 45 | - --admission-control 46 | - "" 47 | - --etcd-servers 48 | - http://${{ .ProjectName }}-etcd:2379 49 | - --etcd-prefix 50 | - /prefix/registry 51 | - --allow-privileged 52 | ${{ if .RuntimeConfig }} 53 | - --runtime-config 54 | - ${{ .RuntimeConfig }} 55 | ${{ end }} 56 | ${{ if .FeatureGates }} 57 | - --feature-gates 58 | - ${{ .FeatureGates }} 59 | ${{ end }} 60 | ${{ if .SecretPort }} 61 | - --bind-address 62 | - 0.0.0.0 63 | - --secure-port 64 | - "6443" 65 | - --tls-cert-file 66 | - ${{ .InClusterAdminCertPath }} 67 | - --tls-private-key-file 68 | - ${{ .InClusterAdminKeyPath }} 69 | - --client-ca-file 70 | - ${{ .InClusterCACertPath }} 71 | - --service-account-key-file 72 | - ${{ .InClusterAdminKeyPath }} 73 | - --service-account-signing-key-file 74 | - ${{ .InClusterAdminKeyPath }} 75 | - --service-account-issuer 76 | - https://kubernetes.default.svc.cluster.local 77 | ${{ else }} 78 | - --insecure-bind-address 79 | - 0.0.0.0 80 | - --insecure-port 81 | - "8080" 82 | ${{ end }} 83 | 84 | ${{ if .SecretPort }} 85 | volumes: 86 | - ${{ .AdminKeyPath }}:${{ .InClusterAdminKeyPath }}:ro 87 | - ${{ .AdminCertPath }}:${{ .InClusterAdminCertPath }}:ro 88 | - ${{ .CACertPath }}:${{ .InClusterCACertPath }}:ro 89 | ${{ end }} 90 | 91 | 92 | # Kube-controller-manager 93 | kube_controller_manager: 94 | container_name: "${{ .ProjectName }}-kube-controller-manager" 95 | image: ${{ .KubeControllerManagerImage }} 96 | restart: always 97 | links: 98 | - kube_apiserver 99 | command: 100 | - kube-controller-manager 101 | - --kubeconfig 102 | - ${{ .InClusterKubeconfigPath }} 103 | ${{ if .FeatureGates }} 104 | - --feature-gates 105 | - ${{ .FeatureGates }} 106 | ${{ end }} 107 | ${{ if .PrometheusPath }} 108 | ${{ if .SecretPort }} 109 | - --bind-address 110 | - 0.0.0.0 111 | - --secure-port 112 | - "10257" 113 | - --authorization-always-allow-paths 114 | - /healthz,/metrics 115 | ${{ else }} 116 | - --address 117 | - 0.0.0.0 118 | - --port 119 | - "10252" 120 | ${{ end }} 121 | ${{ end }} 122 | volumes: 123 | - ${{ .KubeconfigPath }}:${{ .InClusterKubeconfigPath }}:ro 124 | ${{ if .SecretPort }} 125 | - ${{ .AdminKeyPath }}:${{ .InClusterAdminKeyPath }}:ro 126 | - ${{ .AdminCertPath }}:${{ .InClusterAdminCertPath }}:ro 127 | - ${{ .CACertPath }}:${{ .InClusterCACertPath }}:ro 128 | ${{ end }} 129 | 130 | # Kube-scheduler 131 | kube_scheduler: 132 | container_name: "${{ .ProjectName }}-kube-scheduler" 133 | image: ${{ .KubeSchedulerImage }} 134 | restart: always 135 | links: 136 | - kube_apiserver 137 | command: 138 | - kube-scheduler 139 | - --kubeconfig 140 | - ${{ .InClusterKubeconfigPath }} 141 | ${{ if .FeatureGates }} 142 | - --feature-gates 143 | - ${{ .FeatureGates }} 144 | ${{ end }} 145 | ${{ if .PrometheusPath }} 146 | ${{ if .SecretPort }} 147 | - --bind-address 148 | - 0.0.0.0 149 | - --secure-port 150 | - "10259" 151 | - --authorization-always-allow-paths 152 | - /healthz,/metrics 153 | ${{ else }} 154 | - --address 155 | - 0.0.0.0 156 | - --port 157 | - "10251" 158 | ${{ end }} 159 | ${{ end }} 160 | volumes: 161 | - ${{ .KubeconfigPath }}:${{ .InClusterKubeconfigPath }}:ro 162 | ${{ if .SecretPort }} 163 | - ${{ .AdminKeyPath }}:${{ .InClusterAdminKeyPath }}:ro 164 | - ${{ .AdminCertPath }}:${{ .InClusterAdminCertPath }}:ro 165 | - ${{ .CACertPath }}:${{ .InClusterCACertPath }}:ro 166 | ${{ end }} 167 | 168 | # Fake-kubelet 169 | fake_kubelet: 170 | container_name: "${{ .ProjectName }}-fake-kubelet" 171 | image: ${{ .FakeKubeletImage }} 172 | restart: always 173 | command: 174 | - --kubeconfig 175 | - ${{ .InClusterKubeconfigPath }} 176 | links: 177 | - kube_apiserver 178 | environment: 179 | TAKE_OVER_ALL: "true" 180 | NODE_NAME: "${{ .NodeName }}" 181 | GENERATE_NODE_NAME: "${{ .GenerateNodeName }}" 182 | GENERATE_REPLICAS: "${{ .GenerateReplicas }}" 183 | CIDR: 10.0.0.1/24 184 | NODE_TEMPLATE: |- 185 | apiVersion: v1 186 | kind: Node 187 | metadata: 188 | annotations: 189 | node.alpha.kubernetes.io/ttl: "0" 190 | labels: 191 | app: fake-kubelet 192 | beta.kubernetes.io/arch: amd64 193 | beta.kubernetes.io/os: linux 194 | kubernetes.io/arch: amd64 195 | kubernetes.io/hostname: {{ .metadata.name }} 196 | kubernetes.io/os: linux 197 | kubernetes.io/role: agent 198 | node-role.kubernetes.io/agent: "" 199 | type: fake-kubelet 200 | name: {{ .metadata.name }} 201 | NODE_INITIALIZATION_TEMPLATE: |- 202 | {{ with .status }} 203 | 204 | addresses: 205 | {{ with .addresses }} 206 | {{ YAML . 1 }} 207 | {{ else }} 208 | - address: {{ NodeIP }} 209 | type: InternalIP 210 | {{ end }} 211 | 212 | allocatable: 213 | {{ with .allocatable }} 214 | {{ YAML . 1 }} 215 | {{ else }} 216 | cpu: 1k 217 | memory: 1Ti 218 | pods: 1M 219 | {{ end }} 220 | 221 | capacity: 222 | {{ with .capacity }} 223 | {{ YAML . 1 }} 224 | {{ else }} 225 | cpu: 1k 226 | memory: 1Ti 227 | pods: 1M 228 | {{ end }} 229 | 230 | {{ with .nodeInfo }} 231 | nodeInfo: 232 | architecture: {{ with .architecture }} {{ . }} {{ else }} "amd64" {{ end }} 233 | bootID: {{ with .bootID }} {{ . }} {{ else }} "" {{ end }} 234 | containerRuntimeVersion: {{ with .containerRuntimeVersion }} {{ . }} {{ else }} "" {{ end }} 235 | kernelVersion: {{ with .kernelVersion }} {{ . }} {{ else }} "" {{ end }} 236 | kubeProxyVersion: {{ with .kubeProxyVersion }} {{ . }} {{ else }} "fake" {{ end }} 237 | kubeletVersion: {{ with .kubeletVersion }} {{ . }} {{ else }} "fake" {{ end }} 238 | machineID: {{ with .machineID }} {{ . }} {{ else }} "" {{ end }} 239 | operatingSystem: {{ with .operatingSystem }} {{ . }} {{ else }} "linux" {{ end }} 240 | osImage: {{ with .osImage }} {{ . }} {{ else }} "" {{ end }} 241 | systemUUID: {{ with .osImage }} {{ . }} {{ else }} "" {{ end }} 242 | {{ end }} 243 | 244 | phase: Running 245 | 246 | {{ end }} 247 | ${{ if .PrometheusPath }} 248 | SERVER_ADDRESS: :8080 249 | ${{ end }} 250 | volumes: 251 | - ${{ .KubeconfigPath }}:${{ .InClusterKubeconfigPath }}:ro 252 | ${{ if .SecretPort }} 253 | - ${{ .AdminKeyPath }}:${{ .InClusterAdminKeyPath }}:ro 254 | - ${{ .AdminCertPath }}:${{ .InClusterAdminCertPath }}:ro 255 | - ${{ .CACertPath }}:${{ .InClusterCACertPath }}:ro 256 | ${{ end }} 257 | 258 | ${{ if .PrometheusPath }} 259 | # Prometheus 260 | prometheus: 261 | container_name: "${{ .ProjectName }}-prometheus" 262 | image: ${{ .PrometheusImage }} 263 | restart: always 264 | links: 265 | - kube_controller_manager 266 | - kube_scheduler 267 | - kube_apiserver 268 | - etcd 269 | - fake_kubelet 270 | command: 271 | - --config.file 272 | - ${{ .InClusterPrometheusPath }} 273 | ports: 274 | - ${{ .PrometheusPort }}:9090 275 | volumes: 276 | - ${{ .PrometheusPath }}:${{ .InClusterPrometheusPath }}:ro 277 | ${{ if .SecretPort }} 278 | - ${{ .AdminKeyPath }}:${{ .InClusterAdminKeyPath }}:ro 279 | - ${{ .AdminCertPath }}:${{ .InClusterAdminCertPath }}:ro 280 | - ${{ .CACertPath }}:${{ .InClusterCACertPath }}:ro 281 | ${{ end }} 282 | ${{ end }} 283 | 284 | # Network 285 | networks: 286 | default: 287 | name: ${{ .ProjectName }} 288 | -------------------------------------------------------------------------------- /pkg/runtime/compose/init.go: -------------------------------------------------------------------------------- 1 | package compose 2 | 3 | import ( 4 | "github.com/wzshiming/fake-k8s/pkg/runtime" 5 | ) 6 | 7 | func init() { 8 | runtime.Register("docker", NewCluster) 9 | runtime.Register("nerdctl", NewCluster) 10 | } 11 | -------------------------------------------------------------------------------- /pkg/runtime/compose/prometheus.go: -------------------------------------------------------------------------------- 1 | package compose 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "fmt" 7 | "text/template" 8 | ) 9 | 10 | //go:embed prometheus.yaml.tpl 11 | var prometheusYamlTpl string 12 | 13 | var prometheusYamlTemplate = template.Must(template.New("_").Delims("${{", "}}").Parse(prometheusYamlTpl)) 14 | 15 | func BuildPrometheus(conf BuildPrometheusConfig) (string, error) { 16 | buf := bytes.NewBuffer(nil) 17 | err := prometheusYamlTemplate.Execute(buf, conf) 18 | if err != nil { 19 | return "", fmt.Errorf("build prometheus error: %s", err) 20 | } 21 | return buf.String(), nil 22 | } 23 | 24 | type BuildPrometheusConfig struct { 25 | ProjectName string 26 | SecretPort bool 27 | AdminCrtPath string 28 | AdminKeyPath string 29 | } 30 | -------------------------------------------------------------------------------- /pkg/runtime/compose/prometheus.yaml.tpl: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 15s 3 | scrape_timeout: 10s 4 | evaluation_interval: 15s 5 | alerting: 6 | alertmanagers: 7 | - follow_redirects: true 8 | enable_http2: true 9 | scheme: http 10 | timeout: 10s 11 | api_version: v2 12 | static_configs: 13 | - targets: [] 14 | scrape_configs: 15 | - job_name: "prometheus" 16 | scheme: http 17 | honor_timestamps: true 18 | metrics_path: /metrics 19 | follow_redirects: true 20 | enable_http2: true 21 | static_configs: 22 | - targets: 23 | - localhost:9090 24 | - job_name: "etcd" 25 | scheme: http 26 | honor_timestamps: true 27 | metrics_path: /metrics 28 | follow_redirects: true 29 | enable_http2: true 30 | static_configs: 31 | - targets: 32 | - "${{ .ProjectName }}-etcd:2379" 33 | - job_name: "fake-kubelet" 34 | scheme: http 35 | honor_timestamps: true 36 | metrics_path: /metrics 37 | follow_redirects: true 38 | enable_http2: true 39 | static_configs: 40 | - targets: 41 | - "${{ .ProjectName }}-fake-kubelet:8080" 42 | 43 | ${{ if .SecretPort }} 44 | - job_name: "kube-apiserver" 45 | scheme: https 46 | honor_timestamps: true 47 | metrics_path: /metrics 48 | follow_redirects: true 49 | enable_http2: true 50 | tls_config: 51 | cert_file: "${{ .AdminCrtPath }}" 52 | key_file: "${{ .AdminKeyPath }}" 53 | insecure_skip_verify: true 54 | static_configs: 55 | - targets: 56 | - "${{ .ProjectName }}-kube-apiserver:6443" 57 | - job_name: "kube-controller-manager" 58 | scheme: https 59 | honor_timestamps: true 60 | metrics_path: /metrics 61 | follow_redirects: true 62 | enable_http2: true 63 | tls_config: 64 | cert_file: "${{ .AdminCrtPath }}" 65 | key_file: "${{ .AdminKeyPath }}" 66 | insecure_skip_verify: true 67 | static_configs: 68 | - targets: 69 | - "${{ .ProjectName }}-kube-controller-manager:10257" 70 | - job_name: "kube-scheduler" 71 | scheme: https 72 | honor_timestamps: true 73 | metrics_path: /metrics 74 | follow_redirects: true 75 | enable_http2: true 76 | tls_config: 77 | cert_file: "${{ .AdminCrtPath }}" 78 | key_file: "${{ .AdminKeyPath }}" 79 | insecure_skip_verify: true 80 | static_configs: 81 | - targets: 82 | - "${{ .ProjectName }}-kube-scheduler:10259" 83 | ${{ else }} 84 | - job_name: "kube-apiserver" 85 | scheme: http 86 | honor_timestamps: true 87 | metrics_path: /metrics 88 | follow_redirects: true 89 | enable_http2: true 90 | static_configs: 91 | - targets: 92 | - "${{ .ProjectName }}-kube-apiserver:8080" 93 | - job_name: "kube-controller-manager" 94 | scheme: http 95 | honor_timestamps: true 96 | metrics_path: /metrics 97 | follow_redirects: true 98 | enable_http2: true 99 | static_configs: 100 | - targets: 101 | - "${{ .ProjectName }}-kube-controller-manager:10252" 102 | - job_name: "kube-scheduler" 103 | scheme: http 104 | honor_timestamps: true 105 | metrics_path: /metrics 106 | follow_redirects: true 107 | enable_http2: true 108 | static_configs: 109 | - targets: 110 | - "${{ .ProjectName }}-kube-scheduler:10251" 111 | ${{ end }} -------------------------------------------------------------------------------- /pkg/runtime/config.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "time" 7 | 8 | "github.com/wzshiming/fake-k8s/pkg/utils" 9 | ) 10 | 11 | type Config struct { 12 | Name string 13 | ApiserverPort uint32 14 | Workdir string 15 | Runtime string 16 | 17 | PrometheusPort uint32 18 | GenerateNodeName string 19 | GenerateReplicas uint32 20 | NodeName string 21 | 22 | // For docker-compose 23 | EtcdImage string 24 | KubeApiserverImage string 25 | KubeControllerManagerImage string 26 | KubeSchedulerImage string 27 | FakeKubeletImage string 28 | PrometheusImage string 29 | 30 | // For kind 31 | KindNodeImage string 32 | 33 | // For binary 34 | KubeApiserverBinary string 35 | KubeControllerManagerBinary string 36 | KubeSchedulerBinary string 37 | FakeKubeletBinary string 38 | EtcdBinaryTar string 39 | PrometheusBinaryTar string 40 | 41 | // Cache directory 42 | CacheDir string 43 | 44 | // For docker-compose and binary 45 | SecretPort bool 46 | 47 | // Pull image 48 | QuietPull bool 49 | 50 | // Feature gates of Kubernetes 51 | FeatureGates string 52 | 53 | // Runtime config of Kubernetes 54 | RuntimeConfig string 55 | } 56 | 57 | type Runtime interface { 58 | // Install the cluster 59 | Install(ctx context.Context, conf Config) error 60 | 61 | // Uninstall the cluster 62 | Uninstall(ctx context.Context) error 63 | 64 | // Ready check the cluster is ready 65 | Ready(ctx context.Context) (bool, error) 66 | 67 | // WaitReady wait the cluster is ready 68 | WaitReady(ctx context.Context, timeout time.Duration) error 69 | 70 | // Up start the cluster 71 | Up(ctx context.Context) error 72 | 73 | // Down stop the cluster 74 | Down(ctx context.Context) error 75 | 76 | // Start start a container 77 | Start(ctx context.Context, name string) error 78 | 79 | // Stop stop a container 80 | Stop(ctx context.Context, name string) error 81 | 82 | // Config return the cluster config 83 | Config() (*Config, error) 84 | 85 | // InHostKubeconfig return the kubeconfig in host 86 | InHostKubeconfig() (string, error) 87 | 88 | // Kubectl command 89 | Kubectl(ctx context.Context, stm utils.IOStreams, args ...string) error 90 | 91 | // KubectlInCluster command in cluster 92 | KubectlInCluster(ctx context.Context, stm utils.IOStreams, args ...string) error 93 | 94 | // Logs logs of a component 95 | Logs(ctx context.Context, name string, out io.Writer) error 96 | 97 | // LogsFollow follow logs of a component with follow 98 | LogsFollow(ctx context.Context, name string, out io.Writer) error 99 | } 100 | -------------------------------------------------------------------------------- /pkg/runtime/kind/cluster.go: -------------------------------------------------------------------------------- 1 | package kind 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | _ "embed" 7 | "fmt" 8 | "io" 9 | "os" 10 | "strings" 11 | "time" 12 | 13 | "github.com/wzshiming/fake-k8s/pkg/log" 14 | "github.com/wzshiming/fake-k8s/pkg/runtime" 15 | "github.com/wzshiming/fake-k8s/pkg/utils" 16 | ) 17 | 18 | type Cluster struct { 19 | *runtime.Cluster 20 | } 21 | 22 | func NewCluster(name, workdir string, logger log.Logger) (runtime.Runtime, error) { 23 | return &Cluster{ 24 | Cluster: runtime.NewCluster(name, workdir, logger), 25 | }, nil 26 | } 27 | 28 | func (c *Cluster) Install(ctx context.Context, conf runtime.Config) error { 29 | err := c.Cluster.Install(ctx, conf) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | var featureGates []string 35 | var runtimeConfig []string 36 | if conf.FeatureGates != "" { 37 | featureGates = strings.Split(strings.ReplaceAll(conf.FeatureGates, "=", ": "), ",") 38 | } 39 | if conf.RuntimeConfig != "" { 40 | runtimeConfig = strings.Split(strings.ReplaceAll(conf.RuntimeConfig, "=", ": "), ",") 41 | } 42 | kindYaml, err := BuildKind(BuildKindConfig{ 43 | ApiserverPort: conf.ApiserverPort, 44 | PrometheusPort: conf.PrometheusPort, 45 | FeatureGates: featureGates, 46 | RuntimeConfig: runtimeConfig, 47 | }) 48 | if err != nil { 49 | return err 50 | } 51 | err = os.WriteFile(utils.PathJoin(conf.Workdir, runtime.KindName), []byte(kindYaml), 0644) 52 | if err != nil { 53 | return fmt.Errorf("failed to write %s: %w", runtime.KindName, err) 54 | } 55 | 56 | fakeKubeletDeploy, err := BuildFakeKubeletDeploy(BuildFakeKubeletDeployConfig{ 57 | FakeKubeletImage: conf.FakeKubeletImage, 58 | Name: conf.Name, 59 | NodeName: conf.NodeName, 60 | GenerateNodeName: conf.GenerateNodeName, 61 | GenerateReplicas: conf.GenerateReplicas, 62 | }) 63 | if err != nil { 64 | return err 65 | } 66 | err = os.WriteFile(utils.PathJoin(conf.Workdir, runtime.FakeKubeletDeploy), []byte(fakeKubeletDeploy), 0644) 67 | if err != nil { 68 | return fmt.Errorf("failed to write %s: %w", runtime.FakeKubeletDeploy, err) 69 | } 70 | 71 | if conf.PrometheusPort != 0 { 72 | prometheusDeploy, err := BuildPrometheusDeploy(BuildPrometheusDeployConfig{ 73 | PrometheusImage: conf.PrometheusImage, 74 | Name: conf.Name, 75 | }) 76 | if err != nil { 77 | return err 78 | } 79 | err = os.WriteFile(utils.PathJoin(conf.Workdir, runtime.PrometheusDeploy), []byte(prometheusDeploy), 0644) 80 | if err != nil { 81 | return fmt.Errorf("failed to write %s: %w", runtime.PrometheusDeploy, err) 82 | } 83 | } 84 | 85 | var out io.Writer = os.Stderr 86 | if conf.QuietPull { 87 | out = nil 88 | } 89 | images := []string{ 90 | conf.KindNodeImage, 91 | conf.FakeKubeletImage, 92 | } 93 | if conf.PrometheusPort != 0 { 94 | images = append(images, conf.PrometheusImage) 95 | } 96 | for _, image := range images { 97 | err = utils.Exec(ctx, "", utils.IOStreams{}, "docker", "inspect", 98 | image, 99 | ) 100 | if err != nil { 101 | err = utils.Exec(ctx, "", utils.IOStreams{ 102 | Out: out, 103 | ErrOut: out, 104 | }, "docker", "pull", 105 | image, 106 | ) 107 | if err != nil { 108 | return err 109 | } 110 | } 111 | } 112 | return nil 113 | } 114 | 115 | func (c *Cluster) Up(ctx context.Context) error { 116 | conf, err := c.Config() 117 | if err != nil { 118 | return err 119 | } 120 | 121 | err = utils.Exec(ctx, "", utils.IOStreams{ 122 | ErrOut: os.Stderr, 123 | }, conf.Runtime, "create", "cluster", 124 | "--config", utils.PathJoin(conf.Workdir, runtime.KindName), 125 | "--name", conf.Name, 126 | "--image", conf.KindNodeImage, 127 | ) 128 | if err != nil { 129 | return err 130 | } 131 | 132 | kubeconfig, err := c.InHostKubeconfig() 133 | if err != nil { 134 | return err 135 | } 136 | 137 | kubeconfigBuf := bytes.NewBuffer(nil) 138 | err = c.Kubectl(ctx, utils.IOStreams{ 139 | Out: kubeconfigBuf, 140 | }, "config", "view", "--minify=true", "--raw=true") 141 | if err != nil { 142 | return err 143 | } 144 | 145 | err = os.WriteFile(kubeconfig, kubeconfigBuf.Bytes(), 0644) 146 | if err != nil { 147 | return err 148 | } 149 | 150 | err = c.WaitReady(ctx, 30*time.Second) 151 | if err != nil { 152 | return fmt.Errorf("failed to wait for kube-apiserver ready: %v", err) 153 | } 154 | 155 | err = c.Kubectl(ctx, utils.IOStreams{}, "cordon", conf.Name+"-control-plane") 156 | if err != nil { 157 | return err 158 | } 159 | 160 | err = utils.Exec(ctx, "", utils.IOStreams{}, "kind", "load", "docker-image", 161 | conf.FakeKubeletImage, 162 | "--name", conf.Name, 163 | ) 164 | if err != nil { 165 | return err 166 | } 167 | err = c.Kubectl(ctx, utils.IOStreams{ 168 | ErrOut: os.Stderr, 169 | }, "apply", "-f", utils.PathJoin(conf.Workdir, runtime.FakeKubeletDeploy)) 170 | if err != nil { 171 | return err 172 | } 173 | 174 | if conf.PrometheusPort != 0 { 175 | err = utils.Exec(ctx, "", utils.IOStreams{}, "kind", "load", "docker-image", 176 | conf.PrometheusImage, 177 | "--name", conf.Name, 178 | ) 179 | if err != nil { 180 | return err 181 | } 182 | err = c.Kubectl(ctx, utils.IOStreams{ 183 | ErrOut: os.Stderr, 184 | }, "apply", "-f", utils.PathJoin(conf.Workdir, runtime.PrometheusDeploy)) 185 | if err != nil { 186 | return err 187 | } 188 | } 189 | 190 | // set the context in default kubeconfig 191 | c.Kubectl(ctx, utils.IOStreams{}, "config", "set", "contexts."+conf.Name+".cluster", "kind-"+conf.Name) 192 | c.Kubectl(ctx, utils.IOStreams{}, "config", "set", "contexts."+conf.Name+".user", "kind-"+conf.Name) 193 | return nil 194 | } 195 | 196 | func (c *Cluster) Down(ctx context.Context) error { 197 | conf, err := c.Config() 198 | if err != nil { 199 | return err 200 | } 201 | 202 | // unset the context in default kubeconfig 203 | c.Kubectl(ctx, utils.IOStreams{}, "config", "unset", "contexts."+conf.Name+".cluster") 204 | c.Kubectl(ctx, utils.IOStreams{}, "config", "unset", "contexts."+conf.Name+".user") 205 | 206 | err = utils.Exec(ctx, "", utils.IOStreams{ 207 | ErrOut: os.Stderr, 208 | }, conf.Runtime, "delete", "cluster", "--name", conf.Name) 209 | if err != nil { 210 | c.Logger().Printf("failed to delete cluster: %v", err) 211 | } 212 | 213 | return nil 214 | } 215 | 216 | func (c *Cluster) Start(ctx context.Context, name string) error { 217 | conf, err := c.Config() 218 | if err != nil { 219 | return err 220 | } 221 | err = utils.Exec(ctx, "", utils.IOStreams{}, "docker", "exec", conf.Name+"-control-plane", "mv", "/etc/kubernetes/"+name+".yaml.bak", "/etc/kubernetes/manifests/"+name+".yaml") 222 | if err != nil { 223 | return err 224 | } 225 | return nil 226 | } 227 | 228 | func (c *Cluster) Stop(ctx context.Context, name string) error { 229 | conf, err := c.Config() 230 | if err != nil { 231 | return err 232 | } 233 | err = utils.Exec(ctx, "", utils.IOStreams{}, "docker", "exec", conf.Name+"-control-plane", "mv", "/etc/kubernetes/manifests/"+name+".yaml", "/etc/kubernetes/"+name+".yaml.bak") 234 | if err != nil { 235 | return err 236 | } 237 | return nil 238 | } 239 | 240 | func (c *Cluster) logs(ctx context.Context, name string, out io.Writer, follow bool) error { 241 | conf, err := c.Config() 242 | if err != nil { 243 | return err 244 | } 245 | switch name { 246 | case "fake-kubelet", "prometheus": 247 | default: 248 | name = name + "-" + conf.Name + "-control-plane" 249 | } 250 | 251 | args := []string{"logs", "-n", "kube-system"} 252 | if follow { 253 | args = append(args, "-f") 254 | } 255 | args = append(args, name) 256 | 257 | err = c.Kubectl(ctx, utils.IOStreams{ 258 | ErrOut: out, 259 | Out: out, 260 | }, args...) 261 | if err != nil { 262 | return err 263 | } 264 | return nil 265 | } 266 | 267 | func (c *Cluster) Logs(ctx context.Context, name string, out io.Writer) error { 268 | return c.logs(ctx, name, out, false) 269 | } 270 | 271 | func (c *Cluster) LogsFollow(ctx context.Context, name string, out io.Writer) error { 272 | return c.logs(ctx, name, out, true) 273 | } 274 | -------------------------------------------------------------------------------- /pkg/runtime/kind/fake-kubelet-deploy.go: -------------------------------------------------------------------------------- 1 | package kind 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "fmt" 7 | "text/template" 8 | ) 9 | 10 | //go:embed fake-kubelet-deploy.yaml.tpl 11 | var fakeKubeletDeployYamlTpl string 12 | 13 | var fakeKubeletDeployYamlTemplate = template.Must(template.New("_").Delims("${{", "}}").Parse(fakeKubeletDeployYamlTpl)) 14 | 15 | func BuildFakeKubeletDeploy(conf BuildFakeKubeletDeployConfig) (string, error) { 16 | buf := bytes.NewBuffer(nil) 17 | err := fakeKubeletDeployYamlTemplate.Execute(buf, conf) 18 | if err != nil { 19 | return "", fmt.Errorf("failed to execute fake kubelet deploy yaml template: %w", err) 20 | } 21 | return buf.String(), nil 22 | } 23 | 24 | type BuildFakeKubeletDeployConfig struct { 25 | FakeKubeletImage string 26 | Name string 27 | NodeName string 28 | GenerateNodeName string 29 | GenerateReplicas uint32 30 | } 31 | -------------------------------------------------------------------------------- /pkg/runtime/kind/fake-kubelet-deploy.yaml.tpl: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: fake-kubelet 6 | namespace: kube-system 7 | labels: 8 | app: fake-kubelet 9 | --- 10 | apiVersion: rbac.authorization.k8s.io/v1 11 | kind: ClusterRole 12 | metadata: 13 | name: fake-kubelet 14 | labels: 15 | app: fake-kubelet 16 | rules: 17 | - apiGroups: 18 | - "" 19 | resources: 20 | - nodes 21 | verbs: 22 | - watch 23 | - list 24 | - create 25 | - get 26 | - apiGroups: 27 | - "" 28 | resources: 29 | - nodes/status 30 | verbs: 31 | - update 32 | - patch 33 | - apiGroups: 34 | - "" 35 | resources: 36 | - pods 37 | verbs: 38 | - watch 39 | - list 40 | - delete 41 | - update 42 | - patch 43 | - apiGroups: 44 | - "" 45 | resources: 46 | - pods/status 47 | verbs: 48 | - update 49 | - patch 50 | --- 51 | apiVersion: rbac.authorization.k8s.io/v1 52 | kind: ClusterRoleBinding 53 | metadata: 54 | name: fake-kubelet 55 | labels: 56 | app: fake-kubelet 57 | roleRef: 58 | apiGroup: rbac.authorization.k8s.io 59 | kind: ClusterRole 60 | name: fake-kubelet 61 | subjects: 62 | - kind: ServiceAccount 63 | name: fake-kubelet 64 | namespace: kube-system 65 | --- 66 | apiVersion: v1 67 | kind: ConfigMap 68 | metadata: 69 | name: fake-kubelet 70 | namespace: kube-system 71 | labels: 72 | app: fake-kubelet 73 | data: 74 | pod_status_template: |- 75 | {{ $startTime := .metadata.creationTimestamp }} 76 | conditions: 77 | - lastTransitionTime: {{ $startTime }} 78 | status: "True" 79 | type: Initialized 80 | - lastTransitionTime: {{ $startTime }} 81 | status: "True" 82 | type: Ready 83 | - lastTransitionTime: {{ $startTime }} 84 | status: "True" 85 | type: ContainersReady 86 | - lastTransitionTime: {{ $startTime }} 87 | status: "True" 88 | type: PodScheduled 89 | {{ range .spec.readinessGates }} 90 | - lastTransitionTime: {{ $startTime }} 91 | status: "True" 92 | type: {{ .conditionType }} 93 | {{ end }} 94 | containerStatuses: 95 | {{ range .spec.containers }} 96 | - image: {{ .image }} 97 | name: {{ .name }} 98 | ready: true 99 | restartCount: 0 100 | state: 101 | running: 102 | startedAt: {{ $startTime }} 103 | {{ end }} 104 | initContainerStatuses: 105 | {{ range .spec.initContainers }} 106 | - image: {{ .image }} 107 | name: {{ .name }} 108 | ready: true 109 | restartCount: 0 110 | state: 111 | terminated: 112 | exitCode: 0 113 | finishedAt: {{ $startTime }} 114 | reason: Completed 115 | startedAt: {{ $startTime }} 116 | {{ end }} 117 | {{ with .status }} 118 | hostIP: {{ with .hostIP }} {{ . }} {{ else }} {{ NodeIP }} {{ end }} 119 | podIP: {{ with .podIP }} {{ . }} {{ else }} {{ PodIP }} {{ end }} 120 | {{ end }} 121 | phase: Running 122 | startTime: {{ $startTime }} 123 | node_template: |- 124 | apiVersion: v1 125 | kind: Node 126 | metadata: 127 | annotations: 128 | node.alpha.kubernetes.io/ttl: "0" 129 | labels: 130 | app: fake-kubelet 131 | beta.kubernetes.io/arch: amd64 132 | beta.kubernetes.io/os: linux 133 | kubernetes.io/arch: amd64 134 | kubernetes.io/hostname: {{ .metadata.name }} 135 | kubernetes.io/os: linux 136 | kubernetes.io/role: agent 137 | node-role.kubernetes.io/agent: "" 138 | type: fake-kubelet 139 | name: {{ .metadata.name }} 140 | node_heartbeat_template: |- 141 | conditions: 142 | - lastHeartbeatTime: {{ Now }} 143 | lastTransitionTime: {{ StartTime }} 144 | message: kubelet is posting ready status 145 | reason: KubeletReady 146 | status: "True" 147 | type: Ready 148 | - lastHeartbeatTime: {{ Now }} 149 | lastTransitionTime: {{ StartTime }} 150 | message: kubelet has sufficient disk space available 151 | reason: KubeletHasSufficientDisk 152 | status: "False" 153 | type: OutOfDisk 154 | - lastHeartbeatTime: {{ Now }} 155 | lastTransitionTime: {{ StartTime }} 156 | message: kubelet has sufficient memory available 157 | reason: KubeletHasSufficientMemory 158 | status: "False" 159 | type: MemoryPressure 160 | - lastHeartbeatTime: {{ Now }} 161 | lastTransitionTime: {{ StartTime }} 162 | message: kubelet has no disk pressure 163 | reason: KubeletHasNoDiskPressure 164 | status: "False" 165 | type: DiskPressure 166 | - lastHeartbeatTime: {{ Now }} 167 | lastTransitionTime: {{ StartTime }} 168 | message: RouteController created a route 169 | reason: RouteCreated 170 | status: "False" 171 | type: NetworkUnavailable 172 | node_initialization_template: |- 173 | {{ with .status }} 174 | addresses: 175 | {{ with .addresses }} 176 | {{ YAML . 1 }} 177 | {{ else }} 178 | - address: {{ NodeIP }} 179 | type: InternalIP 180 | {{ end }} 181 | allocatable: 182 | {{ with .allocatable }} 183 | {{ YAML . 1 }} 184 | {{ else }} 185 | cpu: 1k 186 | memory: 1Ti 187 | pods: 1M 188 | {{ end }} 189 | capacity: 190 | {{ with .capacity }} 191 | {{ YAML . 1 }} 192 | {{ else }} 193 | cpu: 1k 194 | memory: 1Ti 195 | pods: 1M 196 | {{ end }} 197 | {{ with .nodeInfo }} 198 | nodeInfo: 199 | architecture: {{ with .architecture }} {{ . }} {{ else }} "amd64" {{ end }} 200 | bootID: {{ with .bootID }} {{ . }} {{ else }} "" {{ end }} 201 | containerRuntimeVersion: {{ with .containerRuntimeVersion }} {{ . }} {{ else }} "" {{ end }} 202 | kernelVersion: {{ with .kernelVersion }} {{ . }} {{ else }} "" {{ end }} 203 | kubeProxyVersion: {{ with .kubeProxyVersion }} {{ . }} {{ else }} "fake" {{ end }} 204 | kubeletVersion: {{ with .kubeletVersion }} {{ . }} {{ else }} "fake" {{ end }} 205 | machineID: {{ with .machineID }} {{ . }} {{ else }} "" {{ end }} 206 | operatingSystem: {{ with .operatingSystem }} {{ . }} {{ else }} "linux" {{ end }} 207 | osImage: {{ with .osImage }} {{ . }} {{ else }} "" {{ end }} 208 | systemUUID: {{ with .osImage }} {{ . }} {{ else }} "" {{ end }} 209 | {{ end }} 210 | phase: Running 211 | {{ end }} 212 | --- 213 | apiVersion: v1 214 | kind: Pod 215 | metadata: 216 | name: fake-kubelet 217 | namespace: kube-system 218 | spec: 219 | containers: 220 | - name: fake-kubelet 221 | image: ${{ .FakeKubeletImage }} 222 | imagePullPolicy: IfNotPresent 223 | env: 224 | - name: NODE_NAME 225 | value: "${{ .NodeName }}" # This is to specify a single Node, use GENERATE_NODE_NAME and GENERATE_REPLICAS to generate multiple nodes 226 | - name: GENERATE_NODE_NAME 227 | value: "${{ .GenerateNodeName }}" 228 | - name: GENERATE_REPLICAS 229 | value: "${{ .GenerateReplicas }}" 230 | - name: TAKE_OVER_LABELS_SELECTOR 231 | value: type=fake-kubelet 232 | - name: TAKE_OVER_ALL 233 | value: "false" 234 | - name: CIDR 235 | value: 10.0.0.1/24 236 | - name: HEALTH_ADDRESS # deprecated: use SERVER_ADDRESS instead 237 | value: :8080 238 | - name: SERVER_ADDRESS 239 | value: :8080 240 | - name: NODE_IP 241 | valueFrom: 242 | fieldRef: 243 | fieldPath: status.podIP 244 | - name: POD_STATUS_TEMPLATE 245 | valueFrom: 246 | configMapKeyRef: 247 | name: fake-kubelet 248 | key: pod_status_template 249 | - name: NODE_TEMPLATE 250 | valueFrom: 251 | configMapKeyRef: 252 | name: fake-kubelet 253 | key: node_template 254 | - name: NODE_HEARTBEAT_TEMPLATE 255 | valueFrom: 256 | configMapKeyRef: 257 | name: fake-kubelet 258 | key: node_heartbeat_template 259 | - name: NODE_INITIALIZATION_TEMPLATE 260 | valueFrom: 261 | configMapKeyRef: 262 | name: fake-kubelet 263 | key: node_initialization_template 264 | livenessProbe: 265 | httpGet: 266 | path: /health 267 | port: 8080 268 | scheme: HTTP 269 | initialDelaySeconds: 2 270 | timeoutSeconds: 2 271 | periodSeconds: 10 272 | failureThreshold: 3 273 | readinessProbe: 274 | httpGet: 275 | path: /health 276 | port: 8080 277 | scheme: HTTP 278 | initialDelaySeconds: 2 279 | timeoutSeconds: 2 280 | periodSeconds: 10 281 | failureThreshold: 3 282 | resources: 283 | requests: 284 | cpu: 500m 285 | memory: 100Mi 286 | serviceAccount: fake-kubelet 287 | serviceAccountName: fake-kubelet 288 | restartPolicy: Always 289 | hostNetwork: true 290 | nodeName: ${{ .Name }}-control-plane 291 | --- -------------------------------------------------------------------------------- /pkg/runtime/kind/init.go: -------------------------------------------------------------------------------- 1 | package kind 2 | 3 | import ( 4 | "github.com/wzshiming/fake-k8s/pkg/runtime" 5 | ) 6 | 7 | func init() { 8 | runtime.Register("kind", NewCluster) 9 | } 10 | -------------------------------------------------------------------------------- /pkg/runtime/kind/kind.go: -------------------------------------------------------------------------------- 1 | package kind 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "fmt" 7 | "text/template" 8 | ) 9 | 10 | //go:embed kind.yaml.tpl 11 | var kindYamlTpl string 12 | 13 | var kindYamlTemplate = template.Must(template.New("_").Delims("${{", "}}").Parse(kindYamlTpl)) 14 | 15 | func BuildKind(conf BuildKindConfig) (string, error) { 16 | buf := bytes.NewBuffer(nil) 17 | err := kindYamlTemplate.Execute(buf, conf) 18 | if err != nil { 19 | return "", fmt.Errorf("failed to execute kind yaml template: %w", err) 20 | } 21 | return buf.String(), nil 22 | } 23 | 24 | type BuildKindConfig struct { 25 | ApiserverPort uint32 26 | PrometheusPort uint32 27 | 28 | RuntimeConfig []string 29 | FeatureGates []string 30 | } 31 | -------------------------------------------------------------------------------- /pkg/runtime/kind/kind.yaml.tpl: -------------------------------------------------------------------------------- 1 | kind: Cluster 2 | apiVersion: kind.x-k8s.io/v1alpha4 3 | 4 | networking: 5 | apiServerAddress: "0.0.0.0" 6 | ${{ if .ApiserverPort }} 7 | apiServerPort: ${{ .ApiserverPort }} 8 | ${{ end }} 9 | nodes: 10 | - role: control-plane 11 | 12 | ${{ if .PrometheusPort }} 13 | extraPortMappings: 14 | - containerPort: 9090 15 | hostPort: ${{ .PrometheusPort }} 16 | listenAddress: "0.0.0.0" 17 | protocol: TCP 18 | ${{ end }} 19 | 20 | ${{ if .FeatureGates }} 21 | featureGates: 22 | ${{ range .FeatureGates }} 23 | ${{ . }} 24 | ${{ end }} 25 | ${{ end }} 26 | 27 | ${{ if .RuntimeConfig }} 28 | runtimeConfig: 29 | ${{ range .RuntimeConfig }} 30 | ${{ . }} 31 | ${{ end }} 32 | ${{ end }} -------------------------------------------------------------------------------- /pkg/runtime/kind/prometheus-deploy.go: -------------------------------------------------------------------------------- 1 | package kind 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "fmt" 7 | "text/template" 8 | ) 9 | 10 | //go:embed prometheus-deploy.yaml.tpl 11 | var prometheusDeployYamlTpl string 12 | 13 | var prometheusDeployYamlTemplate = template.Must(template.New("_").Delims("${{", "}}").Parse(prometheusDeployYamlTpl)) 14 | 15 | func BuildPrometheusDeploy(conf BuildPrometheusDeployConfig) (string, error) { 16 | buf := bytes.NewBuffer(nil) 17 | err := prometheusDeployYamlTemplate.Execute(buf, conf) 18 | if err != nil { 19 | return "", fmt.Errorf("failed to execute prometheus deploy yaml template: %w", err) 20 | } 21 | return buf.String(), nil 22 | } 23 | 24 | type BuildPrometheusDeployConfig struct { 25 | PrometheusImage string 26 | Name string 27 | } 28 | -------------------------------------------------------------------------------- /pkg/runtime/kind/prometheus-deploy.yaml.tpl: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: prometheus 5 | rules: 6 | - nonResourceURLs: ["/metrics"] 7 | verbs: ["get"] 8 | --- 9 | apiVersion: v1 10 | kind: ServiceAccount 11 | metadata: 12 | name: prometheus 13 | namespace: kube-system 14 | --- 15 | apiVersion: rbac.authorization.k8s.io/v1 16 | kind: ClusterRoleBinding 17 | metadata: 18 | name: prometheus 19 | roleRef: 20 | apiGroup: rbac.authorization.k8s.io 21 | kind: ClusterRole 22 | name: prometheus 23 | subjects: 24 | - kind: ServiceAccount 25 | name: prometheus 26 | namespace: kube-system 27 | --- 28 | apiVersion: v1 29 | kind: ConfigMap 30 | metadata: 31 | name: prometheus-configmap 32 | namespace: kube-system 33 | data: 34 | prometheus.yaml: | 35 | global: 36 | scrape_interval: 15s 37 | scrape_timeout: 10s 38 | evaluation_interval: 15s 39 | alerting: 40 | alertmanagers: 41 | - follow_redirects: true 42 | enable_http2: true 43 | scheme: http 44 | timeout: 10s 45 | api_version: v2 46 | static_configs: 47 | - targets: [ ] 48 | scrape_configs: 49 | - job_name: "prometheus" 50 | scheme: http 51 | honor_timestamps: true 52 | metrics_path: /metrics 53 | follow_redirects: true 54 | enable_http2: true 55 | static_configs: 56 | - targets: 57 | - "localhost:9090" 58 | - job_name: "etcd" 59 | scheme: https 60 | honor_timestamps: true 61 | metrics_path: /metrics 62 | follow_redirects: true 63 | enable_http2: true 64 | tls_config: 65 | cert_file: /etc/kubernetes/pki/apiserver-etcd-client.crt 66 | key_file: /etc/kubernetes/pki/apiserver-etcd-client.key 67 | insecure_skip_verify: true 68 | static_configs: 69 | - targets: 70 | - "localhost:2379" 71 | - job_name: "fake-kubelet" 72 | scheme: http 73 | honor_timestamps: true 74 | metrics_path: /metrics 75 | follow_redirects: true 76 | enable_http2: true 77 | static_configs: 78 | - targets: 79 | - "localhost:8080" 80 | - job_name: "kube-apiserver" 81 | scheme: https 82 | honor_timestamps: true 83 | metrics_path: /metrics 84 | follow_redirects: true 85 | enable_http2: true 86 | tls_config: 87 | insecure_skip_verify: true 88 | bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token 89 | static_configs: 90 | - targets: 91 | - "localhost:6443" 92 | - job_name: "kube-controller-manager" 93 | scheme: https 94 | honor_timestamps: true 95 | metrics_path: /metrics 96 | follow_redirects: true 97 | enable_http2: true 98 | tls_config: 99 | insecure_skip_verify: true 100 | bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token 101 | static_configs: 102 | - targets: 103 | - "localhost:10257" 104 | - job_name: "kube-scheduler" 105 | scheme: https 106 | honor_timestamps: true 107 | metrics_path: /metrics 108 | follow_redirects: true 109 | enable_http2: true 110 | tls_config: 111 | insecure_skip_verify: true 112 | bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token 113 | static_configs: 114 | - targets: 115 | - "localhost:10259" 116 | --- 117 | apiVersion: v1 118 | kind: Pod 119 | metadata: 120 | name: prometheus 121 | namespace: kube-system 122 | spec: 123 | containers: 124 | - name: prometheus 125 | image: ${{ .PrometheusImage }} 126 | args: 127 | - --config.file 128 | - /etc/prometheus/prometheus.yaml 129 | ports: 130 | - name: web 131 | containerPort: 9090 132 | securityContext: 133 | runAsUser: 0 134 | volumeMounts: 135 | - name: config-volume 136 | mountPath: /etc/prometheus/ 137 | readOnly: true 138 | - mountPath: /etc/kubernetes/pki 139 | name: k8s-certs 140 | readOnly: true 141 | volumes: 142 | - name: config-volume 143 | configMap: 144 | name: prometheus-configmap 145 | - hostPath: 146 | path: /etc/kubernetes/pki 147 | type: DirectoryOrCreate 148 | name: k8s-certs 149 | serviceAccount: prometheus 150 | serviceAccountName: prometheus 151 | restartPolicy: Always 152 | hostNetwork: true 153 | nodeName: ${{ .Name }}-control-plane 154 | -------------------------------------------------------------------------------- /pkg/runtime/list.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/wzshiming/fake-k8s/pkg/vars" 7 | ) 8 | 9 | // ListClusters returns the list of clusters in the directory 10 | func ListClusters(workdir string) ([]string, error) { 11 | entries, err := os.ReadDir(workdir) 12 | if err != nil { 13 | if os.IsNotExist(err) { 14 | return nil, nil 15 | } 16 | return nil, err 17 | } 18 | ret := []string{} 19 | for _, entry := range entries { 20 | if entry.IsDir() { 21 | ret = append(ret, entry.Name()) 22 | } 23 | } 24 | return ret, nil 25 | } 26 | 27 | // ListImagesCompose returns the list of images of compose 28 | func ListImagesCompose() ([]string, error) { 29 | return []string{ 30 | vars.EtcdImage, 31 | vars.KubeApiserverImage, 32 | vars.KubeControllerManagerImage, 33 | vars.KubeSchedulerImage, 34 | vars.FakeKubeletImage, 35 | vars.PrometheusImage, 36 | }, nil 37 | } 38 | 39 | // ListImagesKind returns the list of images of kind 40 | func ListImagesKind() ([]string, error) { 41 | return []string{ 42 | vars.KindNodeImage, 43 | vars.FakeKubeletImage, 44 | vars.PrometheusImage, 45 | }, nil 46 | } 47 | 48 | // ListBinaries returns the list of binaries 49 | func ListBinaries() ([]string, error) { 50 | return []string{ 51 | vars.EtcdBinaryTar, 52 | vars.KubeApiserverBinary, 53 | vars.KubeControllerManagerBinary, 54 | vars.KubeSchedulerBinary, 55 | vars.FakeKubeletBinary, 56 | vars.PrometheusBinaryTar, 57 | }, nil 58 | } 59 | -------------------------------------------------------------------------------- /pkg/runtime/registry.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/wzshiming/fake-k8s/pkg/log" 7 | "sort" 8 | ) 9 | 10 | type NewRuntime func(name, workdir string, logger log.Logger) (Runtime, error) 11 | 12 | var registry = map[string]NewRuntime{} 13 | 14 | // Register a runtime 15 | func Register(name string, rt NewRuntime) { 16 | registry[name] = rt 17 | } 18 | 19 | // Get a runtime 20 | func Get(name string) (rt NewRuntime, ok bool) { 21 | rt, ok = registry[name] 22 | if ok { 23 | return rt, true 24 | } 25 | 26 | return nil, false 27 | } 28 | 29 | func Load(name, workdir string, logger log.Logger) (Runtime, error) { 30 | dc := NewCluster(name, workdir, logger) 31 | conf, err := dc.Load(context.Background()) 32 | if err != nil { 33 | return nil, err 34 | } 35 | nr, ok := Get(conf.Runtime) 36 | if !ok { 37 | return nil, fmt.Errorf("not found runtime %q", conf.Runtime) 38 | } 39 | return nr(name, workdir, logger) 40 | } 41 | 42 | // List all registered runtime 43 | func List() []string { 44 | var rts []string 45 | for name := range registry { 46 | rts = append(rts, name) 47 | } 48 | sort.Strings(rts) 49 | return rts 50 | } 51 | -------------------------------------------------------------------------------- /pkg/utils/cmd.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "os" 10 | "path/filepath" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | func ForkExec(ctx context.Context, dir string, name string, arg ...string) error { 16 | pidPath := PathJoin(dir, "pids", filepath.Base(name)+".pid") 17 | pidData, err := os.ReadFile(pidPath) 18 | if err == nil { 19 | _, err = strconv.Atoi(string(pidData)) 20 | if err == nil { 21 | return nil // already running 22 | } 23 | } 24 | 25 | logPath := PathJoin(dir, "logs", filepath.Base(name)+".log") 26 | cmdlinePath := PathJoin(dir, "cmdline", filepath.Base(name)) 27 | 28 | err = os.MkdirAll(filepath.Dir(pidPath), 0755) 29 | if err != nil { 30 | return err 31 | } 32 | err = os.MkdirAll(filepath.Dir(logPath), 0755) 33 | if err != nil { 34 | return err 35 | } 36 | err = os.MkdirAll(filepath.Dir(cmdlinePath), 0755) 37 | if err != nil { 38 | return err 39 | } 40 | 41 | logFile, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) 42 | if err != nil { 43 | return fmt.Errorf("open log file %s: %w", logPath, err) 44 | } 45 | 46 | args := append([]string{name}, arg...) 47 | 48 | err = os.WriteFile(cmdlinePath, []byte(strings.Join(args, "\x00")), 0644) 49 | if err != nil { 50 | return fmt.Errorf("write cmdline file %s: %w", cmdlinePath, err) 51 | } 52 | 53 | cmd := startProcess(ctx, args[0], args[1:]...) 54 | cmd.Dir = dir 55 | cmd.Stdout = logFile 56 | cmd.Stderr = logFile 57 | err = cmd.Start() 58 | if err != nil { 59 | return err 60 | } 61 | 62 | err = os.WriteFile(pidPath, []byte(strconv.Itoa(cmd.Process.Pid)), 0644) 63 | if err != nil { 64 | return fmt.Errorf("write pid file %s: %w", pidPath, err) 65 | } 66 | return nil 67 | } 68 | 69 | func ForkExecRestart(ctx context.Context, dir string, name string) error { 70 | cmdlinePath := PathJoin(dir, "cmdline", filepath.Base(name)) 71 | 72 | data, err := os.ReadFile(cmdlinePath) 73 | if err != nil { 74 | return err 75 | } 76 | 77 | args := strings.Split(string(data), "\x00") 78 | 79 | return ForkExec(ctx, dir, args[0], args...) 80 | } 81 | 82 | func ForkExecKill(ctx context.Context, dir string, name string) error { 83 | pidPath := PathJoin(dir, "pids", filepath.Base(name)+".pid") 84 | if _, err := os.Stat(pidPath); err != nil { 85 | return nil 86 | } 87 | raw, err := os.ReadFile(pidPath) 88 | if err != nil { 89 | return fmt.Errorf("read pid file %s: %w", pidPath, err) 90 | } 91 | pid, err := strconv.Atoi(string(raw)) 92 | if err != nil { 93 | return fmt.Errorf("parse pid file %s: %w", pidPath, err) 94 | } 95 | err = killProcess(ctx, pid) 96 | if err != nil { 97 | return err 98 | } 99 | err = os.Remove(pidPath) 100 | if err != nil { 101 | return err 102 | } 103 | return nil 104 | } 105 | 106 | func Exec(ctx context.Context, dir string, stm IOStreams, name string, arg ...string) error { 107 | cmd := command(ctx, name, arg...) 108 | cmd.Dir = dir 109 | cmd.Stdin = stm.In 110 | cmd.Stdout = stm.Out 111 | cmd.Stderr = stm.ErrOut 112 | 113 | if cmd.Stderr == nil { 114 | buf := bytes.NewBuffer(nil) 115 | cmd.Stderr = buf 116 | } 117 | err := cmd.Run() 118 | if err != nil { 119 | if buf, ok := cmd.Stderr.(*bytes.Buffer); ok { 120 | return fmt.Errorf("%s %s: %w\n%s", name, strings.Join(arg, " "), err, buf.String()) 121 | } 122 | return fmt.Errorf("%s %s: %w", name, strings.Join(arg, " "), err) 123 | } 124 | return nil 125 | } 126 | 127 | type IOStreams struct { 128 | // In think, os.Stdin 129 | In io.Reader 130 | // Out think, os.Stdout 131 | Out io.Writer 132 | // ErrOut think, os.Stderr 133 | ErrOut io.Writer 134 | } 135 | 136 | func killProcess(ctx context.Context, pid int) error { 137 | process, err := os.FindProcess(pid) 138 | if err != nil { 139 | return fmt.Errorf("find process %d: %w", pid, err) 140 | } 141 | err = process.Kill() 142 | if err != nil { 143 | if errors.Is(err, os.ErrProcessDone) { 144 | return nil 145 | } 146 | return fmt.Errorf("kill process: %w", err) 147 | } 148 | process.Wait() 149 | return nil 150 | } 151 | -------------------------------------------------------------------------------- /pkg/utils/cmd_unix.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package utils 4 | 5 | import ( 6 | "context" 7 | "os/exec" 8 | "syscall" 9 | ) 10 | 11 | func startProcess(ctx context.Context, name string, arg ...string) *exec.Cmd { 12 | cmd := command(ctx, name, arg...) 13 | cmd.SysProcAttr = &syscall.SysProcAttr{ 14 | // Setsid is used to detach the process from the parent (normally a shell) 15 | Setsid: true, 16 | } 17 | return cmd 18 | } 19 | 20 | func command(ctx context.Context, name string, arg ...string) *exec.Cmd { 21 | cmd := exec.CommandContext(ctx, name, arg...) 22 | return cmd 23 | } 24 | -------------------------------------------------------------------------------- /pkg/utils/cmd_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package utils 4 | 5 | import ( 6 | "context" 7 | "os/exec" 8 | "syscall" 9 | 10 | "golang.org/x/sys/windows" 11 | ) 12 | 13 | func startProcess(ctx context.Context, name string, arg ...string) *exec.Cmd { 14 | cmd := command(ctx, name, arg...) 15 | cmd.SysProcAttr.CreationFlags |= windows.CREATE_NEW_CONSOLE 16 | return cmd 17 | } 18 | 19 | func command(ctx context.Context, name string, arg ...string) *exec.Cmd { 20 | cmd := exec.CommandContext(ctx, name, arg...) 21 | cmd.SysProcAttr = &syscall.SysProcAttr{ 22 | HideWindow: true, 23 | } 24 | return cmd 25 | } 26 | -------------------------------------------------------------------------------- /pkg/utils/download.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "io/fs" 8 | "net/http" 9 | "net/url" 10 | "os" 11 | "path/filepath" 12 | "strconv" 13 | ) 14 | 15 | func DownloadWithCacheAndExtract(ctx context.Context, cacheDir, src, dest string, match string, mode fs.FileMode, quiet bool, clean bool) error { 16 | if _, err := os.Stat(dest); err == nil { 17 | return nil 18 | } 19 | 20 | cacheTar, err := getCachePath(cacheDir, src) 21 | if err != nil { 22 | return err 23 | } 24 | cache := PathJoin(filepath.Dir(cacheTar), match) 25 | if _, err = os.Stat(cache); err != nil { 26 | cacheTar, err = getCache(ctx, cacheDir, src, 0644, quiet) 27 | if err != nil { 28 | return err 29 | } 30 | cache = PathJoin(filepath.Dir(cacheTar), match) 31 | err = Untar(cacheTar, func(file string) (string, bool) { 32 | if filepath.Base(file) == match { 33 | return cache, true 34 | } 35 | return "", false 36 | }) 37 | if err != nil { 38 | return err 39 | } 40 | if clean { 41 | os.Remove(cacheTar) 42 | } 43 | os.Chmod(cache, mode) 44 | } 45 | 46 | // link the cache file to the dest file 47 | err = os.Symlink(cache, dest) 48 | if err != nil { 49 | return err 50 | } 51 | return nil 52 | } 53 | 54 | func DownloadWithCache(ctx context.Context, cacheDir, src, dest string, mode fs.FileMode, quiet bool) error { 55 | if _, err := os.Stat(dest); err == nil { 56 | return nil 57 | } 58 | 59 | cache, err := getCache(ctx, cacheDir, src, mode, quiet) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | err = os.MkdirAll(filepath.Dir(dest), 0755) 65 | if err != nil { 66 | return err 67 | } 68 | 69 | // link the cache file to the dest file 70 | err = os.Symlink(cache, dest) 71 | if err != nil { 72 | return err 73 | } 74 | return nil 75 | } 76 | 77 | func getCachePath(cacheDir, src string) (string, error) { 78 | u, err := url.Parse(src) 79 | if err != nil { 80 | return "", err 81 | } 82 | switch u.Scheme { 83 | case "http", "https": 84 | return PathJoin(cacheDir, u.Scheme, u.Host, u.Path), nil 85 | default: 86 | return src, nil 87 | } 88 | } 89 | 90 | func getCache(ctx context.Context, cacheDir, src string, mode fs.FileMode, quiet bool) (string, error) { 91 | cache, err := getCachePath(cacheDir, src) 92 | if err != nil { 93 | return "", err 94 | } 95 | if _, err := os.Stat(cache); err == nil { 96 | return cache, nil 97 | } 98 | 99 | u, err := url.Parse(src) 100 | if err != nil { 101 | return "", err 102 | } 103 | switch u.Scheme { 104 | case "http", "https": 105 | cli := &http.Client{} 106 | req, err := http.NewRequest("GET", u.String(), nil) 107 | if err != nil { 108 | return "", err 109 | } 110 | req = req.WithContext(ctx) 111 | resp, err := cli.Do(req) 112 | if err != nil { 113 | return "", err 114 | } 115 | defer resp.Body.Close() 116 | 117 | contentLength := resp.Header.Get("Content-Length") 118 | if resp.StatusCode != 200 { 119 | return "", fmt.Errorf("%s: %s", u.String(), resp.Status) 120 | } 121 | 122 | err = os.MkdirAll(filepath.Dir(cache), 0755) 123 | if err != nil { 124 | return "", err 125 | } 126 | 127 | d, err := os.OpenFile(cache+".tmp", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, mode) 128 | if err != nil { 129 | return "", err 130 | } 131 | 132 | var srcReader io.Reader = resp.Body 133 | if !quiet { 134 | fmt.Fprintf(os.Stderr, "Download %s\n", src) 135 | pb := NewProgressBar() 136 | contentLengthInt, _ := strconv.Atoi(contentLength) 137 | counter := newCounterWriter(func(counter int) { 138 | pb.Update(counter, contentLengthInt) 139 | pb.Print() 140 | }) 141 | srcReader = io.TeeReader(srcReader, counter) 142 | } 143 | 144 | _, err = io.Copy(d, srcReader) 145 | if err != nil { 146 | d.Close() 147 | fmt.Println() 148 | return "", err 149 | } 150 | d.Close() 151 | 152 | err = os.Rename(cache+".tmp", cache) 153 | if err != nil { 154 | return "", err 155 | } 156 | return cache, nil 157 | default: 158 | return src, nil 159 | } 160 | } 161 | 162 | type counterWriter struct { 163 | fun func(counter int) 164 | counter int 165 | } 166 | 167 | func newCounterWriter(fun func(counter int)) *counterWriter { 168 | return &counterWriter{ 169 | fun: fun, 170 | } 171 | } 172 | func (c *counterWriter) Write(b []byte) (int, error) { 173 | c.counter += len(b) 174 | c.fun(c.counter) 175 | return len(b), nil 176 | } 177 | -------------------------------------------------------------------------------- /pkg/utils/filepath_unix.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package utils 4 | 5 | import ( 6 | "path/filepath" 7 | ) 8 | 9 | func PathJoin(elem ...string) string { 10 | return filepath.Join(elem...) 11 | } 12 | -------------------------------------------------------------------------------- /pkg/utils/filepath_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package utils 4 | 5 | import ( 6 | "path/filepath" 7 | "strings" 8 | ) 9 | 10 | func PathJoin(elem ...string) string { 11 | return strings.Replace(filepath.Join(elem...), `\`, `/`, -1) 12 | } 13 | -------------------------------------------------------------------------------- /pkg/utils/progress_bar.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | type ProgressBar struct { 11 | total int 12 | current int 13 | lastUpdateTime time.Time 14 | startTime time.Time 15 | } 16 | 17 | func NewProgressBar() *ProgressBar { 18 | return &ProgressBar{ 19 | startTime: time.Now(), 20 | } 21 | } 22 | 23 | func (p *ProgressBar) Update(current, total int) { 24 | p.current = current 25 | p.total = total 26 | } 27 | 28 | func (p *ProgressBar) Print() { 29 | if p.total == 0 { 30 | return 31 | } 32 | now := time.Now() 33 | if p.current < p.total && 34 | now.Sub(p.lastUpdateTime) < time.Second/10 { 35 | return 36 | } 37 | p.lastUpdateTime = now 38 | 39 | if p.current >= p.total { 40 | fmt.Fprintf(os.Stderr, "\r%-60s| 100%% %-5s\n", strings.Repeat("#", 60), time.Since(p.startTime).Truncate(time.Second)) 41 | } else { 42 | fmt.Fprintf(os.Stderr, "\r%-60s| %.1f%% %-5s", strings.Repeat("#", int(float64(p.current)/float64(p.total)*60)), float64(p.current)/float64(p.total)*100, time.Since(p.startTime).Truncate(time.Second)) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /pkg/utils/temp.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | ) 7 | 8 | // GetUnusedPort returns an unused port 9 | func GetUnusedPort() (int, error) { 10 | l, err := net.Listen("tcp", ":0") 11 | if err != nil { 12 | return 0, fmt.Errorf("get unused port error: %s", err) 13 | } 14 | defer l.Close() 15 | return l.Addr().(*net.TCPAddr).Port, nil 16 | } 17 | 18 | -------------------------------------------------------------------------------- /pkg/utils/untar.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "archive/tar" 5 | "archive/zip" 6 | "compress/gzip" 7 | "fmt" 8 | "io" 9 | "io/fs" 10 | "os" 11 | "path/filepath" 12 | "strings" 13 | ) 14 | 15 | func Untar(src string, filter func(file string) (string, bool)) error { 16 | if strings.HasSuffix(src, ".tar.gz") { 17 | return Untargz(src, filter) 18 | } else if strings.HasSuffix(src, ".zip") { 19 | return Unzip(src, filter) 20 | } 21 | return fmt.Errorf("unsupported archive format: %s", src) 22 | } 23 | 24 | func Unzip(src string, filter func(file string) (string, bool)) error { 25 | r, err := zip.OpenReader(src) 26 | if err != nil { 27 | return err 28 | } 29 | defer r.Close() 30 | 31 | for _, f := range r.File { 32 | fi := f.FileInfo() 33 | 34 | name := f.Name 35 | if fi.IsDir() { 36 | continue 37 | } 38 | 39 | err = func() error { 40 | name, ok := filter(name) 41 | if !ok { 42 | return nil 43 | } 44 | 45 | err = os.MkdirAll(filepath.Dir(name), 0755) 46 | if err != nil { 47 | return err 48 | } 49 | 50 | rc, err := f.Open() 51 | if err != nil { 52 | return err 53 | } 54 | defer rc.Close() 55 | 56 | outFile, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fi.Mode()) 57 | if err != nil { 58 | return err 59 | } 60 | defer outFile.Close() 61 | 62 | _, err = io.Copy(outFile, rc) 63 | if err != nil { 64 | return err 65 | } 66 | return nil 67 | }() 68 | if err != nil { 69 | return err 70 | } 71 | } 72 | return nil 73 | } 74 | 75 | func Untargz(src string, filter func(file string) (string, bool)) error { 76 | r, err := os.Open(src) 77 | if err != nil { 78 | return err 79 | } 80 | defer r.Close() 81 | 82 | gzr, err := gzip.NewReader(r) 83 | if err != nil { 84 | return err 85 | } 86 | defer gzr.Close() 87 | 88 | tr := tar.NewReader(gzr) 89 | for { 90 | hdr, err := tr.Next() 91 | if err == io.EOF { 92 | break 93 | } 94 | if err != nil { 95 | return err 96 | } 97 | 98 | name := hdr.Name 99 | if hdr.Typeflag != tar.TypeReg { 100 | continue 101 | } 102 | 103 | err = func() error { 104 | name, ok := filter(name) 105 | if !ok { 106 | return nil 107 | } 108 | 109 | err = os.MkdirAll(filepath.Dir(name), 0755) 110 | if err != nil { 111 | return err 112 | } 113 | outFile, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fs.FileMode(hdr.Mode)) 114 | if err != nil { 115 | return err 116 | } 117 | defer outFile.Close() 118 | 119 | if _, err := io.Copy(outFile, tr); err != nil { 120 | return err 121 | } 122 | return nil 123 | }() 124 | if err != nil { 125 | return err 126 | } 127 | } 128 | return nil 129 | } 130 | -------------------------------------------------------------------------------- /pkg/vars/vars.go: -------------------------------------------------------------------------------- 1 | package vars 2 | 3 | import ( 4 | "os" 5 | "runtime" 6 | "strconv" 7 | "strings" 8 | 9 | "github.com/wzshiming/fake-k8s/pkg/k8s" 10 | "github.com/wzshiming/fake-k8s/pkg/utils" 11 | ) 12 | 13 | var ( 14 | // ProjectName is the name of the project. 15 | ProjectName = "fake-k8s" 16 | 17 | // DefaultCluster the default cluster name 18 | DefaultCluster = "default" 19 | 20 | // TempDir creates a temporary directory with the given prefix. 21 | TempDir = utils.PathJoin(os.TempDir(), ProjectName, "clusters") 22 | 23 | // CacheDir creates a cache directory with the given prefix. 24 | CacheDir = utils.PathJoin(os.TempDir(), ProjectName, "cache") 25 | 26 | // ApiserverPort is the port to expose apiserver. 27 | ApiserverPort = getEnvInt("APISERVER_PORT", 0) 28 | 29 | // Runtime is the runtime to use. 30 | Runtime = getEnv("RUNTIME", detectionRuntime()) 31 | 32 | // GenerateReplicas is the number of replicas to generate. 33 | GenerateReplicas = getEnvInt("GENERATE_REPLICAS", 5) 34 | 35 | // GenerateNodeName is the name of the node to generate. 36 | GenerateNodeName = getEnv("GENERATE_NODE_NAME", "fake-") 37 | 38 | // NodeName is the name of the node to use. 39 | NodeName = strings.Split(getEnv("NODE_NAME", ""), ",") 40 | 41 | // PrometheusPort is the port to expose Prometheus metrics. 42 | PrometheusPort = getEnvInt("PROMETHEUS_PORT", 0) 43 | 44 | // FakeVersion is the version of the fake to use. 45 | FakeVersion = getEnv("FAKE_VERSION", "v0.8.0") 46 | 47 | // KubeVersion is the version of Kubernetes to use. 48 | KubeVersion = getEnv("KUBE_VERSION", "v1.24.1") 49 | 50 | // EtcdVersion is the version of etcd to use. 51 | EtcdVersion = getEnv("ETCD_VERSION", k8s.GetEtcdVersion(parseRelease(KubeVersion))) 52 | 53 | // PrometheusVersion is the version of Prometheus to use. 54 | PrometheusVersion = getEnv("PROMETHEUS_VERSION", "v2.35.0") 55 | 56 | // SecurePort is the Apiserver use TLS. 57 | SecurePort = getEnvBool("SECURE_PORT", parseRelease(KubeVersion) > 19) 58 | 59 | // QuietPull is the flag to quiet the pull. 60 | QuietPull = getEnvBool("QUIET_PULL", false) 61 | 62 | // KubeImagePrefix is the prefix of the kubernetes image. 63 | KubeImagePrefix = getEnv("KUBE_IMAGE_PREFIX", "k8s.gcr.io") 64 | 65 | // EtcdImagePrefix is the prefix of the etcd image. 66 | EtcdImagePrefix = getEnv("ETCD_IMAGE_PREFIX", KubeImagePrefix) 67 | 68 | // FakeImagePrefix is the prefix of the fake image. 69 | FakeImagePrefix = getEnv("FAKE_IMAGE_PREFIX", "ghcr.io/wzshiming/fake-kubelet") 70 | 71 | // PrometheusImagePrefix is the prefix of the Prometheus image. 72 | PrometheusImagePrefix = getEnv("PROMETHEUS_IMAGE_PREFIX", "docker.io/prom") 73 | 74 | // EtcdImage is the image of etcd. 75 | EtcdImage = getEnv("ETCD_IMAGE", joinImageURI(EtcdImagePrefix, "etcd", EtcdVersion)) 76 | 77 | // KubeApiserverImage is the image of kube-apiserver. 78 | KubeApiserverImage = getEnv("KUBE_APISERVER_IMAGE", joinImageURI(KubeImagePrefix, "kube-apiserver", KubeVersion)) 79 | 80 | // KubeControllerManagerImage is the image of kube-controller-manager. 81 | KubeControllerManagerImage = getEnv("KUBE_CONTROLLER_MANAGER_IMAGE", joinImageURI(KubeImagePrefix, "kube-controller-manager", KubeVersion)) 82 | 83 | // KubeSchedulerImage is the image of kube-scheduler. 84 | KubeSchedulerImage = getEnv("KUBE_SCHEDULER_IMAGE", joinImageURI(KubeImagePrefix, "kube-scheduler", KubeVersion)) 85 | 86 | // FakeKubeletImage is the image of fake-kubelet. 87 | FakeKubeletImage = getEnv("FAKE_KUBELET_IMAGE", joinImageURI(FakeImagePrefix, "fake-kubelet", FakeVersion)) 88 | 89 | // PrometheusImage is the image of Prometheus. 90 | PrometheusImage = getEnv("PROMETHEUS_IMAGE", joinImageURI(PrometheusImagePrefix, "prometheus", PrometheusVersion)) 91 | 92 | // KindNodeImagePrefix is the prefix of the kind node image. 93 | KindNodeImagePrefix = getEnv("KIND_NODE_IMAGE_PREFIX", "docker.io/kindest") 94 | 95 | // KindNodeImage is the image of kind node. 96 | KindNodeImage = getEnv("KIND_NODE_IMAGE", joinImageURI(KindNodeImagePrefix, "node", KubeVersion)) 97 | 98 | // KubeBinaryPrefix is the prefix of the kubernetes binary. 99 | KubeBinaryPrefix = getEnv("KUBE_BINARY_PREFIX", "https://dl.k8s.io/release/"+KubeVersion+"/bin/"+runtime.GOOS+"/"+runtime.GOARCH) 100 | 101 | // KubeApiserverBinary is the binary of kube-apiserver. 102 | KubeApiserverBinary = getEnv("KUBE_APISERVER_BINARY", KubeBinaryPrefix+"/kube-apiserver"+BinSuffix) 103 | 104 | // KubeControllerManagerBinary is the binary of kube-controller-manager. 105 | KubeControllerManagerBinary = getEnv("KUBE_CONTROLLER_MANAGER_BINARY", KubeBinaryPrefix+"/kube-controller-manager"+BinSuffix) 106 | 107 | // KubeSchedulerBinary is the binary of kube-scheduler. 108 | KubeSchedulerBinary = getEnv("KUBE_SCHEDULER_BINARY", KubeBinaryPrefix+"/kube-scheduler"+BinSuffix) 109 | 110 | // MustKubectlBinary is the binary of kubectl. 111 | MustKubectlBinary = "https://dl.k8s.io/release/" + KubeVersion + "/bin/" + runtime.GOOS + "/" + runtime.GOARCH + "/kubectl" + BinSuffix 112 | 113 | // KubectlBinary is the binary of kubectl. 114 | KubectlBinary = getEnv("KUBECTL_BINARY", KubeBinaryPrefix+"/kubectl"+BinSuffix) 115 | 116 | // EtcdBinaryPrefix is the prefix of the etcd binary. 117 | EtcdBinaryPrefix = getEnv("ETCD_BINARY_PREFIX", "https://github.com/etcd-io/etcd/releases/download") 118 | 119 | // EtcdBinaryTar is the binary of etcd. 120 | EtcdBinaryTar = getEnv("ETCD_BINARY_TAR", EtcdBinaryPrefix+"/v"+strings.TrimSuffix(EtcdVersion, "-0")+"/etcd-v"+strings.TrimSuffix(EtcdVersion, "-0")+"-"+runtime.GOOS+"-"+runtime.GOARCH+"."+func() string { 121 | if runtime.GOOS == "linux" { 122 | return "tar.gz" 123 | } 124 | return "zip" 125 | }()) 126 | 127 | // FakeKubeletBinaryPrefix is the prefix of the fake kubelet binary. 128 | FakeKubeletBinaryPrefix = getEnv("FAKE_KUBELET_BINARY_PREFIX", "https://github.com/wzshiming/fake-kubelet/releases/download") 129 | 130 | // FakeKubeletBinary is the binary of fake-kubelet. 131 | FakeKubeletBinary = getEnv("FAKE_KUBELET_BINARY", FakeKubeletBinaryPrefix+"/"+FakeVersion+"/fake-kubelet_"+runtime.GOOS+"_"+runtime.GOARCH+BinSuffix) 132 | 133 | // PrometheusBinaryPrefix is the prefix of the Prometheus binary. 134 | PrometheusBinaryPrefix = getEnv("PROMETHEUS_BINARY_PREFIX", "https://github.com/prometheus/prometheus/releases/download") 135 | 136 | // PrometheusBinaryTar is the binary of Prometheus. 137 | PrometheusBinaryTar = getEnv("PROMETHEUS_BINARY_TAR", PrometheusBinaryPrefix+"/"+PrometheusVersion+"/prometheus-"+strings.TrimPrefix(PrometheusVersion, "v")+"."+runtime.GOOS+"-"+runtime.GOARCH+"."+func() string { 138 | if runtime.GOOS == "windows" { 139 | return "zip" 140 | } 141 | return "tar.gz" 142 | }()) 143 | 144 | BinSuffix = func() string { 145 | if runtime.GOOS == "windows" { 146 | return ".exe" 147 | } 148 | return "" 149 | }() 150 | 151 | // FeatureGates is a set of key=value pairs that describe feature gates for alpha/experimental features of Kubernetes. 152 | FeatureGates = getEnv("FEATURE_GATES", k8s.GetFeatureGates(parseRelease(KubeVersion))) 153 | 154 | // RuntimeConfig is a set of key=value pairs that enable or disable built-in APIs. 155 | RuntimeConfig = getEnv("RUNTIME_CONFIG", k8s.GetRuntimeConfig(parseRelease(KubeVersion))) 156 | ) 157 | 158 | // getEnv returns the value of the environment variable named by the key. 159 | func getEnv(key, def string) string { 160 | value, ok := os.LookupEnv(key) 161 | if !ok { 162 | return def 163 | } 164 | return value 165 | } 166 | 167 | // getEnvInt returns the value of the environment variable named by the key. 168 | func getEnvInt(key string, def int) int { 169 | v := getEnv(key, "") 170 | if v == "" { 171 | return def 172 | } 173 | i, err := strconv.Atoi(v) 174 | if err != nil { 175 | return def 176 | } 177 | return i 178 | } 179 | 180 | // getEnvBool returns the value of the environment variable named by the key. 181 | func getEnvBool(key string, def bool) bool { 182 | v := getEnv(key, "") 183 | if v == "" { 184 | return def 185 | } 186 | b, err := strconv.ParseBool(v) 187 | if err != nil { 188 | return def 189 | } 190 | return b 191 | } 192 | 193 | // joinImageURI joins the image URI. 194 | func joinImageURI(prefix, name, version string) string { 195 | return prefix + "/" + name + ":" + version 196 | } 197 | 198 | // parseRelease returns the release of the version. 199 | func parseRelease(version string) int { 200 | release := strings.Split(version, ".") 201 | if len(release) < 2 { 202 | return 0 203 | } 204 | r, err := strconv.ParseInt(release[1], 10, 64) 205 | if err != nil { 206 | return 0 207 | } 208 | return int(r) 209 | } 210 | 211 | func detectionRuntime() string { 212 | if runtime.GOOS == "linux" { 213 | return "binary" 214 | } 215 | return "docker" 216 | } 217 | -------------------------------------------------------------------------------- /supported_releases.txt: -------------------------------------------------------------------------------- 1 | v1.24.1 2 | v1.23.6 3 | v1.22.9 4 | v1.21.12 5 | v1.20.15 6 | v1.19.16 7 | v1.18.20 8 | v1.17.17 9 | v1.16.15 10 | v1.15.12 11 | v1.14.10 12 | v1.13.12 13 | v1.12.10 14 | v1.11.10 15 | v1.10.13 16 | --------------------------------------------------------------------------------