[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nlabels: [\"kind/bug\"]\nabout: Create a report to help us improve\n---\n\n**Describe the Bug**\nA clear and concise description of what the bug is.\n\nFor UI issues please also add a screenshot that shows the issue.\n\n**Versions Used**\nKubeSphere:\nKubernetes: (If KubeSphere installer used, you can skip this)\n\n**Environment**\nHow many nodes and their hardware configuration:\n\nFor example: CentOS 7.5 / 3 masters:  8cpu/8g; 3 nodes: 8cpu/16g\n(and other info are welcomed to help us debugging)\n\n**How To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature Request\nabout: Have a good idea? Please don't hesitate to write it down, describe the new feature.\n---\n\n**What's it about?**\n<!--\nA clear and concise description of what this feature request is.\n-->\n\n**What's the reason why we need it?**\n<!--\nPlease tell us if you think it's a necessary feature for Pixiu. Give us as many details about it as you can.\nTwo or more use cases might be very helpful when other contributors try to go through this request. If you have some references,\nplease just add it below.\n-->\n\nI believe this is an important feature for Pixiu. There're a few use cases:\n\n* case one\n* case two\n* ...\n\nPlease leave your comments below if there's anyone agrees with me. Or just give me a thumb up.\n\n**Area Suggestion**\n<!--\nIn order to have a clear issue list, giving an accuracy area is necessary. If you are not sure about it, please just leave it alone.\n\nYou can find some possible areas below. Please attention, sometimes crossing multiple areas might be possible. So, you\ncan keep one or more areas in this issue.\n\n> /area alerting\n> /area api\n> /area apiserver\n> /area app-management\n> /area audit\n> /area console\n> /area devops\n> /area documentation\n> /area edge\n> /area iam\n> /area installation\n> /area logging\n> /area microservice\n> /area monitoring\n> /area multicluster\n> /area networking\n> /area notification\n> /area observability\n> /area performance\n> /area security\n> /area storage\n> /area test\n> /area upgrade\n-->\n\n/kind feature-request\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "#### What type of PR is this?\n\n<!--\nAdd one of the following kinds:\n/kind bug\n/kind cleanup\n/kind documentation\n/kind feature\n/kind design\n\nOptionally add one or more of the following kinds if applicable:\n/kind api-change\n/kind deprecation\n/kind failing-test\n/kind flake\n/kind regression\n-->\n\n#### What this PR does / why we need it:\n\n#### Which issue(s) this PR fixes:\n<!--\n*Automatically closes linked issue when PR is merged.\nUsage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.\n_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*\n-->\nFixes #\n\n#### Special notes for your reviewer:\n\n#### Does this PR introduce a user-facing change?\n<!--\nIf no, just write \"NONE\" in the release-note block below.\nIf yes, a release note is required:\nEnter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string \"action required\".\n-->\n```release-note\n\n```\n\n#### Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.:\n\n<!--\nThis section can be blank if this pull request does not require a release note.\n\nWhen adding links which point to resources within git repositories, like\nKEPs or supporting documentation, please reference a specific commit and avoid\nlinking directly to the master branch. This ensures that links reference a\nspecific point in time, rather than a document that may change over time.\n\nSee here for guidance on getting permanent links to files: https://help.github.com/en/articles/getting-permanent-links-to-files\n\nPlease use the following format for linking documentation:\n- [KEP]: <link>\n- [Usage]: <link>\n- [Other doc]: <link>\n-->\n```docs\n\n```\n"
  },
  {
    "path": ".github/workflows/build-webshell-image.yml",
    "content": "name: Publish webshell image\n\non:\n  push:\n    branches:\n      - \"master\"\n    paths:\n      - 'docker/Dockerfile-toolbox'\n      - 'Makefile'\n\nenv:\n  K8S_VERSION: v1.23.6 # the same as Makefile\n  HELM_VERSION: v3.7.1 # the same as Makefile\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v3\n\n      - name: Get short commit hash\n        run: echo \"COMMIT_HASH=$(git rev-parse --short HEAD)\" >> $GITHUB_ENV\n\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v3\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v3\n\n      - name: Login in dockerhub\n        uses: docker/login-action@v3\n        with:\n          username: ${{ secrets.DOCKER_NAME }}\n          password: ${{ secrets.DOCKER_PASSWORD }}\n\n      - name: Build and push webshell image\n        uses: docker/build-push-action@v5\n        with:\n          context: .\n          file: ./docker/Dockerfile-toolbox\n          platforms: linux/amd64,linux/arm64\n          build-args: |\n            K8S_VERSION=${{ env.K8S_VERSION }}\n            HELM_VERSION=${{ env.HELM_VERSION }}\n          push: true\n          tags: |\n            ${{ secrets.DOCKER_NAME }}/pixiu-webshell:latest\n            ${{ secrets.DOCKER_NAME }}/pixiu-webshell:v0.1\n            ${{ secrets.DOCKER_NAME }}/pixiu-webshell:${{ env.COMMIT_HASH }}\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches:\n      - master\n\nenv:\n  GO_VERSION: '1.17.5'\n\njobs:\n#  markdown-lint:\n#    runs-on: ubuntu-latest\n#    container: node:14\n#    steps:\n#      - name: Checkout\n#        uses: actions/checkout@v4\n#\n#      - name: Install mdl\n#        run: apt update && apt-get install ruby-full -y && gem install chef-utils -v 16.6.14 && gem install mdl -v 0.11.0\n#\n#      - name: Lint markdown files\n#        run: find  ./ -name  \"*.md\" | grep -v '.github' | xargs mdl -r ~MD001,~MD004,~MD005,~MD006,~MD007,~MD010,~MD013,~MD022,~MD023,~MD024,~MD025,~MD029,~MD031,~MD032,~MD033,~MD034,~MD036\n\n  golang-lint:\n    runs-on: ubuntu-latest\n    steps:\n    - name: Checkout\n      uses: actions/checkout@v2\n      with:\n        submodules: true\n\n    - name: Set up Golang\n      uses: actions/setup-go@v2\n      with:\n        go-version: ${{ env.GO_VERSION }}\n\n    - name: Run go fmt test\n      run: hack/verify-gofmt.sh\n      env:\n        GO111MODULE: auto\n\n    - name: Build the pixiu binariy\n      run: go build -v ./...\n\n    - name: Run pixiu unit test\n      run: go test -v ./...\n"
  },
  {
    "path": ".github/workflows/docker-image.yml",
    "content": "name: Publish pixiu image\n\non:\n  push:\n    branches:\n      - \"master\"\n    paths-ignore:\n      - 'docs/**'\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v3\n\n      - name: Get short commit hash\n        run: |\n          echo \"COMMIT_HASH=$(git rev-parse --short HEAD)\" >> $GITHUB_ENV\n          echo \"TIMESTAMP=$(date +%Y%m%d%H%M%S)\" >> $GITHUB_ENV\n\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v3\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v3\n\n      # - name: Login harbor\n      #   uses: docker/login-action@v3\n      #   with:\n      #     registry: crpi-0ecikjs9ylb2hqyo.cn-hangzhou.personal.cr.aliyuncs.com\n      #     username: ${{ secrets.HARBOR_USERNAME }}\n      #     password: ${{ secrets.HARBOR_PASSWORD }}\n\n      # - name: Build and push the pixiu image\n      #   uses: docker/build-push-action@v5\n      #   with:\n      #     context: .\n      #     file: ./docker/Dockerfile\n      #     platforms: linux/amd64,linux/arm64\n      #     build-args: |\n      #       VERSION=${{ env.COMMIT_HASH }}-${{ env.TIMESTAMP }}\n      #     push: true\n      #     tags: |\n      #       crpi-0ecikjs9ylb2hqyo.cn-hangzhou.personal.cr.aliyuncs.com/pixiu-public/pixiu:latest\n      #       crpi-0ecikjs9ylb2hqyo.cn-hangzhou.personal.cr.aliyuncs.com/pixiu-public/pixiu:v1.0.1\n      #       crpi-0ecikjs9ylb2hqyo.cn-hangzhou.personal.cr.aliyuncs.com/pixiu-public/pixiu:${{ env.COMMIT_HASH }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\nbin\ntestbin/*\n\n# Test binary, build with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# Kubernetes Generated files - skip generated files, except for vendored files\n!vendor/**/zz_generated.*\n\n# editor and IDE paraphernalia\n.idea\n.vscode\n.gitcommits\n*.swp\n*.swo\n\n# output\ndist\n\n# licensefmt\nhack/tools/licfmt/licfmt\n\nvendor\n"
  },
  {
    "path": "Makefile",
    "content": ".PHONY: run build image push clean\n\ntag = v0.1\nreleaseName = pixiu\ndockerhubUser ?= jacky06\nk8sVersion ?= v1.23.6\nhelmVersion ?= v3.7.1\ntargetDir ?= dist\ncommitHash = $(shell git rev-parse --short HEAD)\n# e.g. 1862ce5-20240203180617\nversion = $(commitHash)-$(shell date +%Y%m%d%H%M%S)\n\nALL: run\n\nrun: build\n\t./pixiu --configfile ./config.yaml\n\nbuild:\n\tgo build -o $(targetDir)/$(releaseName) -ldflags \"-X 'main.version=$(version)'\" ./cmd/\n\nimage:\n\tdocker build -t $(dockerhubUser)/$(releaseName):$(tag) --build-arg VERSION=$(version) .\n\npush: image\n\tdocker push $(dockerhubUser)/$(releaseName):$(tag)\n\nwebshell-image:\n\tdocker build --build-arg K8S_VERSION=$(k8sVersion) \\\n\t\t--build-arg HELM_VERSION=$(helmVersion) \\\n\t\t-t $(dockerhubUser)/pixiu-webshell:$(tag) -f docker/Dockerfile .\n\npush-webshell-image: webshell-image\n\tdocker push $(dockerhubUser)/pixiu-webshell:$(tag)\n\nlicfmt:\n\tgo run hack/tools/licfmt/licfmt.go -v ./*\n\nclean:\n\t-rm -f ./$(releaseName)\n\n.PHONY: api-docs\napi-docs: ## generate the api docs\n\tswag init --generalInfo ./cmd/pixiuserver.go --output ./api/docs\n"
  },
  {
    "path": "README.md",
    "content": "# Pixiu Overview\n\nPixiu is an open source container platform for cloud-native application management.\n\n![Build Status][build-url]\n[![Release][release-image]][release-url]\n[![License][license-image]][license-url]\n\n## 安装手册\n- [安装手册](install.md)\n\n## 页面展示\n### 首页\n<img width=\"1647\" alt=\"image\" src=\"https://github.com/youdian-xiaoshuai/pixiu/assets/64686398/9fc5e005-95cd-49ee-a13c-13f22949fd74\">\n\n### 部署计划\n- 创建部署计划\n    ```text\n    通过新建部署计划,可以实现通过页面 `点点点` 的方式创建 `kubernetes` 集群, 如同各大云厂商一样\n    ```\n    ![image](https://github.com/pixiu-io/dashboard/blob/master/images/plan_deploy.png?raw=true)\n\n- 新建节点\n    ```text\n    1. 添加各个节点的信息，节点的角色，用户名，密码等\n    2. 各大组件支持高度的自定义，例如：calico，fannel\n    2. kubernetes 版本自主选择\n    ```\n    ![image](https://github.com/pixiu-io/dashboard/blob/master/images/plan_node.png?raw=true)\n\n- 部署详情\n    ```text\n    可以看到部署计划在每个部署的运行状态，以及详细日志\n    ```\n    ![image](https://github.com/pixiu-io/dashboard/blob/master/images/plan_detail.png?raw=true)\n\n### 集群管理\n- 集群概览\n    ```text\n    cpu状态，内存状态，集群的基本信息，网络信息，集群服务\n    ```\n    ![image](https://github.com/pixiu-io/dashboard/blob/master/images/cluster_detail.png?raw=true)\n\n- 集群管理\n![image](https://github.com/pixiu-io/dashboard/blob/master/images/cluster_manager.png?raw=true)\n\n- 集群工作负载deployment\n![image](https://github.com/pixiu-io/dashboard/blob/master/images/cluster_deploy.png?raw=true)\n\n- 集群工作负载pod\n![image](https://github.com/pixiu-io/dashboard/blob/master/images/cluster_pod.png?raw=true)\n\n### 审计功能\n- 审计管理\n![image](https://github.com/pixiu-io/dashboard/blob/master/images/audit_manager.png?raw=true)\n\n## 学习分享\n- [go-learning](https://github.com/caoyingjunz/go-learning)\n\n## 沟通交流\n- 搜索微信号 `yingjuncz`, 备注（pixiu）, 验证通过会加入群聊\n- [bilibili](https://space.bilibili.com/3493104248162809?spm_id_from=333.1007.0.0) 技术分享\n\nCopyright 2019 caoyingjun (cao.yingjunz@gmail.com) Apache License 2.0\n\n[build-url]: https://github.com/caoyingjunz/pixiu/actions/workflows/ci.yml/badge.svg\n[release-image]: https://img.shields.io/badge/release-download-orange.svg\n[release-url]: https://www.apache.org/licenses/LICENSE-2.0.html\n[license-image]: https://img.shields.io/badge/license-Apache%202-4EB1BA.svg\n[license-url]: https://www.apache.org/licenses/LICENSE-2.0.html\n"
  },
  {
    "path": "api/docs/docs.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package docs GENERATED BY SWAG; DO NOT EDIT\n// This file was generated by swaggo/swag\npackage docs\n\nimport \"github.com/swaggo/swag\"\n\nconst docTemplate = `{\n    \"schemes\": {{ marshal .Schemes }},\n    \"swagger\": \"2.0\",\n    \"info\": {\n        \"description\": \"{{escape .Description}}\",\n        \"title\": \"{{.Title}}\",\n        \"termsOfService\": \"https://github.com/caoyingjunz/pixiu\",\n        \"contact\": {\n            \"name\": \"API Support\",\n            \"url\": \"https://github.com/caoyingjunz/pixiu\",\n            \"email\": \"support@pixiu.io\"\n        },\n        \"license\": {\n            \"name\": \"Apache 2.0\",\n            \"url\": \"http://www.apache.org/licenses/LICENSE-2.0.html\"\n        },\n        \"version\": \"{{.Version}}\"\n    },\n    \"host\": \"{{.Host}}\",\n    \"basePath\": \"{{.BasePath}}\",\n    \"paths\": {\n        \"/pixiu/clusters\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"Bearer\": []\n                    }\n                ],\n                \"description\": \"List clusters\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Clusters\"\n                ],\n                \"summary\": \"List clusters\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"allOf\": [\n                                    {\n                                        \"$ref\": \"#/definitions/httputils.Response\"\n                                    },\n                                    {\n                                        \"type\": \"object\",\n                                        \"properties\": {\n                                            \"result\": {\n                                                \"type\": \"array\",\n                                                \"items\": {\n                                                    \"$ref\": \"#/definitions/types.Cluster\"\n                                                }\n                                            }\n                                        }\n                                    }\n                                ]\n                            }\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"Not Found\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/pixiu/clusters/\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"Bearer\": []\n                    }\n                ],\n                \"description\": \"Create by a json cluster\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Clusters\"\n                ],\n                \"summary\": \"Create a cluster\",\n                \"parameters\": [\n                    {\n                        \"description\": \"Create cluster\",\n                        \"name\": \"cluster\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/types.Cluster\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"Not Found\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/pixiu/clusters/{clusterId}\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"Bearer\": []\n                    }\n                ],\n                \"description\": \"Get by cloud cluster ID\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Clusters\"\n                ],\n                \"summary\": \"Get Cluster by clusterId\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"Cluster ID\",\n                        \"name\": \"clusterId\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/httputils.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"result\": {\n                                            \"$ref\": \"#/definitions/types.Cluster\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"Not Found\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    }\n                }\n            },\n            \"put\": {\n                \"security\": [\n                    {\n                        \"Bearer\": []\n                    }\n                ],\n                \"description\": \"Update by json cluster\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Clusters\"\n                ],\n                \"summary\": \"Update an cluster\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"Cluster ID\",\n                        \"name\": \"clusterId\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    },\n                    {\n                        \"description\": \"Update cluster\",\n                        \"name\": \"cluster\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/types.Cluster\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"Not Found\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    }\n                }\n            },\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"Bearer\": []\n                    }\n                ],\n                \"description\": \"Delete by cloud cluster ID\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Clusters\"\n                ],\n                \"summary\": \"Delete cluster by clusterId\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"Cluster ID\",\n                        \"name\": \"clusterId\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"Not Found\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/pixiu/clusters/{clusterId}/ping\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"Bearer\": []\n                    }\n                ],\n                \"description\": \"Do ping\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Clusters\"\n                ],\n                \"summary\": \"Ping cluster\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"Cluster ID\",\n                        \"name\": \"clusterId\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"$ref\": \"#/definitions/httputils.Response\"\n                            }\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"Not Found\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/pixiu/users\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"Bearer\": []\n                    }\n                ],\n                \"description\": \"List users\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Users\"\n                ],\n                \"summary\": \"List users\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"allOf\": [\n                                    {\n                                        \"$ref\": \"#/definitions/httputils.Response\"\n                                    },\n                                    {\n                                        \"type\": \"object\",\n                                        \"properties\": {\n                                            \"result\": {\n                                                \"type\": \"array\",\n                                                \"items\": {\n                                                    \"$ref\": \"#/definitions/types.User\"\n                                                }\n                                            }\n                                        }\n                                    }\n                                ]\n                            }\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"Not Found\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/pixiu/users/\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"Bearer\": []\n                    }\n                ],\n                \"description\": \"Create by a json user\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Users\"\n                ],\n                \"summary\": \"Create a user\",\n                \"parameters\": [\n                    {\n                        \"description\": \"Create user\",\n                        \"name\": \"user\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/types.User\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"Not Found\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/pixiu/users/login\": {\n            \"post\": {\n                \"description\": \"Login by a json user\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Login\"\n                ],\n                \"summary\": \"User login\",\n                \"parameters\": [\n                    {\n                        \"description\": \"User login\",\n                        \"name\": \"user\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/types.User\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"Not Found\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/pixiu/users/{userId}\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"Bearer\": []\n                    }\n                ],\n                \"description\": \"Get by user ID\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Users\"\n                ],\n                \"summary\": \"Get user by userId\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"User ID\",\n                        \"name\": \"userId\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/httputils.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"result\": {\n                                            \"$ref\": \"#/definitions/types.User\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"Not Found\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    }\n                }\n            },\n            \"put\": {\n                \"security\": [\n                    {\n                        \"Bearer\": []\n                    }\n                ],\n                \"description\": \"Update by json user\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Users\"\n                ],\n                \"summary\": \"Update an user\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"User ID\",\n                        \"name\": \"userId\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    },\n                    {\n                        \"description\": \"Update user\",\n                        \"name\": \"user\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/types.User\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"Not Found\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    }\n                }\n            },\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"Bearer\": []\n                    }\n                ],\n                \"description\": \"Delete by userID\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Users\"\n                ],\n                \"summary\": \"Delete user by userId\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"User ID\",\n                        \"name\": \"userId\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"Not Found\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    }\n                }\n            }\n        }\n    },\n    \"definitions\": {\n        \"httputils.Response\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"code\": {\n                    \"description\": \"返回的状态码\",\n                    \"type\": \"integer\"\n                },\n                \"message\": {\n                    \"description\": \"异常返回时的错误信息\",\n                    \"type\": \"string\"\n                },\n                \"result\": {\n                    \"description\": \"正常返回时的数据，可以为任意数据结构\"\n                }\n            }\n        },\n        \"types.Cluster\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"alias_name\": {\n                    \"type\": \"string\"\n                },\n                \"cluster_type\": {\n                    \"description\": \"0：标准集群 1: 自建集群\",\n                    \"type\": \"integer\"\n                },\n                \"description\": {\n                    \"description\": \"集群用途描述，可以为空\",\n                    \"type\": \"string\"\n                },\n                \"gmt_create\": {\n                    \"description\": \"pixiu 对象创建时间\",\n                    \"type\": \"string\"\n                },\n                \"gmt_modified\": {\n                    \"description\": \"pixiu 对象修改时间\",\n                    \"type\": \"string\"\n                },\n                \"id\": {\n                    \"description\": \"pixiu 对象 ID\",\n                    \"type\": \"integer\"\n                },\n                \"kube_config\": {\n                    \"description\": \"k8s kubeConfig base64 字段\",\n                    \"type\": \"string\"\n                },\n                \"kubernetes_version\": {\n                    \"description\": \"集群的版本\",\n                    \"type\": \"string\"\n                },\n                \"name\": {\n                    \"type\": \"string\"\n                },\n                \"nodes\": {\n                    \"description\": \"节点数量\",\n                    \"type\": \"integer\"\n                },\n                \"resource_version\": {\n                    \"description\": \"Pixiu 对象版本号\",\n                    \"type\": \"integer\"\n                },\n                \"resources\": {\n                    \"description\": \"The memory and cpu usage\",\n                    \"$ref\": \"#/definitions/types.Resources\"\n                }\n            }\n        },\n        \"types.Resources\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"cpu\": {\n                    \"type\": \"string\"\n                },\n                \"memory\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"types.User\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"description\": {\n                    \"description\": \"用户描述信息\",\n                    \"type\": \"string\"\n                },\n                \"email\": {\n                    \"description\": \"用户注册邮件\",\n                    \"type\": \"string\"\n                },\n                \"gmt_create\": {\n                    \"description\": \"pixiu 对象创建时间\",\n                    \"type\": \"string\"\n                },\n                \"gmt_modified\": {\n                    \"description\": \"pixiu 对象修改时间\",\n                    \"type\": \"string\"\n                },\n                \"id\": {\n                    \"description\": \"pixiu 对象 ID\",\n                    \"type\": \"integer\"\n                },\n                \"name\": {\n                    \"description\": \"用户名称\",\n                    \"type\": \"string\"\n                },\n                \"password\": {\n                    \"description\": \"用户密码\",\n                    \"type\": \"string\"\n                },\n                \"resource_version\": {\n                    \"description\": \"Pixiu 对象版本号\",\n                    \"type\": \"integer\"\n                },\n                \"role\": {\n                    \"description\": \"用户角色，目前只实现管理员，0: 普通用户 1: 管理员 2: 超级管理员\",\n                    \"type\": \"string\"\n                },\n                \"status\": {\n                    \"description\": \"用户状态标识\",\n                    \"type\": \"integer\"\n                }\n            }\n        }\n    },\n    \"securityDefinitions\": {\n        \"Bearer\": {\n            \"description\": \"Type \\\"Bearer\\\" followed by a space and JWT token\",\n            \"type\": \"apiKey\",\n            \"name\": \"Authorization\",\n            \"in\": \"header\"\n        }\n    }\n}`\n\n// SwaggerInfo holds exported Swagger Info so clients can modify it\nvar SwaggerInfo = &swag.Spec{\n\tVersion:          \"1.0\",\n\tHost:             \"localhost:8090\",\n\tBasePath:         \"\",\n\tSchemes:          []string{\"http\", \"https\"},\n\tTitle:            \"Pixiu API Documentation\",\n\tDescription:      \"\",\n\tInfoInstanceName: \"swagger\",\n\tSwaggerTemplate:  docTemplate,\n}\n\nfunc init() {\n\tswag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)\n}\n"
  },
  {
    "path": "api/docs/swagger.json",
    "content": "{\n    \"schemes\": [\n        \"http\",\n        \"https\"\n    ],\n    \"swagger\": \"2.0\",\n    \"info\": {\n        \"title\": \"Pixiu API Documentation\",\n        \"termsOfService\": \"https://github.com/caoyingjunz/pixiu\",\n        \"contact\": {\n            \"name\": \"API Support\",\n            \"url\": \"https://github.com/caoyingjunz/pixiu\",\n            \"email\": \"support@pixiu.io\"\n        },\n        \"license\": {\n            \"name\": \"Apache 2.0\",\n            \"url\": \"http://www.apache.org/licenses/LICENSE-2.0.html\"\n        },\n        \"version\": \"1.0\"\n    },\n    \"host\": \"localhost:8090\",\n    \"paths\": {\n        \"/pixiu/clusters\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"Bearer\": []\n                    }\n                ],\n                \"description\": \"List clusters\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Clusters\"\n                ],\n                \"summary\": \"List clusters\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"allOf\": [\n                                    {\n                                        \"$ref\": \"#/definitions/httputils.Response\"\n                                    },\n                                    {\n                                        \"type\": \"object\",\n                                        \"properties\": {\n                                            \"result\": {\n                                                \"type\": \"array\",\n                                                \"items\": {\n                                                    \"$ref\": \"#/definitions/types.Cluster\"\n                                                }\n                                            }\n                                        }\n                                    }\n                                ]\n                            }\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"Not Found\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/pixiu/clusters/\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"Bearer\": []\n                    }\n                ],\n                \"description\": \"Create by a json cluster\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Clusters\"\n                ],\n                \"summary\": \"Create a cluster\",\n                \"parameters\": [\n                    {\n                        \"description\": \"Create cluster\",\n                        \"name\": \"cluster\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/types.Cluster\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"Not Found\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/pixiu/clusters/{clusterId}\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"Bearer\": []\n                    }\n                ],\n                \"description\": \"Get by cloud cluster ID\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Clusters\"\n                ],\n                \"summary\": \"Get Cluster by clusterId\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"Cluster ID\",\n                        \"name\": \"clusterId\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/httputils.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"result\": {\n                                            \"$ref\": \"#/definitions/types.Cluster\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"Not Found\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    }\n                }\n            },\n            \"put\": {\n                \"security\": [\n                    {\n                        \"Bearer\": []\n                    }\n                ],\n                \"description\": \"Update by json cluster\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Clusters\"\n                ],\n                \"summary\": \"Update an cluster\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"Cluster ID\",\n                        \"name\": \"clusterId\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    },\n                    {\n                        \"description\": \"Update cluster\",\n                        \"name\": \"cluster\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/types.Cluster\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"Not Found\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    }\n                }\n            },\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"Bearer\": []\n                    }\n                ],\n                \"description\": \"Delete by cloud cluster ID\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Clusters\"\n                ],\n                \"summary\": \"Delete cluster by clusterId\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"Cluster ID\",\n                        \"name\": \"clusterId\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"Not Found\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/pixiu/clusters/{clusterId}/ping\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"Bearer\": []\n                    }\n                ],\n                \"description\": \"Do ping\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Clusters\"\n                ],\n                \"summary\": \"Ping cluster\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"Cluster ID\",\n                        \"name\": \"clusterId\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"$ref\": \"#/definitions/httputils.Response\"\n                            }\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"Not Found\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/pixiu/users\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"Bearer\": []\n                    }\n                ],\n                \"description\": \"List users\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Users\"\n                ],\n                \"summary\": \"List users\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"allOf\": [\n                                    {\n                                        \"$ref\": \"#/definitions/httputils.Response\"\n                                    },\n                                    {\n                                        \"type\": \"object\",\n                                        \"properties\": {\n                                            \"result\": {\n                                                \"type\": \"array\",\n                                                \"items\": {\n                                                    \"$ref\": \"#/definitions/types.User\"\n                                                }\n                                            }\n                                        }\n                                    }\n                                ]\n                            }\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"Not Found\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/pixiu/users/\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"Bearer\": []\n                    }\n                ],\n                \"description\": \"Create by a json user\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Users\"\n                ],\n                \"summary\": \"Create a user\",\n                \"parameters\": [\n                    {\n                        \"description\": \"Create user\",\n                        \"name\": \"user\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/types.User\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"Not Found\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/pixiu/users/login\": {\n            \"post\": {\n                \"description\": \"Login by a json user\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Login\"\n                ],\n                \"summary\": \"User login\",\n                \"parameters\": [\n                    {\n                        \"description\": \"User login\",\n                        \"name\": \"user\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/types.User\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"Not Found\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/pixiu/users/{userId}\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"Bearer\": []\n                    }\n                ],\n                \"description\": \"Get by user ID\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Users\"\n                ],\n                \"summary\": \"Get user by userId\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"User ID\",\n                        \"name\": \"userId\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/httputils.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"result\": {\n                                            \"$ref\": \"#/definitions/types.User\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"Not Found\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    }\n                }\n            },\n            \"put\": {\n                \"security\": [\n                    {\n                        \"Bearer\": []\n                    }\n                ],\n                \"description\": \"Update by json user\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Users\"\n                ],\n                \"summary\": \"Update an user\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"User ID\",\n                        \"name\": \"userId\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    },\n                    {\n                        \"description\": \"Update user\",\n                        \"name\": \"user\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/types.User\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"Not Found\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    }\n                }\n            },\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"Bearer\": []\n                    }\n                ],\n                \"description\": \"Delete by userID\",\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Users\"\n                ],\n                \"summary\": \"Delete user by userId\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"User ID\",\n                        \"name\": \"userId\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"Not Found\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/httputils.Response\"\n                        }\n                    }\n                }\n            }\n        }\n    },\n    \"definitions\": {\n        \"httputils.Response\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"code\": {\n                    \"description\": \"返回的状态码\",\n                    \"type\": \"integer\"\n                },\n                \"message\": {\n                    \"description\": \"异常返回时的错误信息\",\n                    \"type\": \"string\"\n                },\n                \"result\": {\n                    \"description\": \"正常返回时的数据，可以为任意数据结构\"\n                }\n            }\n        },\n        \"types.Cluster\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"alias_name\": {\n                    \"type\": \"string\"\n                },\n                \"cluster_type\": {\n                    \"description\": \"0：标准集群 1: 自建集群\",\n                    \"type\": \"integer\"\n                },\n                \"description\": {\n                    \"description\": \"集群用途描述，可以为空\",\n                    \"type\": \"string\"\n                },\n                \"gmt_create\": {\n                    \"description\": \"pixiu 对象创建时间\",\n                    \"type\": \"string\"\n                },\n                \"gmt_modified\": {\n                    \"description\": \"pixiu 对象修改时间\",\n                    \"type\": \"string\"\n                },\n                \"id\": {\n                    \"description\": \"pixiu 对象 ID\",\n                    \"type\": \"integer\"\n                },\n                \"kube_config\": {\n                    \"description\": \"k8s kubeConfig base64 字段\",\n                    \"type\": \"string\"\n                },\n                \"kubernetes_version\": {\n                    \"description\": \"集群的版本\",\n                    \"type\": \"string\"\n                },\n                \"name\": {\n                    \"type\": \"string\"\n                },\n                \"nodes\": {\n                    \"description\": \"节点数量\",\n                    \"type\": \"integer\"\n                },\n                \"resource_version\": {\n                    \"description\": \"Pixiu 对象版本号\",\n                    \"type\": \"integer\"\n                },\n                \"resources\": {\n                    \"description\": \"The memory and cpu usage\",\n                    \"$ref\": \"#/definitions/types.Resources\"\n                }\n            }\n        },\n        \"types.Resources\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"cpu\": {\n                    \"type\": \"string\"\n                },\n                \"memory\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"types.User\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"description\": {\n                    \"description\": \"用户描述信息\",\n                    \"type\": \"string\"\n                },\n                \"email\": {\n                    \"description\": \"用户注册邮件\",\n                    \"type\": \"string\"\n                },\n                \"gmt_create\": {\n                    \"description\": \"pixiu 对象创建时间\",\n                    \"type\": \"string\"\n                },\n                \"gmt_modified\": {\n                    \"description\": \"pixiu 对象修改时间\",\n                    \"type\": \"string\"\n                },\n                \"id\": {\n                    \"description\": \"pixiu 对象 ID\",\n                    \"type\": \"integer\"\n                },\n                \"name\": {\n                    \"description\": \"用户名称\",\n                    \"type\": \"string\"\n                },\n                \"password\": {\n                    \"description\": \"用户密码\",\n                    \"type\": \"string\"\n                },\n                \"resource_version\": {\n                    \"description\": \"Pixiu 对象版本号\",\n                    \"type\": \"integer\"\n                },\n                \"role\": {\n                    \"description\": \"用户角色，目前只实现管理员，0: 普通用户 1: 管理员 2: 超级管理员\",\n                    \"type\": \"string\"\n                },\n                \"status\": {\n                    \"description\": \"用户状态标识\",\n                    \"type\": \"integer\"\n                }\n            }\n        }\n    },\n    \"securityDefinitions\": {\n        \"Bearer\": {\n            \"description\": \"Type \\\"Bearer\\\" followed by a space and JWT token\",\n            \"type\": \"apiKey\",\n            \"name\": \"Authorization\",\n            \"in\": \"header\"\n        }\n    }\n}"
  },
  {
    "path": "api/docs/swagger.yaml",
    "content": "definitions:\n  httputils.Response:\n    properties:\n      code:\n        description: 返回的状态码\n        type: integer\n      message:\n        description: 异常返回时的错误信息\n        type: string\n      result:\n        description: 正常返回时的数据，可以为任意数据结构\n    type: object\n  types.Cluster:\n    properties:\n      alias_name:\n        type: string\n      cluster_type:\n        description: '0：标准集群 1: 自建集群'\n        type: integer\n      description:\n        description: 集群用途描述，可以为空\n        type: string\n      gmt_create:\n        description: pixiu 对象创建时间\n        type: string\n      gmt_modified:\n        description: pixiu 对象修改时间\n        type: string\n      id:\n        description: pixiu 对象 ID\n        type: integer\n      kube_config:\n        description: k8s kubeConfig base64 字段\n        type: string\n      kubernetes_version:\n        description: 集群的版本\n        type: string\n      name:\n        type: string\n      nodes:\n        description: 节点数量\n        type: integer\n      resource_version:\n        description: Pixiu 对象版本号\n        type: integer\n      resources:\n        $ref: '#/definitions/types.Resources'\n        description: The memory and cpu usage\n    type: object\n  types.Resources:\n    properties:\n      cpu:\n        type: string\n      memory:\n        type: string\n    type: object\n  types.User:\n    properties:\n      description:\n        description: 用户描述信息\n        type: string\n      email:\n        description: 用户注册邮件\n        type: string\n      gmt_create:\n        description: pixiu 对象创建时间\n        type: string\n      gmt_modified:\n        description: pixiu 对象修改时间\n        type: string\n      id:\n        description: pixiu 对象 ID\n        type: integer\n      name:\n        description: 用户名称\n        type: string\n      password:\n        description: 用户密码\n        type: string\n      resource_version:\n        description: Pixiu 对象版本号\n        type: integer\n      role:\n        description: '用户角色，目前只实现管理员，0: 普通用户 1: 管理员 2: 超级管理员'\n        type: string\n      status:\n        description: 用户状态标识\n        type: integer\n    type: object\nhost: localhost:8090\ninfo:\n  contact:\n    email: support@pixiu.io\n    name: API Support\n    url: https://github.com/caoyingjunz/pixiu\n  license:\n    name: Apache 2.0\n    url: http://www.apache.org/licenses/LICENSE-2.0.html\n  termsOfService: https://github.com/caoyingjunz/pixiu\n  title: Pixiu API Documentation\n  version: \"1.0\"\npaths:\n  /pixiu/clusters:\n    get:\n      consumes:\n      - application/json\n      description: List clusters\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: OK\n          schema:\n            items:\n              allOf:\n              - $ref: '#/definitions/httputils.Response'\n              - properties:\n                  result:\n                    items:\n                      $ref: '#/definitions/types.Cluster'\n                    type: array\n                type: object\n            type: array\n        \"400\":\n          description: Bad Request\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"404\":\n          description: Not Found\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"500\":\n          description: Internal Server Error\n          schema:\n            $ref: '#/definitions/httputils.Response'\n      security:\n      - Bearer: []\n      summary: List clusters\n      tags:\n      - Clusters\n  /pixiu/clusters/:\n    post:\n      consumes:\n      - application/json\n      description: Create by a json cluster\n      parameters:\n      - description: Create cluster\n        in: body\n        name: cluster\n        required: true\n        schema:\n          $ref: '#/definitions/types.Cluster'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: OK\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"400\":\n          description: Bad Request\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"404\":\n          description: Not Found\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"500\":\n          description: Internal Server Error\n          schema:\n            $ref: '#/definitions/httputils.Response'\n      security:\n      - Bearer: []\n      summary: Create a cluster\n      tags:\n      - Clusters\n  /pixiu/clusters/{clusterId}:\n    delete:\n      consumes:\n      - application/json\n      description: Delete by cloud cluster ID\n      parameters:\n      - description: Cluster ID\n        in: path\n        name: clusterId\n        required: true\n        type: integer\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: OK\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"400\":\n          description: Bad Request\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"404\":\n          description: Not Found\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"500\":\n          description: Internal Server Error\n          schema:\n            $ref: '#/definitions/httputils.Response'\n      security:\n      - Bearer: []\n      summary: Delete cluster by clusterId\n      tags:\n      - Clusters\n    get:\n      consumes:\n      - application/json\n      description: Get by cloud cluster ID\n      parameters:\n      - description: Cluster ID\n        in: path\n        name: clusterId\n        required: true\n        type: integer\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: OK\n          schema:\n            allOf:\n            - $ref: '#/definitions/httputils.Response'\n            - properties:\n                result:\n                  $ref: '#/definitions/types.Cluster'\n              type: object\n        \"400\":\n          description: Bad Request\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"404\":\n          description: Not Found\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"500\":\n          description: Internal Server Error\n          schema:\n            $ref: '#/definitions/httputils.Response'\n      security:\n      - Bearer: []\n      summary: Get Cluster by clusterId\n      tags:\n      - Clusters\n    put:\n      consumes:\n      - application/json\n      description: Update by json cluster\n      parameters:\n      - description: Cluster ID\n        in: path\n        name: clusterId\n        required: true\n        type: integer\n      - description: Update cluster\n        in: body\n        name: cluster\n        required: true\n        schema:\n          $ref: '#/definitions/types.Cluster'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: OK\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"400\":\n          description: Bad Request\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"404\":\n          description: Not Found\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"500\":\n          description: Internal Server Error\n          schema:\n            $ref: '#/definitions/httputils.Response'\n      security:\n      - Bearer: []\n      summary: Update an cluster\n      tags:\n      - Clusters\n  /pixiu/clusters/{clusterId}/ping:\n    get:\n      consumes:\n      - application/json\n      description: Do ping\n      parameters:\n      - description: Cluster ID\n        in: path\n        name: clusterId\n        required: true\n        type: integer\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: OK\n          schema:\n            items:\n              $ref: '#/definitions/httputils.Response'\n            type: array\n        \"400\":\n          description: Bad Request\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"404\":\n          description: Not Found\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"500\":\n          description: Internal Server Error\n          schema:\n            $ref: '#/definitions/httputils.Response'\n      security:\n      - Bearer: []\n      summary: Ping cluster\n      tags:\n      - Clusters\n  /pixiu/users:\n    get:\n      consumes:\n      - application/json\n      description: List users\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: OK\n          schema:\n            items:\n              allOf:\n              - $ref: '#/definitions/httputils.Response'\n              - properties:\n                  result:\n                    items:\n                      $ref: '#/definitions/types.User'\n                    type: array\n                type: object\n            type: array\n        \"400\":\n          description: Bad Request\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"404\":\n          description: Not Found\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"500\":\n          description: Internal Server Error\n          schema:\n            $ref: '#/definitions/httputils.Response'\n      security:\n      - Bearer: []\n      summary: List users\n      tags:\n      - Users\n  /pixiu/users/:\n    post:\n      consumes:\n      - application/json\n      description: Create by a json user\n      parameters:\n      - description: Create user\n        in: body\n        name: user\n        required: true\n        schema:\n          $ref: '#/definitions/types.User'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: OK\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"400\":\n          description: Bad Request\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"404\":\n          description: Not Found\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"500\":\n          description: Internal Server Error\n          schema:\n            $ref: '#/definitions/httputils.Response'\n      security:\n      - Bearer: []\n      summary: Create a user\n      tags:\n      - Users\n  /pixiu/users/{userId}:\n    delete:\n      consumes:\n      - application/json\n      description: Delete by userID\n      parameters:\n      - description: User ID\n        in: path\n        name: userId\n        required: true\n        type: integer\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: OK\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"400\":\n          description: Bad Request\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"404\":\n          description: Not Found\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"500\":\n          description: Internal Server Error\n          schema:\n            $ref: '#/definitions/httputils.Response'\n      security:\n      - Bearer: []\n      summary: Delete user by userId\n      tags:\n      - Users\n    get:\n      consumes:\n      - application/json\n      description: Get by user ID\n      parameters:\n      - description: User ID\n        in: path\n        name: userId\n        required: true\n        type: integer\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: OK\n          schema:\n            allOf:\n            - $ref: '#/definitions/httputils.Response'\n            - properties:\n                result:\n                  $ref: '#/definitions/types.User'\n              type: object\n        \"400\":\n          description: Bad Request\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"404\":\n          description: Not Found\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"500\":\n          description: Internal Server Error\n          schema:\n            $ref: '#/definitions/httputils.Response'\n      security:\n      - Bearer: []\n      summary: Get user by userId\n      tags:\n      - Users\n    put:\n      consumes:\n      - application/json\n      description: Update by json user\n      parameters:\n      - description: User ID\n        in: path\n        name: userId\n        required: true\n        type: integer\n      - description: Update user\n        in: body\n        name: user\n        required: true\n        schema:\n          $ref: '#/definitions/types.User'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: OK\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"400\":\n          description: Bad Request\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"404\":\n          description: Not Found\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"500\":\n          description: Internal Server Error\n          schema:\n            $ref: '#/definitions/httputils.Response'\n      security:\n      - Bearer: []\n      summary: Update an user\n      tags:\n      - Users\n  /pixiu/users/login:\n    post:\n      consumes:\n      - application/json\n      description: Login by a json user\n      parameters:\n      - description: User login\n        in: body\n        name: user\n        required: true\n        schema:\n          $ref: '#/definitions/types.User'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: OK\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"400\":\n          description: Bad Request\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"404\":\n          description: Not Found\n          schema:\n            $ref: '#/definitions/httputils.Response'\n        \"500\":\n          description: Internal Server Error\n          schema:\n            $ref: '#/definitions/httputils.Response'\n      summary: User login\n      tags:\n      - Login\nschemes:\n- http\n- https\nsecurityDefinitions:\n  Bearer:\n    description: Type \"Bearer\" followed by a space and JWT token\n    in: header\n    name: Authorization\n    type: apiKey\nswagger: \"2.0\"\n"
  },
  {
    "path": "api/server/errors/errors.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage errors\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/util/errors\"\n)\n\ntype Error struct {\n\tCode int\n\tErr  error\n}\n\nfunc (e Error) Error() string {\n\treturn e.Err.Error()\n}\n\nfunc NewError(err error, code int) Error {\n\treturn Error{\n\t\tCode: code,\n\t\tErr:  err,\n\t}\n}\n\nvar (\n\tErrUnauthorized = Error{\n\t\tCode: http.StatusUnauthorized,\n\t\tErr:  errors.NoUserIdError,\n\t}\n\tErrForbidden = Error{\n\t\tCode: http.StatusForbidden,\n\t\tErr:  errors.NoPermission,\n\t}\n\tErrInvalidRequest = Error{\n\t\tCode: http.StatusBadRequest,\n\t\tErr:  errors.ErrReqParams,\n\t}\n\tErrServerInternal = Error{\n\t\tCode: http.StatusInternalServerError,\n\t\tErr:  errors.ErrInternal,\n\t}\n\tErrUserNotFound = Error{\n\t\tCode: http.StatusNotFound,\n\t\tErr:  errors.ErrUserNotFound,\n\t}\n\tErrNotAcceptable = Error{\n\t\tCode: http.StatusNotAcceptable,\n\t\tErr:  errors.ErrNotAcceptable,\n\t}\n\tErrUserExists = Error{\n\t\tCode: http.StatusConflict,\n\t\tErr:  errors.UserExistError,\n\t}\n\tErrInvalidPassword = Error{\n\t\tCode: http.StatusUnauthorized,\n\t\tErr:  errors.ErrUserPassword,\n\t}\n\tErrDuplicatedPassword = Error{\n\t\tCode: http.StatusConflict,\n\t\tErr:  errors.ErrDuplicatedPassword,\n\t}\n\tErrClusterNotFound = Error{\n\t\tCode: http.StatusNotFound,\n\t\tErr:  errors.ErrClusterNotFound,\n\t}\n\tErrTenantExists = Error{\n\t\tCode: http.StatusConflict,\n\t\tErr:  errors.TenantExistError,\n\t}\n\tErrTenantNotFound = Error{\n\t\tCode: http.StatusNotFound,\n\t\tErr:  errors.ErrTenantNotFound,\n\t}\n\tErrAuditNotFound = Error{\n\t\tCode: http.StatusNotFound,\n\t\tErr:  errors.ErrAuditNotFound,\n\t}\n\tErrAuditExists = Error{\n\t\tCode: http.StatusConflict,\n\t\tErr:  errors.ErrAuditExists,\n\t}\n\tErrRBACPolicyExists = Error{\n\t\tCode: http.StatusConflict,\n\t\tErr:  errors.PolicyExistError,\n\t}\n\tErrRBACPolicyNotFound = Error{\n\t\tCode: http.StatusNotFound,\n\t\tErr:  errors.PolicyNotExistError,\n\t}\n\tErrGroupBindingExists = Error{\n\t\tCode: http.StatusConflict,\n\t\tErr:  errors.PolicyExistError,\n\t}\n\tErrGroupBindingNotFound = Error{\n\t\tCode: http.StatusNotFound,\n\t\tErr:  errors.PolicyNotExistError,\n\t}\n\tErrRootAlreadyExists = Error{\n\t\tCode: http.StatusConflict,\n\t\tErr:  errors.ErrRootAlreadyExists,\n\t}\n)\n"
  },
  {
    "path": "api/server/httpstatus/status.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage httpstatus\n\n// TODO: 自定义状态码\n"
  },
  {
    "path": "api/server/httputils/docs.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage httputils\n\n// HttpOK 正常返回\ntype HttpOK struct {\n\tCode   int    `json:\"code\" example:\"200\"`\n\tResult string `json:\"result\" example:\"any result\"`\n}\n\n// HttpError 异常返回\ntype HttpError struct {\n\tCode    int    `json:\"code\" example:\"400\"`\n\tMessage string `json:\"message\" example:\"status bad request\"`\n}\n"
  },
  {
    "path": "api/server/httputils/httputils.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage httputils\n\nimport (\n\t\"context\"\n\tgoerrors \"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/go-playground/validator/v10\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/errors\"\n\tvalidatorutil \"github.com/caoyingjunz/pixiu/api/server/validator\"\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n)\n\ntype Response struct {\n\tCode    int         `json:\"code\"`              // 返回的状态码\n\tResult  interface{} `json:\"result,omitempty\"`  // 正常返回时的数据，可以为任意数据结构\n\tMessage string      `json:\"message,omitempty\"` // 异常返回时的错误信息\n}\n\nfunc (r *Response) SetCode(c int) {\n\tr.Code = c\n}\n\nfunc (r *Response) SetMessage(m interface{}) {\n\tswitch msg := m.(type) {\n\tcase error:\n\t\tr.Message = msg.Error()\n\tcase string:\n\t\tr.Message = msg\n\t}\n}\n\nfunc (r *Response) SetMessageWithCode(m interface{}, c int) {\n\tr.SetCode(c)\n\tr.SetMessage(m)\n}\n\nfunc (r *Response) Error() string {\n\treturn r.Message\n}\n\nfunc (r *Response) String() string {\n\t//data, _ := json.Marshal(r)\n\t//return string(data)\n\treturn \"\"\n}\n\n// NewResponse 构造 http 返回值\n// SetSuccess 时设置 code 为 200 并追加 success 的标识\n// SetFailed 时设置 code 为 400，也可以自定义设置错误码，并追加报错信息\nfunc NewResponse() *Response {\n\treturn &Response{}\n}\n\n// SetSuccess 设置成功返回值\nfunc SetSuccess(c *gin.Context, r *Response) {\n\t_ = contextBind(c).withResponseCode(http.StatusOK)\n\tr.SetMessageWithCode(\"success\", http.StatusOK)\n\tc.JSON(http.StatusOK, r)\n}\n\n// SetFailed 设置错误返回值\nfunc SetFailed(c *gin.Context, r *Response, err error) {\n\tswitch e := err.(type) {\n\tcase errors.Error:\n\t\tsetFailedWithCode(c, r, e.Code, e)\n\tcase validator.ValidationErrors:\n\t\tsetFailedWithValidationError(c, r, validatorutil.TranslateError(e))\n\tdefault:\n\t\tsetFailedWithCode(c, r, http.StatusBadRequest, err)\n\t}\n}\n\n// SetFailedWithCode 设置错误返回值\nfunc setFailedWithCode(c *gin.Context, r *Response, code int, err error) {\n\t_ = contextBind(c).withResponseCode(code).withRawError(err)\n\tr.SetMessageWithCode(err, code)\n\tc.JSON(http.StatusOK, r)\n}\n\nfunc setFailedWithValidationError(c *gin.Context, r *Response, e string) {\n\t_ = contextBind(c).withResponseCode(http.StatusBadRequest).withRawError(goerrors.New(e))\n\tr.SetMessageWithCode(e, http.StatusBadRequest)\n\tc.JSON(http.StatusOK, r)\n}\n\n// AbortFailedWithCode 设置错误，code 返回值并终止请求\nfunc AbortFailedWithCode(c *gin.Context, code int, err error) {\n\tr := NewResponse()\n\t_ = contextBind(c).withResponseCode(code).withRawError(err)\n\tr.SetMessageWithCode(err, code)\n\tc.JSON(http.StatusOK, r)\n\tc.Abort()\n}\n\nfunc ShouldBindAny(c *gin.Context, jsonObject interface{}, uriObject interface{}, queryObject interface{}) error {\n\tvar err error\n\tif jsonObject != nil {\n\t\tif err = c.ShouldBindJSON(jsonObject); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif uriObject != nil {\n\t\tif err = c.ShouldBindUri(uriObject); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif queryObject != nil {\n\t\tif err = c.ShouldBindQuery(queryObject); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nconst userKey = \"user\"\n\nfunc GetUserFromRequest(ctx context.Context) (*model.User, error) {\n\tval := ctx.Value(userKey)\n\tif val == nil {\n\t\treturn nil, fmt.Errorf(\"get nil user\")\n\t}\n\n\tuser, ok := val.(*model.User)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"failed to assert user\")\n\t}\n\treturn user, nil\n}\n\nfunc GetUserIdFromContext(ctx context.Context) (int64, error) {\n\tuser, err := GetUserFromRequest(ctx)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn user.Id, nil\n}\n\nfunc SetUserToContext(c *gin.Context, user *model.User) {\n\tc.Set(userKey, user)\n}\n\nfunc GetObjectFromRequest(c *gin.Context) (string, string, bool) {\n\treturn getObjectFromRequest(c.Request.URL.Path)\n}\n\n// getObjectFromRequest cuts and returns the object from the request path.\n// e.g. /pixiu/clusters/1 -> \"clusters\" \"1\" true\nfunc getObjectFromRequest(path string) (obj, sid string, ok bool) {\n\t// must start with /\n\tl := len(path)\n\tif l == 0 || path[0] != '/' {\n\t\treturn\n\t}\n\tsubs := strings.Split(path[1:l], \"/\")\n\tl = len(subs)\n\tif l < 2 || subs[0] != \"pixiu\" {\n\t\treturn\n\t}\n\tif l == 2 {\n\t\t// e.g. /pixiu/clusters -> \"clusters\" \"\" true\n\t\treturn subs[1], \"\", subs[1] != \"\"\n\t}\n\treturn subs[1], subs[2], subs[1] != \"\" && subs[2] != \"\"\n}\n\nconst (\n\tobjIDsKey = \"objIDs\"\n)\n\nfunc SetIdRangeContext(c *gin.Context, ids []int64) {\n\tc.Set(objIDsKey, ids)\n}\n\nfunc GetIdRangeFromListReq(ctx context.Context) (exists bool, ids []int64) {\n\tval := ctx.Value(objIDsKey)\n\tif val == nil {\n\t\treturn\n\t}\n\n\tids, exists = val.([]int64)\n\treturn\n}\n\nconst (\n\tResponseCodeKey = \"response_code\"\n\tRawErrorKey     = \"raw_error\"\n)\n\ntype ctxBind struct {\n\t*gin.Context\n}\n\nfunc contextBind(c *gin.Context) *ctxBind {\n\treturn &ctxBind{c}\n}\n\n// withResponseCode puts the response code into the HTTP context.\nfunc (cb *ctxBind) withResponseCode(code int) *ctxBind {\n\tcb.Set(ResponseCodeKey, code)\n\treturn cb\n}\n\n// withRawError puts the raw error into the HTTP context.\nfunc (cb *ctxBind) withRawError(err error) *ctxBind {\n\tcb.Set(RawErrorKey, err)\n\treturn cb\n}\n\n// GetResponseCode gets the response code from the HTTP context.\nfunc GetResponseCode(ctx context.Context) (code int) {\n\tval := ctx.Value(ResponseCodeKey)\n\tif val == nil {\n\t\treturn\n\t}\n\n\tcode = val.(int)\n\treturn\n}\n\n// GetRawError gets the raw error from the HTTP context.\nfunc GetRawError(ctx context.Context) (err error) {\n\tval := ctx.Value(RawErrorKey)\n\tif val == nil {\n\t\treturn\n\t}\n\n\terr = val.(error)\n\treturn\n}\n"
  },
  {
    "path": "api/server/httputils/httputils_test.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage httputils\n\nimport \"testing\"\n\nfunc Test_getObjectFromRequest(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\tpath    string\n\t\twantObj string\n\t\twantSid string\n\t\twantOk  bool\n\t}{\n\t\t{\n\t\t\tname:    \"test0\",\n\t\t\tpath:    \"\",\n\t\t\twantObj: \"\",\n\t\t\twantOk:  false,\n\t\t},\n\t\t{\n\t\t\tname:    \"test1\",\n\t\t\tpath:    \"/\",\n\t\t\twantObj: \"\",\n\t\t\twantOk:  false,\n\t\t},\n\t\t{\n\t\t\tname:    \"test2\",\n\t\t\tpath:    \"//\",\n\t\t\twantObj: \"\",\n\t\t\twantOk:  false,\n\t\t},\n\t\t{\n\t\t\tname:    \"test3\",\n\t\t\tpath:    \"pixiu\",\n\t\t\twantObj: \"\",\n\t\t\twantOk:  false,\n\t\t},\n\t\t{\n\t\t\tname:    \"test4\",\n\t\t\tpath:    \"/pixiu\",\n\t\t\twantObj: \"\",\n\t\t\twantOk:  false,\n\t\t},\n\t\t{\n\t\t\tname:    \"test5\",\n\t\t\tpath:    \"/pixiu/\",\n\t\t\twantObj: \"\",\n\t\t\twantOk:  false,\n\t\t},\n\t\t{\n\t\t\tname:    \"test6\",\n\t\t\tpath:    \"/pixiu/users\",\n\t\t\twantObj: \"users\",\n\t\t\twantSid: \"\",\n\t\t\twantOk:  true,\n\t\t},\n\t\t{\n\t\t\tname:    \"test7\",\n\t\t\tpath:    \"/pixiu/users/\",\n\t\t\twantObj: \"users\",\n\t\t\twantOk:  false,\n\t\t},\n\t\t{\n\t\t\tname:    \"test8\",\n\t\t\tpath:    \"/pixiu/users/1\",\n\t\t\twantObj: \"users\",\n\t\t\twantSid: \"1\",\n\t\t\twantOk:  true,\n\t\t},\n\t\t{\n\t\t\tname:    \"test9\",\n\t\t\tpath:    \"/pixiu//\",\n\t\t\twantObj: \"\",\n\t\t\twantOk:  false,\n\t\t},\n\t\t{\n\t\t\tname:    \"test10\",\n\t\t\tpath:    \"///\",\n\t\t\twantObj: \"\",\n\t\t\twantOk:  false,\n\t\t},\n\t\t{\n\t\t\tname:    \"test11\",\n\t\t\tpath:    \"/pixiu/users/1/password\",\n\t\t\twantObj: \"users\",\n\t\t\twantSid: \"1\",\n\t\t\twantOk:  true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgotObj, gotSid, gotOk := getObjectFromRequest(tt.path)\n\t\t\tif gotObj != tt.wantObj {\n\t\t\t\tt.Errorf(\"getObjectFromRequest() gotObj = %v, want %v\", gotObj, tt.wantObj)\n\t\t\t}\n\t\t\tif gotSid != tt.wantSid {\n\t\t\t\tt.Errorf(\"getObjectFromRequest() gotSid = %v, want %v\", gotSid, tt.wantSid)\n\t\t\t}\n\t\t\tif gotOk != tt.wantOk {\n\t\t\t\tt.Errorf(\"getObjectFromRequest() gotOk = %v, want %v\", gotOk, tt.wantOk)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "api/server/middleware/admission.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage middleware\n\nimport \"github.com/gin-gonic/gin\"\n\n// Admission 准入控制\nfunc Admission() gin.HandlerFunc {\n\treturn func(c *gin.Context) {}\n}\n"
  },
  {
    "path": "api/server/middleware/audit.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage middleware\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/gin-contrib/requestid\"\n\t\"github.com/gin-gonic/gin\"\n\t\"k8s.io/klog/v2\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/httputils\"\n\t\"github.com/caoyingjunz/pixiu/cmd/app/options\"\n\t\"github.com/caoyingjunz/pixiu/pkg/db\"\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n)\n\nconst (\n\tdefaultAuditQueueSize = 2048\n\tdefaultAuditWorkers   = 2\n\tauditWriteTimeout     = 3 * time.Second\n)\n\ntype auditRecorder struct {\n\tfactory db.ShareDaoFactory\n\tqueue   chan *model.Audit\n}\n\nvar (\n\trecorderOnce sync.Once\n\trecorderInst *auditRecorder\n)\n\nfunc getAuditRecorder(o *options.Options) *auditRecorder {\n\trecorderOnce.Do(func() {\n\t\trecorderInst = &auditRecorder{\n\t\t\tfactory: o.Factory,\n\t\t\tqueue:   make(chan *model.Audit, defaultAuditQueueSize),\n\t\t}\n\t\tfor i := 0; i < defaultAuditWorkers; i++ {\n\t\t\tgo recorderInst.run()\n\t\t}\n\t})\n\treturn recorderInst\n}\n\nfunc (r *auditRecorder) run() {\n\tfor record := range r.queue {\n\t\tr.write(record)\n\t}\n}\n\nfunc (r *auditRecorder) write(record *model.Audit) {\n\tctx, cancel := context.WithTimeout(context.Background(), auditWriteTimeout)\n\tdefer cancel()\n\n\tif _, err := r.factory.Audit().Create(ctx, record); err != nil {\n\t\tklog.Errorf(\"failed to create audit record [%s]: %v\", record.String(), err)\n\t}\n}\n\nfunc (r *auditRecorder) enqueue(record *model.Audit) {\n\tselect {\n\tcase r.queue <- record:\n\tdefault:\n\t\t// 队列满时降级同步写入，确保非 GET 请求都能落库\n\t\tklog.Warningf(\"audit queue is full, fallback to direct write: %s\", record.Path)\n\t\tr.write(record)\n\t}\n}\n\nfunc Audit(o *options.Options) gin.HandlerFunc {\n\trecorder := getAuditRecorder(o)\n\treturn func(c *gin.Context) {\n\t\tif !shouldAudit(c) {\n\t\t\tc.Next()\n\t\t\treturn\n\t\t}\n\n\t\tstartTime := time.Now()\n\t\tc.Next()\n\t\trecorder.enqueue(buildAuditRecord(c, startTime))\n\t}\n}\n\nfunc buildAuditRecord(c *gin.Context, startTime time.Time) *model.Audit {\n\tuserName := \"unknown\"\n\tif user, err := httputils.GetUserFromRequest(c); err == nil && user != nil {\n\t\tuserName = user.Name\n\t}\n\n\tcluster, resourceName, resourceNamespace := parseK8sProxyPath(c.Request.URL.Path)\n\n\treturn &model.Audit{\n\t\tRequestId:         requestid.Get(c),\n\t\tAction:            c.Request.Method,\n\t\tIp:                c.ClientIP(),\n\t\tOperator:          userName,\n\t\tPath:              c.Request.RequestURI,\n\t\tObjectType:        detectObjectType(c),\n\t\tStatus:            getAuditStatus(c),\n\t\tDuration:          time.Since(startTime).Milliseconds(),\n\t\tResponseCode:      c.Writer.Status(),\n\t\tCluster:           cluster,\n\t\tResourceName:      resourceName,\n\t\tResourceNamespace: resourceNamespace,\n\t}\n}\n\n// parseK8sProxyPath 从 K8s proxy URL 路径中解析集群、资源名称和命名空间。\n// 支持以下路径格式：\n//   - /pixiu/proxy/{cluster}/api/v1/namespaces/{namespace}/{resource}/{name}\n//   - /pixiu/proxy/{cluster}/apis/{group}/{version}/namespaces/{namespace}/{resource}/{name}\n//   - /pixiu/proxy/{cluster}/api/v1/{resource}/{name}（集群级资源）\nfunc parseK8sProxyPath(path string) (cluster, resourceName, resourceNamespace string) {\n\t// /pixiu/proxy/{cluster}/...\n\tconst proxyPrefix = \"/pixiu/proxy/\"\n\tif !strings.HasPrefix(path, proxyPrefix) {\n\t\treturn\n\t}\n\n\trest := path[len(proxyPrefix):]\n\tparts := strings.SplitN(rest, \"/\", 2)\n\tif len(parts) < 1 || parts[0] == \"\" {\n\t\treturn\n\t}\n\tcluster = parts[0]\n\tif len(parts) < 2 {\n\t\treturn\n\t}\n\n\tsegments := strings.Split(parts[1], \"/\")\n\t// 查找 namespaces/{ns} 段\n\tfor i := 0; i < len(segments)-1; i++ {\n\t\tif segments[i] == \"namespaces\" {\n\t\t\tresourceNamespace = segments[i+1]\n\t\t\t// namespace 之后还有 {resource}/{name}\n\t\t\tif i+3 < len(segments) {\n\t\t\t\tresourceName = segments[i+3]\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\t// 无 namespaces 段，集群级资源：api/v1/{resource}/{name} 或 apis/.../.../{resource}/{name}\n\tif len(segments) > 0 {\n\t\tresourceName = segments[len(segments)-1]\n\t}\n\treturn\n}\n\nfunc shouldAudit(c *gin.Context) bool {\n\tif c.Request.Method == http.MethodGet || c.Request.Method == http.MethodOptions {\n\t\treturn false\n\t}\n\treturn strings.HasPrefix(c.Request.URL.Path, \"/pixiu\")\n}\n\nfunc detectObjectType(c *gin.Context) model.ObjectType {\n\tobj, _, ok := httputils.GetObjectFromRequest(c)\n\tif !ok {\n\t\treturn model.ObjectAll\n\t}\n\tot := model.ObjectType(obj)\n\tif _, exists := model.ObjectTypeMap[ot]; exists {\n\t\treturn ot\n\t}\n\treturn model.ObjectAll\n}\n\n// getAuditStatus returns the status of operation.\nfunc getAuditStatus(c *gin.Context) model.AuditOperationStatus {\n\trespCode := httputils.GetResponseCode(c)\n\tif respCode == 0 {\n\t\trespCode = c.Writer.Status()\n\t\tif respCode == 0 {\n\t\t\treturn model.AuditOpUnknown\n\t\t}\n\t}\n\n\tif responseOK(respCode) {\n\t\treturn model.AuditOpSuccess\n\t}\n\n\treturn model.AuditOpFail\n}\n\nfunc responseOK(code int) bool {\n\treturn code == http.StatusOK ||\n\t\tcode == http.StatusCreated ||\n\t\tcode == http.StatusAccepted\n}\n"
  },
  {
    "path": "api/server/middleware/authentication.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage middleware\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/errors\"\n\t\"github.com/caoyingjunz/pixiu/api/server/httputils\"\n\t\"github.com/caoyingjunz/pixiu/cmd/app/options\"\n\ttokenutil \"github.com/caoyingjunz/pixiu/pkg/util/token\"\n)\n\n// Authentication 身份认证\nfunc Authentication(o *options.Options) gin.HandlerFunc {\n\tkeyBytes := []byte(o.ComponentConfig.Default.JWTKey)\n\n\treturn func(c *gin.Context) {\n\t\tif o.ComponentConfig.Default.Mode.InDebug() {\n\t\t\t// Considered all as root user when running in debug mode.\n\t\t\troot, err := o.Factory.User().GetRoot(c)\n\t\t\tif err != nil {\n\t\t\t\thttputils.AbortFailedWithCode(c, http.StatusInternalServerError, err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\thttputils.SetUserToContext(c, root)\n\t\t\treturn\n\t\t}\n\n\t\tif alwaysAllowPath.Has(c.Request.URL.Path) || allowCustomRequest(c) {\n\t\t\treturn\n\t\t}\n\n\t\tif err := validate(c, o, keyBytes); err != nil {\n\t\t\thttputils.AbortFailedWithCode(c, http.StatusUnauthorized, err)\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc validate(c *gin.Context, o *options.Options, keyBytes []byte) error {\n\ttoken, err := extractToken(c, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\tclaim, err := tokenutil.ParseToken(token, keyBytes)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\texistToken, err := o.Controller.User().GetLoginToken(c, claim.Id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"未登陆或者密码被修改，请重新登陆\")\n\t}\n\tif token != existToken {\n\t\treturn fmt.Errorf(\"已被他人登陆\")\n\t}\n\n\tuser, err := o.Factory.User().Get(c, claim.Id)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif user == nil {\n\t\treturn errors.ErrUnauthorized\n\t}\n\thttputils.SetUserToContext(c, user)\n\n\treturn nil\n}\n\n// 从请求头中获取 token\nfunc extractToken(c *gin.Context, ws bool) (string, error) {\n\temptyFunc := func(t string) bool { return len(t) == 0 }\n\tif ws {\n\t\twsToken := c.GetHeader(\"Sec-WebSocket-Protocol\")\n\t\tif emptyFunc(wsToken) {\n\t\t\treturn \"\", fmt.Errorf(\"authorization header is not provided\")\n\t\t}\n\t\treturn wsToken, nil\n\t}\n\n\ttoken := c.GetHeader(\"Authorization\")\n\tif emptyFunc(token) {\n\t\treturn \"\", fmt.Errorf(\"authorization header is not provided\")\n\t}\n\tfields := strings.Fields(token)\n\tif len(fields) != 2 {\n\t\treturn \"\", fmt.Errorf(\"invalid authorization header format\")\n\t}\n\tif fields[0] != \"Bearer\" {\n\t\treturn \"\", fmt.Errorf(\"unsupported authorization type\")\n\t}\n\n\treturn fields[1], nil\n}\n"
  },
  {
    "path": "api/server/middleware/authorization.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage middleware\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/httputils\"\n\t\"github.com/caoyingjunz/pixiu/api/server/router/cluster\"\n\t\"github.com/caoyingjunz/pixiu/api/server/router/proxy\"\n\t\"github.com/caoyingjunz/pixiu/cmd/app/options\"\n\tctrlutil \"github.com/caoyingjunz/pixiu/pkg/controller/util\"\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n)\n\n// HTTP method to operation\nvar operationsMap = map[string]model.Operation{\n\thttp.MethodGet:    model.OpRead,\n\thttp.MethodPost:   model.OpCreate,\n\thttp.MethodPatch:  model.OpUpdate,\n\thttp.MethodPut:    model.OpUpdate,\n\thttp.MethodDelete: model.OpDelete,\n}\n\n// Authorization 鉴权\nfunc Authorization(o *options.Options) gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\t// 允许请求直接通过\n\t\tif o.ComponentConfig.Default.Mode.InDebug() || alwaysAllowPath.Has(c.Request.URL.Path) || allowCustomRequest(c) {\n\t\t\treturn\n\t\t}\n\n\t\tuser, err := httputils.GetUserFromRequest(c)\n\t\tif err != nil {\n\t\t\thttputils.AbortFailedWithCode(c, http.StatusMethodNotAllowed, err)\n\t\t\treturn\n\t\t}\n\n\t\tswitch user.Status {\n\t\tcase 1:\n\t\t\t// status 为 1，表示用户只读模式, 只读模式只允许查询请求\n\t\t\tif c.Request.Method != http.MethodGet && c.Request.Method != http.MethodOptions {\n\t\t\t\thttputils.AbortFailedWithCode(c, http.StatusForbidden, fmt.Errorf(\"无操作权限\"))\n\t\t\t\treturn\n\t\t\t}\n\t\tcase 2:\n\t\t\t// 禁用用户无法进行任何操作\n\t\t\thttputils.AbortFailedWithCode(c, http.StatusForbidden, fmt.Errorf(\"用户已被禁用\"))\n\t\t\treturn\n\t\t}\n\n\t\t// Proxy path should be skipped now.\n\t\t// TODO: get object and ID from proxy path\n\t\tif proxy.IsProxyPath(c) || cluster.IsKubeProxyPath(c) || cluster.IsHelmPath(c) {\n\t\t\treturn\n\t\t}\n\n\t\tobj, id, ok := httputils.GetObjectFromRequest(c)\n\t\tif !ok {\n\t\t\treturn\n\t\t}\n\n\t\top := operationsMap[c.Request.Method]\n\t\t// load policy for consistency\n\t\t// ref: https://github.com/casbin/casbin/issues/679#issuecomment-761525328\n\t\tif err := o.Enforcer.LoadPolicy(); err != nil {\n\t\t\thttputils.AbortFailedWithCode(c, http.StatusInternalServerError, err)\n\t\t\treturn\n\t\t}\n\t\tok, err = o.Enforcer.Enforce(user.Name, obj, id, op.String())\n\t\tif err != nil {\n\t\t\thttputils.AbortFailedWithCode(c, http.StatusMethodNotAllowed, err)\n\t\t\treturn\n\t\t}\n\t\tif !ok {\n\t\t\thttputils.AbortFailedWithCode(c, http.StatusForbidden, fmt.Errorf(\"无操作权限\"))\n\t\t}\n\t\tif id != \"\" {\n\t\t\treturn\n\t\t}\n\t\t// this is a list API\n\t\tif err := ctrlutil.SetIdRangeContext(c, o.Enforcer, user, obj); err != nil {\n\t\t\thttputils.AbortFailedWithCode(c, http.StatusInternalServerError, err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "api/server/middleware/cors.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage middleware\n\nimport (\n\t\"time\"\n\n\t\"github.com/gin-contrib/cors\"\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc Cors() gin.HandlerFunc {\n\tc := cors.Config{\n\t\tAllowAllOrigins: true,\n\t\tAllowMethods:    []string{\"GET\", \"POST\", \"PUT\", \"DELETE\", \"PATCH\"},\n\t\tAllowHeaders:    []string{\"Content-Type\", \"Access-Token\", \"Authorization\"},\n\t\tMaxAge:          6 * time.Hour,\n\t}\n\n\treturn cors.New(c)\n\n}\n"
  },
  {
    "path": "api/server/middleware/limiter.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage middleware\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/juju/ratelimit\"\n\t\"golang.org/x/time/rate\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/httputils\"\n\t\"github.com/caoyingjunz/pixiu/pkg/util/errors\"\n\t\"github.com/caoyingjunz/pixiu/pkg/util/lru\"\n)\n\nconst (\n\tcapacity = 100\n\tquantum  = 20\n\tcap      = 200\n)\n\n// UserRateLimiter 针对每个用户的请求进行限速\n// TODO 限速大小从配置中读取\nfunc UserRateLimiter() gin.HandlerFunc {\n\tcache := lru.NewLRUCache(cap)\n\n\treturn func(c *gin.Context) {\n\t\tclientIP := c.ClientIP()\n\t\tif !cache.Contains(clientIP) {\n\t\t\tcache.Add(clientIP, ratelimit.NewBucketWithQuantum(time.Second, capacity, quantum))\n\t\t\treturn\n\t\t}\n\t\t// 通过 ClientIP 取出 bucket\n\t\tval := cache.Get(clientIP)\n\t\tif val == nil {\n\t\t\treturn\n\t\t}\n\n\t\t// 判断是否还有可用的 bucket\n\t\tbucket := val.(*ratelimit.Bucket)\n\t\tif bucket.TakeAvailable(1) == 0 {\n\t\t\thttputils.AbortFailedWithCode(c, http.StatusForbidden, errors.ErrBusySystem)\n\t\t}\n\t}\n}\n\nfunc Limiter() gin.HandlerFunc {\n\t// 初始化一个限速器，每秒产生 1000 个令牌，桶的大小为 1000 个\n\t// 初始化状态桶是满的\n\t// TODO: 限速的值从配置或者环境变量中获取\n\tlimiter := rate.NewLimiter(1000, 1000)\n\n\treturn func(c *gin.Context) {\n\t\tif !limiter.Allow() {\n\t\t\thttputils.AbortFailedWithCode(c, http.StatusForbidden, errors.ErrBusySystem)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "api/server/middleware/log.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage middleware\n\nimport (\n\t\"github.com/gin-contrib/requestid\"\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/httputils\"\n\t\"github.com/caoyingjunz/pixiu/pkg/db\"\n\tlogutil \"github.com/caoyingjunz/pixiu/pkg/util/log\"\n)\n\nfunc Logger(cfg *logutil.LogOptions) gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tl := logutil.NewLogger(cfg)\n\t\tc.Set(db.SQLContextKey, new(db.SQLs)) // set SQL context key\n\n\t\t// 处理请求操作\n\t\tc.Next()\n\n\t\tl.WithLogFields(map[string]interface{}{\n\t\t\t\"request_id\":              requestid.Get(c),\n\t\t\t\"method\":                  c.Request.Method,\n\t\t\t\"uri\":                     c.Request.RequestURI,\n\t\t\thttputils.ResponseCodeKey: httputils.GetResponseCode(c),\n\t\t\t\"client_ip\":               c.ClientIP(),\n\t\t})\n\t\tl.Log(c, logutil.InfoLevel, httputils.GetRawError(c))\n\t}\n}\n"
  },
  {
    "path": "api/server/middleware/middleware.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage middleware\n\nimport (\n\t\"github.com/gin-contrib/requestid\"\n\t\"github.com/gin-gonic/gin\"\n\t\"k8s.io/apimachinery/pkg/util/sets\"\n\n\t\"github.com/caoyingjunz/pixiu/cmd/app/options\"\n\t\"github.com/caoyingjunz/pixiu/pkg/util\"\n)\n\nvar alwaysAllowPath sets.String\n\nfunc init() {\n\talwaysAllowPath = sets.NewString(\"/pixiu/users/login\")\n}\n\n// 允许特定请求不经过验证\nfunc allowCustomRequest(c *gin.Context) bool {\n\t// TODO: 其他请求\n\treturn false\n}\n\nfunc InstallMiddlewares(o *options.Options) {\n\t// 依次进行跨域，日志，单用户限速，总量限速，验证，鉴权和审计\n\to.HttpEngine.Use(\n\t\trequestid.New(requestid.WithGenerator(func() string {\n\t\t\treturn util.GenerateRequestID()\n\t\t})),\n\t\tCors(),\n\t\tLogger(&o.ComponentConfig.Default.LogOptions),\n\t\tUserRateLimiter(),\n\t\tLimiter(),\n\t\tAuthentication(o),\n\t\tAuthorization(o),\n\t\tAdmission(),\n\t\tAudit(o),\n\t)\n}\n"
  },
  {
    "path": "api/server/router/audit/audit.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage audit\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/caoyingjunz/pixiu/cmd/app/options\"\n\t\"github.com/caoyingjunz/pixiu/pkg/controller\"\n)\n\ntype auditRouter struct {\n\tc controller.PixiuInterface\n}\n\nfunc NewRouter(o *options.Options) {\n\trouter := &auditRouter{\n\t\tc: o.Controller,\n\t}\n\trouter.initRoutes(o.HttpEngine)\n}\n\nfunc (a *auditRouter) initRoutes(httpEngine *gin.Engine) {\n\tauditRoute := httpEngine.Group(\"/pixiu/audits\")\n\t{\n\t\t// get 日志\n\t\tauditRoute.GET(\"/:auditId\", a.getAudit)\n\t\tauditRoute.GET(\"\", a.listAudits)\n\t}\n}\n"
  },
  {
    "path": "api/server/router/audit/audit_routes.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage audit\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/httputils\"\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n)\n\ntype AuditMeta struct {\n\tAuditId int64 `uri:\"auditId\" binding:\"required\"`\n}\n\nfunc (a *auditRouter) getAudit(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\topt AuditMeta\n\t\terr error\n\t)\n\tif err = c.ShouldBindUri(&opt); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif r.Result, err = a.c.Audit().Get(c, opt.AuditId); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (a *auditRouter) listAudits(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\tlistOption types.AuditListOptions\n\t\terr        error\n\t)\n\tif err = httputils.ShouldBindAny(c, nil, nil, &listOption); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif r.Result, err = a.c.Audit().List(c, listOption); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n"
  },
  {
    "path": "api/server/router/auth/auth.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage auth\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/caoyingjunz/pixiu/cmd/app/options\"\n\t\"github.com/caoyingjunz/pixiu/pkg/controller\"\n)\n\nconst (\n\tAuthBasePath   = \"/pixiu/auth\"\n\tPolicySubPath  = \"/policy\"\n\tBindingSubPath = \"/binding\"\n)\n\ntype authRouter struct {\n\tc controller.PixiuInterface\n}\n\nfunc NewRouter(o *options.Options) {\n\trouter := &authRouter{\n\t\tc: o.Controller,\n\t}\n\trouter.initRoutes(o.HttpEngine)\n}\n\nfunc (a *authRouter) initRoutes(ge *gin.Engine) {\n\tauthRoute := ge.Group(AuthBasePath)\n\t{\n\t\tpolicyRoute := authRoute.Group(PolicySubPath)\n\t\tpolicyRoute.POST(\"\", a.createPolicy)\n\t\tpolicyRoute.DELETE(\"\", a.deletePolicy)\n\t\tpolicyRoute.GET(\"\", a.listPolicies)\n\t}\n\t{\n\t\tbindingRoute := authRoute.Group(BindingSubPath)\n\t\tbindingRoute.POST(\"\", a.createBinding)\n\t\tbindingRoute.DELETE(\"\", a.deleteBinding)\n\t\tbindingRoute.GET(\"\", a.listBindings)\n\t}\n}\n"
  },
  {
    "path": "api/server/router/auth/auth_routes.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage auth\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/httputils\"\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n)\n\ntype IdMeta struct {\n\tPolicyId int64 `uri:\"policyId\" binding:\"required\"`\n}\n\nfunc (a *authRouter) listPolicies(c *gin.Context) {\n\tr := httputils.NewResponse()\n\tvar (\n\t\treq types.ListRBACPolicyRequest\n\t\terr error\n\t)\n\tif err = c.ShouldBindQuery(&req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif r.Result, err = a.c.Auth().ListRBACPolicies(c, &req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (a *authRouter) createPolicy(c *gin.Context) {\n\tr := httputils.NewResponse()\n\tvar req types.RBACPolicyRequest\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err := a.c.Auth().CreateRBACPolicy(c, &req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (a *authRouter) deletePolicy(c *gin.Context) {\n\tr := httputils.NewResponse()\n\tvar req types.RBACPolicyRequest\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err := a.c.Auth().DeleteRBACPolicy(c, &req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (a *authRouter) listBindings(c *gin.Context) {\n\tr := httputils.NewResponse()\n\tvar (\n\t\treq types.ListGroupBindingRequest\n\t\terr error\n\t)\n\tif err = c.ShouldBindQuery(&req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif r.Result, err = a.c.Auth().ListGroupBindings(c, &req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (a *authRouter) createBinding(c *gin.Context) {\n\tr := httputils.NewResponse()\n\tvar req types.GroupBindingRequest\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err := a.c.Auth().CreateGroupBinding(c, &req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (a *authRouter) deleteBinding(c *gin.Context) {\n\tr := httputils.NewResponse()\n\tvar req types.GroupBindingRequest\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err := a.c.Auth().DeleteGroupBinding(c, &req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n"
  },
  {
    "path": "api/server/router/cluster/cluster.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cluster\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/caoyingjunz/pixiu/cmd/app/options\"\n\t\"github.com/caoyingjunz/pixiu/pkg/controller\"\n)\n\nconst (\n\tkubeProxyBaseURL = \"/pixiu/kubeproxy\"\n\thelmBaseURL      = \"/pixiu/helms\"\n\tindexerBaseURL   = \"/pixiu/indexer\"\n)\n\n// clusterRouter is a router to talk with the cluster controller\ntype clusterRouter struct {\n\tc controller.PixiuInterface\n}\n\n// NewRouter initializes a new cluster router\nfunc NewRouter(o *options.Options) {\n\ts := &clusterRouter{\n\t\tc: o.Controller,\n\t}\n\ts.initRoutes(o.HttpEngine)\n}\n\nfunc (cr *clusterRouter) initRoutes(httpEngine *gin.Engine) {\n\tclusterRoute := httpEngine.Group(\"/pixiu/clusters\")\n\t{\n\t\tclusterRoute.POST(\"\", cr.createCluster)\n\t\tclusterRoute.PUT(\"/:clusterId\", cr.updateCluster)\n\t\tclusterRoute.DELETE(\"/:clusterId\", cr.deleteCluster)\n\t\tclusterRoute.GET(\"/:clusterId\", cr.getCluster)\n\t\tclusterRoute.GET(\"\", cr.listClusters)\n\n\t\t// 检查 kubernetes 的连通性\n\t\tclusterRoute.POST(\"/ping\", cr.pingCluster)\n\n\t\t// 设置集群的删除保护模式\n\t\tclusterRoute.POST(\"/protect/:clusterId\", cr.protectCluster)\n\t}\n\n\t// 调用 kubernetes 对象\n\tkubeRoute := httpEngine.Group(kubeProxyBaseURL)\n\t{\n\t\t// 获取指定对象的日志\n\t\tkubeRoute.GET(\"/clusters/:cluster/namespaces/:namespace/pods/:pod/log\", cr.watchPodLog)\n\t\t// Deprecated 聚合 events\n\t\tkubeRoute.GET(\"/clusters/:cluster/namespaces/:namespace/name/:name/kind/:kind/events\", cr.aggregateEvents)\n\t\t// 获取指定对象的 events，支持事件聚合\n\t\tkubeRoute.GET(\"/clusters/:cluster/api/v1/events\", cr.getEventList)\n\n\t\t// pod ws\n\t\tkubeRoute.GET(\"/ws\", cr.webShell)\n\t\t// node ws\n\t\tkubeRoute.GET(\"/nodes/ws\", cr.nodeWebShell)\n\n\t\t// 重启Job action=rerun\n\t\tkubeRoute.POST(\"/clusters/:cluster/namespaces/:namespace/jobs/:name\", cr.ReRunJob)\n\t}\n\n\t// 从 pixiu 缓存中获取 kubernetes 对象\n\t//indexerRoute := httpEngine.Group(indexerBaseURL)\n\t//{\n\t//\t// 从缓存中获取指定对象\n\t//\tindexerRoute.GET(\"/clusters/:cluster/resources/:resource/namespaces/:namespace/name/:name\", cr.getIndexerResource)\n\t//\t// 从缓存中获取对象列表\n\t//\tindexerRoute.GET(\"/clusters/:cluster/resources/:resource/namespaces/:namespace\", cr.listIndexerResources)\n\t//}\n}\n"
  },
  {
    "path": "api/server/router/cluster/cluster_routes.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cluster\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/httputils\"\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n)\n\ntype IdMeta struct {\n\tClusterId int64 `uri:\"clusterId\" binding:\"required\"`\n}\n\n// CreateCluster godoc\n//\n//\t@Summary      Create a cluster\n//\t@Description  Create by a json cluster\n//\t@Tags         Clusters\n//\t@Accept       json\n//\t@Produce      json\n//\t@Param        cluster  body      types.Cluster  true  \"Create cluster\"\n//\t@Success      200      {object}  httputils.Response\n//\t@Failure      400      {object}  httputils.Response\n//\t@Failure      404      {object}  httputils.Response\n//\t@Failure      500      {object}  httputils.Response\n//\t@Router       /pixiu/clusters/ [post]\n//\t@Security     Bearer\nfunc (cr *clusterRouter) createCluster(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar req types.CreateClusterRequest\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err := cr.c.Cluster().Create(c, &req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\n// UpdateCluster godoc\n//\n//\t@Summary      Update an cluster\n//\t@Description  Update by json cluster\n//\t@Tags         Clusters\n//\t@Accept       json\n//\t@Produce      json\n//\t@Param        clusterId  path      int            true  \"Cluster ID\"\n//\t@Param        cluster    body      types.Cluster  true  \"Update cluster\"\n//\t@Success      200        {object}  httputils.Response\n//\t@Failure      400        {object}  httputils.Response\n//\t@Failure      404        {object}  httputils.Response\n//\t@Failure      500        {object}  httputils.Response\n//\t@Router       /pixiu/clusters/{clusterId} [put]\n//\t@Security     Bearer\nfunc (cr *clusterRouter) updateCluster(c *gin.Context) {\n\tr := httputils.NewResponse()\n\tvar (\n\t\tidMeta IdMeta\n\t\terr    error\n\t)\n\tif err = c.ShouldBindUri(&idMeta); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\tvar req types.UpdateClusterRequest\n\tif err = c.ShouldBindJSON(&req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\tif err = cr.c.Cluster().Update(c, idMeta.ClusterId, &req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\n// DeleteCluster godoc\n//\n//\t@Summary      Delete cluster by clusterId\n//\t@Description  Delete by cloud cluster ID\n//\t@Tags         Clusters\n//\t@Accept       json\n//\t@Produce      json\n//\t@Param        clusterId  path      int  true  \"Cluster ID\"\n//\t@Success      200        {object}  httputils.Response\n//\t@Failure      400        {object}  httputils.Response\n//\t@Failure      404        {object}  httputils.Response\n//\t@Failure      500        {object}  httputils.Response\n//\t@Router       /pixiu/clusters/{clusterId} [delete]\n//\t@Security     Bearer\nfunc (cr *clusterRouter) deleteCluster(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\tidMeta IdMeta\n\t\terr    error\n\t)\n\tif err = c.ShouldBindUri(&idMeta); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\tif err = cr.c.Cluster().Delete(c, idMeta.ClusterId); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\thttputils.SetSuccess(c, r)\n}\n\n// GetCluster godoc\n//\n//\t@Summary      Get Cluster by clusterId\n//\t@Description  Get by cloud cluster ID\n//\t@Tags         Clusters\n//\t@Accept       json\n//\t@Produce      json\n//\t@Param        clusterId  path      int  true  \"Cluster ID\"\n//\t@Success      200        {object}  httputils.Response{result=types.Cluster}\n//\t@Failure      400        {object}  httputils.Response\n//\t@Failure      404        {object}  httputils.Response\n//\t@Failure      500        {object}  httputils.Response\n//\t@Router       /pixiu/clusters/{clusterId} [get]\n//\t@Security     Bearer\nfunc (cr *clusterRouter) getCluster(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\tidMeta IdMeta\n\t\terr    error\n\t)\n\tif err = c.ShouldBindUri(&idMeta); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\tif r.Result, err = cr.c.Cluster().Get(c, idMeta.ClusterId); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\n// ListClusters godoc\n//\n//\t@Summary      List clusters\n//\t@Description  List clusters with pagination and filter\n//\t@Tags         Clusters\n//\t@Accept       json\n//\t@Produce      json\n//\t@Param        page          query     int     false  \"Page number (1-based)\"\n//\t@Param        limit         query     int     false  \"Page size\"\n//\t@Param        nameSelector  query     string  false  \"Fuzzy match on alias_name\"\n//\t@Param        status        query     int     false  \"Cluster status (0-4)\"\n//\t@Success      200  {object}  httputils.Response{result=types.PageResponse}\n//\t@Failure      400  {object}  httputils.Response\n//\t@Failure      500  {object}  httputils.Response\n//\t@Router       /pixiu/clusters [get]\n//\t@Security     Bearer\nfunc (cr *clusterRouter) listClusters(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar req types.ListClusterRequest\n\tif err := c.ShouldBindQuery(&req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\tvar err error\n\tif r.Result, err = cr.c.Cluster().List(c, &req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\n// PingCluster godoc\n//\n//\t@Summary      Ping cluster\n//\t@Description  Do ping\n//\t@Tags         Clusters\n//\t@Accept       json\n//\t@Produce      json\n//\t@Param        clusterId  path      int  true  \"Cluster ID\"\n//\t@Success      200        {array}   httputils.Response\n//\t@Failure      400        {object}  httputils.Response\n//\t@Failure      404        {object}  httputils.Response\n//\t@Failure      500        {object}  httputils.Response\n//\t@Router       /pixiu/clusters/{clusterId}/ping [get]\n//\t@Security     Bearer\nfunc (cr *clusterRouter) pingCluster(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\tcluster types.Cluster\n\t\terr     error\n\t)\n\tif err = c.ShouldBindJSON(&cluster); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err = cr.c.Cluster().Ping(c, cluster.KubeConfig); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (cr *clusterRouter) protectCluster(c *gin.Context) {\n\tr := httputils.NewResponse()\n\tvar (\n\t\tidMeta IdMeta\n\t\treq    types.ProtectClusterRequest\n\t\terr    error\n\t)\n\tif err = httputils.ShouldBindAny(c, &req, &idMeta, nil); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err = cr.c.Cluster().Protect(c, idMeta.ClusterId, &req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (cr *clusterRouter) aggregateEvents(c *gin.Context) {\n\tr := httputils.NewResponse()\n\tvar (\n\t\toptMeta struct {\n\t\t\tCluster   string `uri:\"cluster\" binding:\"required\"`\n\t\t\tNamespace string `uri:\"namespace\" binding:\"required\"`\n\t\t\tName      string `uri:\"name\" binding:\"required\"`\n\t\t\tKind      string `uri:\"kind\" binding:\"required\"`\n\t\t}\n\t\terr error\n\t)\n\n\tif err = c.ShouldBindUri(&optMeta); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif r.Result, err = cr.c.Cluster().AggregateEvents(c, optMeta.Cluster, optMeta.Namespace, optMeta.Name, optMeta.Kind); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (cr *clusterRouter) getEventList(c *gin.Context) {\n\tr := httputils.NewResponse()\n\tvar (\n\t\topts struct {\n\t\t\tCluster string `uri:\"cluster\" binding:\"required\"`\n\t\t}\n\t\teventOpt types.EventOptions\n\t\terr      error\n\t)\n\tif err = httputils.ShouldBindAny(c, nil, &opts, &eventOpt); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif r.Result, err = cr.c.Cluster().GetEventList(c, opts.Cluster, eventOpt); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (cr *clusterRouter) watchPodLog(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\topts struct {\n\t\t\tCluster   string `uri:\"cluster\" binding:\"required\"`\n\t\t\tNamespace string `uri:\"namespace\" binding:\"required\"`\n\t\t\tPod       string `uri:\"pod\" binding:\"required\"` //pod name\n\t\t}\n\t\tlogOpt types.PodLogOptions\n\t\terr    error\n\t)\n\tif err = httputils.ShouldBindAny(c, nil, &opts, &logOpt); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\t// websocket\n\tif err = cr.c.Cluster().WatchPodLog(c, opts.Cluster, opts.Namespace, opts.Pod, logOpt.Container, logOpt.TailLines, c.Writer, c.Request); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "api/server/router/cluster/helper.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cluster\n\nimport (\n\t\"strings\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc IsKubeProxyPath(c *gin.Context) bool {\n\treturn strings.HasPrefix(c.Request.URL.Path, kubeProxyBaseURL)\n}\n\nfunc IsHelmPath(c *gin.Context) bool {\n\treturn strings.HasPrefix(c.Request.URL.Path, helmBaseURL)\n}\n"
  },
  {
    "path": "api/server/router/cluster/informer.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cluster\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/httputils\"\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n)\n\ntype ResourceMeta struct {\n\tCluster   string `uri:\"cluster\" binding:\"required\"`\n\tResource  string `uri:\"resource\" binding:\"required\"`\n\tNamespace string `uri:\"namespace\"`\n\tName      string `uri:\"name\"`\n}\n\nfunc (cr *clusterRouter) getIndexerResource(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\tresourceMeta ResourceMeta\n\t\terr          error\n\t)\n\tif err = httputils.ShouldBindAny(c, nil, &resourceMeta, nil); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif r.Result, err = cr.c.Cluster().GetIndexerResource(c, resourceMeta.Cluster, resourceMeta.Resource, resourceMeta.Namespace, resourceMeta.Name); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (cr *clusterRouter) listIndexerResources(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\tresourceMeta ResourceMeta\n\t\tlistOption   types.ListOptions // 分页设置\n\t\terr          error\n\t)\n\tif err = httputils.ShouldBindAny(c, nil, &resourceMeta, &listOption); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif r.Result, err = cr.c.Cluster().ListIndexerResources(c, resourceMeta.Cluster, resourceMeta.Resource, resourceMeta.Namespace, listOption); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n"
  },
  {
    "path": "api/server/router/cluster/proxy.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cluster\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/httputils\"\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n)\n\ntype Action struct {\n\tAct             string `form:\"action\" binding:\"required\"`\n\tResourceVersion string `form:\"resourceVersion\" binding:\"required\"`\n}\n\nfunc (cr *clusterRouter) ReRunJob(c *gin.Context) {\n\tr := httputils.NewResponse()\n\tvar (\n\t\tjobMeta types.PixiuObjectMeta\n\t\taction  Action\n\t\terr     error\n\t)\n\tif err = httputils.ShouldBindAny(c, nil, &jobMeta, &action); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err = cr.c.Cluster().ReRunJob(c, jobMeta.Cluster, jobMeta.Namespace, jobMeta.Name, action.ResourceVersion); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n"
  },
  {
    "path": "api/server/router/cluster/ws.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cluster\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/httputils\"\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n)\n\nfunc (cr *clusterRouter) webShell(c *gin.Context) {\n\tr := httputils.NewResponse()\n\tvar (\n\t\terr error\n\t\topt types.WebShellOptions\n\t)\n\tif err = c.ShouldBindQuery(&opt); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err = cr.c.Cluster().WsHandler(c, &opt, c.Writer, c.Request); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n}\n\nfunc (cr *clusterRouter) nodeWebShell(c *gin.Context) {\n\tr := httputils.NewResponse()\n\tvar (\n\t\tsshConfig types.WebSSHRequest\n\t\terr       error\n\t)\n\tif err = httputils.ShouldBindAny(c, nil, nil, &sshConfig); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err = cr.c.Cluster().WsNodeHandler(c, &sshConfig, c.Writer, c.Request); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "api/server/router/helm/helm.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helm\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/caoyingjunz/pixiu/cmd/app/options\"\n\t\"github.com/caoyingjunz/pixiu/pkg/controller\"\n)\n\nconst (\n\thelmBaseURL = \"/pixiu/helms\"\n)\n\ntype helmRouter struct {\n\tc controller.PixiuInterface\n}\n\nfunc NewRouter(o *options.Options) {\n\thr := &helmRouter{\n\t\tc: o.Controller,\n\t}\n\thr.initRoutes(o.HttpEngine)\n}\n\nfunc (hr *helmRouter) initRoutes(httpEngine *gin.Engine) {\n\n\thelmRoute := httpEngine.Group(helmBaseURL)\n\t{\n\t\t// helm Repository\n\t\thelmRoute.POST(\"/repositories\", hr.createRepository)\n\t\thelmRoute.PUT(\"/repositories/:id\", hr.updateRepository)\n\t\thelmRoute.DELETE(\"/repositories/:id\", hr.deleteRepository)\n\t\thelmRoute.GET(\"/repositories/:id\", hr.getRepository)\n\t\thelmRoute.GET(\"/repositories\", hr.listRepositories)\n\n\t\thelmRoute.GET(\"/repositories/:id/charts\", hr.getRepoCharts)\n\t\thelmRoute.GET(\"/repositories/charts\", hr.getRepoChartsByURL)\n\t\thelmRoute.GET(\"/repositories/values\", hr.getChartValues)\n\n\t\t// Helm Release\n\t\thelmRoute.POST(\"/clusters/:cluster/namespaces/:namespace/releases\", hr.InstallRelease)\n\t\thelmRoute.PUT(\"/clusters/:cluster/namespaces/:namespace/releases\", hr.UpgradeRelease)\n\t\thelmRoute.DELETE(\"/clusters/:cluster/namespaces/:namespace/releases/:name\", hr.UninstallRelease)\n\t\thelmRoute.GET(\"/clusters/:cluster/namespaces/:namespace/releases/:name\", hr.GetRelease)\n\t\thelmRoute.GET(\"/clusters/:cluster/namespaces/:namespace/releases\", hr.ListReleases)\n\n\t\thelmRoute.GET(\"/clusters/:cluster/namespaces/:namespace/releases/:name/history\", hr.GetReleaseHistory)\n\t\thelmRoute.POST(\"/clusters/:cluster/namespaces/:namespace/releases/:name/rollback\", hr.RollbackRelease)\n\t}\n}\n"
  },
  {
    "path": "api/server/router/helm/release_routes.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helm\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/httputils\"\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n)\n\n// GetRelease retrieves a release by its name in the specified namespace and cluster\n//\n// @Summary get a release\n// @Description retrieves a release from the specified namespace and cluster\n// @Tags helm\n// @Accept json\n// @Produce json\n// @Param cluster path string true \"Kubernetes cluster name\"\n// @Param namespace path string true \"Kubernetes namespace\"\n// @Param name path string true \"Release name\"\n// @Success 200 {object} httputils.Response{result=types.Release}\n// @Failure 400 {object} httputils.Response\n// @Failure 404 {object} httputils.Response\n// @Failure 500 {object} httputils.Response\n// @Router /helm/releases/{cluster}/{namespace}/{name} [get]\nfunc (hr *helmRouter) GetRelease(c *gin.Context) {\n\tr := httputils.NewResponse()\n\tvar (\n\t\terr      error\n\t\thelmMeta types.PixiuObjectMeta\n\t)\n\tif err = c.ShouldBindUri(&helmMeta); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\tif r.Result, err = hr.c.Helm().Release(helmMeta.Cluster, helmMeta.Namespace).Get(c, helmMeta.Name); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\thttputils.SetSuccess(c, r)\n}\n\n// ListReleases lists all releases in the specified namespace and cluster\n//\n// @Summary list releases\n// @Description lists all releases in the specified namespace and cluster\n// @Tags helm\n// @Accept json\n// @Produce json\n// @Param cluster path string true \"Kubernetes cluster name\"\n// @Param namespace path string true \"Kubernetes namespace\"\n// @Success 200 {object} httputils.Response{result=[]types.Release}\n// @Failure 400 {object} httputils.Response\n// @Failure 404 {object} httputils.Response\n// @Failure 500 {object} httputils.Response\n// @Router /helm/releases/{cluster}/{namespace} [get]\nfunc (hr *helmRouter) ListReleases(c *gin.Context) {\n\tr := httputils.NewResponse()\n\tvar (\n\t\terr      error\n\t\thelmMeta types.PixiuObjectMeta\n\t)\n\tif err = c.ShouldBindUri(&helmMeta); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\tif r.Result, err = hr.c.Helm().Release(helmMeta.Cluster, helmMeta.Namespace).List(c); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\n// InstallRelease installs a new release in the specified namespace and cluster\n//\n// @Summary install a release\n// @Description installs a release in the specified Kubernetes namespace and cluster\n// @Tags helm\n// @Accept json\n// @Produce json\n// @Param cluster path string true \"Kubernetes cluster name\"\n// @Param namespace path string true \"Kubernetes namespace\"\n// @Param body body types.ReleaseForm true \"Release information\"\n// @Success 200 {object} httputils.Response\n// @Failure 400 {object} httputils.Response\n// @Failure 500 {object} httputils.Response\n// @Router /helm/releases/{cluster}/{namespace} [post]\nfunc (hr *helmRouter) InstallRelease(c *gin.Context) {\n\tr := httputils.NewResponse()\n\tvar (\n\t\terr        error\n\t\thelmMeta   types.PixiuObjectMeta\n\t\treleaseOpt types.Release\n\t)\n\tif err = httputils.ShouldBindAny(c, &releaseOpt, &helmMeta, nil); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\tif r.Result, err = hr.c.Helm().Release(helmMeta.Cluster, helmMeta.Namespace).Install(c, &releaseOpt); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\n// UninstallRelease uninstalls a release from the specified namespace and cluster\n//\n// @Summary uninstall a release\n// @Description uninstalls a release from the specified Kubernetes namespace and cluster\n// @Tags helm\n// @Accept json\n// @Produce json\n// @Param cluster path string true \"Kubernetes cluster name\"\n// @Param namespace path string true \"Kubernetes namespace\"\n// @Param name path string true \"Release name\"\n// @Success 200 {object} httputils.Response\n// @Failure 400 {object} httputils.Response\n// @Failure 404 {object} httputils.Response\n// @Failure 500 {object} httputils.Response\n// @Router /helm/releases/{cluster}/{namespace}/{name} [delete]\nfunc (hr *helmRouter) UninstallRelease(c *gin.Context) {\n\tr := httputils.NewResponse()\n\tvar (\n\t\terr      error\n\t\thelmMeta types.PixiuObjectMeta\n\t)\n\tif err = c.ShouldBindUri(&helmMeta); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\tif r.Result, err = hr.c.Helm().Release(helmMeta.Cluster, helmMeta.Namespace).Uninstall(c, helmMeta.Name); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\n// UpgradeRelease upgrades a release in the specified namespace and cluster\n//\n// @Summary upgrade a release\n// @Description upgrades a release in the specified Kubernetes namespace and cluster\n// @Tags helm\n// @Accept json\n// @Produce json\n// @Param cluster path string true \"Kubernetes cluster name\"\n// @Param namespace path string true \"Kubernetes namespace\"\n// @Param name path string true \"Release name\"\n// @Param body body types.ReleaseForm true \"Release information\"\n// @Success 200 {object} httputils.Response\n// @Failure 400 {object} httputils.Response\n// @Failure 500 {object} httputils.Response\n// @Router /helm/releases/{cluster}/{namespace}/{name} [put]\nfunc (hr *helmRouter) UpgradeRelease(c *gin.Context) {\n\tr := httputils.NewResponse()\n\tvar (\n\t\terr        error\n\t\thelmMeta   types.PixiuObjectMeta\n\t\treleaseOpt types.Release\n\t)\n\tif err = httputils.ShouldBindAny(c, &releaseOpt, &helmMeta, nil); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\tif r.Result, err = hr.c.Helm().Release(helmMeta.Cluster, helmMeta.Namespace).Upgrade(c, &releaseOpt); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\n// GetReleaseHistory retrieves the history of a release in the specified namespace and cluster\n//\n// @Summary get a release history\n// @Description retrieves the history of a release from the specified Kubernetes namespace and cluster\n// @Tags helm\n// @Accept json\n// @Produce json\n// @Param cluster path string true \"Kubernetes cluster name\"\n// @Param namespace path string true \"Kubernetes namespace\"\n// @Param name path string true \"Release name\"\n// @Success 200 {object} httputils.Response{result=types.ReleaseHistory}\n// @Failure 400 {object} httputils.Response\n// @Failure 404 {object} httputils.Response\n// @Failure 500 {object} httputils.Response\n// @Router /helm/releases/history/{cluster}/{namespace}/{name} [get]\nfunc (hr *helmRouter) GetReleaseHistory(c *gin.Context) {\n\tr := httputils.NewResponse()\n\tvar (\n\t\terr      error\n\t\thelmMeta types.PixiuObjectMeta\n\t)\n\tif err = c.ShouldBindUri(&helmMeta); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\tif r.Result, err = hr.c.Helm().Release(helmMeta.Cluster, helmMeta.Namespace).History(c, helmMeta.Name); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\n// RollbackRelease rolls back a release in the specified namespace and cluster to the specified revision\n//\n// @Summary rollback a release\n// @Description rolls back a release from the specified Kubernetes namespace and cluster to the specified revision\n// @Tags helm\n// @Accept json\n// @Produce json\n// @Param cluster path string true \"Kubernetes cluster name\"\n// @Param namespace path string true \"Kubernetes namespace\"\n// @Param name path string true \"Release name\"\n// @Param version query int true \"Release revision\"\n// @Success 200 {object} httputils.Response\n// @Failure 400 {object} httputils.Response\n// @Failure 404 {object} httputils.Response\n// @Failure 500 {object} httputils.Response\n// @Router /helm/releases/rollback/{cluster}/{namespace}/{name} [post]\nfunc (hr *helmRouter) RollbackRelease(c *gin.Context) {\n\tr := httputils.NewResponse()\n\tvar (\n\t\terr          error\n\t\thelmMeta     types.PixiuObjectMeta\n\t\treverionMeta types.ReleaseHistory\n\t)\n\tif err = httputils.ShouldBindAny(c, nil, &helmMeta, &reverionMeta); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\tif err = hr.c.Helm().Release(helmMeta.Cluster, helmMeta.Namespace).Rollback(c, helmMeta.Name, reverionMeta.Version); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n"
  },
  {
    "path": "api/server/router/helm/respository_routes.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helm\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/httputils\"\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n)\n\n// createRepository creates a new repository in the specified cluster\n//\n// @Summary create a repository\n// @Description creates a new repository in the specified Kubernetes cluster\n// @Tags repositories\n// @Accept json\n// @Produce json\n// @Param cluster query string true \"Kubernetes cluster name\"\n// @Param body body types.RepoForm true \"Repository information\"\n// @Success 200 {object} httputils.Response\n// @Failure 400 {object} httputils.Response\n// @Failure 500 {object} httputils.Response\n// @Router /repositories [post]\nfunc (hr *helmRouter) createRepository(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\terr error\n\t\treq types.CreateRepository\n\t)\n\tif err = httputils.ShouldBindAny(c, &req, nil, nil); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\tif err = hr.c.Helm().Repository().Create(c, &req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\n// deleteRepository deletes a repository by its ID\n//\n// @Summary delete a repository by ID\n// @Description deletes a repository from the system using the provided ID\n// @Tags repositories\n// @Accept json\n// @Produce json\n// @Param id path int true \"Repository ID\"\n// @Success 200 {object} httputils.Response\n// @Failure 400 {object} httputils.Response\n// @Failure 404 {object} httputils.Response\n// @Failure 500 {object} httputils.Response\n// @Router /repositories/{id} [delete]\nfunc (hr *helmRouter) deleteRepository(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\terr      error\n\t\trepoMeta types.RepoId\n\t)\n\tif err = c.ShouldBindUri(&repoMeta); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err = hr.c.Helm().Repository().Delete(c, repoMeta.Id); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\n// updateRepository updates a repository by its ID\n//\n// @Summary update a repository by ID\n// @Description updates a repository in the system using the provided ID and update information\n// @Tags repositories\n// @Accept json\n// @Produce json\n// @Param id path int true \"Repository ID\"\n// @Param body body types.RepoUpdateForm true \"Repository update information\"\n// @Success 200 {object} httputils.Response\n// @Failure 400 {object} httputils.Response\n// @Failure 404 {object} httputils.Response\n// @Failure 500 {object} httputils.Response\nfunc (hr *helmRouter) updateRepository(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\terr      error\n\t\trepoMeta types.RepoId\n\t\tformData types.UpdateRepository\n\t)\n\tif err = httputils.ShouldBindAny(c, &formData, &repoMeta, nil); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err = hr.c.Helm().Repository().Update(c, repoMeta.Id, &formData); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\n// getRepository retrieves a repository by its ID\n//\n// @Summary get a repository by ID\n// @Description retrieves a repository from the system using the provided ID\n// @Tags repositories\n// @Accept json\n// @Produce json\n// @Param id path int true \"Repository ID\"\n// @Success 200 {object} httputils.Response{result=types.Repository}\n// @Failure 400 {object} httputils.Response\n// @Failure 404 {object} httputils.Response\n// @Failure 500 {object} httputils.Response\n// @Router /repositories/{id} [get]\nfunc (hr *helmRouter) getRepository(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\terr      error\n\t\trepoMeta types.RepoId\n\t)\n\tif err = c.ShouldBindUri(&repoMeta); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif r.Result, err = hr.c.Helm().Repository().Get(c, repoMeta.Id); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\n// listRepositories retrieves a list of all repositories\n//\n// @Summary list repositories\n// @Description retrieves a list of all repositories in the system\n// @Tags repositories\n// @Accept json\n// @Produce json\n// @Success 200 {object} httputils.Response{result=[]types.Repository}\n// @Failure 400 {object} httputils.Response\n// @Failure 500 {object} httputils.Response\n// @Router /repositories [get]\nfunc (hr *helmRouter) listRepositories(c *gin.Context) {\n\tr := httputils.NewResponse()\n\tvar err error\n\n\tif r.Result, err = hr.c.Helm().Repository().List(c); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\n// getRepoCharts retrieves charts of a repository by its ID\n//\n// @Summary get repository charts by ID\n// @Description retrieves charts associated with a repository from the system using the provided ID\n// @Tags repositories\n// @Accept json\n// @Produce json\n// @Param id path int true \"Repository ID\"\n// @Success 200 {object} httputils.Response{result=model.ChartIndex}\n// @Failure 400 {object} httputils.Response\n// @Failure 404 {object} httputils.Response\n// @Failure 500 {object} httputils.Response\n// @Router /repositories/{id}/charts [get]\nfunc (hr *helmRouter) getRepoCharts(c *gin.Context) {\n\tr := httputils.NewResponse()\n\tvar (\n\t\terr      error\n\t\trepoMeta types.RepoId\n\t)\n\n\tif err = c.ShouldBindUri(&repoMeta); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif r.Result, err = hr.c.Helm().Repository().GetChartsById(c, repoMeta.Id); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\n// getRepoChartsByURL retrieves charts of a repository by its URL\n//\n// @Summary get repository charts by URL\n// @Description retrieves charts associated with a repository from the system using the provided URL\n// @Tags repositories\n// @Accept json\n// @Produce json\n// @Param url query string true \"Repository URL\"\n// @Success 200 {object} httputils.Response{result=model.ChartIndex}\n// @Failure 400 {object} httputils.Response\n// @Failure 404 {object} httputils.Response\n// @Failure 500 {object} httputils.Response\n// @Router /repositories/charts [get]\nfunc (hr *helmRouter) getRepoChartsByURL(c *gin.Context) {\n\tr := httputils.NewResponse()\n\tvar (\n\t\terr      error\n\t\trepoMeta types.RepoURL\n\t)\n\n\tif err = httputils.ShouldBindAny(c, nil, nil, &repoMeta); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif r.Result, err = hr.c.Helm().Repository().GetChartsByURL(c, repoMeta.Url); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\n// getChartValues retrieves the values of a specific chart version\n//\n// @Summary get chart values\n// @Description retrieves values for a specific chart version using the provided chart name and version\n// @Tags charts\n// @Accept json\n// @Produce json\n// @Param chart query string true \"Chart name\"\n// @Param version query string true \"Chart version\"\n// @Success 200 {object} httputils.Response{result=types.ChartValues}\n// @Failure 400 {object} httputils.Response\n// @Failure 404 {object} httputils.Response\n// @Failure 500 {object} httputils.Response\n// @Router /repositories/chartvalues [get]\nfunc (hr *helmRouter) getChartValues(c *gin.Context) {\n\n\tr := httputils.NewResponse()\n\tvar (\n\t\terr      error\n\t\trepoMeta types.ChartValues\n\t)\n\n\tif err = httputils.ShouldBindAny(c, nil, nil, &repoMeta); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif r.Result, err = hr.c.Helm().Repository().GetChartValues(c, repoMeta.Chart, repoMeta.Version); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n\n}\n"
  },
  {
    "path": "api/server/router/plan/config_routes.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plan\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/httputils\"\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n)\n\ntype planConfigMeta struct {\n\tplanMeta\n\n\tConfigId int64 `uri:\"configId\" binding:\"required\"`\n}\n\nfunc (t *planRouter) createPlanConfig(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\topt planMeta\n\t\treq types.CreatePlanConfigRequest\n\t\terr error\n\t)\n\tif err = httputils.ShouldBindAny(c, &req, &opt, nil); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err = t.c.Plan().CreateConfig(c, opt.PlanId, &req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (t *planRouter) updatePlanConfig(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\topt planConfigMeta\n\t\treq types.UpdatePlanConfigRequest\n\t\terr error\n\t)\n\tif err = httputils.ShouldBindAny(c, &req, &opt, nil); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err = t.c.Plan().UpdateConfig(c, opt.PlanId, opt.ConfigId, &req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (t *planRouter) deletePlanConfig(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\topt planConfigMeta\n\t\terr error\n\t)\n\tif err = httputils.ShouldBindAny(c, nil, &opt, nil); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err = t.c.Plan().DeleteConfig(c, opt.PlanId, opt.ConfigId); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (t *planRouter) getPlanConfig(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\topt planMeta\n\t\terr error\n\t)\n\tif err = httputils.ShouldBindAny(c, nil, &opt, nil); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif r.Result, err = t.c.Plan().GetConfig(c, opt.PlanId); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n"
  },
  {
    "path": "api/server/router/plan/node_routes.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plan\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/httputils\"\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n)\n\ntype planNodeMeta struct {\n\tplanMeta `json:\",inline\"`\n\n\tNodeId int64 `uri:\"nodeId\" binding:\"required\"`\n}\n\nfunc (t *planRouter) createPlanNode(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\topt planMeta\n\t\treq types.CreatePlanNodeRequest\n\t\terr error\n\t)\n\tif err = httputils.ShouldBindAny(c, &req, &opt, nil); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err = t.c.Plan().CreateNode(c, opt.PlanId, &req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (t *planRouter) updatePlanNode(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\topt planNodeMeta\n\t\treq types.UpdatePlanNodeRequest\n\t\terr error\n\t)\n\tif err = httputils.ShouldBindAny(c, &req, &opt, nil); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err = t.c.Plan().UpdateNode(c, opt.PlanId, opt.NodeId, &req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (t *planRouter) deletePlanNode(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\topt planNodeMeta\n\t\terr error\n\t)\n\tif err = httputils.ShouldBindAny(c, nil, &opt, nil); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err = t.c.Plan().DeleteNode(c, opt.PlanId, opt.NodeId); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (t *planRouter) getPlanNode(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\topt planNodeMeta\n\t\terr error\n\t)\n\tif err = httputils.ShouldBindAny(c, nil, &opt, nil); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif r.Result, err = t.c.Plan().GetNode(c, opt.PlanId, opt.NodeId); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (t *planRouter) listPlanNodes(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\topt planMeta\n\t\terr error\n\t)\n\tif err = httputils.ShouldBindAny(c, nil, &opt, nil); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif r.Result, err = t.c.Plan().ListNodes(c, opt.PlanId); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n"
  },
  {
    "path": "api/server/router/plan/plan.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plan\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/caoyingjunz/pixiu/cmd/app/options\"\n\t\"github.com/caoyingjunz/pixiu/pkg/controller\"\n)\n\ntype planRouter struct {\n\tc controller.PixiuInterface\n}\n\nfunc NewRouter(o *options.Options) {\n\trouter := &planRouter{\n\t\tc: o.Controller,\n\t}\n\trouter.initRoutes(o.HttpEngine)\n}\n\nfunc (t *planRouter) initRoutes(ginEngine *gin.Engine) {\n\tplanRoute := ginEngine.Group(\"/pixiu/plans\")\n\t{\n\t\tplanRoute.POST(\"\", t.createPlan)\n\t\tplanRoute.PUT(\"/:planId\", t.updatePlan)\n\t\tplanRoute.DELETE(\"/:planId\", t.deletePlan)\n\t\tplanRoute.GET(\"/:planId\", t.getPlan)\n\t\tplanRoute.GET(\"\", t.listPlans)\n\n\t\tplanRoute.GET(\"/:planId/resources\", t.getPlanWithSubResources)\n\n\t\t// 启动部署任务\n\t\tplanRoute.POST(\"/:planId/start\", t.startPlan)\n\t\t// 终止部署任务\n\t\tplanRoute.POST(\"/:planId/stop\", t.stopPlan)\n\n\t\t// 部署计划的节点API\n\t\tplanRoute.POST(\"/:planId/nodes\", t.createPlanNode)\n\t\tplanRoute.PUT(\"/:planId/nodes/:nodeId\", t.updatePlanNode)\n\t\tplanRoute.DELETE(\"/:planId/nodes/:nodeId\", t.deletePlanNode)\n\t\tplanRoute.GET(\"/:planId/nodes/:nodeId\", t.getPlanNode)\n\t\tplanRoute.GET(\"/:planId/nodes\", t.listPlanNodes)\n\n\t\t// 部署计划的部署配置\n\t\tplanRoute.POST(\"/:planId/configs\", t.createPlanConfig)\n\t\tplanRoute.PUT(\"/:planId/configs/:configId\", t.updatePlanConfig)\n\t\tplanRoute.DELETE(\"/:planId/configs/:configId\", t.deletePlanConfig)\n\t\tplanRoute.GET(\"/:planId/configs\", t.getPlanConfig)\n\n\t\t// 执行指定任务\n\t\tplanRoute.POST(\"/:planId/tasks/:taskId\", t.runTasks)\n\t\t// 查询任务列表\n\t\tplanRoute.GET(\"/:planId/tasks\", t.listTasks)\n\t\t// 实时查询任务进度\n\t\tplanRoute.GET(\"/:planId/tasks/:taskId/logs\", t.watchTaskLog)\n\n\t\t// 获取 os 与 os version\n\t\tplanRoute.GET(\"/distributions\", t.getDistributions)\n\t}\n}\n"
  },
  {
    "path": "api/server/router/plan/plan_routes.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plan\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/httputils\"\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n)\n\ntype planMeta struct {\n\tPlanId int64 `uri:\"planId\" binding:\"required\"`\n}\n\ntype watchTaskLogMeta struct {\n\tPlanId int64 `uri:\"planId\" binding:\"required\"`\n\tTaskId int64 `uri:\"taskId\" binding:\"required\"`\n}\n\ntype WatchMeta struct {\n\tWatch bool `form:\"watch\"`\n}\n\n// 创建部署计划，同时创建配置和节点\nfunc (t *planRouter) createPlan(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar req types.CreatePlanRequest\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err := t.c.Plan().Create(c, &req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (t *planRouter) updatePlan(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\topt planMeta\n\t\treq types.UpdatePlanRequest\n\t\terr error\n\t)\n\tif err = httputils.ShouldBindAny(c, &req, &opt, nil); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err = t.c.Plan().Update(c, opt.PlanId, &req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (t *planRouter) deletePlan(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\topt planMeta\n\t\terr error\n\t)\n\tif err = c.ShouldBindUri(&opt); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err = t.c.Plan().Delete(c, opt.PlanId); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (t *planRouter) getPlan(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\topt planMeta\n\t\terr error\n\t)\n\tif err = c.ShouldBindUri(&opt); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif r.Result, err = t.c.Plan().Get(c, opt.PlanId); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\n// getPlanWithSubResources\n// 获取 plan\n// 获取 configs\n// 获取 nodes\nfunc (t *planRouter) getPlanWithSubResources(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\topt planMeta\n\t\terr error\n\t)\n\tif err = c.ShouldBindUri(&opt); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif r.Result, err = t.c.Plan().GetWithSubResources(c, opt.PlanId); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (t *planRouter) listPlans(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\treq types.ListPlanRequest\n\t\terr error\n\t)\n\tif err = c.ShouldBindQuery(&req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif r.Result, err = t.c.Plan().List(c, &req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (t *planRouter) startPlan(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\topt planMeta\n\t\terr error\n\t)\n\tif err = c.ShouldBindUri(&opt); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err = t.c.Plan().Start(c, opt.PlanId); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (t *planRouter) stopPlan(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\topt planMeta\n\t\terr error\n\t)\n\tif err = c.ShouldBindUri(&opt); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err = t.c.Plan().Stop(c, opt.PlanId); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\ntype DistributionsMeta struct {\n\tCentos    []string `json:\"centos,omitempty\"`\n\tUbuntu    []string `json:\"ubuntu,omitempty\"`\n\tDebian    []string `json:\"debian,omitempty\"`\n\tOpenEuler []string `json:\"openEuler,omitempty\"`\n\tRocky     []string `json:\"rocky,omitempty\"`\n}\n\nfunc (t *planRouter) getDistributions(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tr.Result = &DistributionsMeta{\n\t\tCentos:    []string{\"centos7\"},\n\t\tUbuntu:    []string{\"ubuntu18.04\", \"ubuntu20.04\", \"ubuntu22.04\"},\n\t\tDebian:    []string{\"debian10\", \"debian11\"},\n\t\tOpenEuler: []string{\"openEuler22.03\"},\n\t\tRocky:     []string{\"rocky8.5\", \"rocky9.2\", \"rocky9.3\"},\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n"
  },
  {
    "path": "api/server/router/plan/task_routes.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plan\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/httputils\"\n)\n\ntype taskNodeMeta struct {\n\tplanMeta `json:\",inline\"`\n\n\tTaskId int64 `uri:\"taskId\" binding:\"required\"`\n}\n\nfunc (t *planRouter) runTasks(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (t *planRouter) listTasks(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\topt   planMeta\n\t\twatch WatchMeta\n\t\terr   error\n\t)\n\tif err = httputils.ShouldBindAny(c, nil, &opt, &watch); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\t// 不是长连接请求则直接返回\n\tif !watch.Watch {\n\t\tif r.Result, err = t.c.Plan().ListTasks(c, opt.PlanId); err != nil {\n\t\t\thttputils.SetFailed(c, r, err)\n\t\t\treturn\n\t\t}\n\t\thttputils.SetSuccess(c, r)\n\t\treturn\n\t}\n\n\t// 长连接请求\n\tt.c.Plan().WatchTasks(c, opt.PlanId, c.Writer, c.Request)\n}\n\nfunc (t *planRouter) watchTaskLog(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\topt watchTaskLogMeta\n\t\terr error\n\t)\n\tif err = httputils.ShouldBindAny(c, nil, &opt, nil); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err = t.c.Plan().WatchTaskLog(c, opt.PlanId, opt.TaskId, c.Writer, c.Request); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "api/server/router/proxy/helper.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage proxy\n\nimport (\n\t\"strings\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// IsProxyPath returns true when the request path is a proxy one.\nfunc IsProxyPath(c *gin.Context) bool {\n\treturn strings.HasPrefix(c.Request.URL.Path, proxyBaseURL)\n}\n"
  },
  {
    "path": "api/server/router/proxy/proxy.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage proxy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"k8s.io/apimachinery/pkg/util/proxy\"\n\t\"k8s.io/client-go/rest\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/httputils\"\n\t\"github.com/caoyingjunz/pixiu/cmd/app/options\"\n\t\"github.com/caoyingjunz/pixiu/pkg/controller\"\n)\n\nconst (\n\tproxyBaseURL = \"/pixiu/proxy\"\n)\n\ntype proxyRouter struct {\n\tc controller.PixiuInterface\n}\n\nfunc NewRouter(o *options.Options) {\n\ts := &proxyRouter{\n\t\tc: o.Controller,\n\t}\n\ts.initRoutes(o.HttpEngine)\n}\n\nfunc (p *proxyRouter) initRoutes(ginEngine *gin.Engine) {\n\tproxyRoute := ginEngine.Group(\"/pixiu/\")\n\t{\n\t\tproxyRoute.Any(\"/proxy/:clusterName/*act\", p.proxyHandler)\n\t}\n}\n\nfunc (p *proxyRouter) proxyHandler(c *gin.Context) {\n\tresp := httputils.NewResponse()\n\n\tvar cluster struct {\n\t\tName string `uri:\"clusterName\" binding:\"required\"`\n\t}\n\tif err := c.ShouldBindUri(&cluster); err != nil {\n\t\thttputils.SetFailed(c, resp, err)\n\t\treturn\n\t}\n\n\tname := cluster.Name\n\tconfig, err := p.c.Cluster().GetKubeConfigByName(context.TODO(), name)\n\tif err != nil {\n\t\thttputils.SetFailed(c, resp, fmt.Errorf(\"failed to get cluster %q kubeconfig\", name))\n\t\treturn\n\t}\n\n\ttransport, err := rest.TransportFor(config)\n\tif err != nil {\n\t\thttputils.SetFailed(c, resp, err)\n\t\treturn\n\t}\n\ttarget, err := p.parseTarget(*c.Request.URL, config.Host, name)\n\tif err != nil {\n\t\thttputils.SetFailed(c, resp, err)\n\t\treturn\n\t}\n\n\thttpProxy := proxy.NewUpgradeAwareHandler(target, transport, false, false, nil)\n\thttpProxy.UpgradeTransport = proxy.NewUpgradeRequestRoundTripper(transport, transport)\n\thttpProxy.ServeHTTP(c.Writer, c.Request)\n}\n\nfunc (p *proxyRouter) parseTarget(target url.URL, host string, name string) (*url.URL, error) {\n\tkubeURL, err := url.Parse(host)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// TODO: 检查 URL 是否规范\n\ttarget.Path = target.Path[len(proxyBaseURL+\"/\"+name):]\n\n\ttarget.Host = kubeURL.Host\n\ttarget.Scheme = kubeURL.Scheme\n\treturn &target, nil\n}\n"
  },
  {
    "path": "api/server/router/router.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage router\n\nimport (\n\t\"embed\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\tswaggerFiles \"github.com/swaggo/files\"\n\tginSwagger \"github.com/swaggo/gin-swagger\"\n\n\t// 导入 docs.json 文件\n\t_ \"github.com/caoyingjunz/pixiu/api/docs\"\n\t_ \"github.com/caoyingjunz/pixiu/api/server/validator\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/middleware\"\n\t\"github.com/caoyingjunz/pixiu/api/server/router/audit\"\n\t\"github.com/caoyingjunz/pixiu/api/server/router/auth\"\n\t\"github.com/caoyingjunz/pixiu/api/server/router/cluster\"\n\t\"github.com/caoyingjunz/pixiu/api/server/router/helm\"\n\t\"github.com/caoyingjunz/pixiu/api/server/router/plan\"\n\t\"github.com/caoyingjunz/pixiu/api/server/router/proxy\"\n\t\"github.com/caoyingjunz/pixiu/api/server/router/tenant\"\n\t\"github.com/caoyingjunz/pixiu/api/server/router/user\"\n\t\"github.com/caoyingjunz/pixiu/cmd/app/options\"\n\t\"github.com/caoyingjunz/pixiu/pkg/static\"\n)\n\ntype RegisterFunc func(o *options.Options)\n\n//go:embed static\nvar EmbedFS embed.FS\n\nfunc InstallRouters(o *options.Options) {\n\tfs := []RegisterFunc{\n\t\tmiddleware.InstallMiddlewares,\n\t\tcluster.NewRouter,\n\t\thelm.NewRouter,\n\t\tproxy.NewRouter,\n\t\ttenant.NewRouter,\n\t\tuser.NewRouter,\n\t\tplan.NewRouter,\n\t\taudit.NewRouter,\n\t\tauth.NewRouter,\n\t}\n\n\tinstall(o, fs...)\n\n\t// StaticFiles 启用前端集成\n\to.HttpEngine.Use(static.Serve(\"/\", static.LocalFile(o.ComponentConfig.Default.StaticFiles, true)))\n\n\t// 启动健康检查\n\to.HttpEngine.GET(\"/healthz\", func(c *gin.Context) { c.String(http.StatusOK, \"ok\") })\n\t// 启动 APIs 服务\n\to.HttpEngine.GET(\"/api-ref/*any\", ginSwagger.WrapHandler(swaggerFiles.Handler))\n}\n\nfunc install(o *options.Options, fs ...RegisterFunc) {\n\tfor _, f := range fs {\n\t\tf(o)\n\t}\n}\n"
  },
  {
    "path": "api/server/router/static/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>欢迎使用 Pixiu</title>\n    <style>\n        body {\n            font-family: Arial, sans-serif;\n            background-color: #f7f7f7;\n            color: #333;\n            margin: 0;\n            padding: 0;\n            display: flex;\n            justify-content: center;\n            align-items: center;\n            height: 100vh;\n            text-align: center;\n        }\n        .container {\n            width: 100%; /* 占满整个屏幕宽度 */\n            height: 100vh; /* 占满整个屏幕高度 */\n            padding: 20px;\n            background-color: #fff;\n            border-radius: 0; /* 移除圆角 */\n            box-shadow: none; /* 移除阴影 */\n            display: flex;\n            flex-direction: column;\n            justify-content: center;\n            align-items: center;\n        }\n        h1 {\n            font-size: 2.5rem;\n            margin-bottom: 10px;\n        }\n    </style>\n</head>\n<body>\n<div class=\"container\">\n    <h1>欢迎使用 Pixiu</h1>\n</div>\n</body>\n</html>"
  },
  {
    "path": "api/server/router/tenant/tenant.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage tenant\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/caoyingjunz/pixiu/cmd/app/options\"\n\t\"github.com/caoyingjunz/pixiu/pkg/controller\"\n)\n\ntype tenantRouter struct {\n\tc controller.PixiuInterface\n}\n\nfunc NewRouter(o *options.Options) {\n\trouter := &tenantRouter{\n\t\tc: o.Controller,\n\t}\n\trouter.initRoutes(o.HttpEngine)\n}\n\nfunc (t *tenantRouter) initRoutes(ginEngine *gin.Engine) {\n\ttenantRoute := ginEngine.Group(\"/pixiu/tenants\")\n\t{\n\t\ttenantRoute.POST(\"\", t.createTenant)\n\t\ttenantRoute.PUT(\"/:tenantId\", t.updateTenant)\n\t\ttenantRoute.DELETE(\"/:tenantId\", t.deleteTenant)\n\t\ttenantRoute.GET(\"/:tenantId\", t.getTenant)\n\t\ttenantRoute.GET(\"\", t.listTenants)\n\t}\n}\n"
  },
  {
    "path": "api/server/router/tenant/tenant_routes.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage tenant\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/httputils\"\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n)\n\ntype TenantMeta struct {\n\tTenantId int64 `uri:\"tenantId\" binding:\"required\"`\n}\n\nfunc (t *tenantRouter) createTenant(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar req types.CreateTenantRequest\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err := t.c.Tenant().Create(c, &req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (t *tenantRouter) updateTenant(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\topt TenantMeta\n\t\terr error\n\t)\n\tif err = c.ShouldBindUri(&opt); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tvar req types.UpdateTenantRequest\n\tif err = c.ShouldBindJSON(&req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err = t.c.Tenant().Update(c, opt.TenantId, &req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (t *tenantRouter) deleteTenant(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\topt TenantMeta\n\t\terr error\n\t)\n\tif err = c.ShouldBindUri(&opt); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err = t.c.Tenant().Delete(c, opt.TenantId); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (t *tenantRouter) getTenant(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\topt TenantMeta\n\t\terr error\n\t)\n\tif err = c.ShouldBindUri(&opt); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif r.Result, err = t.c.Tenant().Get(c, opt.TenantId); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\nfunc (t *tenantRouter) listTenants(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar err error\n\tif r.Result, err = t.c.Tenant().List(c); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n"
  },
  {
    "path": "api/server/router/user/user.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage user\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/caoyingjunz/pixiu/cmd/app/options\"\n\t\"github.com/caoyingjunz/pixiu/pkg/controller\"\n)\n\ntype userRouter struct {\n\tc controller.PixiuInterface\n}\n\nfunc NewRouter(o *options.Options) {\n\trouter := &userRouter{\n\t\tc: o.Controller,\n\t}\n\trouter.initRoutes(o.HttpEngine)\n}\n\nfunc (u *userRouter) initRoutes(httpEngine *gin.Engine) {\n\t// TODO: Base pixiu 后续作为常量定义\n\tuserRoute := httpEngine.Group(\"/pixiu/users\")\n\t{\n\t\tuserRoute.POST(\"\", u.createUser)\n\t\tuserRoute.PUT(\"/:userId\", u.updateUser)\n\t\tuserRoute.DELETE(\"/:userId\", u.deleteUser)\n\t\tuserRoute.GET(\"/:userId\", u.getUser)\n\t\tuserRoute.GET(\"\", u.listUsers)\n\n\t\t// 用户修改密码或者管理员重置密码\n\t\tuserRoute.PUT(\"/:userId/password\", u.updatePassword)\n\n\t\t// 用户的登陆或者退出\n\t\tuserRoute.POST(\"/login\", u.login)\n\t\tuserRoute.POST(\"/:userId/logout\", u.logout)\n\t}\n}\n"
  },
  {
    "path": "api/server/router/user/user_routes.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage user\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/httputils\"\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n)\n\ntype IdMeta struct {\n\tUserId int64 `uri:\"userId\" binding:\"required\"`\n}\n\n// CreateUser godoc\n//\n//\t@Summary      Create a user\n//\t@Description  Create by a json user\n//\t@Tags         Users\n//\t@Accept       json\n//\t@Produce      json\n//\t@Param        user  body      types.CreateUserRequest  true  \"Create user\"\n//\t@Success      200   {object}  httputils.Response\n//\t@Failure      400   {object}  httputils.Response\n//\t@Failure      404   {object}  httputils.Response\n//\t@Failure      500   {object}  httputils.Response\n//\t@Router       /pixiu/users/ [post]\n//\t              @Security  Bearer\nfunc (u *userRouter) createUser(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\treq types.CreateUserRequest\n\t\terr error\n\t)\n\tif err = c.ShouldBindJSON(&req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err = u.c.User().Create(c, &req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\n// UpdateUser godoc\n//\n//\t@Summary      Update an user\n//\t@Description  Update by json user\n//\t@Tags         Users\n//\t@Accept       json\n//\t@Produce      json\n//\t@Param        userId  path      int                      true  \"User ID\"\n//\t@Param        user    body      types.UpdateUserRequest  true  \"Update user\"\n//\t@Success      200     {object}  httputils.Response\n//\t@Failure      400     {object}  httputils.Response\n//\t@Failure      404     {object}  httputils.Response\n//\t@Failure      500     {object}  httputils.Response\n//\t@Router       /pixiu/users/{userId} [put]\n//\t              @Security  Bearer\nfunc (u *userRouter) updateUser(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\tidMeta IdMeta\n\t\treq    types.UpdateUserRequest\n\t\terr    error\n\t)\n\tif err = httputils.ShouldBindAny(c, &req, &idMeta, nil); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err = u.c.User().Update(c, idMeta.UserId, &req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\n// UpdateUserPassword godoc\n//\n//\t@Summary      Update user password\n//\t@Description  Update by json user\n//\t@Tags         Users\n//\t@Accept       json\n//\t@Produce      json\n//\t@Param        userId  path      int                              true  \"User ID\"\n//\t@Param        user    body      types.UpdateUserPasswordRequest  true  \"Update user password\"\n//\t@Success      200     {object}  httputils.Response\n//\t@Failure      400     {object}  httputils.Response\n//\t@Failure      404     {object}  httputils.Response\n//\t@Failure      500     {object}  httputils.Response\n//\t@Router       /pixiu/users/password [put]\n//\t              @Security  Bearer\nfunc (u *userRouter) updatePassword(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\tidMeta IdMeta\n\t\treq    types.UpdateUserPasswordRequest\n\t\terr    error\n\t)\n\tif err = httputils.ShouldBindAny(c, &req, &idMeta, nil); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err = u.c.User().UpdatePassword(c, idMeta.UserId, &req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\n// DeleteUser godoc\n//\n//\t@Summary      Delete user by userId\n//\t@Description  Delete by userID\n//\t@Tags         Users\n//\t@Accept       json\n//\t@Produce      json\n//\t@Param        userId  path      int  true  \"User ID\"\n//\t@Success      200     {object}  httputils.Response\n//\t@Failure      400     {object}  httputils.Response\n//\t@Failure      404     {object}  httputils.Response\n//\t@Failure      500     {object}  httputils.Response\n//\t@Router       /pixiu/users/{userId} [delete]\n//\t              @Security  Bearer\nfunc (u *userRouter) deleteUser(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\tidMeta IdMeta\n\t\terr    error\n\t)\n\tif err = c.ShouldBindUri(&idMeta); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif err = u.c.User().Delete(c, idMeta.UserId); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\n// Getuser godoc\n//\n//\t@Summary      Get user by userId\n//\t@Description  Get by user ID\n//\t@Tags         Users\n//\t@Accept       json\n//\t@Produce      json\n//\t@Param        userId  path      int  true  \"User ID\"\n//\t@Success      200     {object}  httputils.Response{result=types.User}\n//\t@Failure      400     {object}  httputils.Response\n//\t@Failure      404     {object}  httputils.Response\n//\t@Failure      500     {object}  httputils.Response\n//\t@Router       /pixiu/users/{userId} [get]\n//\t              @Security  Bearer\nfunc (u *userRouter) getUser(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\tidMeta IdMeta\n\t\terr    error\n\t)\n\tif err = c.ShouldBindUri(&idMeta); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tif r.Result, err = u.c.User().Get(c, idMeta.UserId); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\n// Listusers godoc\n//\n//\t@Summary      List users\n//\t@Description  List users\n//\t@Tags         Users\n//\t@Accept       json\n//\t@Produce      json\n//\t@Success      200  {array}   httputils.Response{result=[]types.User}\n//\t@Failure      400  {object}  httputils.Response\n//\t@Failure      404  {object}  httputils.Response\n//\t@Failure      500  {object}  httputils.Response\n//\t@Router       /pixiu/users [get]\n//\t              @Security  Bearer\nfunc (u *userRouter) listUsers(c *gin.Context) {\n\tr := httputils.NewResponse()\n\tvar req types.ListUserRequest\n\tif err := c.ShouldBindQuery(&req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\tvar err error\n\tr.Result, err = u.c.User().List(c, &req)\n\tif err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\n\thttputils.SetSuccess(c, r)\n}\n\n// Login godoc\n//\n//\t@Summary      User login\n//\t@Description  Login by a json user\n//\t@Tags         Login\n//\t@Accept       json\n//\t@Produce      json\n//\t@Param        user  body      types.LoginRequest  true  \"User login\"\n//\t@Success      200   {object}  httputils.Response\n//\t@Failure      400   {object}  httputils.Response\n//\t@Failure      404   {object}  httputils.Response\n//\t@Failure      500   {object}  httputils.Response\n//\t@Router       /pixiu/users/login [post]\nfunc (u *userRouter) login(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\tvar (\n\t\treq types.LoginRequest\n\t\terr error\n\t)\n\tif err = c.ShouldBindJSON(&req); err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tloginResp, err := u.c.User().Login(c, &req)\n\tif err != nil {\n\t\thttputils.SetFailed(c, r, err)\n\t\treturn\n\t}\n\tr.Result = loginResp\n\thttputils.SetUserToContext(c, loginResp.User)\n\n\thttputils.SetSuccess(c, r)\n}\n\n// TODO\nfunc (u *userRouter) logout(c *gin.Context) {\n\tr := httputils.NewResponse()\n\n\thttputils.SetSuccess(c, r)\n}\n"
  },
  {
    "path": "api/server/validator/helper.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage validator\n\nimport (\n\t\"strings\"\n\n\t\"github.com/go-playground/validator/v10\"\n)\n\n// TranslateError returns the translated message of the validation error.\nfunc TranslateError(errs validator.ValidationErrors) string {\n\tmessages := make([]string, len(errs))\n\tfor i, err := range errs {\n\t\tmessages[i] = err.Translate(tran)\n\t}\n\n\treturn strings.Join(messages, \"; \")\n}\n"
  },
  {
    "path": "api/server/validator/password.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage validator\n\nimport (\n\t\"github.com/go-playground/validator/v10\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/util\"\n)\n\nfunc init() {\n\tregister(\n\t\t&passwordValidator{pixiuValidator: newPixiuValidator(\"password\", \"密码不符合要求，至少包含一个大写字母、一个小写字母、一个数字\")},\n\t)\n}\n\n// passwordValidator is a customized validator for validating user password.\ntype passwordValidator struct {\n\tpixiuValidator\n}\n\n// validate validates the password in request.\nfunc (pv *passwordValidator) validate(fl validator.FieldLevel) bool {\n\treturn util.ValidateStrongPassword(fl.Field().String())\n}\n"
  },
  {
    "path": "api/server/validator/rbac.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage validator\n\nimport (\n\t\"strconv\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n\t\"github.com/go-playground/validator/v10\"\n)\n\nfunc init() {\n\tregister(\n\t\t&objectValidator{pixiuValidator: newPixiuValidator(\"rbac_object\", \"对象类型不支持\")},\n\t\t&operationValidator{pixiuValidator: newPixiuValidator(\"rbac_operation\", \"操作不支持\")},\n\t\t&stringIDValidator{pixiuValidator: newPixiuValidator(\"rbac_sid\", \"不合法\")},\n\t)\n}\n\ntype objectValidator struct {\n\tpixiuValidator\n}\n\nfunc (ov *objectValidator) validate(fl validator.FieldLevel) bool {\n\tobj := fl.Field().Interface().(model.ObjectType)\n\t_, ok := model.ObjectTypeMap[obj]\n\treturn ok\n}\n\ntype operationValidator struct {\n\tpixiuValidator\n}\n\nfunc (ov *operationValidator) validate(fl validator.FieldLevel) bool {\n\top := fl.Field().Interface().(model.Operation)\n\t_, ok := model.OperationMap[op]\n\treturn ok\n}\n\ntype stringIDValidator struct {\n\tpixiuValidator\n}\n\nfunc (sv *stringIDValidator) validate(fl validator.FieldLevel) bool {\n\tsid := fl.Field().String()\n\tif sid == \"*\" {\n\t\treturn true\n\t}\n\t_, err := strconv.Atoi(sid)\n\treturn err == nil\n}\n"
  },
  {
    "path": "api/server/validator/validator.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage validator\n\nimport (\n\t\"github.com/gin-gonic/gin/binding\"\n\t\"github.com/go-playground/locales/zh\"\n\tut \"github.com/go-playground/universal-translator\"\n\t\"github.com/go-playground/validator/v10\"\n\tzt \"github.com/go-playground/validator/v10/translations/zh\"\n)\n\ntype customValidator interface {\n\tgetTag() string\n\ttranslateError(ut ut.Translator) error\n\ttranslate(ut ut.Translator, fe validator.FieldError) string\n\n\t// Should be implemented by the custom validator.\n\tvalidate(fl validator.FieldLevel) bool\n}\n\nvar tran ut.Translator\nvar customValidators []customValidator\n\n// register adds a new custom validator to the validator list\nfunc register(validators ...customValidator) {\n\tcustomValidators = append(customValidators, validators...)\n}\n\nfunc init() {\n\t_zh := zh.New() // default is Chinese\n\tuni := ut.New(_zh, _zh)\n\ttran, _ = uni.GetTranslator(\"zh\")\n\n\tif v, ok := binding.Validator.Engine().(*validator.Validate); ok {\n\t\t_ = zt.RegisterDefaultTranslations(v, tran)\n\n\t\tfor _, c := range customValidators {\n\t\t\t_ = v.RegisterValidation(c.getTag(), c.validate)\n\t\t\t_ = v.RegisterTranslation(c.getTag(), tran, c.translateError, c.translate)\n\t\t}\n\t}\n}\n\ntype pixiuValidator struct {\n\ttag string\n\terr string\n}\n\nfunc newPixiuValidator(tag, err string) pixiuValidator {\n\treturn pixiuValidator{\n\t\ttag: tag,\n\t\terr: err,\n\t}\n}\n\nfunc (c pixiuValidator) getTag() string {\n\treturn c.tag\n}\n\nfunc (c pixiuValidator) translateError(ut ut.Translator) error {\n\treturn ut.Add(c.tag, \"{0}\"+c.err, true)\n}\n\nfunc (c pixiuValidator) translate(ut ut.Translator, fe validator.FieldError) string {\n\tt, _ := ut.T(c.tag, fe.Field())\n\treturn t\n}\n"
  },
  {
    "path": "cmd/app/config/config.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage config\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/jobmanager\"\n\tlogutil \"github.com/caoyingjunz/pixiu/pkg/util/log\"\n)\n\ntype Mode string\n\nconst (\n\tDebugMode   Mode = \"debug\"\n\tReleaseMode Mode = \"release\"\n)\n\nfunc (m Mode) InDebug() bool {\n\treturn m == DebugMode\n}\n\ntype Config struct {\n\tDefault DefaultOptions          `yaml:\"default\"`\n\tMysql   MysqlOptions            `yaml:\"mysql\"`\n\tWorker  WorkerOptions           `yaml:\"worker\"`\n\tAudit   jobmanager.AuditOptions `yaml:\"audit\"`\n\tTLS     *TLS                    `yaml:\"tls\"`\n}\n\ntype DefaultOptions struct {\n\tMode   Mode   `yaml:\"mode\"`\n\tListen int    `yaml:\"listen\"`\n\tJWTKey string `yaml:\"jwt_key\"`\n\n\t// 自动创建指定模型的数据库表结构，不会更新已存在的数据库表\n\tAutoMigrate bool `yaml:\"auto_migrate\"`\n\n\tlogutil.LogOptions `yaml:\",inline\"`\n\t// 静态文件路径\n\tStaticFiles string `yaml:\"static_files\"`\n\n\t// 超级管理员初始化配置，留空则使用默认值\n\tAdminUser     string `yaml:\"admin_user\"`\n\tAdminPassword string `yaml:\"admin_password\"`\n}\n\nfunc (o DefaultOptions) Valid() error {\n\tif err := o.LogOptions.Valid(); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// MysqlOptions 数据库具体配置\ntype MysqlOptions struct {\n\tHost     string `yaml:\"host\"`\n\tUser     string `yaml:\"user\"`\n\tPassword string `yaml:\"password\"`\n\tPort     int    `yaml:\"port\"`\n\tName     string `yaml:\"name\"`\n}\n\nfunc (o MysqlOptions) Valid() error {\n\t// TODO\n\treturn nil\n}\n\ntype WorkerOptions struct {\n\tWorkDir string   `yaml:\"work_dir\"`\n\tEngines []Engine `yaml:\"engines\"`\n}\n\ntype Engine struct {\n\tImage       string   `yaml:\"image\"`\n\tOSSupported []string `yaml:\"os_supported\"`\n}\n\nfunc (w WorkerOptions) Valid() error {\n\t// TODO\n\treturn nil\n}\n\ntype TLS struct {\n\tCertFile string `yaml:\"cert_file\"`\n\tKeyFile  string `yaml:\"key_file\"`\n}\n\nfunc (t *TLS) Valid() error {\n\tif t != nil {\n\t\tif len(t.CertFile) == 0 {\n\t\t\treturn fmt.Errorf(\"listen on tls, no cert_file found\")\n\t\t}\n\n\t\tif len(t.KeyFile) == 0 {\n\t\t\treturn fmt.Errorf(\"listen on tls, no key_file found\")\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (c *Config) Valid() (err error) {\n\tif err = c.Default.Valid(); err != nil {\n\t\treturn\n\t}\n\tif err = c.Mysql.Valid(); err != nil {\n\t\treturn\n\t}\n\tif err = c.Worker.Valid(); err != nil {\n\t\treturn\n\t}\n\tif err = c.TLS.Valid(); err != nil {\n\t\treturn err\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "cmd/app/options/options.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage options\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/casbin/casbin/v2\"\n\t\"github.com/casbin/casbin/v2/model\"\n\tgormadapter \"github.com/casbin/gorm-adapter/v3\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/spf13/cobra\"\n\t\"gorm.io/driver/mysql\"\n\t\"gorm.io/gorm\"\n\t\"gorm.io/gorm/logger\"\n\t\"k8s.io/klog/v2\"\n\n\t\"github.com/caoyingjunz/pixiu/cmd/app/config\"\n\t\"github.com/caoyingjunz/pixiu/pkg/controller\"\n\tpixiudb \"github.com/caoyingjunz/pixiu/pkg/db\"\n\tpixiuModel \"github.com/caoyingjunz/pixiu/pkg/db/model\"\n\t\"github.com/caoyingjunz/pixiu/pkg/jobmanager\"\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n\tlogutil \"github.com/caoyingjunz/pixiu/pkg/util/log\"\n\tpixiuConfig \"github.com/caoyingjunz/pixiulib/config\"\n)\n\nconst (\n\tmaxIdleConns = 10\n\tmaxOpenConns = 100\n\n\tdefaultListen     = 8080\n\tdefaultTokenKey   = \"pixiu\"\n\tdefaultConfigFile = \"/etc/pixiu/config.yaml\"\n\tdefaultLogFormat  = logutil.LogFormatJson\n\tdefaultWorkDir    = \"/etc/pixiu\"\n\tdefaultStaticDir  = \"/static\"\n\n\tdefaultAdminUser     = \"admin\"\n\tdefaultAdminPassword = \"Pixiu123456!\"\n\n\tdefaultSlowSQLDuration = 1 * time.Second\n\n\trulesTableName = \"rules\"\n)\n\n// Options has all the params needed to run a pixiu\ntype Options struct {\n\t// The default values.\n\tComponentConfig config.Config\n\tHttpEngine      *gin.Engine\n\n\t// 数据库接口\n\tdb      *gorm.DB\n\tFactory pixiudb.ShareDaoFactory\n\t// 貔貅主控制接口\n\tController controller.PixiuInterface\n\n\t// ConfigFile is the location of the pixiu server's configuration file.\n\tConfigFile string\n\n\t// Authorization enforcement and policy management\n\tEnforcer *casbin.SyncedEnforcer\n\n\tJobManager *jobmanager.Manager\n}\n\nfunc NewOptions() (*Options, error) {\n\treturn &Options{\n\t\tHttpEngine: gin.Default(), // 初始化默认 api 路由\n\t\tConfigFile: defaultConfigFile,\n\t}, nil\n}\n\n// Complete completes all the required options\nfunc (o *Options) Complete() error {\n\t// 配置文件优先级: 默认配置，环境变量，命令行\n\tif len(o.ConfigFile) == 0 {\n\t\t// Try to read config file path from env.\n\t\tif cfgFile := os.Getenv(\"ConfigFile\"); cfgFile != \"\" {\n\t\t\to.ConfigFile = cfgFile\n\t\t} else {\n\t\t\to.ConfigFile = defaultConfigFile\n\t\t}\n\t}\n\n\tc := pixiuConfig.New()\n\tc.SetConfigFile(o.ConfigFile)\n\tc.SetConfigType(\"yaml\")\n\tif err := c.Binding(&o.ComponentConfig); err != nil {\n\t\treturn err\n\t}\n\n\t// TODO: move to config initialization?\n\tif o.ComponentConfig.Default.Listen == 0 {\n\t\to.ComponentConfig.Default.Listen = defaultListen\n\t}\n\tif len(o.ComponentConfig.Default.JWTKey) == 0 {\n\t\to.ComponentConfig.Default.JWTKey = defaultTokenKey\n\t}\n\tif o.ComponentConfig.Default.LogFormat == \"\" {\n\t\to.ComponentConfig.Default.LogFormat = defaultLogFormat\n\t}\n\tif o.ComponentConfig.Worker.WorkDir == \"\" {\n\t\to.ComponentConfig.Worker.WorkDir = defaultWorkDir\n\t}\n\tif len(o.ComponentConfig.Default.StaticFiles) == 0 {\n\t\to.ComponentConfig.Default.StaticFiles = defaultStaticDir\n\t}\n\tif o.ComponentConfig.Audit.Schedule == \"\" {\n\t\to.ComponentConfig.Audit.Schedule = jobmanager.DefaultSchedule\n\t}\n\tif o.ComponentConfig.Audit.DaysReserved == 0 {\n\t\to.ComponentConfig.Audit.DaysReserved = jobmanager.DefaultDaysReserved\n\t}\n\tif len(o.ComponentConfig.Default.AdminUser) == 0 {\n\t\to.ComponentConfig.Default.AdminUser = defaultAdminUser\n\t}\n\tif len(o.ComponentConfig.Default.AdminPassword) == 0 {\n\t\to.ComponentConfig.Default.AdminPassword = defaultAdminPassword\n\t}\n\n\tif err := o.ComponentConfig.Valid(); err != nil {\n\t\treturn err\n\t}\n\n\to.ComponentConfig.Default.LogOptions.Init()\n\n\t// 注册依赖组件\n\tif err := o.register(); err != nil {\n\t\treturn err\n\t}\n\n\to.Controller = controller.New(o.ComponentConfig, o.Factory, o.Enforcer)\n\n\tif err := o.bootstrapRootUser(); err != nil {\n\t\treturn err\n\t}\n\n\to.JobManager = jobmanager.NewManager(\n\t\t&o.ComponentConfig.Default.LogOptions,\n\t\tjobmanager.NewAuditsCleaner(o.ComponentConfig.Audit, o.Factory),\n\t\tjobmanager.NewClusterSyncer(o.Factory),\n\t)\n\treturn nil\n}\n\n// BindFlags binds the pixiu Configuration struct fields\nfunc (o *Options) BindFlags(cmd *cobra.Command) {\n\tcmd.Flags().StringVar(&o.ConfigFile, \"configfile\", defaultConfigFile, \"The location of the pixiu configuration file\")\n}\n\nfunc (o *Options) register() error {\n\t// 注册数据库\n\tif err := o.registerDatabase(); err != nil {\n\t\treturn err\n\t}\n\n\t// TODO: 注册其他依赖\n\tif err := o.registerEnforcer(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// This panics if o.db is nil.\nfunc (o *Options) registerEnforcer() error {\n\t// Casbin\n\ta, err := gormadapter.NewAdapterByDBUseTableName(o.db, \"\", rulesTableName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tm, err := model.NewModelFromString(pixiuModel.RBACModel)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif o.Enforcer, err = casbin.NewSyncedEnforcer(m, a); err != nil {\n\t\treturn err\n\t}\n\n\t// Add an super admin policy.\n\t_, err = o.Enforcer.AddPolicy(pixiuModel.AdminPolicy.Raw())\n\treturn err\n}\n\nfunc (o *Options) registerDatabase() error {\n\tsqlConfig := o.ComponentConfig.Mysql\n\tdsn := fmt.Sprintf(\"%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local\",\n\t\tsqlConfig.User,\n\t\tsqlConfig.Password,\n\t\tsqlConfig.Host,\n\t\tsqlConfig.Port,\n\t\tsqlConfig.Name)\n\n\topt := &gorm.Config{\n\t\tLogger: pixiudb.NewLogger(logger.Info, defaultSlowSQLDuration),\n\t}\n\tdb, err := gorm.Open(mysql.Open(dsn), opt)\n\tif err != nil {\n\t\treturn err\n\t}\n\to.db = db\n\n\t// 设置数据库连接池\n\tsqlDB, err := db.DB()\n\tif err != nil {\n\t\treturn err\n\t}\n\tsqlDB.SetMaxIdleConns(maxIdleConns)\n\tsqlDB.SetMaxOpenConns(maxOpenConns)\n\n\to.Factory, err = pixiudb.NewDaoFactory(db, o.ComponentConfig.Default.AutoMigrate)\n\treturn err\n}\n\n// Validate validates all the required options.\nfunc (o *Options) Validate() error {\n\t// TODO\n\treturn nil\n}\n\n// bootstrapRootUser 启动时自动初始化超级管理员账户\n// 若超管已存在则跳过，若不存在则使用配置文件中的用户名和密码创建\n// 密码经由 Controller.User().Create() 内部调用 util.EncryptUserPassword() 加密后入库\nfunc (o *Options) bootstrapRootUser() error {\n\tctx := context.Background()\n\troot, err := o.Factory.User().GetRoot(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to check root user: %v\", err)\n\t}\n\tif root != nil {\n\t\tklog.Info(\"root user already exists, skipping\")\n\t\treturn nil\n\t}\n\n\tadminUser := o.ComponentConfig.Default.AdminUser\n\tadminPassword := o.ComponentConfig.Default.AdminPassword\n\tklog.Infof(\"initializing root user: %s\", adminUser)\n\n\treturn o.Controller.User().Create(ctx, &types.CreateUserRequest{\n\t\tName:     adminUser,\n\t\tPassword: adminPassword,\n\t\tRole:     pixiuModel.RoleRoot,\n\t})\n}\n"
  },
  {
    "path": "cmd/app/server.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage app\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n\t\"k8s.io/klog/v2\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/router\"\n\t\"github.com/caoyingjunz/pixiu/cmd/app/options\"\n)\n\nfunc NewServerCommand(version string) *cobra.Command {\n\topts, err := options.NewOptions()\n\tif err != nil {\n\t\tklog.Fatalf(\"unable to initialize command options: %v\", err)\n\t}\n\n\tcmd := &cobra.Command{\n\t\tUse:  \"pixiu-server\",\n\t\tLong: \"The pixiu server controller is a daemon that embeds the core control loops.\",\n\t\tRun: func(cmd *cobra.Command, args []string) {\n\t\t\tif err = opts.Complete(); err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"%v\\n\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\tif err = opts.Validate(); err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"%v\\n\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\tif err = Run(opts); err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"%v\\n\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t},\n\t\tArgs: func(cmd *cobra.Command, args []string) error {\n\t\t\tfor _, arg := range args {\n\t\t\t\tif len(arg) > 0 {\n\t\t\t\t\treturn fmt.Errorf(\"%q does not take any arguments, got %q\", cmd.CommandPath(), args)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\n\t// 绑定命令行参数\n\topts.BindFlags(cmd)\n\n\tverCmd := &cobra.Command{\n\t\tUse:   \"version\",\n\t\tShort: \"Print the version\",\n\t\tLong:  \"Print version and exit.\",\n\t\tRun: func(cmd *cobra.Command, args []string) {\n\t\t\tfmt.Println(version)\n\t\t},\n\t}\n\tcmd.AddCommand(verCmd)\n\treturn cmd\n}\n\n// Run 优雅启动貔貅服务\nfunc Run(opt *options.Options) error {\n\tsrv := &http.Server{\n\t\tAddr:    fmt.Sprintf(\":%d\", opt.ComponentConfig.Default.Listen),\n\t\tHandler: opt.HttpEngine,\n\t}\n\n\t// TODO: 暂未设置优雅退出\n\t// 启动集群相关控制器\n\truners := []func(context.Context, int) error{opt.Controller.Plan().Run, opt.Controller.Cluster().Run}\n\tfor _, runner := range runers {\n\t\tif err := runner(context.TODO(), 5); err != nil {\n\t\t\tklog.Fatal(\"failed to start manager: \", err)\n\t\t}\n\t}\n\n\t// 安装 http 路由\n\trouter.InstallRouters(opt)\n\n\t// Initializing the server in a goroutine so that it won't block the graceful shutdown handling below\n\tgo func() {\n\t\tvar err error\n\t\tif opt.ComponentConfig.TLS != nil {\n\t\t\tklog.Info(\"starting pixiu server with TLS\")\n\t\t\terr = srv.ListenAndServeTLS(opt.ComponentConfig.TLS.CertFile, opt.ComponentConfig.TLS.KeyFile)\n\t\t} else {\n\t\t\tklog.Info(\"starting pixiu server with no TLS\")\n\t\t\terr = srv.ListenAndServe()\n\t\t}\n\t\tif err != nil && err != http.ErrServerClosed {\n\t\t\tklog.Fatal(\"failed to listen pixiu server: \", err)\n\t\t}\n\t}()\n\n\tklog.Info(\"starting job manager\")\n\topt.JobManager.Run()\n\n\t// Wait for interrupt signal to gracefully shut down the server with a timeout of 5 seconds.\n\tquit := make(chan os.Signal)\n\tsignal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)\n\t<-quit\n\tklog.Info(\"shutting pixiu server down ...\")\n\n\t// The context is used to inform the server it has 5 seconds to finish the request\n\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\tdefer cancel()\n\n\tif err := srv.Shutdown(ctx); err != nil {\n\t\tklog.Fatalf(\"pixiu server forced to shutdown: %v\", err)\n\t}\n\n\tklog.Info(\"shutting job manager down ...\")\n\topt.JobManager.Stop()\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/pixiuserver.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"io\"\n\t\"math/rand\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"k8s.io/klog/v2\"\n\n\t\"github.com/caoyingjunz/pixiu/cmd/app\"\n)\n\nvar version string\n\n// @title           Pixiu API Documentation\n// @version         1.0\n// @termsOfService  https://github.com/caoyingjunz/pixiu\n\n// @contact.name   API Support\n// @contact.url    https://github.com/caoyingjunz/pixiu\n// @contact.email  support@pixiu.io\n\n// @license.name  Apache 2.0\n// @license.url   http://www.apache.org/licenses/LICENSE-2.0.html\n// @schemes       http https\n// @host          localhost:8090\n\n// @securityDefinitions.apikey  Bearer\n// @in                          header\n// @name                        Authorization\n// @description                 Use the Pixiu APIs to your cloud\n// @description                 Type \"Bearer\" followed by a space and JWT token\nfunc main() {\n\tklog.InitFlags(nil)\n\trand.Seed(time.Now().UnixNano())\n\n\tgin.SetMode(gin.ReleaseMode)\n\tgin.DefaultWriter = io.Discard\n\n\tcmd := app.NewServerCommand(version)\n\tif err := cmd.Execute(); err != nil {\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "config.yaml",
    "content": "default:\n  # 运行模式，可选 debug 和 release\n  mode: debug\n  # 服务监听端口\n  listen: 8091\n  # jwt 签名的 key\n  jwt_key: pixiu\n  # 自动创建指定模型的数据库表结构，不会更新已存在的数据库表\n  auto_migrate: true\n  # 日志的格式，可选 text 和 json\n  log_format: json\n  log_level: info\n  # 静态文件路径\n  static_files: /static\n\n  # 超级管理初始化用户名和密码，默认为 admin/Pixiu123456!\n  admin_user: pixiu\n  admin_password: 123456\n\n# 配置前端请求地址\ndashboard:\n  url: http://localhost:8080\n\n#tls:\n#  cert_file: test.pem\n#  key_file: test.key\n\n# 数据库地址信息\nmysql:\n  host: peng\n  user: root\n  password: Pixiu868686\n  port: 3306\n  name: pixiu\n\nworker:\n  work_dir: /tmp/pixiu\n  engines:\n    - image: ccr.ccs.tencentyun.com/pixiucloud/kubez-ansible:v2.0.1\n      os_supported:\n        - centos7\n        - debian10\n        - ubuntu18.04\n    - image: ccr.ccs.tencentyun.com/pixiucloud/kubez-ansible:v3.0.1\n      os_supported:\n        - debian11\n        - ubuntu20.04\n        - ubuntu22.04\n        - rocky8.5\n        - rocky9.2\n        - rocky9.3\n        - openEuler22.03\n"
  },
  {
    "path": "deploy/README.md",
    "content": "# Pixiu\n\nPixiu是貔貅的服务端, 貔貅是一个广泛使用的、具有web-ui的、实现多集群管理的、非常好用的kubernetes(k8s)容器管理平台.\n\n本 chart 使用 [Helm](https://helm.sh) 包管理器实现在[Kubernetes](https://kubernetes.io) (k8s)集群上部署pixiu\n\n## 安装Chart\n\n注意: 暂时只支持helm3\n\n- 通过源码安装(本示例默认源码安装)\n\n  ```\n  helm install [RELEASE_NAME] pixiu/\n  ```\n\n- 修改默认参数后, 打包上传到repos后, 例如https://artifacthub.io/, 再行安装\n\n  ```\n  helm repo add pixiu https://xxxx.xxx.xx/pixiu\n  helm repo update\n  helm install [RELEASE_NAME] ./pixiu\n  ```\n\n## 卸载Chart\n\n通过以下命令卸载:\n\n```console\nhelm delete pixiu\n```\n\n## 更新Chart\n\n```\nhelm upgrade [RELEASE_NAME] [CHART] --install\n```\n\n## 配置\n\n- 详情请参考: [values.yaml](./values.yaml)\n\n- 自定义参数:\n\n  ```\n  helm install pixiu pixiu/ --set=service.externalPort=8080,resources.limits.cpu=300m\n  ```\n"
  },
  {
    "path": "deploy/pixiu/.helmignore",
    "content": "# Patterns to ignore when building packages.\n# This supports shell glob matching, relative path matching, and\n# negation (prefixed with !). Only one pattern per line.\n.DS_Store\n# Common VCS dirs\n.git/\n.gitignore\n.bzr/\n.bzrignore\n.hg/\n.hgignore\n.svn/\n# Common backup files\n*.swp\n*.bak\n*.tmp\n*.orig\n*~\n# Various IDEs\n.project\n.idea/\n*.tmproj\n.vscode/\n"
  },
  {
    "path": "deploy/pixiu/Chart.yaml",
    "content": "apiVersion: v2\nname: pixiu\ndescription: A Helm chart for Kubernetes\n\n# A chart can be either an 'application' or a 'library' chart.\n#\n# Application charts are a collection of templates that can be packaged into versioned archives\n# to be deployed.\n#\n# Library charts provide useful utilities or functions for the chart developer. They're included as\n# a dependency of application charts to inject those utilities and functions into the rendering\n# pipeline. Library charts do not define any templates and therefore cannot be deployed.\ntype: application\n\n# This is the chart version. This version number should be incremented each time you make changes\n# to the chart and its templates, including the app version.\n# Versions are expected to follow Semantic Versioning (https://semver.org/)\nversion: 0.1.0\n\n# This is the version number of the application being deployed. This version number should be\n# incremented each time you make changes to the application. Versions are not expected to\n# follow Semantic Versioning. They should reflect the version the application is using.\n# It is recommended to use it with quotes.\nappVersion: \"1.16.0\"\n"
  },
  {
    "path": "deploy/pixiu/templates/NOTES.txt",
    "content": "1. Get the application URL by running these commands:\n{{- if .Values.ingress.enabled }}\n{{- range $host := .Values.ingress.hosts }}\n  {{- range .paths }}\n  http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}\n  {{- end }}\n{{- end }}\n{{- else if contains \"NodePort\" .Values.service.type }}\n  export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath=\"{.spec.ports[0].nodePort}\" services {{ include \"pixiu.fullname\" . }})\n  export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath=\"{.items[0].status.addresses[0].address}\")\n  echo http://$NODE_IP:$NODE_PORT\n{{- else if contains \"LoadBalancer\" .Values.service.type }}\n     NOTE: It may take a few minutes for the LoadBalancer IP to be available.\n           You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include \"pixiu.fullname\" . }}'\n  export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include \"pixiu.fullname\" . }} --template \"{{\"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}\"}}\")\n  echo http://$SERVICE_IP:{{ .Values.service.port }}\n{{- else if contains \"ClusterIP\" .Values.service.type }}\n  export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l \"app.kubernetes.io/name={{ include \"pixiu.name\" . }},app.kubernetes.io/instance={{ .Release.Name }}\" -o jsonpath=\"{.items[0].metadata.name}\")\n  export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath=\"{.spec.containers[0].ports[0].containerPort}\")\n  echo \"Visit http://127.0.0.1:8080 to use your application\"\n  kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT\n{{- end }}\n"
  },
  {
    "path": "deploy/pixiu/templates/_helpers.tpl",
    "content": "{{/*\nExpand the name of the chart.\n*/}}\n{{- define \"pixiu.name\" -}}\n{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n\n{{/*\nCreate a default fully qualified app name.\nWe truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).\nIf release name contains chart name it will be used as a full name.\n*/}}\n{{- define \"pixiu.fullname\" -}}\n{{- if .Values.fullnameOverride }}\n{{- .Values.fullnameOverride | trunc 63 | trimSuffix \"-\" }}\n{{- else }}\n{{- $name := default .Chart.Name .Values.nameOverride }}\n{{- if contains $name .Release.Name }}\n{{- .Release.Name | trunc 63 | trimSuffix \"-\" }}\n{{- else }}\n{{- printf \"%s-%s\" .Release.Name $name | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n{{- end }}\n{{- end }}\n\n{{/*\nCreate chart name and version as used by the chart label.\n*/}}\n{{- define \"pixiu.chart\" -}}\n{{- printf \"%s-%s\" .Chart.Name .Chart.Version | replace \"+\" \"_\" | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n\n{{/*\nCommon labels\n*/}}\n{{- define \"pixiu.labels\" -}}\nhelm.sh/chart: {{ include \"pixiu.chart\" . }}\n{{ include \"pixiu.selectorLabels\" . }}\n{{- if .Chart.AppVersion }}\napp.kubernetes.io/version: {{ .Chart.AppVersion | quote }}\n{{- end }}\napp.kubernetes.io/managed-by: {{ .Release.Service }}\n{{- end }}\n\n{{/*\nSelector labels\n*/}}\n{{- define \"pixiu.selectorLabels\" -}}\napp.kubernetes.io/name: {{ include \"pixiu.name\" . }}\napp.kubernetes.io/instance: {{ .Release.Name }}\n{{- end }}\n\n{{/*\nCreate the name of the service account to use\n*/}}\n{{- define \"pixiu.serviceAccountName\" -}}\n{{- if .Values.serviceAccount.create }}\n{{- default (include \"pixiu.fullname\" .) .Values.serviceAccount.name }}\n{{- else }}\n{{- default \"default\" .Values.serviceAccount.name }}\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "deploy/pixiu/templates/configmap.yaml",
    "content": "# Copyright 2021 The Pixiu Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  labels:\n    {{- include \"pixiu.labels\" . | nindent 4 }}\n      {{- if .Values.commonLabels }}\n      {{- include \"common.tplvalues.render\" ( dict \"value\" .Values.commonLabels \"context\" $ ) | nindent 4 }}\n      {{- end }}\n  annotations:\n    {{- if .Values.commonAnnotations }}\n      {{- include \"common.tplvalues.render\" ( dict \"value\" .Values.commonAnnotations \"context\" $ ) | nindent 4 }}\n      {{- end }}\n  name: {{ template \"pixiu.fullname\" . }}-config\n  namespace: {{ .Release.Namespace }}\ndata:\n  config.yaml: |\n    default:\n      listen: {{ .Values.default.listen }}\n      log_type: {{ .Values.default.log_type  }}\n      log_level: {{ .Values.default.log_level  }}\n      log_dir: {{ .Values.default.log_dir  }}\n      jwt_key: {{ .Values.default.jwt_key  }}\n    mysql:\n      host: {{ .Values.mysql.host  }}\n      user: {{ .Values.mysql.user  }}\n      password: {{ .Values.mysql.password  }}\n      port: {{ .Values.mysql.port  }}\n      name: {{ .Values.mysql.name  }}\n    cicd:\n      driver: {{ .Values.cicd.driver  }}\n      jenkins:\n        host: {{ .Values.cicd.host  }}\n        user: {{ .Values.cicd.user  }}\n        password: {{ .Values.cicd.password  }}    \n"
  },
  {
    "path": "deploy/pixiu/templates/deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ include \"pixiu.fullname\" . }}\n  labels:\n    {{- include \"pixiu.labels\" . | nindent 4 }}\nspec:\n  {{- if not .Values.autoscaling.enabled }}\n  replicas: {{ .Values.replicaCount }}\n  {{- end }}\n  selector:\n    matchLabels:\n      {{- include \"pixiu.selectorLabels\" . | nindent 6 }}\n  template:\n    metadata:\n      {{- with .Values.podAnnotations }}\n      annotations:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n      labels:\n        {{- include \"pixiu.selectorLabels\" . | nindent 8 }}\n    spec:\n      {{- with .Values.imagePullSecrets }}\n      imagePullSecrets:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n      serviceAccountName: {{ include \"pixiu.serviceAccountName\" . }}\n      securityContext:\n        {{- toYaml .Values.podSecurityContext | nindent 8 }}\n      containers:\n        - name: {{ .Chart.Name }}\n          securityContext:\n            {{- toYaml .Values.securityContext | nindent 12 }}\n          image: \"{{ .Values.image.pixiu }}\"\n          imagePullPolicy: {{ .Values.image.pullPolicy }}\n          volumeMounts:\n          - mountPath: /etc/pixiu\n            name: config-volume\n            readOnly: true\n          resources:\n            {{- toYaml .Values.resources | nindent 12 }}\n        - name: dashboard\n          securityContext:\n            {{- toYaml .Values.securityContext | nindent 12 }}\n          image: \"{{ .Values.image.dashboard }}\"\n          imagePullPolicy: {{ .Values.image.pullPolicy }}\n          ports:\n            - name: http\n              containerPort: 80\n              protocol: TCP\n          resources:\n            {{- toYaml .Values.resources | nindent 12 }}\n      {{- with .Values.nodeSelector }}\n      nodeSelector:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n      {{- with .Values.affinity }}\n      affinity:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n      {{- with .Values.tolerations }}\n      tolerations:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n      volumes:\n        - configMap:\n            defaultMode: 420\n            name: pixiu-config\n          name: config-volume"
  },
  {
    "path": "deploy/pixiu/templates/hpa.yaml",
    "content": "{{- if .Values.autoscaling.enabled }}\napiVersion: autoscaling/v2beta1\nkind: HorizontalPodAutoscaler\nmetadata:\n  name: {{ include \"pixiu.fullname\" . }}\n  labels:\n    {{- include \"pixiu.labels\" . | nindent 4 }}\nspec:\n  scaleTargetRef:\n    apiVersion: apps/v1\n    kind: Deployment\n    name: {{ include \"pixiu.fullname\" . }}\n  minReplicas: {{ .Values.autoscaling.minReplicas }}\n  maxReplicas: {{ .Values.autoscaling.maxReplicas }}\n  metrics:\n    {{- if .Values.autoscaling.targetCPUUtilizationPercentage }}\n    - type: Resource\n      resource:\n        name: cpu\n        targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}\n    {{- end }}\n    {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}\n    - type: Resource\n      resource:\n        name: memory\n        targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}\n    {{- end }}\n{{- end }}\n"
  },
  {
    "path": "deploy/pixiu/templates/ingress.yaml",
    "content": "{{- if .Values.ingress.enabled -}}\n{{- $fullName := include \"pixiu.fullname\" . -}}\n{{- $svcPort := .Values.service.port -}}\n{{- if semverCompare \">=1.14-0\" .Capabilities.KubeVersion.GitVersion -}}\napiVersion: networking.k8s.io/v1beta1\n{{- else -}}\napiVersion: extensions/v1beta1\n{{- end }}\nkind: Ingress\nmetadata:\n  name: {{ $fullName }}\n  labels:\n    {{- include \"pixiu.labels\" . | nindent 4 }}\n  {{- with .Values.ingress.annotations }}\n  annotations:\n    {{- toYaml . | nindent 4 }}\n  {{- end }}\nspec:\n  {{- if .Values.ingress.tls }}\n  tls:\n    {{- range .Values.ingress.tls }}\n    - hosts:\n        {{- range .hosts }}\n        - {{ . | quote }}\n        {{- end }}\n      secretName: {{ .secretName }}\n    {{- end }}\n  {{- end }}\n  rules:\n    {{- range .Values.ingress.hosts }}\n    - host: {{ .host | quote }}\n      http:\n        paths:\n          {{- range .paths }}\n          - path: {{ .path }}\n            backend:\n              serviceName: {{ $fullName }}\n              servicePort: {{ $svcPort }}\n          {{- end }}\n    {{- end }}\n  {{- end }}\n"
  },
  {
    "path": "deploy/pixiu/templates/service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: {{ include \"pixiu.fullname\" . }}\n  labels:\n    {{- include \"pixiu.labels\" . | nindent 4 }}\nspec:\n  type: {{ .Values.service.type }}\n  ports:\n    - port: {{ .Values.service.port }}\n      targetPort: http\n      protocol: TCP\n      name: http\n  selector:\n    {{- include \"pixiu.selectorLabels\" . | nindent 4 }}\n"
  },
  {
    "path": "deploy/pixiu/templates/serviceaccount.yaml",
    "content": "{{- if .Values.serviceAccount.create -}}\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: {{ include \"pixiu.serviceAccountName\" . }}\n  labels:\n    {{- include \"pixiu.labels\" . | nindent 4 }}\n  {{- with .Values.serviceAccount.annotations }}\n  annotations:\n    {{- toYaml . | nindent 4 }}\n  {{- end }}\n{{- end }}\n"
  },
  {
    "path": "deploy/pixiu/values.yaml",
    "content": "# Default values for pixiu.\n# This is a YAML-formatted file.\n# Declare variables to be passed into your templates.\n\ndefault:\n  # 服务监听端口\n  listen: 8090\n  # 日志类型，支持 stdout, stderr 和 file, 默认为 stdout\n  log_type: stdout\n  # 日志级别，支持 INFO, WARN 和 ERROR, 默认为 INFO\n  log_level: INFO\n  # 日志路径，在日志类型为 file 的时候生效\n  log_dir: /var/log/pixiu\n  # jwt 签名的 key\n  jwt_key: pixiu\n\n# 数据库地址信息\nmysql:\n  host: pixiu\n  user: root\n  password: GoPixiu868686\n  port: 3306\n  name: pixiu\n\ncicd:\n  enable: false\n  driver: jenkins\n  jenkins:\n    host: http://127.0.0.1:9090\n    user: adminops\n    password: adminops@321\n\nreplicaCount: 1\n\nimage:\n  pixiu: jacky06/pixiu:v0.1 \n  dashboard: jacky06/pixiu-dashboard\n  pullPolicy: IfNotPresent\n\nimagePullSecrets: []\nnameOverride: \"\"\nfullnameOverride: \"\"\n\nserviceAccount:\n  # Specifies whether a service account should be created\n  create: false \n  # Annotations to add to the service account\n  annotations: {}\n  # The name of the service account to use.\n  # If not set and create is true, a name is generated using the fullname template\n  name: \"\"\n\npodAnnotations: {}\n\npodSecurityContext: {}\n  # fsGroup: 2000\n\nsecurityContext: {}\n  # capabilities:\n  #   drop:\n  #   - ALL\n  # readOnlyRootFilesystem: true\n  # runAsNonRoot: true\n  # runAsUser: 1000\n\nservice:\n  type: ClusterIP\n  port: 80\n\ningress:\n  enabled: false\n  annotations: {}\n    # kubernetes.io/ingress.class: nginx\n    # kubernetes.io/tls-acme: \"true\"\n  hosts:\n    - host: chart-example.local\n      paths:\n      - path: /\n        backend:\n          serviceName: chart-example.local\n          servicePort: 80\n  tls: []\n  #  - secretName: chart-example-tls\n  #    hosts:\n  #      - chart-example.local\n\nresources: {}\n  # We usually recommend not to specify default resources and to leave this as a conscious\n  # choice for the user. This also increases chances charts run on environments with little\n  # resources, such as Minikube. If you do want to specify resources, uncomment the following\n  # lines, adjust them as necessary, and remove the curly braces after 'resources:'.\n  # limits:\n  #   cpu: 100m\n  #   memory: 128Mi\n  # requests:\n  #   cpu: 100m\n  #   memory: 128Mi\n\nautoscaling:\n  enabled: false\n  minReplicas: 1\n  maxReplicas: 100\n  targetCPUUtilizationPercentage: 80\n  # targetMemoryUtilizationPercentage: 80\n\nnodeSelector: {}\n\ntolerations: []\n\naffinity: {}\n"
  },
  {
    "path": "docker/Dockerfile",
    "content": "FROM golang:1.17-alpine as builder\nWORKDIR /app\nARG VERSION\nENV GOPROXY=https://goproxy.cn\nCOPY ../go.mod ./\nCOPY ../go.sum ./\n#RUN go mod download\nCOPY .. .\nRUN CGO_ENABLED=0 go build -ldflags \"-s -w -X 'main.version=${VERSION}'\" -o pixiu ./cmd\n\nFROM node:16.18.0-alpine as dashboard-builder\nWORKDIR /build\nRUN apk add git\nRUN git clone https://github.com/pixiu-io/dashboard.git\nRUN cd dashboard && npm install && npm run build\n\nFROM busybox as runner\nLABEL MAINTAINER=\"PIXIU\"\n\nCOPY --from=builder /app/pixiu /app\nCOPY --from=dashboard-builder /build/dashboard/dist /static\nCOPY docker/start.sh /usr/local/bin/pixiu_start\n\nCMD [\"pixiu_start\"]\n"
  },
  {
    "path": "docker/Dockerfile-toolbox",
    "content": "# 使用 Ubuntu 作为基础镜像\nFROM ubuntu:22.04\n\nARG K8S_VERSION\nARG HELM_VERSION\n\n# 安装vim\nRUN apt-get update && apt-get install -y vim\n\n# 安装必要的软件包\nRUN DEBIAN_FRONTEND=noninteractive apt-get install -y curl bash-completion\n\n# 安装 kubectl\nRUN ARCH=$(uname -m|sed 's|x86_64|amd64|'|sed 's|aarch64|arm64|') && \\\n    curl -LO \"https://dl.k8s.io/release/${K8S_VERSION}/bin/linux/${ARCH}/kubectl\" && \\\n    install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl && \\\n    rm -rf kubectl\n\n# 安装 helm\nRUN ARCH=$(uname -m|sed 's|x86_64|amd64|'|sed 's|aarch64|arm64|') && \\\n    curl -LO \"https://get.helm.sh/helm-${HELM_VERSION}-linux-${ARCH}.tar.gz\" && \\\n    tar -zxvf helm-${HELM_VERSION}-linux-${ARCH}.tar.gz && \\\n    rm -rf helm-${HELM_VERSION}-linux-${ARCH}.tar.gz && \\\n    mv linux-${ARCH}/helm /usr/local/bin/helm && \\\n    rm -rf linux-${ARCH}\n\n# 配置 bash 补全和自定义的 PS1 提示符\nRUN echo 'source /etc/profile.d/bash_completion.sh' >> /root/.bashrc \\\n    && echo 'source <(kubectl completion bash)' >> /root/.bashrc \\\n    && echo 'source <(helm completion bash)' >> /root/.bashrc \\\n    && echo \"PS1='[\\[\\033[0;34m\\]\\u\\[\\033[0;37m\\]@\\[\\033[0;35m\\]\\h\\[\\033[0;33m\\] \\w\\[\\033[0;37m\\]]\\[\\033[0;31m\\]\\$\\[\\033[00m\\] '\" >> /root/.bashrc\n\n# 设置工作目录\nWORKDIR /root\n\n# 设置环境变量等（可选）\nENV PATH=\"/usr/local/bin:${PATH}\"\n\n# 启动 bash 终端\nCMD [\"sleep\", \"infinity\"]\n"
  },
  {
    "path": "docker/start.sh",
    "content": "#!/bin/sh\nset -o errexit\nset -o xtrace\n\nif [[ ! -d \"/etc/pixiu\" ]]; then\n    mkdir -p /etc/pixiu\nfi\n\nif [[ -e \"/static/config.json\" ]]; then\n    rm -rf /static/config.json\nfi\n\nURL=$(grep \"url\" /etc/pixiu/config.yaml | awk -F'url:' '{print $2}' | tr -d '[:space:]')\nif [[  -z \"$URL\" ]]; then\n    URL=\"http://localhost:8080\"\nfi\ncat >/static/config.json << EOF\n{\n    \"url\": \"${URL}\"\n}\nEOF\n\n/app\n"
  },
  {
    "path": "docs/OWNERS",
    "content": "reviewers:\n  - caoyingjun\n"
  },
  {
    "path": "docs/README.md",
    "content": "# Pixiu\n\n### 初始账号\n```shell\npixiu/Pixiu123456!\n```\n\n### 主进程启动\n```shell\ngo run cmd/pixiuserver.go --configfile ./config.yaml\n```\n\n### 访问 APIs doc\n```shell\ncurl http://127.0.0.1:8090/api-ref/index.html\n```\n"
  },
  {
    "path": "docs/apis.md",
    "content": "# Pixiu API Specification\n\n- Pixiu API 是对 Pixiu 资源的调用接口，通常接口有 list, show details, create, update 和 delete, 以及对 resource 的具体动作, 如: 启动 cicd 资源的 job.\n\n- API 应当遵循（以 cicd 资源的 job 为例):\n\n## List\n- method: GET\n- URL: /cicd/jobs\n- 正常返回码: 200\n- 异常返回码: badRequest(400), unauthorized(401), forbidden(403)\n- 请求参数:\n- 响应结果:\n\n## Show Details\n- method: GET\n- URL: /cicd/jobs/{job_id}\n- 正常返回码: 200\n- 异常返回码: badRequest(400), unauthorized(401), forbidden(403), itemNotFound(404)\n- 请求参数:\n- 响应结果:\n\n## Create\n- method: POST\n- URL: /cicd/jobs\n- 正常返回码: 200\n- 异常返回码: badRequest(400), unauthorized(401), forbidden(403)\n- 请求参数:\n- 响应结果:\n\n## Update\n- method: PUT\n- URL: /cicd/jobs/{image_id}\n- 正常返回码: 200\n- 异常返回码: badRequest(400), unauthorized(401), forbidden(403), itemNotFound(404)\n- 请求参数:\n- 响应结果:\n\n## Delete\n- method: DELETE\n- URL: /cicd/jobs/{job_id}\n- 正常返回码: 200\n- 异常返回码: badRequest(400), unauthorized(401), forbidden(403), itemNotFound(404)\n- 请求参数:\n- 响应结果:\n\n## Run\n- method: POST\n- URL: /cicd/jobs/{job_id}/run\n- 正常返回码: 200\n- 异常返回码: badRequest(400), unauthorized(401), forbidden(403), itemNotFound(404), NotImplemented(501)\n- 请求参数:\n- 响应结果:\n"
  },
  {
    "path": "docs/sql.md",
    "content": "# 创建 `pixiu` 数据库\n```sql\nCREATE DATABASE pixiu;\n```\n\n## 创建 `clusters` 表\n```sql\nCREATE TABLE `clusters` (\n    id int primary key NOT NULL AUTO_INCREMENT COMMENT '主键' ,\n    gmt_create datetime COMMENT '创建时间',\n    gmt_modified datetime COMMENT '修改时间',\n    resource_version int COMMENT '版本号',\n    plan_id int COMMENT 'plan表的id号',\n    name varchar(128) COMMENT 'k8s 集群名称',\n    alias_name varchar(128) COMMENT 'k8s 集群中文名称',\n    cluster_type int COMMENT 'Kubernetes 集群的类型',\n    status tinyint(4) COMMENT '集群状态',\n    kubernetes_version varchar(64) COMMENT 'k8s 集群版本',\n    nodes text COMMENT '集群节点详情',\n    protected bool COMMENT '集群删除保护',\n    kube_config text COMMENT 'kubeConfig 文件内容',\n    description text COMMENT 'k8s 集群描述信息',\n    extension text COMMENT '扩展预留',\n    KEY `idx_name` (`name`),\n    UNIQUE KEY `name` (`name`)\n) ENGINE=InnoDB CHARSET=utf8 AUTO_INCREMENT=20220801;\n```\n\n## 创建 `users` 表\n```sql\nCREATE TABLE `users` (\n    id int primary key NOT NULL AUTO_INCREMENT COMMENT '主键' ,\n    gmt_create datetime COMMENT '创建时间',\n    gmt_modified datetime COMMENT '修改时间',\n    resource_version int COMMENT '版本号',\n    name varchar(128) COMMENT '用户名',\n    password varchar(256) COMMENT '用户密码',\n    email varchar(128) COMMENT '邮箱',\n    status tinyint COMMENT '状态: 1启用,2未启用',\n    role varchar(128) COMMENT '角色',\n    description text COMMENT '描述',\n    extension text COMMENT '扩展字段',\n    KEY `idx_name` (`name`),\n    UNIQUE KEY `name` (`name`)\n) ENGINE=InnoDB CHARSET=utf8 AUTO_INCREMENT=21220801;\n```\n\n### 创建 `pixiu` 用户\n```sql\n# 用户 pixiu 的初始密码为 Pixiu123456!\ninsert into users(name, password) values ('pixiu', '$2a$10$SamcBWw.aPMDv5QadDr7f.2rDBWiwfTwnbh5sEEhaTkWfVwO96PfW');\n```\n\n## 创建 `tenants` 表\n```sql\nCREATE TABLE `tenants` (\n    id int primary key NOT NULL AUTO_INCREMENT COMMENT '主键' ,\n    gmt_create datetime COMMENT '创建时间',\n    gmt_modified datetime COMMENT '修改时间',\n    resource_version int COMMENT '版本号',\n    name varchar(128) COMMENT '租户名',\n    description text COMMENT '描述',\n    extension text COMMENT '扩展字段',\n    KEY `idx_name` (`name`),\n    UNIQUE KEY `name` (`name`)\n) ENGINE=InnoDB CHARSET=utf8 AUTO_INCREMENT=22220801;\n```\n\n## 创建 `roles` 表\n```sql\nCREATE TABLE `roles` (\n    id int primary key NOT NULL AUTO_INCREMENT COMMENT '主键' ,\n    gmt_create datetime COMMENT '创建时间',\n    gmt_modified datetime COMMENT '修改时间',\n    resource_version int COMMENT '版本号',\n    name varchar(128) COMMENT '用户名',\n    status tinyint(4) COMMENT '状态',\n    sequence bigint(20) NOT NULL,\n    parent_id bigint(20) NOT NULL,\n    memo varchar(128) DEFAULT NULL,\n    KEY `idx_name` (`name`),\n    UNIQUE KEY `name` (`name`)\n) ENGINE=InnoDB CHARSET=utf8 AUTO_INCREMENT=23220801;\n```\n\n## 创建 `clouds` 表\n```sql\nCREATE TABLE `clouds` (\n    id int primary key NOT NULL AUTO_INCREMENT COMMENT '主键' ,\n    gmt_create datetime COMMENT '创建时间',\n    gmt_modified datetime COMMENT '修改时间',\n    resource_version int COMMENT '版本号',\n    name varchar(128) COMMENT '用户名',\n    alias_name varchar(128) COMMENT '别名',\n    status int COMMENT '集群状态',\n    cloud_type int COMMENT '集群类型',\n    kube_version varchar(128) COMMENT 'k8s 集群版本',\n    node_number int COMMENT '集群节点数量',\n    resources varchar(128) COMMENT '资源数量',\n    description text COMMENT '描述',\n    extension text COMMENT '扩展字段',\n    KEY `idx_name` (`name`),\n    UNIQUE KEY `name` (`name`)\n) ENGINE=InnoDB CHARSET=utf8 AUTO_INCREMENT=22220801;\n```\n\n## 创建 `kube_configs` 表\n```sql\nCREATE TABLE `kube_configs` (\n    id int primary key NOT NULL AUTO_INCREMENT COMMENT '主键' ,\n    gmt_create datetime COMMENT '创建时间',\n    gmt_modified datetime COMMENT '修改时间',\n    resource_version int COMMENT '版本号',\n    service_account varchar(128) COMMENT 'k8s service account',\n    cloud_name varchar(128) COMMENT '集群名',\n    cloud_id int COMMENT '所属 cloud id',\n    cluster_role varchar(128) COMMENT 'k8s cluster role',\n    config text COMMENT 'k8s kube_config',\n    expiration_timestamp text COMMENT '过期时间',\n    KEY `idx_cloud_name` (`cloud_name`),\n    UNIQUE KEY `service_account` (`service_account`)\n) ENGINE=InnoDB CHARSET=utf8 AUTO_INCREMENT=22220801;\n```\n\n## 创建 `nodes` 表\n```sql\nCREATE TABLE `nodes` (\n    id int primary key NOT NULL AUTO_INCREMENT COMMENT '主键' ,\n    gmt_create datetime COMMENT '创建时间',\n    gmt_modified datetime COMMENT '修改时间',\n    resource_version int COMMENT '版本号',\n    cloud_id int COMMENT 'cloud ID',\n    role varchar(128) COMMENT '节点类型',\n    host_name varchar(128) COMMENT '节点名称',\n    address varchar(128) COMMENT '节点 ip 地址',\n    user varchar(128) COMMENT '用户名',\n    password varchar(128) COMMENT '节点密码',\n    KEY `idx_cloud` (`cloud_id`)\n) ENGINE=InnoDB CHARSET=utf8 AUTO_INCREMENT=24220801;\n```\n\n## 创建 `events` 表\n```sql\nCREATE TABLE `events` (\n    id int primary key NOT NULL AUTO_INCREMENT COMMENT '主键' ,\n    gmt_create datetime COMMENT '创建时间',\n    gmt_modified datetime COMMENT '修改时间',\n    resource_version int COMMENT '版本号',\n    user varchar(128) COMMENT '用户名称',\n    client_ip varchar(128) COMMENT '登陆地址',\n    operator varchar(128) COMMENT '操作类型',\n    object varchar(128) COMMENT '操作对象',\n    message varchar(128) COMMENT '消息'\n) ENGINE=InnoDB CHARSET=utf8 AUTO_INCREMENT=26220801;\n```\n\n## 创建 `audit`\n```sql\nCREATE TABLE `audits` (\n  `id` int primary key NOT NULL AUTO_INCREMENT COMMENT '主键' ,\n  `gmt_create` datetime COMMENT '创建时间',\n  `gmt_modified` datetime COMMENT '修改时间',\n  `resource_version` int COMMENT '版本号',\n  `operator` varchar(255) COLLATE utf8mb4_bin NOT NULL COMMENT '操作人',\n  `action` varchar(255) COLLATE utf8mb4_bin NOT NULL COMMENT '动作',\n  `ip` varchar(128) COLLATE utf8mb4_bin NOT NULL COMMENT '来源ip',\n  `status` tinyint(4) COLLATE utf8mb4_bin NOT NULL COMMENT '执行是否成功：0-失败，1-成功',\n  `path` varchar(255) COLLATE utf8mb4_bin NOT NULL COMMENT '详细内容',\n  `resource_type` varchar(128) COLLATE utf8mb4_bin NOT NULL COMMENT '操作的资源类型'\n) ENGINE=InnoDB AUTO_INCREMENT=3355 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin\n```"
  },
  {
    "path": "go.mod",
    "content": "module github.com/caoyingjunz/pixiu\n\ngo 1.17\n\nrequire (\n\tgithub.com/bmatcuk/doublestar/v4 v4.6.1\n\tgithub.com/caoyingjunz/pixiulib v0.0.0-20220819163605-c3c10ec3ed3c\n\tgithub.com/casbin/casbin/v2 v2.97.0\n\tgithub.com/casbin/gorm-adapter/v3 v3.12.0\n\tgithub.com/docker/docker v20.10.12+incompatible\n\tgithub.com/gin-contrib/cors v1.4.0\n\tgithub.com/gin-contrib/requestid v0.0.6\n\tgithub.com/gin-gonic/gin v1.8.1\n\tgithub.com/go-playground/locales v0.14.1\n\tgithub.com/go-playground/universal-translator v0.18.1\n\tgithub.com/go-playground/validator/v10 v10.19.0\n\tgithub.com/go-sql-driver/mysql v1.6.0\n\tgithub.com/golang-jwt/jwt/v4 v4.4.2\n\tgithub.com/google/uuid v1.3.0\n\tgithub.com/gorilla/websocket v1.4.2\n\tgithub.com/juju/ratelimit v1.0.2\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/pelletier/go-toml/v2 v2.2.0 // indirect\n\tgithub.com/pkg/sftp v1.13.6\n\tgithub.com/robfig/cron/v3 v3.0.0\n\tgithub.com/sirupsen/logrus v1.9.3\n\tgithub.com/spf13/cobra v1.5.0\n\tgithub.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a\n\tgithub.com/swaggo/gin-swagger v1.5.3\n\tgithub.com/swaggo/swag v1.8.6\n\tgolang.org/x/crypto v0.21.0\n\tgolang.org/x/sync v0.1.0\n\tgolang.org/x/time v0.1.0\n\tgorm.io/driver/mysql v1.4.1\n\tgorm.io/gorm v1.24.0\n\thelm.sh/helm/v3 v3.8.2\n\tk8s.io/api v0.23.5\n\tk8s.io/apimachinery v0.23.5\n\tk8s.io/cli-runtime v0.23.5\n\tk8s.io/client-go v0.23.5\n\tk8s.io/klog/v2 v2.80.1\n\tk8s.io/metrics v0.23.5\n\tk8s.io/utils v0.0.0-20221012122500-cfd413dd9e85 // indirect\n)\n\nrequire (\n\tgithub.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect\n\tgithub.com/BurntSushi/toml v1.2.0 // indirect\n\tgithub.com/KyleBanks/depth v1.2.1 // indirect\n\tgithub.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect\n\tgithub.com/Masterminds/goutils v1.1.1 // indirect\n\tgithub.com/Masterminds/semver/v3 v3.1.1 // indirect\n\tgithub.com/Masterminds/sprig/v3 v3.2.2 // indirect\n\tgithub.com/Masterminds/squirrel v1.5.2 // indirect\n\tgithub.com/Microsoft/go-winio v0.5.1 // indirect\n\tgithub.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/casbin/govaluate v1.1.0 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.1.2 // indirect\n\tgithub.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect\n\tgithub.com/containerd/containerd v1.6.1 // indirect\n\tgithub.com/cyphar/filepath-securejoin v0.2.3 // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/docker/cli v20.10.11+incompatible // indirect\n\tgithub.com/docker/distribution v2.7.1+incompatible // indirect\n\tgithub.com/docker/docker-credential-helpers v0.6.4 // indirect\n\tgithub.com/docker/go-connections v0.4.0 // indirect\n\tgithub.com/docker/go-metrics v0.0.1 // indirect\n\tgithub.com/docker/go-units v0.4.0 // indirect\n\tgithub.com/dustin/go-humanize v1.0.1 // indirect\n\tgithub.com/evanphx/json-patch v4.12.0+incompatible // indirect\n\tgithub.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect\n\tgithub.com/fatih/color v1.13.0 // indirect\n\tgithub.com/gabriel-vasile/mimetype v1.4.3 // indirect\n\tgithub.com/gin-contrib/sse v0.1.0 // indirect\n\tgithub.com/glebarez/go-sqlite v1.20.3 // indirect\n\tgithub.com/glebarez/sqlite v1.5.0 // indirect\n\tgithub.com/go-errors/errors v1.0.1 // indirect\n\tgithub.com/go-logr/logr v1.2.2 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.19.5 // indirect\n\tgithub.com/go-openapi/jsonreference v0.20.0 // indirect\n\tgithub.com/go-openapi/spec v0.20.7 // indirect\n\tgithub.com/go-openapi/swag v0.22.3 // indirect\n\tgithub.com/gobwas/glob v0.2.3 // indirect\n\tgithub.com/goccy/go-json v0.10.2 // indirect\n\tgithub.com/gogo/protobuf v1.3.2 // indirect\n\tgithub.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect\n\tgithub.com/golang-sql/sqlexp v0.1.0 // indirect\n\tgithub.com/golang/protobuf v1.5.2 // indirect\n\tgithub.com/google/btree v1.0.1 // indirect\n\tgithub.com/google/go-cmp v0.5.9 // indirect\n\tgithub.com/google/gofuzz v1.2.0 // indirect\n\tgithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect\n\tgithub.com/googleapis/gnostic v0.5.5 // indirect\n\tgithub.com/gorilla/mux v1.8.0 // indirect\n\tgithub.com/gosuri/uitable v0.0.4 // indirect\n\tgithub.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect\n\tgithub.com/huandu/xstrings v1.3.2 // indirect\n\tgithub.com/imdario/mergo v0.3.13 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.0.0 // indirect\n\tgithub.com/jackc/chunkreader/v2 v2.0.1 // indirect\n\tgithub.com/jackc/pgconn v1.13.0 // indirect\n\tgithub.com/jackc/pgio v1.0.0 // indirect\n\tgithub.com/jackc/pgpassfile v1.0.0 // indirect\n\tgithub.com/jackc/pgproto3/v2 v2.3.1 // indirect\n\tgithub.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect\n\tgithub.com/jackc/pgtype v1.12.0 // indirect\n\tgithub.com/jackc/pgx/v4 v4.17.2 // indirect\n\tgithub.com/jinzhu/inflection v1.0.0 // indirect\n\tgithub.com/jinzhu/now v1.1.5 // indirect\n\tgithub.com/jmoiron/sqlx v1.3.4 // indirect\n\tgithub.com/josharian/intern v1.0.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/klauspost/compress v1.13.6 // indirect\n\tgithub.com/kr/fs v0.1.0 // indirect\n\tgithub.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect\n\tgithub.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect\n\tgithub.com/leodido/go-urn v1.4.0 // indirect\n\tgithub.com/lib/pq v1.10.4 // indirect\n\tgithub.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect\n\tgithub.com/mailru/easyjson v0.7.7 // indirect\n\tgithub.com/mattn/go-colorable v0.1.12 // indirect\n\tgithub.com/mattn/go-runewidth v0.0.9 // indirect\n\tgithub.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect\n\tgithub.com/microsoft/go-mssqldb v0.17.0 // indirect\n\tgithub.com/mitchellh/copystructure v1.2.0 // indirect\n\tgithub.com/mitchellh/go-wordwrap v1.0.0 // indirect\n\tgithub.com/mitchellh/reflectwalk v1.0.2 // indirect\n\tgithub.com/moby/locker v1.0.1 // indirect\n\tgithub.com/moby/spdystream v0.2.0 // indirect\n\tgithub.com/moby/term v0.0.0-20210610120745-9d4ed1856297 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.2 // indirect\n\tgithub.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect\n\tgithub.com/morikuni/aec v1.0.0 // indirect\n\tgithub.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect\n\tgithub.com/opencontainers/go-digest v1.0.0 // indirect\n\tgithub.com/opencontainers/image-spec v1.0.2 // indirect\n\tgithub.com/peterbourgon/diskv v2.0.1+incompatible // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/prometheus/client_golang v1.11.0 // indirect\n\tgithub.com/prometheus/client_model v0.2.0 // indirect\n\tgithub.com/prometheus/common v0.30.0 // indirect\n\tgithub.com/prometheus/procfs v0.7.3 // indirect\n\tgithub.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578 // indirect\n\tgithub.com/rogpeppe/go-internal v1.12.0 // indirect\n\tgithub.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc // indirect\n\tgithub.com/russross/blackfriday v1.5.2 // indirect\n\tgithub.com/shopspring/decimal v1.2.0 // indirect\n\tgithub.com/spf13/cast v1.4.1 // indirect\n\tgithub.com/spf13/pflag v1.0.5 // indirect\n\tgithub.com/stretchr/testify v1.9.0 // indirect\n\tgithub.com/ugorji/go/codec v1.2.12 // indirect\n\tgithub.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect\n\tgithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect\n\tgithub.com/xeipuuv/gojsonschema v1.2.0 // indirect\n\tgithub.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect\n\tgo.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect\n\tgolang.org/x/mod v0.9.0 // indirect\n\tgolang.org/x/net v0.22.0 // indirect\n\tgolang.org/x/oauth2 v0.1.0 // indirect\n\tgolang.org/x/sys v0.19.0 // indirect\n\tgolang.org/x/term v0.18.0 // indirect\n\tgolang.org/x/text v0.14.0 // indirect\n\tgolang.org/x/tools v0.6.0 // indirect\n\tgoogle.golang.org/appengine v1.6.7 // indirect\n\tgoogle.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90 // indirect\n\tgoogle.golang.org/grpc v1.47.0 // indirect\n\tgoogle.golang.org/protobuf v1.33.0 // indirect\n\tgopkg.in/gorp.v1 v1.7.2 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tgopkg.in/yaml.v2 v2.4.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tgorm.io/driver/postgres v1.4.4 // indirect\n\tgorm.io/driver/sqlserver v1.4.1 // indirect\n\tgorm.io/plugin/dbresolver v1.3.0 // indirect\n\tk8s.io/apiextensions-apiserver v0.23.5 // indirect\n\tk8s.io/apiserver v0.23.5 // indirect\n\tk8s.io/component-base v0.23.5 // indirect\n\tk8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect\n\tk8s.io/kubectl v0.23.5 // indirect\n\tmodernc.org/libc v1.22.2 // indirect\n\tmodernc.org/mathutil v1.5.0 // indirect\n\tmodernc.org/memory v1.5.0 // indirect\n\tmodernc.org/sqlite v1.20.3 // indirect\n\toras.land/oras-go v1.1.1 // indirect\n\tsigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect\n\tsigs.k8s.io/kustomize/api v0.10.1 // indirect\n\tsigs.k8s.io/kustomize/kyaml v0.13.0 // indirect\n\tsigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect\n\tsigs.k8s.io/yaml v1.3.0 // indirect\n)\n\nreplace (\n\t// github.com/glebarez/sqlite => github.com/glebarez/sqlite v1.4.0\n\tgithub.com/pelletier/go-toml/v2 => github.com/pelletier/go-toml/v2 v2.1.1\n\tgolang.org/x/net => golang.org/x/net v0.17.0\n// gorm.io/driver/sqlserver => gorm.io/driver/sqlserver v1.4.1\n)\n"
  },
  {
    "path": "go.sum",
    "content": "bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=\nbazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM=\ncloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=\ncloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=\ncloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=\ncloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=\ncloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=\ncloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=\ncloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=\ncloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=\ncloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=\ncloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=\ncloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=\ncloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=\ncloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=\ncloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=\ncloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=\ncloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=\ncloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=\ncloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=\ncloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=\ncloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=\ncloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=\ncloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=\ncloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=\ncloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=\ncloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM=\ncloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=\ncloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=\ncloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=\ncloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=\ncloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=\ncloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=\ncloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=\ncloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=\ncloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=\ncloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=\ncloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=\ncloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=\ncloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=\ncloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=\ncloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=\ncloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=\ncloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=\ncloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=\ncloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=\ncloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=\ncloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=\ncloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=\ncloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=\ncloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=\ncloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=\ncloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=\ncloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=\ncloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=\ncloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=\ncloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=\ndmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=\ngithub.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg=\ngithub.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=\ngithub.com/Azure/azure-sdk-for-go v56.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=\ngithub.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.0/go.mod h1:+6sju8gk8FRmSajX3Oz4G5Gm7P+mbqE9FVaXXFYTkCM=\ngithub.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=\ngithub.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=\ngithub.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=\ngithub.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=\ngithub.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=\ngithub.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=\ngithub.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=\ngithub.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=\ngithub.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=\ngithub.com/Azure/go-autorest/autorest v0.11.20/go.mod h1:o3tqFY+QR40VOlk+pV4d77mORO64jOXSgEnPQgLK6JY=\ngithub.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg=\ngithub.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=\ngithub.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=\ngithub.com/Azure/go-autorest/autorest/adal v0.9.15/go.mod h1:tGMin8I49Yij6AQ+rvV+Xa/zwxYQB5hmsd6DkfAx2+A=\ngithub.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=\ngithub.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=\ngithub.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=\ngithub.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=\ngithub.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=\ngithub.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=\ngithub.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=\ngithub.com/AzureAD/microsoft-authentication-library-for-go v0.4.0/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=\ngithub.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0=\ngithub.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=\ngithub.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=\ngithub.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=\ngithub.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=\ngithub.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=\ngithub.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=\ngithub.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=\ngithub.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=\ngithub.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU=\ngithub.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=\ngithub.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=\ngithub.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=\ngithub.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=\ngithub.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=\ngithub.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=\ngithub.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=\ngithub.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=\ngithub.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=\ngithub.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=\ngithub.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8=\ngithub.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk=\ngithub.com/Masterminds/squirrel v1.5.2 h1:UiOEi2ZX4RCSkpiNDQN5kro/XIBpSRk9iTqdIRPzUXE=\ngithub.com/Masterminds/squirrel v1.5.2/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=\ngithub.com/Masterminds/vcs v1.13.3/go.mod h1:TiE7xuEjl1N4j016moRd6vezp6e6Lz23gypeXfzXeW8=\ngithub.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=\ngithub.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=\ngithub.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=\ngithub.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=\ngithub.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=\ngithub.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=\ngithub.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=\ngithub.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=\ngithub.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY=\ngithub.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=\ngithub.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=\ngithub.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=\ngithub.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ=\ngithub.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8=\ngithub.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg=\ngithub.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00=\ngithub.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600=\ngithub.com/Microsoft/hcsshim v0.8.20/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4=\ngithub.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4=\ngithub.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg=\ngithub.com/Microsoft/hcsshim v0.9.2 h1:wB06W5aYFfUB3IvootYAY2WnOmIdgPGfqSI6tufQNnY=\ngithub.com/Microsoft/hcsshim v0.9.2/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc=\ngithub.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU=\ngithub.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY=\ngithub.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=\ngithub.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=\ngithub.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=\ngithub.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=\ngithub.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=\ngithub.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=\ngithub.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=\ngithub.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=\ngithub.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=\ngithub.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=\ngithub.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=\ngithub.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0=\ngithub.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk=\ngithub.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=\ngithub.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=\ngithub.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=\ngithub.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=\ngithub.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=\ngithub.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=\ngithub.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=\ngithub.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=\ngithub.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=\ngithub.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY=\ngithub.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=\ngithub.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=\ngithub.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=\ngithub.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=\ngithub.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=\ngithub.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=\ngithub.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=\ngithub.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=\ngithub.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=\ngithub.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=\ngithub.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=\ngithub.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=\ngithub.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=\ngithub.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=\ngithub.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=\ngithub.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=\ngithub.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=\ngithub.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=\ngithub.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70=\ngithub.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=\ngithub.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=\ngithub.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=\ngithub.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng=\ngithub.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=\ngithub.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ=\ngithub.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=\ngithub.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o=\ngithub.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=\ngithub.com/caoyingjunz/pixiulib v0.0.0-20220819163605-c3c10ec3ed3c h1:SyWVmVBugbQp3dQ3xz8rlkxU43MxmG86fY8JoxqtSBs=\ngithub.com/caoyingjunz/pixiulib v0.0.0-20220819163605-c3c10ec3ed3c/go.mod h1:rQFvtjWLu4D7VSo6H/bI8h0tyWneUtuJJM+AcZBalPk=\ngithub.com/casbin/casbin/v2 v2.55.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=\ngithub.com/casbin/casbin/v2 v2.97.0 h1:FFHIzY+6fLIcoAB/DKcG5xvscUo9XqRpBniRYhlPWkg=\ngithub.com/casbin/casbin/v2 v2.97.0/go.mod h1:jX8uoN4veP85O/n2674r2qtfSXI6myvxW85f6TH50fw=\ngithub.com/casbin/gorm-adapter/v3 v3.12.0 h1:jUJCwaLnB+QfUadOETD6ymxeDmOR9jQXTmUTzLMpS3g=\ngithub.com/casbin/gorm-adapter/v3 v3.12.0/go.mod h1:jqaf4bUITbCyMPUellaTd8IQJ77JfVAbe77gZZnx98w=\ngithub.com/casbin/govaluate v1.1.0 h1:6xdCWIpE9CwHdZhlVQW+froUrCsjb6/ZYNcXODfLT+E=\ngithub.com/casbin/govaluate v1.1.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=\ngithub.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=\ngithub.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=\ngithub.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=\ngithub.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=\ngithub.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=\ngithub.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=\ngithub.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 h1:7aWHqerlJ41y6FOsEUvknqgXnGmJyJSbjhAWq5pO4F8=\ngithub.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw=\ngithub.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=\ngithub.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=\ngithub.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=\ngithub.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc=\ngithub.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs=\ngithub.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=\ngithub.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=\ngithub.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=\ngithub.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=\ngithub.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=\ngithub.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=\ngithub.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=\ngithub.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=\ngithub.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=\ngithub.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=\ngithub.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo=\ngithub.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=\ngithub.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=\ngithub.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE=\ngithub.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU=\ngithub.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU=\ngithub.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU=\ngithub.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E=\ngithub.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss=\ngithub.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss=\ngithub.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI=\ngithub.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=\ngithub.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM=\ngithub.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo=\ngithub.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo=\ngithub.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE=\ngithub.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU=\ngithub.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4=\ngithub.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8=\ngithub.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=\ngithub.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=\ngithub.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE=\ngithub.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw=\ngithub.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ=\ngithub.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=\ngithub.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=\ngithub.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=\ngithub.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=\ngithub.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=\ngithub.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=\ngithub.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=\ngithub.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=\ngithub.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=\ngithub.com/containerd/containerd v1.4.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=\ngithub.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ=\ngithub.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU=\ngithub.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI=\ngithub.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s=\ngithub.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g=\ngithub.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c=\ngithub.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s=\ngithub.com/containerd/containerd v1.6.1 h1:oa2uY0/0G+JX4X7hpGCYvkp9FjUancz56kSNnb1sG3o=\ngithub.com/containerd/containerd v1.6.1/go.mod h1:1nJz5xCZPusx6jJU8Frfct988y0NpumIq9ODB0kLtoE=\ngithub.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=\ngithub.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=\ngithub.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=\ngithub.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo=\ngithub.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y=\ngithub.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ=\ngithub.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM=\ngithub.com/containerd/continuity v0.2.2/go.mod h1:pWygW9u7LtS1o4N/Tn0FoCFDIXZ7rxcMX7HX1Dmibvk=\ngithub.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=\ngithub.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=\ngithub.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0=\ngithub.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0=\ngithub.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4=\ngithub.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4=\ngithub.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU=\ngithub.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk=\ngithub.com/containerd/go-cni v1.1.0/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA=\ngithub.com/containerd/go-cni v1.1.3/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA=\ngithub.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=\ngithub.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=\ngithub.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g=\ngithub.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok=\ngithub.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok=\ngithub.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0=\ngithub.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA=\ngithub.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow=\ngithub.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms=\ngithub.com/containerd/imgcrypt v1.1.3/go.mod h1:/TPA1GIDXMzbj01yd8pIbQiLdQxed5ue1wb8bP7PQu4=\ngithub.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c=\ngithub.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY=\ngithub.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY=\ngithub.com/containerd/stargz-snapshotter/estargz v0.4.1/go.mod h1:x7Q9dg9QYb4+ELgxmo4gBUeJB0tl5dqH1Sdz0nJU1QM=\ngithub.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=\ngithub.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=\ngithub.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8=\ngithub.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=\ngithub.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=\ngithub.com/containerd/ttrpc v1.1.0/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Evzy5KFQpQ=\ngithub.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=\ngithub.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk=\ngithub.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg=\ngithub.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s=\ngithub.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw=\ngithub.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y=\ngithub.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY=\ngithub.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY=\ngithub.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY=\ngithub.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=\ngithub.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=\ngithub.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=\ngithub.com/containernetworking/cni v1.0.1/go.mod h1:AKuhXbN5EzmD4yTNtfSsX3tPcmtrBI6QcRV0NiNt15Y=\ngithub.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM=\ngithub.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8=\ngithub.com/containernetworking/plugins v1.0.1/go.mod h1:QHCfGpaTwYTbbH+nZXKVTxNBDZcxSOplJT5ico8/FLE=\ngithub.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc=\ngithub.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4=\ngithub.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY=\ngithub.com/containers/ocicrypt v1.1.2/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY=\ngithub.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=\ngithub.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=\ngithub.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=\ngithub.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=\ngithub.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=\ngithub.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=\ngithub.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=\ngithub.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=\ngithub.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=\ngithub.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=\ngithub.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=\ngithub.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=\ngithub.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=\ngithub.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=\ngithub.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=\ngithub.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=\ngithub.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI=\ngithub.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=\ngithub.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=\ngithub.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=\ngithub.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=\ngithub.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I=\ngithub.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE=\ngithub.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=\ngithub.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=\ngithub.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=\ngithub.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=\ngithub.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=\ngithub.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684 h1:DBZ2sN7CK6dgvHVpQsQj4sRMCbWTmd17l+5SUCjnQSY=\ngithub.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684/go.mod h1:UfCu3YXJJCI+IdnqGgYP82dk2+Joxmv+mUTVBES6wac=\ngithub.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=\ngithub.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=\ngithub.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=\ngithub.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=\ngithub.com/docker/cli v20.10.11+incompatible h1:tXU1ezXcruZQRrMP8RN2z9N91h+6egZTS1gsPsKantc=\ngithub.com/docker/cli v20.10.11+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=\ngithub.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=\ngithub.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=\ngithub.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=\ngithub.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=\ngithub.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=\ngithub.com/docker/docker v20.10.11+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=\ngithub.com/docker/docker v20.10.12+incompatible h1:CEeNmFM0QZIsJCZKMkZx0ZcahTiewkrgiwfYD+dfl1U=\ngithub.com/docker/docker v20.10.12+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=\ngithub.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=\ngithub.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o=\ngithub.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c=\ngithub.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=\ngithub.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=\ngithub.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=\ngithub.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=\ngithub.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=\ngithub.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=\ngithub.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=\ngithub.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=\ngithub.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=\ngithub.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=\ngithub.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4=\ngithub.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=\ngithub.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=\ngithub.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=\ngithub.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=\ngithub.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=\ngithub.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=\ngithub.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=\ngithub.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc=\ngithub.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=\ngithub.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=\ngithub.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=\ngithub.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=\ngithub.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ=\ngithub.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws=\ngithub.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=\ngithub.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=\ngithub.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=\ngithub.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=\ngithub.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM=\ngithub.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=\ngithub.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=\ngithub.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=\ngithub.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=\ngithub.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=\ngithub.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=\ngithub.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=\ngithub.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=\ngithub.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=\ngithub.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=\ngithub.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=\ngithub.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=\ngithub.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=\ngithub.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=\ngithub.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=\ngithub.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=\ngithub.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=\ngithub.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=\ngithub.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=\ngithub.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg=\ngithub.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=\ngithub.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g=\ngithub.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs=\ngithub.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=\ngithub.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=\ngithub.com/gin-contrib/requestid v0.0.6 h1:mGcxTnHQ45F6QU5HQRgQUDsAfHprD3P7g2uZ4cSZo9o=\ngithub.com/gin-contrib/requestid v0.0.6/go.mod h1:9i4vKATX/CdggbkY252dPVasgVucy/ggBeELXuQztm4=\ngithub.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=\ngithub.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=\ngithub.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=\ngithub.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=\ngithub.com/glebarez/go-sqlite v1.19.1/go.mod h1:9AykawGIyIcxoSfpYWiX1SgTNHTNsa/FVc75cDkbp4M=\ngithub.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4=\ngithub.com/glebarez/go-sqlite v1.20.3/go.mod h1:u3N6D/wftiAzIOJtZl6BmedqxmmkDfH3q+ihjqxC9u0=\ngithub.com/glebarez/sqlite v1.5.0 h1:+8LAEpmywqresSoGlqjjT+I9m4PseIM3NcerIJ/V7mk=\ngithub.com/glebarez/sqlite v1.5.0/go.mod h1:0wzXzTvfVJIN2GqRhCdMbnYd+m+aH5/QV7B30rM6NgY=\ngithub.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=\ngithub.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=\ngithub.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=\ngithub.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=\ngithub.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=\ngithub.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=\ngithub.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=\ngithub.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=\ngithub.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=\ngithub.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=\ngithub.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro=\ngithub.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=\ngithub.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=\ngithub.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=\ngithub.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=\ngithub.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=\ngithub.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=\ngithub.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=\ngithub.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=\ngithub.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=\ngithub.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=\ngithub.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=\ngithub.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=\ngithub.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=\ngithub.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=\ngithub.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=\ngithub.com/go-openapi/spec v0.20.7 h1:1Rlu/ZrOCCob0n+JKKJAWhNWMPW8bOZRg8FJaY+0SKI=\ngithub.com/go-openapi/spec v0.20.7/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=\ngithub.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=\ngithub.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=\ngithub.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=\ngithub.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=\ngithub.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=\ngithub.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=\ngithub.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=\ngithub.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=\ngithub.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=\ngithub.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=\ngithub.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=\ngithub.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=\ngithub.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=\ngithub.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=\ngithub.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=\ngithub.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=\ngithub.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=\ngithub.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=\ngithub.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=\ngithub.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=\ngithub.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=\ngithub.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=\ngithub.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=\ngithub.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=\ngithub.com/gobuffalo/logger v1.0.3 h1:YaXOTHNPCvkqqA7w05A4v0k2tCdpr+sgFlgINbQ6gqc=\ngithub.com/gobuffalo/logger v1.0.3/go.mod h1:SoeejUwldiS7ZsyCBphOGURmWdwUFXs0J7TCjEhjKxM=\ngithub.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM=\ngithub.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI=\ngithub.com/gobuffalo/packr/v2 v2.8.1 h1:tkQpju6i3EtMXJ9uoF5GT6kB+LMTimDWD8Xvbz6zDVA=\ngithub.com/gobuffalo/packr/v2 v2.8.1/go.mod h1:c/PLlOuTU+p3SybaJATW3H6lX/iK7xEz5OeMf+NnJpg=\ngithub.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=\ngithub.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=\ngithub.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=\ngithub.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=\ngithub.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=\ngithub.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=\ngithub.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=\ngithub.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=\ngithub.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE=\ngithub.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=\ngithub.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=\ngithub.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=\ngithub.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU=\ngithub.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c=\ngithub.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=\ngithub.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=\ngithub.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=\ngithub.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=\ngithub.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=\ngithub.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=\ngithub.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=\ngithub.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=\ngithub.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=\ngithub.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=\ngithub.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=\ngithub.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=\ngithub.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=\ngithub.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=\ngithub.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=\ngithub.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=\ngithub.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=\ngithub.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=\ngithub.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=\ngithub.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=\ngithub.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=\ngithub.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=\ngithub.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk=\ngithub.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k=\ngithub.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=\ngithub.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=\ngithub.com/google/cel-go v0.9.0/go.mod h1:U7ayypeSkw23szu4GaQTPJGx66c20mx8JklMSxrmI1w=\ngithub.com/google/cel-spec v0.6.0/go.mod h1:Nwjgxy5CbjlPrtCWjeDjUyKMl8w41YBYGjsyDdqk0xA=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=\ngithub.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=\ngithub.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=\ngithub.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=\ngithub.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=\ngithub.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=\ngithub.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=\ngithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=\ngithub.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=\ngithub.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=\ngithub.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=\ngithub.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=\ngithub.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=\ngithub.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=\ngithub.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=\ngithub.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=\ngithub.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=\ngithub.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=\ngithub.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=\ngithub.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=\ngithub.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=\ngithub.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=\ngithub.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=\ngithub.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=\ngithub.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=\ngithub.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=\ngithub.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=\ngithub.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=\ngithub.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=\ngithub.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=\ngithub.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=\ngithub.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=\ngithub.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=\ngithub.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=\ngithub.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY=\ngithub.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=\ngithub.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=\ngithub.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=\ngithub.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=\ngithub.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=\ngithub.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=\ngithub.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=\ngithub.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=\ngithub.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=\ngithub.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=\ngithub.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=\ngithub.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=\ngithub.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=\ngithub.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=\ngithub.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=\ngithub.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=\ngithub.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=\ngithub.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=\ngithub.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=\ngithub.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=\ngithub.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=\ngithub.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=\ngithub.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=\ngithub.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=\ngithub.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=\ngithub.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=\ngithub.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=\ngithub.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=\ngithub.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=\ngithub.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=\ngithub.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=\ngithub.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=\ngithub.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=\ngithub.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=\ngithub.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=\ngithub.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=\ngithub.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=\ngithub.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=\ngithub.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=\ngithub.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=\ngithub.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=\ngithub.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=\ngithub.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=\ngithub.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=\ngithub.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=\ngithub.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=\ngithub.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=\ngithub.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=\ngithub.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=\ngithub.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=\ngithub.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=\ngithub.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=\ngithub.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=\ngithub.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=\ngithub.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=\ngithub.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=\ngithub.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ=\ngithub.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=\ngithub.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw=\ngithub.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=\ngithub.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=\ngithub.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=\ngithub.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=\ngithub.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=\ngithub.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=\ngithub.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=\ngithub.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=\ngithub.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=\ngithub.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=\ngithub.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=\ngithub.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys=\ngithub.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI=\ngithub.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=\ngithub.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=\ngithub.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=\ngithub.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=\ngithub.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=\ngithub.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=\ngithub.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=\ngithub.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=\ngithub.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=\ngithub.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=\ngithub.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=\ngithub.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=\ngithub.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=\ngithub.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=\ngithub.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=\ngithub.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=\ngithub.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y=\ngithub.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=\ngithub.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=\ngithub.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=\ngithub.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=\ngithub.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=\ngithub.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=\ngithub.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=\ngithub.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=\ngithub.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w=\ngithub.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=\ngithub.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=\ngithub.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=\ngithub.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=\ngithub.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=\ngithub.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E=\ngithub.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw=\ngithub.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=\ngithub.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=\ngithub.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=\ngithub.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=\ngithub.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=\ngithub.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=\ngithub.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=\ngithub.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=\ngithub.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=\ngithub.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=\ngithub.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=\ngithub.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=\ngithub.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w=\ngithub.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=\ngithub.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8=\ngithub.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=\ngithub.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=\ngithub.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=\ngithub.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=\ngithub.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=\ngithub.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=\ngithub.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=\ngithub.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=\ngithub.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=\ngithub.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=\ngithub.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=\ngithub.com/karrick/godirwalk v1.15.8 h1:7+rWAZPn9zuRxaIqqT8Ohs2Q2Ac0msBqwRdxNCr2VVs=\ngithub.com/karrick/godirwalk v1.15.8/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk=\ngithub.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=\ngithub.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=\ngithub.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=\ngithub.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=\ngithub.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=\ngithub.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc=\ngithub.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=\ngithub.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=\ngithub.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=\ngithub.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=\ngithub.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=\ngithub.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=\ngithub.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=\ngithub.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=\ngithub.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=\ngithub.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=\ngithub.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=\ngithub.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=\ngithub.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=\ngithub.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=\ngithub.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=\ngithub.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=\ngithub.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=\ngithub.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=\ngithub.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=\ngithub.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=\ngithub.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=\ngithub.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo=\ngithub.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=\ngithub.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=\ngithub.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=\ngithub.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=\ngithub.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=\ngithub.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=\ngithub.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=\ngithub.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=\ngithub.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=\ngithub.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI=\ngithub.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc=\ngithub.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY=\ngithub.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI=\ngithub.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI=\ngithub.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=\ngithub.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=\ngithub.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=\ngithub.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=\ngithub.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=\ngithub.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=\ngithub.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=\ngithub.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=\ngithub.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=\ngithub.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=\ngithub.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=\ngithub.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=\ngithub.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=\ngithub.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=\ngithub.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=\ngithub.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=\ngithub.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=\ngithub.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=\ngithub.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI=\ngithub.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=\ngithub.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=\ngithub.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=\ngithub.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=\ngithub.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=\ngithub.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=\ngithub.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=\ngithub.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=\ngithub.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=\ngithub.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=\ngithub.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY=\ngithub.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE=\ngithub.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ=\ngithub.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=\ngithub.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=\ngithub.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=\ngithub.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=\ngithub.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=\ngithub.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=\ngithub.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=\ngithub.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4=\ngithub.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=\ngithub.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=\ngithub.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=\ngithub.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=\ngithub.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=\ngithub.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=\ngithub.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=\ngithub.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=\ngithub.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=\ngithub.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=\ngithub.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=\ngithub.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=\ngithub.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=\ngithub.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=\ngithub.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=\ngithub.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=\ngithub.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=\ngithub.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=\ngithub.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=\ngithub.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=\ngithub.com/moby/sys/mountinfo v0.5.0 h1:2Ks8/r6lopsxWi9m58nlwjaeSzUX9iiL1vj5qB/9ObI=\ngithub.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=\ngithub.com/moby/sys/signal v0.6.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg=\ngithub.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ=\ngithub.com/moby/sys/symlink v0.2.0/go.mod h1:7uZVF2dqJjG/NsClqul95CqKOBRQyYSNnJ6BMgR/gFs=\ngithub.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=\ngithub.com/moby/term v0.0.0-20210610120745-9d4ed1856297 h1:yH0SvLzcbZxcJXho2yh7CqdENGMQe73Cw3woZBpPli0=\ngithub.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=\ngithub.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=\ngithub.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=\ngithub.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=\ngithub.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=\ngithub.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=\ngithub.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=\ngithub.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\ngithub.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\ngithub.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=\ngithub.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=\ngithub.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=\ngithub.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=\ngithub.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=\ngithub.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=\ngithub.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=\ngithub.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=\ngithub.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=\ngithub.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=\ngithub.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=\ngithub.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=\ngithub.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=\ngithub.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=\ngithub.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=\ngithub.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=\ngithub.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=\ngithub.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=\ngithub.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=\ngithub.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=\ngithub.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=\ngithub.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=\ngithub.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=\ngithub.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU=\ngithub.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=\ngithub.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=\ngithub.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=\ngithub.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=\ngithub.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=\ngithub.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=\ngithub.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=\ngithub.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=\ngithub.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=\ngithub.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=\ngithub.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=\ngithub.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=\ngithub.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=\ngithub.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=\ngithub.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=\ngithub.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=\ngithub.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0=\ngithub.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0=\ngithub.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc=\ngithub.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=\ngithub.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=\ngithub.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=\ngithub.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=\ngithub.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=\ngithub.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=\ngithub.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs=\ngithub.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE=\ngithub.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo=\ngithub.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8=\ngithub.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=\ngithub.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=\ngithub.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=\ngithub.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=\ngithub.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=\ngithub.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=\ngithub.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=\ngithub.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=\ngithub.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=\ngithub.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=\ngithub.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=\ngithub.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=\ngithub.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=\ngithub.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=\ngithub.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=\ngithub.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=\ngithub.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=\ngithub.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=\ngithub.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=\ngithub.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=\ngithub.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=\ngithub.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=\ngithub.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=\ngithub.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=\ngithub.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=\ngithub.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=\ngithub.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=\ngithub.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=\ngithub.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=\ngithub.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=\ngithub.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=\ngithub.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=\ngithub.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=\ngithub.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=\ngithub.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=\ngithub.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=\ngithub.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=\ngithub.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=\ngithub.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=\ngithub.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=\ngithub.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=\ngithub.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=\ngithub.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=\ngithub.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=\ngithub.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=\ngithub.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=\ngithub.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=\ngithub.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=\ngithub.com/prometheus/common v0.28.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=\ngithub.com/prometheus/common v0.30.0 h1:JEkYlQnpzrzQFxi6gnukFPdQ+ac82oRhzMcIduJu/Ug=\ngithub.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=\ngithub.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=\ngithub.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=\ngithub.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=\ngithub.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=\ngithub.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=\ngithub.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=\ngithub.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=\ngithub.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=\ngithub.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=\ngithub.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=\ngithub.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=\ngithub.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=\ngithub.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=\ngithub.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578 h1:VstopitMQi3hZP0fzvnsLmzXZdQGc4bEcgu24cp+d4M=\ngithub.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=\ngithub.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E=\ngithub.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=\ngithub.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=\ngithub.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=\ngithub.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=\ngithub.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=\ngithub.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=\ngithub.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=\ngithub.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=\ngithub.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=\ngithub.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=\ngithub.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=\ngithub.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc h1:BD7uZqkN8CpjJtN/tScAKiccBikU4dlqe/gNrkRaPY4=\ngithub.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc/go.mod h1:HFLT6i9iR4QBOF5rdCyjddC9t59ArqWJV2xx+jwcCMo=\ngithub.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=\ngithub.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=\ngithub.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=\ngithub.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=\ngithub.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=\ngithub.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=\ngithub.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=\ngithub.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=\ngithub.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=\ngithub.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=\ngithub.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=\ngithub.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=\ngithub.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=\ngithub.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=\ngithub.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=\ngithub.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=\ngithub.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=\ngithub.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=\ngithub.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=\ngithub.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=\ngithub.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=\ngithub.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=\ngithub.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=\ngithub.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=\ngithub.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=\ngithub.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=\ngithub.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=\ngithub.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=\ngithub.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=\ngithub.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=\ngithub.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=\ngithub.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=\ngithub.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=\ngithub.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=\ngithub.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=\ngithub.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=\ngithub.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=\ngithub.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=\ngithub.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=\ngithub.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=\ngithub.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=\ngithub.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=\ngithub.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=\ngithub.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=\ngithub.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=\ngithub.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=\ngithub.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=\ngithub.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=\ngithub.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4=\ngithub.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=\ngithub.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=\ngithub.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=\ngithub.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=\ngithub.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=\ngithub.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=\ngithub.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=\ngithub.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=\ngithub.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=\ngithub.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=\ngithub.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=\ngithub.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=\ngithub.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=\ngithub.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=\ngithub.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngithub.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=\ngithub.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngithub.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=\ngithub.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a h1:kAe4YSu0O0UFn1DowNo2MY5p6xzqtJ/wQ7LZynSvGaY=\ngithub.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=\ngithub.com/swaggo/gin-swagger v1.5.3 h1:8mWmHLolIbrhJJTflsaFoZzRBYVmEE7JZGIq08EiC0Q=\ngithub.com/swaggo/gin-swagger v1.5.3/go.mod h1:3XJKSfHjDMB5dBo/0rrTXidPmgLeqsX89Yp4uA50HpI=\ngithub.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ=\ngithub.com/swaggo/swag v1.8.6 h1:2rgOaLbonWu1PLP6G+/rYjSvPg0jQE0HtrEKuE380eg=\ngithub.com/swaggo/swag v1.8.6/go.mod h1:jMLeXOOmYyjk8PvHTsXBdrubsNd9gUJTTCzL5iBnseg=\ngithub.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=\ngithub.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=\ngithub.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=\ngithub.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=\ngithub.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=\ngithub.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=\ngithub.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=\ngithub.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=\ngithub.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM=\ngithub.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=\ngithub.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=\ngithub.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=\ngithub.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=\ngithub.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=\ngithub.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=\ngithub.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=\ngithub.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=\ngithub.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=\ngithub.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=\ngithub.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=\ngithub.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=\ngithub.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=\ngithub.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=\ngithub.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=\ngithub.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=\ngithub.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=\ngithub.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=\ngithub.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=\ngithub.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=\ngithub.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=\ngithub.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=\ngithub.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=\ngithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=\ngithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=\ngithub.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=\ngithub.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=\ngithub.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=\ngithub.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=\ngithub.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI=\ngithub.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=\ngithub.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=\ngithub.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngithub.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngithub.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngithub.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI=\ngithub.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=\ngithub.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE=\ngithub.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=\ngithub.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY=\ngithub.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=\ngithub.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=\ngithub.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=\ngithub.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=\ngo.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=\ngo.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=\ngo.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=\ngo.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=\ngo.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg=\ngo.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=\ngo.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=\ngo.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=\ngo.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=\ngo.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=\ngo.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=\ngo.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=\ngo.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE=\ngo.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc=\ngo.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4=\ngo.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=\ngo.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=\ngo.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=\ngo.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=\ngo.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=\ngo.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=\ngo.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0/go.mod h1:vEhqr0m4eTc+DWxfsXoXue2GBgV2uUwVznkGIHW/e5w=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4=\ngo.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo=\ngo.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs=\ngo.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM=\ngo.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0/go.mod h1:hO1KLR7jcKaDDKDkvI9dP/FIhpmna5lkqPUQdEjFAM8=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0/go.mod h1:keUU7UfnwWTWpJ+FWnyqmogPa82nuU5VUANFq49hlMY=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.3.0/go.mod h1:QNX1aly8ehqqX1LEa6YniTU7VY9I6R3X/oPxhGdTceE=\ngo.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU=\ngo.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw=\ngo.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc=\ngo.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs=\ngo.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE=\ngo.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE=\ngo.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw=\ngo.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk=\ngo.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=\ngo.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ=\ngo.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc=\ngo.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o=\ngo.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=\ngo.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=\ngo.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=\ngo.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=\ngo.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=\ngo.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=\ngo.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=\ngo.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=\ngo.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=\ngo.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=\ngo.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=\ngo.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=\ngo.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=\ngo.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=\ngo.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=\ngo.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=\ngo.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=\ngolang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=\ngolang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=\ngolang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=\ngolang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=\ngolang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=\ngolang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=\ngolang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=\ngolang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=\ngolang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=\ngolang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=\ngolang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=\ngolang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=\ngolang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=\ngolang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=\ngolang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=\ngolang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=\ngolang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=\ngolang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=\ngolang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=\ngolang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=\ngolang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=\ngolang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=\ngolang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=\ngolang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=\ngolang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=\ngolang.org/x/oauth2 v0.1.0 h1:isLCZuhj4v+tYv7eskaN4v/TM+A1begWWgyVJDdl1+Y=\ngolang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=\ngolang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=\ngolang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=\ngolang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=\ngolang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=\ngolang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=\ngolang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=\ngolang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=\ngolang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=\ngolang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA=\ngolang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=\ngolang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200308013534-11ec41452d41/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=\ngolang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=\ngolang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=\ngolang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=\ngolang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM=\ngolang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=\ngolang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=\ngolang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=\ngolang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=\ngolang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=\ngoogle.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=\ngoogle.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=\ngoogle.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=\ngoogle.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=\ngoogle.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=\ngoogle.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=\ngoogle.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=\ngoogle.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=\ngoogle.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=\ngoogle.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=\ngoogle.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=\ngoogle.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=\ngoogle.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=\ngoogle.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=\ngoogle.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=\ngoogle.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=\ngoogle.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=\ngoogle.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=\ngoogle.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=\ngoogle.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=\ngoogle.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=\ngoogle.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw=\ngoogle.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=\ngoogle.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=\ngoogle.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=\ngoogle.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=\ngoogle.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=\ngoogle.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=\ngoogle.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=\ngoogle.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=\ngoogle.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=\ngoogle.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=\ngoogle.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=\ngoogle.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=\ngoogle.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=\ngoogle.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=\ngoogle.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201102152239-715cce707fb0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=\ngoogle.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=\ngoogle.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=\ngoogle.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=\ngoogle.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=\ngoogle.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=\ngoogle.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=\ngoogle.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=\ngoogle.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=\ngoogle.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=\ngoogle.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=\ngoogle.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=\ngoogle.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=\ngoogle.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=\ngoogle.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=\ngoogle.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=\ngoogle.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=\ngoogle.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=\ngoogle.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=\ngoogle.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=\ngoogle.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=\ngoogle.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90 h1:4SPz2GL2CXJt28MTF8V6Ap/9ZiVbQlJeGSd9qtA7DLs=\ngoogle.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=\ngoogle.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=\ngoogle.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=\ngoogle.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=\ngoogle.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=\ngoogle.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=\ngoogle.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=\ngoogle.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=\ngoogle.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=\ngoogle.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=\ngoogle.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=\ngoogle.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=\ngoogle.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=\ngoogle.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=\ngoogle.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=\ngoogle.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=\ngoogle.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=\ngoogle.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=\ngoogle.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=\ngoogle.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=\ngoogle.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8=\ngoogle.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=\ngoogle.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngoogle.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=\ngoogle.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=\ngopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=\ngopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=\ngopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\ngopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=\ngopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=\ngopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw=\ngopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw=\ngopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=\ngopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=\ngopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=\ngopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=\ngopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=\ngopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=\ngopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngorm.io/driver/mysql v1.3.2/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U=\ngorm.io/driver/mysql v1.4.1 h1:4InA6SOaYtt4yYpV1NF9B2kvUKe9TbvUd1iWrvxnjic=\ngorm.io/driver/mysql v1.4.1/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c=\ngorm.io/driver/postgres v1.4.4 h1:zt1fxJ+C+ajparn0SteEnkoPg0BQ6wOWXEQ99bteAmw=\ngorm.io/driver/postgres v1.4.4/go.mod h1:whNfh5WhhHs96honoLjBAMwJGYEuA3m1hvgUbNXhPCw=\ngorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0=\ngorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig=\ngorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=\ngorm.io/gorm v1.23.7/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=\ngorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=\ngorm.io/gorm v1.24.0 h1:j/CoiSm6xpRpmzbFJsQHYj+I8bGYWLXVHeYEyyKlF74=\ngorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=\ngorm.io/plugin/dbresolver v1.3.0 h1:uFDX3bIuH9Lhj5LY2oyqR/bU6pqWuDgas35NAPF4X3M=\ngorm.io/plugin/dbresolver v1.3.0/go.mod h1:Pr7p5+JFlgDaiM6sOrli5olekJD16YRunMyA2S7ZfKk=\ngotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=\ngotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=\ngotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=\ngotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=\ngotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=\nhelm.sh/helm/v3 v3.8.2 h1:HDhe2nKek976VLMPZlIgJbNqwcqvHYBp1qy+sXQ4jiY=\nhelm.sh/helm/v3 v3.8.2/go.mod h1:NxtE2KObf2PrzDl6SIamPFPKyAqWi10iWuvKlQn/Yao=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=\nhonnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nhonnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nk8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo=\nk8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ=\nk8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8=\nk8s.io/api v0.21.2/go.mod h1:Lv6UGJZ1rlMI1qusN8ruAp9PUBFyBwpEHAdG24vIsiU=\nk8s.io/api v0.22.5/go.mod h1:mEhXyLaSD1qTOf40rRiKXkc+2iCem09rWLlFwhCEiAs=\nk8s.io/api v0.23.5 h1:zno3LUiMubxD/V1Zw3ijyKO3wxrhbUF1Ck+VjBvfaoA=\nk8s.io/api v0.23.5/go.mod h1:Na4XuKng8PXJ2JsploYYrivXrINeTaycCGcYgF91Xm8=\nk8s.io/apiextensions-apiserver v0.23.5 h1:5SKzdXyvIJKu+zbfPc3kCbWpbxi+O+zdmAJBm26UJqI=\nk8s.io/apiextensions-apiserver v0.23.5/go.mod h1:ntcPWNXS8ZPKN+zTXuzYMeg731CP0heCTl6gYBxLcuQ=\nk8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=\nk8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=\nk8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc=\nk8s.io/apimachinery v0.21.2/go.mod h1:CdTY8fU/BlvAbJ2z/8kBwimGki5Zp8/fbVuLY8gJumM=\nk8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0=\nk8s.io/apimachinery v0.22.5/go.mod h1:xziclGKwuuJ2RM5/rSFQSYAj0zdbci3DH8kj+WvyN0U=\nk8s.io/apimachinery v0.23.5 h1:Va7dwhp8wgkUPWsEXk6XglXWU4IKYLKNlv8VkX7SDM0=\nk8s.io/apimachinery v0.23.5/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM=\nk8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU=\nk8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM=\nk8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q=\nk8s.io/apiserver v0.22.5/go.mod h1:s2WbtgZAkTKt679sYtSudEQrTGWUSQAPe6MupLnlmaQ=\nk8s.io/apiserver v0.23.5 h1:2Ly8oUjz5cnZRn1YwYr+aFgDZzUmEVL9RscXbnIeDSE=\nk8s.io/apiserver v0.23.5/go.mod h1:7wvMtGJ42VRxzgVI7jkbKvMbuCbVbgsWFT7RyXiRNTw=\nk8s.io/cli-runtime v0.23.5 h1:Z7XUpGoJZYZB2uNjQfJjMbyDKyVkoBGye62Ap0sWQHY=\nk8s.io/cli-runtime v0.23.5/go.mod h1:oY6QDF2qo9xndSq32tqcmRp2UyXssdGrLfjAVymgbx4=\nk8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y=\nk8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k=\nk8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0=\nk8s.io/client-go v0.22.5/go.mod h1:cs6yf/61q2T1SdQL5Rdcjg9J1ElXSwbjSrW2vFImM4Y=\nk8s.io/client-go v0.23.5 h1:zUXHmEuqx0RY4+CsnkOn5l0GU+skkRXKGJrhmE2SLd8=\nk8s.io/client-go v0.23.5/go.mod h1:flkeinTO1CirYgzMPRWxUCnV0G4Fbu2vLhYCObnt/r4=\nk8s.io/code-generator v0.19.7/go.mod h1:lwEq3YnLYb/7uVXLorOJfxg+cUu2oihFhHZ0n9NIla0=\nk8s.io/code-generator v0.23.5/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk=\nk8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk=\nk8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI=\nk8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM=\nk8s.io/component-base v0.22.5/go.mod h1:VK3I+TjuF9eaa+Ln67dKxhGar5ynVbwnGrUiNF4MqCI=\nk8s.io/component-base v0.23.5 h1:8qgP5R6jG1BBSXmRYW+dsmitIrpk8F/fPEvgDenMCCE=\nk8s.io/component-base v0.23.5/go.mod h1:c5Nq44KZyt1aLl0IpHX82fhsn84Sb0jjzwjpcA42bY0=\nk8s.io/component-helpers v0.23.5/go.mod h1:5riXJgjTIs+ZB8xnf5M2anZ8iQuq37a0B/0BgoPQuSM=\nk8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM=\nk8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI=\nk8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI=\nk8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc=\nk8s.io/cri-api v0.23.1/go.mod h1:REJE3PSU0h/LOV1APBrupxrEJqnoxZC8KWzkBUHwrK4=\nk8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=\nk8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=\nk8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=\nk8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=\nk8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=\nk8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=\nk8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=\nk8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=\nk8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=\nk8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=\nk8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4=\nk8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=\nk8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o=\nk8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM=\nk8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE=\nk8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=\nk8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=\nk8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 h1:E3J9oCLlaobFUqsjG9DfKbP2BmgwBL2p7pn0A3dG9W4=\nk8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk=\nk8s.io/kubectl v0.23.5 h1:DmDULqCaF4qstj0Im143XmncvqWtJxHzK8IrW2BzlU0=\nk8s.io/kubectl v0.23.5/go.mod h1:lLgw7cVY8xbd7o637vOXPca/w6HC205KsPCRDYRCxwE=\nk8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=\nk8s.io/metrics v0.23.5 h1:u+3oGwdUWr30LT2v6MHdSJQ4Pbht9s0cVIWyuY5nf9c=\nk8s.io/metrics v0.23.5/go.mod h1:WNAtV2a5BYbmDS8+7jSqYYV6E3efuGTpIwJ8PTD1wgs=\nk8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=\nk8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=\nk8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=\nk8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=\nk8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=\nk8s.io/utils v0.0.0-20221012122500-cfd413dd9e85 h1:cTdVh7LYu82xeClmfzGtgyspNh6UxpwLWGi8R4sspNo=\nk8s.io/utils v0.0.0-20221012122500-cfd413dd9e85/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=\nlukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=\nlukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=\nmodernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=\nmodernc.org/cc/v3 v3.37.0/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20=\nmodernc.org/cc/v3 v3.38.1/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20=\nmodernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=\nmodernc.org/ccgo/v3 v3.0.0-20220904174949-82d86e1b6d56/go.mod h1:YSXjPL62P2AMSxBphRHPn7IkzhVHqkvOnRKAKh+W6ZI=\nmodernc.org/ccgo/v3 v3.0.0-20220910160915-348f15de615a/go.mod h1:8p47QxPkdugex9J4n9P2tLZ9bK01yngIVp00g4nomW0=\nmodernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo=\nmodernc.org/ccgo/v3 v3.16.13-0.20221017192402-261537637ce8/go.mod h1:fUB3Vn0nVPReA+7IG7yZDfjv1TMWjhQP8gCxrFAtL5g=\nmodernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=\nmodernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=\nmodernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=\nmodernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0=\nmodernc.org/libc v1.17.4/go.mod h1:WNg2ZH56rDEwdropAJeZPQkXmDwh+JCA1s/htl6r2fA=\nmodernc.org/libc v1.18.0/go.mod h1:vj6zehR5bfc98ipowQOM2nIDUZnVew/wNC/2tOGS+q0=\nmodernc.org/libc v1.19.0/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0=\nmodernc.org/libc v1.20.3/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0=\nmodernc.org/libc v1.21.4/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI=\nmodernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0=\nmodernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug=\nmodernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=\nmodernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=\nmodernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=\nmodernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=\nmodernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=\nmodernc.org/memory v1.3.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=\nmodernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=\nmodernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=\nmodernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=\nmodernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=\nmodernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=\nmodernc.org/sqlite v1.19.1/go.mod h1:UfQ83woKMaPW/ZBruK0T7YaFCrI+IE0LeWVY6pmnVms=\nmodernc.org/sqlite v1.20.3 h1:SqGJMMxjj1PHusLxdYxeQSodg7Jxn9WWkaAQjKrntZs=\nmodernc.org/sqlite v1.20.3/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A=\nmodernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=\nmodernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=\nmodernc.org/tcl v1.14.0/go.mod h1:gQ7c1YPMvryCHCcmf8acB6VPabE59QBeuRQLL7cTUlM=\nmodernc.org/tcl v1.15.0/go.mod h1:xRoGotBZ6dU+Zo2tca+2EqVEeMmOUBzHnhIwq4YrVnE=\nmodernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=\nmodernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=\nmodernc.org/z v1.6.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ=\nmodernc.org/z v1.7.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ=\noras.land/oras-go v1.1.1 h1:gI00ftziRivKXaw1BdMeEoIA4uBgga33iVlOsEwefFs=\noras.land/oras-go v1.1.1/go.mod h1:n2TE1ummt9MUyprGhT+Q7kGZUF4kVUpYysPFxeV2IpQ=\nrsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=\nrsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=\nrsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=\nsigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=\nsigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=\nsigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=\nsigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30/go.mod h1:fEO7lRTdivWO2qYVCVG7dEADOMo/MLDCVr8So2g88Uw=\nsigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s=\nsigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs=\nsigs.k8s.io/kustomize/api v0.10.1 h1:KgU7hfYoscuqag84kxtzKdEC3mKMb99DPI3a0eaV1d0=\nsigs.k8s.io/kustomize/api v0.10.1/go.mod h1:2FigT1QN6xKdcnGS2Ppp1uIWrtWN28Ms8A3OZUZhwr8=\nsigs.k8s.io/kustomize/cmd/config v0.10.2/go.mod h1:K2aW7nXJ0AaT+VA/eO0/dzFLxmpFcTzudmAgDwPY1HQ=\nsigs.k8s.io/kustomize/kustomize/v4 v4.4.1/go.mod h1:qOKJMMz2mBP+vcS7vK+mNz4HBLjaQSWRY22EF6Tb7Io=\nsigs.k8s.io/kustomize/kyaml v0.13.0 h1:9c+ETyNfSrVhxvphs+K2dzT3dh5oVPPEqPOE/cUpScY=\nsigs.k8s.io/kustomize/kyaml v0.13.0/go.mod h1:FTJxEZ86ScK184NpGSAQcfEqee0nul8oLCK30D47m4E=\nsigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=\nsigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=\nsigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=\nsigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=\nsigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=\nsigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y=\nsigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=\nsigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=\nsigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=\nsigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=\nsigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=\n"
  },
  {
    "path": "hack/tools/licfmt/licfmt.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"time\"\n\n\t\"github.com/bmatcuk/doublestar/v4\"\n\t\"golang.org/x/sync/errgroup\"\n)\n\nconst tpml = `/*\nCopyright %s The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n`\n\nvar (\n\tignorePatterns = stringSlice{\"vendor/**\", \"docs/**\"}\n\tyear           = flag.String(\"y\", fmt.Sprint(time.Now().Year()), \"copyright year(s)\")\n\tverbose        = flag.Bool(\"v\", false, \"verbose mode: print the name of the files that are modified or were skipped\")\n)\n\nfunc init() {\n\tflag.Var(&ignorePatterns, \"i\", \"file patterns to ignore, for example: -i vendor/**\")\n}\n\ntype stringSlice []string\n\nfunc (i *stringSlice) String() string {\n\treturn fmt.Sprint(*i)\n}\n\nfunc (i *stringSlice) Set(value string) error {\n\t*i = append(*i, value)\n\treturn nil\n}\n\ntype file struct {\n\tpath string\n\tmode os.FileMode\n}\n\nfunc main() {\n\tflag.Parse()\n\tif flag.NArg() == 0 {\n\t\tflag.Usage()\n\t\tos.Exit(1)\n\t}\n\n\tfor _, p := range ignorePatterns {\n\t\tif !doublestar.ValidatePattern(p) {\n\t\t\tlog.Fatalf(\"ignore pattern %q is invalid\", p)\n\t\t}\n\t}\n\n\theaderText := fmt.Sprintf(tpml, *year)\n\n\t// process at most CPU number files in parallel\n\tch := make(chan *file, runtime.NumCPU())\n\tdone := make(chan struct{})\n\tgo func() {\n\t\tvar wg errgroup.Group\n\t\tfor f := range ch {\n\t\t\tf := f\n\t\t\twg.Go(func() error {\n\t\t\t\tupdated, err := addLicenseHeader(f.path, headerText, f.mode)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Printf(\"%s: %v\", f.path, err)\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif *verbose && updated {\n\t\t\t\t\tlog.Printf(\"%s updated\", f.path)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t})\n\t\t}\n\t\terr := wg.Wait()\n\t\tclose(done)\n\t\tif err != nil {\n\t\t\tos.Exit(1)\n\t\t}\n\t}()\n\n\tfor _, d := range flag.Args() {\n\t\tif err := walk(ch, d); err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t}\n\tclose(ch)\n\t<-done\n}\n\n// walk walks the file tree from root, sends go file to task queue.\nfunc walk(ch chan<- *file, root string) error {\n\treturn filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {\n\t\tif err != nil {\n\t\t\tlog.Printf(\"%s error: %v\", path, err)\n\t\t}\n\t\tif fi.IsDir() {\n\t\t\treturn nil\n\t\t}\n\t\tif match(path, ignorePatterns) || filepath.Ext(path) != \".go\" {\n\t\t\tif *verbose {\n\t\t\t\tlog.Printf(\"skipping: %s\", path)\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t\tch <- &file{path, fi.Mode()}\n\t\treturn nil\n\t})\n}\n\n// match returns if path matches one of the provided file patterns.\nfunc match(path string, patterns []string) bool {\n\tfor _, p := range patterns {\n\t\tif ok, _ := doublestar.Match(p, path); ok {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// hasLicense returns if there is a license in the file header already.\nfunc hasLicense(header []byte) bool {\n\treturn bytes.Contains(header, []byte(\"Copyright\")) &&\n\t\tbytes.Contains(header, []byte(\"Apache License\"))\n}\n\nfunc notAlowEditd(header []byte) bool {\n\treturn bytes.Contains(header, []byte(\"DO NOT EDIT\")) ||\n\t\tbytes.Contains(header, []byte(\"generated by\"))\n}\n\n// addLicenseHeader adds a license header to the file if it does not exist.\nfunc addLicenseHeader(path, license string, fmode os.FileMode) (bool, error) {\n\tf, err := os.OpenFile(path, os.O_RDONLY, fmode)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer f.Close()\n\n\tbuf, dropped := scanLines(f)\n\tif hasLicense(buf.Bytes()) || notAlowEditd(buf.Bytes()) {\n\t\treturn false, nil\n\t}\n\n\t// add license header at the beginning\n\tnew := bytes.NewBuffer(buf.Bytes())\n\tif _, err := new.WriteString(license + \"\\n\"); err != nil {\n\t\treturn false, err\n\t}\n\n\t// Caculate position of the line contains `package `.\n\toffset := int64(buf.Len())\n\tif dropped > 1 {\n\t\t// CR characters are dropped, they should be added back.\n\t\toffset += dropped - 1\n\t}\n\t// Move the cursor of original go file to the position.\n\tif _, err := f.Seek(offset, io.SeekStart); err != nil {\n\t\treturn false, err\n\t}\n\n\tif _, err := new.ReadFrom(f); err != nil {\n\t\treturn false, err\n\t}\n\n\t// create a temporary file\n\ttmpFile, err := os.CreateTemp(\"\", \"licfmt-\")\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer os.Remove(tmpFile.Name())\n\tdefer tmpFile.Close()\n\n\tif err = os.Chmod(tmpFile.Name(), fmode); err != nil {\n\t\treturn false, err\n\t}\n\n\t// rewrite the whole file to the temporary file\n\tif _, err := new.WriteTo(tmpFile); err != nil {\n\t\treturn false, err\n\t}\n\n\tif err = tmpFile.Sync(); err != nil {\n\t\treturn false, err\n\t}\n\n\t// rename the tmp file to the original file\n\terr = os.Rename(tmpFile.Name(), path)\n\treturn err == nil, err\n}\n\n// scanLines handles the file line by line\n// buf: the lines before the package declaration\n// dropped: the number of CR (`\\r`) characters dropped\nfunc scanLines(file *os.File) (buf bytes.Buffer, dropped int64) {\n\tscanner := bufio.NewScanner(file)\n\tdropCR := func(data []byte) []byte {\n\t\tif len(data) > 0 && data[len(data)-1] == '\\r' {\n\t\t\tdropped++\n\t\t\treturn data[0 : len(data)-1]\n\t\t}\n\t\treturn data\n\t}\n\tscanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {\n\t\tif atEOF && len(data) == 0 {\n\t\t\treturn 0, nil, nil\n\t\t}\n\n\t\tif i := bytes.IndexByte(data, '\\n'); i >= 0 {\n\t\t\t// We have a full newline-terminated line.\n\t\t\treturn i + 1, dropCR(data[0:i]), nil\n\t\t}\n\t\t// If we're at EOF, we have a final, non-terminated line. Return it.\n\t\tif atEOF {\n\t\t\treturn len(data), dropCR(data), nil\n\t\t}\n\t\t// Request more data.\n\t\treturn 0, nil, nil\n\t})\n\tfor scanner.Scan() {\n\t\tline := scanner.Bytes()\n\t\tif bytes.HasPrefix(line, []byte(\"package \")) {\n\t\t\t// reading until the line contains `package p`\n\t\t\tbreak\n\t\t}\n\t\tbuf.Write(line)\n\t\tbuf.WriteString(\"\\n\")\n\t}\n\treturn\n}\n"
  },
  {
    "path": "hack/update-gofmt.sh",
    "content": "#!/bin/bash\n\n# Copyright 2021 The Pixiu Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nset -o nounset\nset -o errexit\nset -o pipefail\n\nfind . -name \"*.go\" | grep -v vendor | xargs gofmt -s -w\n"
  },
  {
    "path": "hack/update-image.sh",
    "content": "#!/bin/bash\nfunction update() {\n    docker ccr.ccs.tencentyun.com/pixiucloud/pixiu-aio:latest\n    docker rm -f pixiu-aio\n    sleep 3\n    docker run -d --net host --restart=always --privileged=true -v /etc/pixiu:/etc/pixiu -v /var/run/docker.sock:/var/run/docker.sock --name pixiu-aio ccr.ccs.tencentyun.com/pixiucloud/pixiu-aio:latest\n}\n\nupdate\n"
  },
  {
    "path": "hack/verify-gofmt.sh",
    "content": "#!/bin/bash\n\n# Copyright 2021 The Pixiu Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nset -o errexit\nset -o nounset\nset -o pipefail\n\nHELM_ROOT=$(dirname \"${BASH_SOURCE}\")/..\ncd \"${HELM_ROOT}\"\n\nfind_files() {\n  find . -not \\( \\\n      \\( \\\n        -wholename './output' \\\n        -o -wholename '*/vendor/*' \\\n      \\) -prune \\\n    \\) -name '*.go'\n}\n\nGOFMT=\"gofmt -s\"\n\nbad_files=$(find_files | xargs $GOFMT -l)\nif [[ -n \"${bad_files}\" ]]; then\n  echo \"Please run hack/update-gofmt.sh to fix the following files:\"\n  echo \"${bad_files}\"\n  exit 1\nfi\n"
  },
  {
    "path": "install.md",
    "content": "# 前置准备\n```bash\n确保 docker 已经安装\n```\n# 数据库\n```bash\n# 选择1：直接提供可用数据库\n\n# 选择2：快速启动数据库\ndocker run -d --net host --restart=always --privileged=true --name mariadb -e MYSQL_ROOT_PASSWORD=\"Pixiu868686\" ccr.ccs.tencentyun.com/pixiucloud/mysql:5.7\n\n# 创建 pixiu 数据库\nCREATE DATABASE pixiu;\n```\n\n# 获取部署驱动镜像\n```shell\ndocker pull ccr.ccs.tencentyun.com/pixiucloud/kubez-ansible:v2.0.1\ndocker pull ccr.ccs.tencentyun.com/pixiucloud/kubez-ansible:v3.0.1\n```\n\n# 启动 pixiu 服务端\n```bash\n# 创建配置文件夹\nmkdir -p /etc/pixiu/\n\n# 后端配置(host 根据实际情况调整)\nvim /etc/pixiu/config.yaml 写入后端如下配置\ndefault:\n  # 运行模式，可选 debug 和 release\n  mode: debug\n  # 服务监听端口\n  listen: 8090\n  # 自动创建指定模型的数据库表结构，不会更新已存在的数据库表\n  auto_migrate: true\n\n# 前端配置(ip 根据实际情况调整，如果是虚拟机，则配置成虚拟机的公网IP，安全组放通80(http)或者443(https)端口)\n# 前端的端口需要和后端监听端口保持一致\ndashboard:\n  url: http://localhost:8090\n\n# 数据库地址信息, 根据实际情况配置\nmysql:\n  host: pixiu # 数据库的ip\n  user: root\n  password: Pixiu868686\n  port: 3306\n  name: pixiu\n\nworker:\n  engines:\n    - image: ccr.ccs.tencentyun.com/pixiucloud/kubez-ansible:v2.0.1\n      os_supported:\n        - centos7\n        - debian10\n        - ubuntu18.04\n    - image: ccr.ccs.tencentyun.com/pixiucloud/kubez-ansible:v3.0.1\n      os_supported:\n        - debian11\n        - ubuntu20.04\n        - ubuntu22.04\n        - rocky8.5\n        - rocky9.2\n        - rocky9.3\n        - openEuler22.03\n```\n# 启动 pixiu\n```bash\ndocker run -d --net host --restart=always --privileged=true -v /etc/pixiu:/etc/pixiu -v /var/run/docker.sock:/var/run/docker.sock --name pixiu ccr.ccs.tencentyun.com/pixiucloud/pixiu\n登录效果\n浏览器登陆: http://192.168.16.156\n```\n\n\n\n"
  },
  {
    "path": "pkg/client/cache.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"context\"\n\t\"sync\"\n\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/client-go/informers\"\n\t\"k8s.io/client-go/kubernetes\"\n\tappsv1 \"k8s.io/client-go/listers/apps/v1\"\n\tbatchv1 \"k8s.io/client-go/listers/batch/v1\"\n\tv1 \"k8s.io/client-go/listers/core/v1\"\n\trestclient \"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n\tresourceclient \"k8s.io/metrics/pkg/client/clientset/versioned/typed/metrics/v1beta1\"\n)\n\nvar (\n\tgroupVersionResources = []schema.GroupVersionResource{\n\t\t{Group: \"\", Version: \"v1\", Resource: \"pods\"},\n\t\t{Group: \"\", Version: \"v1\", Resource: \"nodes\"},\n\t\t{Group: \"apps\", Version: \"v1\", Resource: \"deployments\"},\n\t\t{Group: \"apps\", Version: \"v1\", Resource: \"statefulsets\"},\n\t\t{Group: \"apps\", Version: \"v1\", Resource: \"daemonsets\"},\n\t\t{Group: \"batch\", Version: \"v1\", Resource: \"cronjobs\"},\n\t\t{Group: \"batch\", Version: \"v1\", Resource: \"jobs\"},\n\t}\n)\n\ntype PixiuInformer struct {\n\tShared informers.SharedInformerFactory\n\tCancel context.CancelFunc\n}\n\nfunc (p PixiuInformer) NodesLister() v1.NodeLister {\n\treturn p.Shared.Core().V1().Nodes().Lister()\n}\n\nfunc (p PixiuInformer) PodsLister() v1.PodLister {\n\treturn p.Shared.Core().V1().Pods().Lister()\n}\n\nfunc (p PixiuInformer) NamespacesLister() v1.NamespaceLister {\n\treturn p.Shared.Core().V1().Namespaces().Lister()\n}\n\nfunc (p PixiuInformer) DeploymentsLister() appsv1.DeploymentLister {\n\treturn p.Shared.Apps().V1().Deployments().Lister()\n}\n\nfunc (p *PixiuInformer) StatefulSetsLister() appsv1.StatefulSetLister {\n\treturn p.Shared.Apps().V1().StatefulSets().Lister()\n}\n\nfunc (p *PixiuInformer) DaemonSetsLister() appsv1.DaemonSetLister {\n\treturn p.Shared.Apps().V1().DaemonSets().Lister()\n}\n\nfunc (p *PixiuInformer) CronJobsLister() batchv1.CronJobLister {\n\treturn p.Shared.Batch().V1().CronJobs().Lister()\n}\n\nfunc (p *PixiuInformer) JobsLister() batchv1.JobLister { return p.Shared.Batch().V1().Jobs().Lister() }\n\ntype ClusterSet struct {\n\tClient   *kubernetes.Clientset\n\tConfig   *restclient.Config\n\tMetric   *resourceclient.MetricsV1beta1Client\n\tInformer *PixiuInformer\n}\n\nfunc (cs *ClusterSet) Complete(cfg []byte) error {\n\tvar err error\n\tif cs.Config, err = clientcmd.RESTConfigFromKubeConfig(cfg); err != nil {\n\t\treturn err\n\t}\n\tif cs.Client, err = kubernetes.NewForConfig(cs.Config); err != nil {\n\t\treturn err\n\t}\n\tif cs.Metric, err = resourceclient.NewForConfig(cs.Config); err != nil {\n\t\treturn err\n\t}\n\n\tsharedInformer, cancel, err := NewSharedInformers(cs.Config)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcs.Informer = &PixiuInformer{\n\t\tShared: sharedInformer,\n\t\tCancel: cancel,\n\t}\n\treturn nil\n}\n\nfunc NewSharedInformers(c *restclient.Config) (informers.SharedInformerFactory, context.CancelFunc, error) {\n\t// 重新构造客户端\n\tclientSet, err := kubernetes.NewForConfig(c)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tinformerFactory := informers.NewSharedInformerFactory(clientSet, 0)\n\tfor _, gvr := range groupVersionResources {\n\t\tif _, err = informerFactory.ForResource(gvr); err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t}\n\n\tctx, cancel := context.WithCancel(context.Background())\n\t// Start all informers.\n\tinformerFactory.Start(ctx.Done())\n\t// Wait for all caches to sync.\n\tinformerFactory.WaitForCacheSync(ctx.Done())\n\n\treturn informerFactory, cancel, nil\n}\n\ntype store map[string]ClusterSet\n\ntype Cache struct {\n\tsync.RWMutex\n\tstore\n}\n\nfunc NewClusterCache() *Cache {\n\treturn &Cache{\n\t\tstore: make(store),\n\t}\n}\n\nfunc (s *Cache) Get(name string) (ClusterSet, bool) {\n\ts.RLock()\n\tdefer s.RUnlock()\n\n\tcluster, ok := s.store[name]\n\treturn cluster, ok\n}\n\nfunc (s *Cache) GetConfig(name string) (*restclient.Config, bool) {\n\ts.RLock()\n\tdefer s.RUnlock()\n\n\tclusterSet, ok := s.store[name]\n\tif !ok {\n\t\treturn nil, false\n\t}\n\treturn clusterSet.Config, true\n}\n\nfunc (s *Cache) GetClient(name string) (*kubernetes.Clientset, bool) {\n\ts.RLock()\n\tdefer s.RUnlock()\n\n\tclusterSet, ok := s.store[name]\n\tif !ok {\n\t\treturn nil, false\n\t}\n\n\treturn clusterSet.Client, true\n}\n\nfunc (s *Cache) Set(name string, cs ClusterSet) {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tif s.store == nil {\n\t\ts.store = store{}\n\t}\n\ts.store[name] = cs\n}\n\nfunc (s *Cache) Delete(name string) {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\t// Cancel informer\n\tcluster, ok := s.store[name]\n\tif !ok {\n\t\treturn\n\t}\n\tcluster.Informer.Cancel()\n\n\t// 从缓存移除集群数据\n\tdelete(s.store, name)\n}\n\nfunc (s *Cache) List() store {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\treturn s.store\n}\n\nfunc (s *Cache) Clear() {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\ts.store = store{}\n}\n"
  },
  {
    "path": "pkg/client/client.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"encoding/base64\"\n\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n)\n\nfunc ParseKubeConfigBytes(cfg string) ([]byte, error) {\n\tkubeConfigBytes, err := base64.StdEncoding.DecodeString(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn kubeConfigBytes, err\n}\n\nfunc NewClientSetFromBytes(data []byte) (*kubernetes.Clientset, error) {\n\tconfig, err := clientcmd.RESTConfigFromKubeConfig(data)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn kubernetes.NewForConfig(config)\n}\n\nfunc NewClientSetFromString(cfg string) (*kubernetes.Clientset, error) {\n\tkubeConfigBytes, err := ParseKubeConfigBytes(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn NewClientSetFromBytes(kubeConfigBytes)\n}\n\nfunc NewClusterSet(cfg string) (*ClusterSet, error) {\n\tkubeConfigBytes, err := ParseKubeConfigBytes(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcs := &ClusterSet{}\n\tif err = cs.Complete(kubeConfigBytes); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn cs, nil\n}\n"
  },
  {
    "path": "pkg/client/helm.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"k8s.io/apimachinery/pkg/api/meta\"\n\t\"k8s.io/cli-runtime/pkg/genericclioptions\"\n\t\"k8s.io/client-go/discovery\"\n\tmemory \"k8s.io/client-go/discovery/cached\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/restmapper\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n)\n\ntype HelmRESTClientGetter struct {\n\tkubeConfig *rest.Config\n}\n\nvar _ genericclioptions.RESTClientGetter = &HelmRESTClientGetter{}\n\n// ToDiscoveryClient implements action.RESTClientGetter.\nfunc (h *HelmRESTClientGetter) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {\n\th.kubeConfig.Burst = 100\n\tdiscoveryClient, err := discovery.NewDiscoveryClientForConfig(h.kubeConfig)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn memory.NewMemCacheClient(discoveryClient), nil\n}\n\n// ToRESTConfig implements action.RESTClientGetter.\nfunc (h *HelmRESTClientGetter) ToRESTConfig() (*rest.Config, error) {\n\treturn h.kubeConfig, nil\n}\n\n// ToRESTMapper implements action.RESTClientGetter.\nfunc (h *HelmRESTClientGetter) ToRESTMapper() (meta.RESTMapper, error) {\n\n\tdiscoveryClient, err := h.ToDiscoveryClient()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tmapper := restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient)\n\texpander := restmapper.NewShortcutExpander(mapper, discoveryClient)\n\treturn expander, nil\n}\n\nfunc (h *HelmRESTClientGetter) ToRawKubeConfigLoader() clientcmd.ClientConfig {\n\tloadingRules := clientcmd.NewDefaultClientConfigLoadingRules()\n\tloadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig\n\toverrides := &clientcmd.ConfigOverrides{ClusterDefaults: clientcmd.ClusterDefaults}\n\treturn clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides)\n}\n\nfunc NewHelmRESTClientGetter(kubeConfig *rest.Config) *HelmRESTClientGetter {\n\treturn &HelmRESTClientGetter{\n\t\tkubeConfig: kubeConfig,\n\t}\n}\n"
  },
  {
    "path": "pkg/client/task.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\t\"k8s.io/apimachinery/pkg/util/wait\"\n\t\"k8s.io/klog/v2\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/db\"\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n)\n\ntype WrapObject struct {\n\tLatestTime time.Time    // 最近一次获取时间\n\tObject     []model.Task // TODO，临时实现，使用task的结构，后续优化成任意类型\n}\n\ntype Task struct {\n\tsync.RWMutex\n\n\tLister func(ctx context.Context, planId int64, opts ...db.Options) ([]model.Task, error)\n\titems  map[int64]WrapObject\n}\n\nfunc NewTaskCache() *Task {\n\tt := &Task{items: make(map[int64]WrapObject)}\n\tt.Run()\n\n\treturn t\n}\n\nfunc (t *Task) SetLister(Lister func(ctx context.Context, planId int64, opts ...db.Options) ([]model.Task, error)) {\n\tt.Lock()\n\tdefer t.Unlock()\n\n\tt.Lister = Lister\n}\n\nfunc (t *Task) Get(planId int64) ([]model.Task, bool) {\n\tt.Lock()\n\tdefer t.Unlock()\n\n\twrapObject, ok := t.items[planId]\n\tif !ok {\n\t\treturn nil, false\n\t}\n\n\twrapObject.LatestTime = time.Now()\n\tt.items[planId] = wrapObject\n\treturn wrapObject.Object, ok\n}\n\nfunc (t *Task) Set(planId int64, tasks []model.Task) {\n\tt.Lock()\n\tdefer t.Unlock()\n\n\tif t.items == nil {\n\t\tt.items = map[int64]WrapObject{}\n\t}\n\n\tnow := time.Now()\n\tklog.Infof(\"add plan(%d) tasks into cache at %v\", planId, now)\n\tt.items[planId] = WrapObject{\n\t\tObject:     tasks,\n\t\tLatestTime: now,\n\t}\n}\n\nfunc (t *Task) SetByTask(planId int64, task model.Task) {\n\tt.Lock()\n\tdefer t.Unlock()\n\n\twrapObject, ok := t.items[planId]\n\tif !ok {\n\t\treturn\n\t}\n\n\tvar (\n\t\tindex int\n\t\tfound bool\n\t)\n\tfor i, s := range wrapObject.Object {\n\t\tif s.Id == task.Id {\n\t\t\tindex = i\n\t\t\tfound = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !found {\n\t\treturn\n\t}\n\n\twrapObject.Object[index] = task\n\twrapObject.LatestTime = time.Now()\n\n\tt.items[planId] = wrapObject\n}\n\nfunc (t *Task) Delete(planId int64) {\n\tt.Lock()\n\tdefer t.Unlock()\n\n\tdelete(t.items, planId)\n}\n\n// WaitForCacheSync\n// 判断缓存中是否已经存在，如果不存在则先写入\nfunc (t *Task) WaitForCacheSync(planId int64) error {\n\t_, ok := t.Get(planId)\n\tif ok {\n\t\treturn nil\n\t}\n\n\ttasks, err := t.Lister(context.TODO(), planId)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get plan(%d) tasks from database: %v\", planId, err)\n\t}\n\tt.Set(planId, tasks)\n\n\treturn nil\n}\n\nfunc (t *Task) syncTasks() {\n\tif t.Lister == nil || len(t.items) == 0 {\n\t\tklog.V(2).Infof(\"syncing and waiting for the next loop\")\n\t\treturn\n\t}\n\n\tt.Lock()\n\tdefer t.Unlock()\n\n\tfor planId, wrapObject := range t.items {\n\t\tnow := time.Now()\n\t\tif now.Sub(wrapObject.LatestTime) > 5*time.Second {\n\t\t\t// 如果对象5分钟未被操作，则从缓存中清理\n\t\t\tklog.Infof(\"remove plan(%d) tasks from cache due to it not be handled for 5s\", planId)\n\t\t\tdelete(t.items, planId)\n\t\t\t// 处理下一个对象\n\t\t\tcontinue\n\t\t}\n\n\t\tnewTasks, err := t.Lister(context.TODO(), planId)\n\t\tif err != nil {\n\t\t\tklog.Errorf(\"[syncTasks] failed to list plan(%d) tasks: %v\", planId, err)\n\t\t\tdelete(t.items, planId)\n\t\t\tcontinue\n\t\t}\n\t\tt.items[planId] = WrapObject{\n\t\t\tObject:     newTasks,\n\t\t\tLatestTime: now,\n\t\t}\n\t}\n}\n\n// Run 启动 syncTasks\n// TODO: 后续优化 stopCh\nfunc (t *Task) Run() {\n\tstopCh := make(<-chan struct{})\n\t// 10s 主动加载一次 task\n\t// 不发起长连接情况下，无任何开销\n\tgo wait.Until(t.syncTasks, 10*time.Second, stopCh)\n}\n"
  },
  {
    "path": "pkg/client/token_cache.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport \"sync\"\n\n// TokenCache\n// TODO: 临时实现，后续优化\ntype TokenCache struct {\n\tsync.RWMutex\n\titems map[int64]string\n}\n\nfunc NewTokenCache() *TokenCache {\n\treturn &TokenCache{\n\t\titems: map[int64]string{},\n\t}\n}\n\nfunc (s *TokenCache) Get(uid int64) (string, bool) {\n\ts.RLock()\n\tdefer s.RUnlock()\n\n\tt, ok := s.items[uid]\n\treturn t, ok\n}\n\nfunc (s *TokenCache) Set(uid int64, token string) {\n\ts.RLock()\n\tdefer s.RUnlock()\n\n\tif s.items == nil {\n\t\ts.items = map[int64]string{}\n\t}\n\ts.items[uid] = token\n}\n\nfunc (s *TokenCache) Delete(uid int64) {\n\ts.RLock()\n\tdefer s.RUnlock()\n\n\tdelete(s.items, uid)\n}\n\nfunc (s *TokenCache) Clear() {\n\ts.RLock()\n\tdefer s.RUnlock()\n\n\ts.items = map[int64]string{}\n}\n"
  },
  {
    "path": "pkg/client/user_cache.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage client\n\nimport \"sync\"\n\ntype UserCache struct {\n\tsync.RWMutex\n\titems map[int64]int\n}\n\nfunc NewUserCache() *UserCache {\n\treturn &UserCache{\n\t\titems: map[int64]int{},\n\t}\n}\n\nfunc (s *UserCache) Get(uid int64) (int, bool) {\n\ts.RLock()\n\tdefer s.RUnlock()\n\n\tstatus, ok := s.items[uid]\n\treturn status, ok\n}\n\nfunc (s *UserCache) Set(uid int64, status int) {\n\ts.RLock()\n\tdefer s.RUnlock()\n\n\tif s.items == nil {\n\t\ts.items = map[int64]int{}\n\t}\n\ts.items[uid] = status\n}\n\nfunc (s *UserCache) Delete(uid int64) {\n\ts.RLock()\n\tdefer s.RUnlock()\n\n\tdelete(s.items, uid)\n}\n\nfunc (s *UserCache) Clear() {\n\ts.RLock()\n\tdefer s.RUnlock()\n\n\ts.items = map[int64]int{}\n}\n"
  },
  {
    "path": "pkg/controller/audit/audit.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage audit\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"k8s.io/klog/v2\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/errors\"\n\t\"github.com/caoyingjunz/pixiu/cmd/app/config\"\n\t\"github.com/caoyingjunz/pixiu/pkg/db\"\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n)\n\ntype AuditGetter interface {\n\tAudit() Interface\n}\n\ntype Interface interface {\n\tList(ctx context.Context, listOption types.AuditListOptions) (interface{}, error)\n\tGet(ctx context.Context, aid int64) (*types.Audit, error)\n}\n\ntype audit struct {\n\tcc      config.Config\n\tfactory db.ShareDaoFactory\n}\n\nfunc (a *audit) Get(ctx context.Context, aid int64) (*types.Audit, error) {\n\tobject, err := a.factory.Audit().Get(ctx, aid)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get audit %d: %v\", aid, err)\n\t\treturn nil, errors.ErrServerInternal\n\t}\n\tif object == nil {\n\t\treturn nil, errors.ErrAuditNotFound\n\t}\n\treturn a.model2Type(object), nil\n}\n\nfunc (a *audit) List(ctx context.Context, listOption types.AuditListOptions) (interface{}, error) {\n\t// 构建公共过滤 opts\n\tfilterOpts := buildAuditFilterOpts(listOption)\n\n\t// 使用相同过滤条件获取总数\n\ttotal, err := a.factory.Audit().Count(ctx, filterOpts...)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get audits count: %v\", err)\n\t\treturn nil, err\n\t}\n\n\tpage := listOption.Page\n\tif page <= 0 {\n\t\tpage = 1\n\t}\n\tlimit := int(listOption.Limit)\n\tif limit <= 0 {\n\t\tlimit = 20\n\t}\n\n\tpaginationOpts := append(filterOpts,\n\t\tdb.WithOffset((page-1)*limit),\n\t\tdb.WithLimit(limit),\n\t\tdb.WithOrderByDesc(),\n\t)\n\n\tobjects, err := a.factory.Audit().List(ctx, paginationOpts...)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get audit events: %v\", err)\n\t\treturn nil, errors.ErrServerInternal\n\t}\n\n\tvar ts []types.Audit\n\tfor _, object := range objects {\n\t\tts = append(ts, *a.model2Type(&object))\n\t}\n\treturn types.PageResponse{\n\t\tPageRequest: listOption.PageRequest,\n\t\tTotal:       int(total),\n\t\tItems:       ts,\n\t}, nil\n}\n\nfunc buildAuditFilterOpts(opt types.AuditListOptions) []db.Options {\n\tvar opts []db.Options\n\tif opt.Operator != \"\" {\n\t\topts = append(opts, db.WithAuditOperatorLike(opt.Operator))\n\t}\n\tif opt.Action != \"\" {\n\t\topts = append(opts, db.WithAuditAction(opt.Action))\n\t}\n\tif opt.ObjectType != \"\" {\n\t\topts = append(opts, db.WithAuditObjectType(opt.ObjectType))\n\t}\n\tif opt.Cluster != \"\" {\n\t\topts = append(opts, db.WithAuditCluster(opt.Cluster))\n\t}\n\tif opt.Status != nil {\n\t\topts = append(opts, db.WithAuditStatus(*opt.Status))\n\t}\n\tif opt.StartTime != \"\" {\n\t\tif t, err := time.Parse(time.RFC3339, opt.StartTime); err == nil {\n\t\t\topts = append(opts, db.WithAuditCreatedAfter(t))\n\t\t}\n\t}\n\tif opt.EndTime != \"\" {\n\t\tif t, err := time.Parse(time.RFC3339, opt.EndTime); err == nil {\n\t\t\topts = append(opts, db.WithCreatedBefore(t))\n\t\t}\n\t}\n\treturn opts\n}\n\nfunc (a *audit) model2Type(o *model.Audit) *types.Audit {\n\treturn &types.Audit{\n\t\tPixiuMeta: types.PixiuMeta{\n\t\t\tId:              o.Id,\n\t\t\tResourceVersion: o.ResourceVersion,\n\t\t},\n\t\tTimeMeta: types.TimeMeta{\n\t\t\tGmtCreate:   o.GmtCreate,\n\t\t\tGmtModified: o.GmtModified,\n\t\t},\n\t\tIp:                o.Ip,\n\t\tAction:            o.Action,\n\t\tStatus:            o.Status,\n\t\tOperator:          o.Operator,\n\t\tPath:              o.Path,\n\t\tObjectType:        o.ObjectType,\n\t\tDuration:          o.Duration,\n\t\tResponseCode:      o.ResponseCode,\n\t\tCluster:           o.Cluster,\n\t\tResourceName:      o.ResourceName,\n\t\tResourceNamespace: o.ResourceNamespace,\n\t}\n}\n\nfunc NewAudit(cfg config.Config, f db.ShareDaoFactory) *audit {\n\treturn &audit{\n\t\tcc:      cfg,\n\t\tfactory: f,\n\t}\n}\n"
  },
  {
    "path": "pkg/controller/auth/auth.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage auth\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/casbin/casbin/v2\"\n\t\"k8s.io/klog/v2\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/errors\"\n\tctrlutil \"github.com/caoyingjunz/pixiu/pkg/controller/util\"\n\t\"github.com/caoyingjunz/pixiu/pkg/db\"\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n)\n\ntype AuthGetter interface {\n\tAuth() Interface\n}\n\ntype (\n\tInterface interface {\n\t\tCreateRBACPolicy(ctx context.Context, req *types.RBACPolicyRequest) error\n\t\tDeleteRBACPolicy(ctx context.Context, req *types.RBACPolicyRequest) error\n\t\tListRBACPolicies(ctx context.Context, req *types.ListRBACPolicyRequest) ([]types.RBACPolicy, error)\n\n\t\tCreateGroupBinding(ctx context.Context, req *types.GroupBindingRequest) error\n\t\tDeleteGroupBinding(ctx context.Context, req *types.GroupBindingRequest) error\n\t\tListGroupBindings(ctx context.Context, req *types.ListGroupBindingRequest) ([]types.RBACPolicy, error)\n\t}\n)\n\ntype auth struct {\n\tenforcer *casbin.SyncedEnforcer\n\tfactory  db.ShareDaoFactory\n}\n\nfunc NewAuth(factory db.ShareDaoFactory, enforcer *casbin.SyncedEnforcer) Interface {\n\treturn &auth{\n\t\tfactory:  factory,\n\t\tenforcer: enforcer,\n\t}\n}\n\n// getPolicy returns the RBAC policy represented by the request body\nfunc (a *auth) getPolicy(ctx context.Context, req *types.RBACPolicyRequest) (model.Policy, error) {\n\tif req.UserId != nil {\n\t\t// user RBAC policy\n\t\tuser, err := a.factory.User().Get(ctx, *req.UserId)\n\t\tif err != nil {\n\t\t\tklog.Errorf(\"failed to get user(%d): %v\", *req.UserId, err)\n\t\t\treturn nil, errors.ErrServerInternal\n\t\t}\n\t\tif user == nil {\n\t\t\treturn nil, errors.NewError(fmt.Errorf(\"user(%d) is not found\", *req.UserId), http.StatusBadRequest)\n\t\t}\n\t\treturn model.NewUserPolicy(user.Name, req.ObjectType, req.SID, req.Operation), nil\n\t}\n\t// group RBAC policy\n\treturn model.NewGroupPolicy(*req.GroupName, req.ObjectType, req.SID, req.Operation), nil\n}\n\n// getBinding returns the group binding policy  represented by the request body\nfunc (a *auth) getBinding(ctx context.Context, req *types.GroupBindingRequest) (model.Policy, error) {\n\tuser, err := a.factory.User().Get(ctx, req.UserId)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get user(%d): %v\", req.UserId, err)\n\t\treturn nil, errors.ErrServerInternal\n\t}\n\tif user == nil {\n\t\treturn nil, errors.NewError(fmt.Errorf(\"user(%d) is not found\", req.UserId), http.StatusBadRequest)\n\t}\n\n\tpolicy, err := ctrlutil.GetGroupPolicy(a.enforcer, req.GroupName)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get group(%d): %v\", req.GroupName, err)\n\t\treturn nil, errors.ErrServerInternal\n\t}\n\tif policy == nil {\n\t\treturn nil, errors.NewError(fmt.Errorf(\"group(%s) is not found\", req.GroupName), http.StatusBadRequest)\n\t}\n\n\treturn model.NewGroupBinding(user.Name, req.GroupName), nil\n}\n\nfunc (a *auth) CreateRBACPolicy(ctx context.Context, req *types.RBACPolicyRequest) error {\n\tpolicy, err := a.getPolicy(ctx, req)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tok, err := a.enforcer.AddPolicy(policy.Raw())\n\tif err != nil {\n\t\tklog.Errorf(\"failed to create policy %v: %v\", policy.Raw(), err)\n\t\treturn errors.ErrServerInternal\n\t}\n\tif !ok {\n\t\treturn errors.ErrRBACPolicyExists\n\t}\n\n\treturn nil\n}\n\nfunc (a *auth) DeleteRBACPolicy(ctx context.Context, req *types.RBACPolicyRequest) error {\n\tpolicy, err := a.getPolicy(ctx, req)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch p := policy.(type) {\n\tcase model.UserPolicy:\n\t\tklog.Infof(\"delete user policy: %v\", policy.Raw())\n\tcase model.GroupPolicy:\n\t\t// check existing group bindings\n\t\tklog.Infof(\"delete group policy: %v\", policy.Raw())\n\t\tbindings, err := ctrlutil.GetGroupBindings(a.enforcer, ctrlutil.QueryWithGroupName(p.GetGroupName()))\n\t\tif err != nil {\n\t\t\tklog.Errorf(\"failed to get bindings of group policy(%v): %v\", policy.Raw(), err)\n\t\t\treturn errors.ErrServerInternal\n\t\t}\n\t\tif len(bindings) > 0 {\n\t\t\treturn errors.NewError(fmt.Errorf(\"用户组 %s 已绑定至某些用户\", p.GetGroupName()), http.StatusForbidden)\n\t\t}\n\t}\n\n\tok, err := a.enforcer.RemovePolicy(policy.Raw())\n\tif err != nil {\n\t\tklog.Errorf(\"failed to delete policy %v: %v\", policy.Raw(), err)\n\t\treturn errors.ErrServerInternal\n\t}\n\tif !ok {\n\t\treturn errors.ErrRBACPolicyNotFound\n\t}\n\n\treturn nil\n}\n\nfunc (a *auth) ListRBACPolicies(ctx context.Context, req *types.ListRBACPolicyRequest) ([]types.RBACPolicy, error) {\n\tuser, err := a.factory.User().Get(ctx, req.UserId)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get user(%d): %v\", req.UserId, err)\n\t\treturn nil, errors.ErrServerInternal\n\t}\n\tif user == nil {\n\t\treturn nil, errors.NewError(fmt.Errorf(\"user(%d) is not found\", req.UserId), http.StatusBadRequest)\n\t}\n\n\tconds := make([]ctrlutil.PolicyCondition, 0)\n\tif req.ObjectType != nil {\n\t\tconds = append(conds, ctrlutil.WithObjectType(*req.ObjectType))\n\t}\n\tif req.SID != nil {\n\t\tconds = append(conds, ctrlutil.WithStringID(*req.SID))\n\t}\n\tif req.Operation != nil {\n\t\tconds = append(conds, ctrlutil.WithOperation(*req.Operation))\n\t}\n\n\tpolicies, err := ctrlutil.GetUserPolicies(a.enforcer, user, conds...)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to list policies: %v\", err)\n\t\treturn nil, errors.ErrServerInternal\n\t}\n\n\trbacPolicies := make([]types.RBACPolicy, len(policies))\n\tfor i, policy := range policies {\n\t\trbacPolicies[i] = *model2Type(policy)\n\t}\n\treturn rbacPolicies, nil\n}\n\nfunc (a *auth) CreateGroupBinding(ctx context.Context, req *types.GroupBindingRequest) error {\n\tbinding, err := a.getBinding(ctx, req)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tok, err := a.enforcer.AddGroupingPolicy(binding.Raw())\n\tif err != nil {\n\t\tklog.Errorf(\"failed to create group binding %v: %v\", binding.Raw(), err)\n\t\treturn errors.ErrServerInternal\n\t}\n\tif !ok {\n\t\treturn errors.ErrGroupBindingExists\n\t}\n\n\treturn nil\n}\n\nfunc (a *auth) DeleteGroupBinding(ctx context.Context, req *types.GroupBindingRequest) error {\n\tbinding, err := a.getBinding(ctx, req)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tok, err := a.enforcer.RemoveGroupingPolicy(binding.Raw())\n\tif err != nil {\n\t\tklog.Errorf(\"failed to delete group binding %v: %v\", binding.Raw(), err)\n\t\treturn errors.ErrServerInternal\n\t}\n\tif !ok {\n\t\treturn errors.ErrGroupBindingNotFound\n\t}\n\n\treturn nil\n}\n\nfunc (a *auth) ListGroupBindings(ctx context.Context, req *types.ListGroupBindingRequest) ([]types.RBACPolicy, error) {\n\tconds := make([]ctrlutil.BindingQueryCondition, 0)\n\tif req.UserId != nil {\n\t\tuser, err := a.factory.User().Get(ctx, *req.UserId)\n\t\tif err != nil {\n\t\t\tklog.Errorf(\"failed to get user(%d): %v\", *req.UserId, err)\n\t\t\treturn nil, errors.ErrServerInternal\n\t\t}\n\t\tif user == nil {\n\t\t\treturn nil, errors.NewError(fmt.Errorf(\"user(%d) is not found\", *req.UserId), http.StatusBadRequest)\n\t\t}\n\n\t\tconds = append(conds, ctrlutil.QueryWithUserName(user.Name))\n\t}\n\tif req.GroupName != nil {\n\t\tpolicy, err := ctrlutil.GetGroupPolicy(a.enforcer, *req.GroupName)\n\t\tif err != nil {\n\t\t\tklog.Errorf(\"failed to get group(%s): %v\", *req.GroupName, err)\n\t\t\treturn nil, errors.ErrServerInternal\n\t\t}\n\t\tif policy == nil {\n\t\t\treturn nil, errors.NewError(fmt.Errorf(\"group(%s) is not found\", *req.GroupName), http.StatusBadRequest)\n\t\t}\n\n\t\tconds = append(conds, ctrlutil.QueryWithGroupName(*req.GroupName))\n\t}\n\n\tbindings, err := ctrlutil.GetGroupBindings(a.enforcer, conds...)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get group bindings: %v\", *req.GroupName, err)\n\t\treturn nil, errors.ErrServerInternal\n\t}\n\n\tbindingPolicies := make([]types.RBACPolicy, len(bindings))\n\tfor i, binding := range bindings {\n\t\tbindingPolicies[i] = *model2Type(binding)\n\t}\n\treturn bindingPolicies, nil\n}\n\nfunc model2Type(policy model.Policy) *types.RBACPolicy {\n\tswitch p := policy.(type) {\n\tcase model.UserPolicy:\n\t\treturn &types.RBACPolicy{\n\t\t\tUserName:   p.GetUserName(),\n\t\t\tObjectType: p.GetObjectType(),\n\t\t\tStringID:   p.GetSID(),\n\t\t\tOperation:  p.GetOperation(),\n\t\t}\n\tcase model.GroupPolicy:\n\t\treturn &types.RBACPolicy{\n\t\t\tGroupName:  p.GetGroupName(),\n\t\t\tObjectType: p.GetObjectType(),\n\t\t\tStringID:   p.GetSID(),\n\t\t\tOperation:  p.GetOperation(),\n\t\t}\n\tcase model.GroupBinding:\n\t\treturn &types.RBACPolicy{\n\t\t\tUserName:  p.GetUserName(),\n\t\t\tGroupName: p.GetGroupName(),\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/controller/cluster/cluster.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cluster\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/casbin/casbin/v2\"\n\t\"github.com/gorilla/websocket\"\n\tbatchv1 \"k8s.io/api/batch/v1\"\n\tv1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/resource\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\tapitypes \"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/apimachinery/pkg/util/sets\"\n\t\"k8s.io/apimachinery/pkg/util/wait\"\n\t\"k8s.io/client-go/kubernetes\"\n\trestclient \"k8s.io/client-go/rest\"\n\t\"k8s.io/klog/v2\"\n\t\"k8s.io/metrics/pkg/apis/metrics/v1beta1\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/errors\"\n\t\"github.com/caoyingjunz/pixiu/api/server/httputils\"\n\t\"github.com/caoyingjunz/pixiu/cmd/app/config\"\n\t\"github.com/caoyingjunz/pixiu/pkg/client\"\n\tctrlutil \"github.com/caoyingjunz/pixiu/pkg/controller/util\"\n\t\"github.com/caoyingjunz/pixiu/pkg/db\"\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n\t\"github.com/caoyingjunz/pixiu/pkg/util\"\n\t\"github.com/caoyingjunz/pixiu/pkg/util/uuid\"\n)\n\ntype ClusterGetter interface {\n\tCluster() Interface\n}\n\ntype Interface interface {\n\tCreate(ctx context.Context, req *types.CreateClusterRequest) error\n\tUpdate(ctx context.Context, cid int64, req *types.UpdateClusterRequest) error\n\tDelete(ctx context.Context, cid int64) error\n\tGet(ctx context.Context, cid int64) (*types.Cluster, error)\n\tList(ctx context.Context, req *types.ListClusterRequest) (*types.PageResponse, error)\n\n\t// Ping 检查和 k8s 集群的连通性\n\tPing(ctx context.Context, kubeConfig string) error\n\n\t// Protect 设置集群的保护策略\n\tProtect(ctx context.Context, cid int64, req *types.ProtectClusterRequest) error\n\n\t// GetEventList 获取指定对象的事件，支持做聚合\n\tGetEventList(ctx context.Context, cluster string, options types.EventOptions) (*v1.EventList, error)\n\n\t// AggregateEvents 聚合指定资源的 events\n\tAggregateEvents(ctx context.Context, cluster string, namespace string, name string, kind string) (*v1.EventList, error)\n\t// WsHandler pod 的 webShell\n\tWsHandler(ctx context.Context, webShellOptions *types.WebShellOptions, w http.ResponseWriter, r *http.Request) error\n\t// WsNodeHandler node 的 webShell\n\tWsNodeHandler(ctx context.Context, sshConfig *types.WebSSHRequest, w http.ResponseWriter, r *http.Request) error\n\n\t// WatchPodLog 实时获取 pod 的日志\n\tWatchPodLog(ctx context.Context, cluster string, namespace string, podName string, containerName string, tailLine int64, w http.ResponseWriter, r *http.Request) error\n\t// ReRunJob 重新执行指定任务\n\tReRunJob(ctx context.Context, cluster string, namespace string, jobName string, resourceVersion string) error\n\n\tGetKubeConfigByName(ctx context.Context, name string) (*restclient.Config, error)\n\n\tGetIndexerResource(ctx context.Context, cluster string, resource string, namespace string, name string) (interface{}, error)\n\tListIndexerResources(ctx context.Context, cluster string, resource string, namespace string, listOption types.ListOptions) (interface{}, error)\n\n\t// Run 启动 cluster worker 处理协程\n\tRun(ctx context.Context, workers int) error\n}\n\nvar ClusterIndexer client.Cache\n\nfunc init() {\n\tClusterIndexer = *client.NewClusterCache()\n}\n\ntype (\n\tlisterFunc func(ctx context.Context, informer *client.PixiuInformer, namespace string, listOption types.ListOptions) (interface{}, error)\n\tgetterFunc func(ctx context.Context, informer *client.PixiuInformer, namespace, name string) (interface{}, error)\n)\n\ntype InformerResource struct {\n\t// k8s 资源类型，比如 deployment, sts, daemonset 等\n\tResourceType string\n\tListerFunc   listerFunc\n\tGetterFunc   getterFunc\n}\n\ntype cluster struct {\n\tcc       config.Config\n\tfactory  db.ShareDaoFactory\n\tenforcer *casbin.SyncedEnforcer\n\n\tlisterFuncs map[string]listerFunc\n\tgetterFuncs map[string]getterFunc\n}\n\nfunc (c *cluster) preCreate(ctx context.Context, req *types.CreateClusterRequest) error {\n\t// 实际创建前，先创建集群的连通性\n\tif err := c.Ping(ctx, req.KubeConfig); err != nil {\n\t\treturn fmt.Errorf(\"尝试连接 kubernetes API 失败: %v\", err)\n\t}\n\treturn nil\n}\n\nfunc (c *cluster) Create(ctx context.Context, req *types.CreateClusterRequest) error {\n\tuser, err := httputils.GetUserFromRequest(ctx)\n\tif err != nil {\n\t\treturn errors.NewError(err, http.StatusInternalServerError)\n\t}\n\n\tif err := c.preCreate(ctx, req); err != nil {\n\t\treturn errors.NewError(err, http.StatusBadRequest)\n\t}\n\t// TODO: 集群名称必须是由英文，数字组成\n\tif len(req.Name) == 0 {\n\t\treq.Name = uuid.NewRandName(8)\n\t}\n\n\tvar cs *client.ClusterSet\n\tvar txFunc = func(cluster *model.Cluster) (err error) {\n\t\tif cs, err = client.NewClusterSet(req.KubeConfig); err != nil {\n\t\t\treturn\n\t\t}\n\n\t\t// insert a user RBAC policy\n\t\tpolicy := model.NewPolicyFromModels(user, model.ObjectCluster, cluster.Model, model.OpAll)\n\t\t_, err = c.enforcer.AddPolicy(policy.Raw())\n\t\treturn\n\t}\n\n\tkubeNode := types.KubeNode{}\n\tnodes, _ := kubeNode.Marshal()\n\tif _, err := c.factory.Cluster().Create(ctx, &model.Cluster{\n\t\tName:        req.Name,\n\t\tAliasName:   req.AliasName,\n\t\tClusterType: req.Type,\n\t\tProtected:   req.Protected,\n\t\tKubeConfig:  req.KubeConfig,\n\t\tDescription: req.Description,\n\t\tNodes:       nodes,\n\t}, txFunc); err != nil {\n\t\tklog.Errorf(\"failed to create cluster %s: %v\", req.Name, err)\n\t\treturn errors.ErrServerInternal\n\t}\n\n\t// TODO: 暂时不做创建后动作\n\tClusterIndexer.Set(req.Name, *cs)\n\treturn nil\n}\n\nfunc (c *cluster) Update(ctx context.Context, cid int64, req *types.UpdateClusterRequest) error {\n\tobject, err := c.factory.Cluster().Get(ctx, cid)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get cluster(%d): %v\", cid, err)\n\t\treturn errors.ErrServerInternal\n\t}\n\tif object == nil {\n\t\treturn errors.ErrClusterNotFound\n\t}\n\tupdates := make(map[string]interface{})\n\tif req.AliasName != nil {\n\t\tupdates[\"alias_name\"] = *req.AliasName\n\t}\n\tif req.Description != nil {\n\t\tupdates[\"description\"] = *req.Description\n\t}\n\tif len(updates) == 0 {\n\t\treturn errors.ErrInvalidRequest\n\t}\n\tif err = c.factory.Cluster().Update(ctx, cid, *req.ResourceVersion, updates); err != nil {\n\t\tklog.Errorf(\"failed to update cluster(%d): %v\", cid, err)\n\t\treturn errors.ErrServerInternal\n\t}\n\treturn nil\n}\n\n// 删除前置检查\n// 开启集群删除保护，则不允许删除\nfunc (c *cluster) preDelete(ctx context.Context, cid int64) (cluster *model.Cluster, err error) {\n\tif cluster, err = c.factory.Cluster().Get(ctx, cid); err != nil {\n\t\tklog.Errorf(\"failed to get cluster(%d): %v\", cid, err)\n\t\treturn\n\t}\n\tif cluster == nil {\n\t\treturn nil, errors.ErrClusterNotFound\n\t}\n\t// 开启集群删除保护，则不允许删除\n\tif cluster.Protected {\n\t\treturn nil, errors.NewError(fmt.Errorf(\"已开启集群删除保护功能，不允许删除 %s\", cluster.AliasName),\n\t\t\thttp.StatusForbidden)\n\t}\n\n\t// TODO: 其他删除策略检查\n\treturn\n}\n\nfunc (c *cluster) Delete(ctx context.Context, cid int64) error {\n\tuser, err := httputils.GetUserFromRequest(ctx)\n\tif err != nil {\n\t\treturn errors.NewError(err, http.StatusInternalServerError)\n\t}\n\n\tcluster, err := c.preDelete(ctx, cid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar txFunc = func(cluster *model.Cluster) (err error) {\n\t\t_, err = c.enforcer.RemoveNamedPolicy(\"p\", user.Name, model.ObjectCluster.String(), cluster.GetSID())\n\t\treturn\n\t}\n\tif err := c.factory.Cluster().Delete(ctx, cluster, txFunc); err != nil {\n\t\tklog.Errorf(\"failed to delete cluster(%d): %v\", cid, err)\n\t\treturn errors.ErrServerInternal\n\t}\n\n\t// 从缓存中移除 clusterSet\n\tClusterIndexer.Delete(cluster.Name)\n\treturn nil\n}\n\nfunc (c *cluster) Get(ctx context.Context, cid int64) (*types.Cluster, error) {\n\tobject, err := c.factory.Cluster().Get(ctx, cid)\n\tif err != nil {\n\t\treturn nil, errors.ErrServerInternal\n\t}\n\tif object == nil {\n\t\treturn nil, errors.ErrClusterNotFound\n\t}\n\n\treturn c.model2Type(object), nil\n}\n\nfunc (c *cluster) List(ctx context.Context, req *types.ListClusterRequest) (*types.PageResponse, error) {\n\topts := ctrlutil.MakeDbOptions(ctx)\n\n\tif req != nil && req.NameSelector != \"\" {\n\t\topts = append(opts, db.WithAliasNameLike(req.NameSelector))\n\t}\n\tif req != nil && req.Status != nil {\n\t\topts = append(opts, db.WithClusterStatus(*req.Status))\n\t}\n\n\ttotal, err := c.factory.Cluster().Count(ctx, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpageReq := types.PageRequest{}\n\tif req != nil {\n\t\tpageReq = req.PageRequest\n\t\tif req.Page > 0 && req.Limit > 0 {\n\t\t\topts = append(opts, db.WithOffset((req.Page-1)*req.Limit), db.WithLimit(req.Limit))\n\t\t}\n\t}\n\n\tobjects, err := c.factory.Cluster().List(ctx, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcs := make([]types.Cluster, len(objects))\n\tfor i, object := range objects {\n\t\tcs[i] = *c.model2Type(&object)\n\t}\n\n\treturn &types.PageResponse{\n\t\tPageRequest: pageReq,\n\t\tTotal:       int(total),\n\t\tItems:       cs,\n\t}, nil\n}\n\n// Ping 检查和 k8s 集群的连通性\n// 如果能获取到 k8s 接口的正常返回，则返回 nil，否则返回具体 error\n// kubeConfig 为 k8s 证书的 base64 字符串\nfunc (c *cluster) Ping(ctx context.Context, kubeConfig string) error {\n\tclientSet, err := client.NewClientSetFromString(kubeConfig)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// 调用 ns 资源，确保连通\n\tvar timeout int64 = 1\n\tif _, err = clientSet.CoreV1().Namespaces().List(ctx, metav1.ListOptions{\n\t\tTimeoutSeconds: &timeout,\n\t}); err != nil {\n\t\tklog.Errorf(\"failed to ping kubernetes: %v\", err)\n\t\t// 处理原始报错信息，仅返回连接不通的信息\n\t\treturn fmt.Errorf(\"kubernetes 集群连接测试失败\")\n\t}\n\n\treturn nil\n}\n\nfunc (c *cluster) Protect(ctx context.Context, cid int64, req *types.ProtectClusterRequest) error {\n\tif err := c.factory.Cluster().Update(ctx, cid, *req.ResourceVersion, map[string]interface{}{\n\t\t\"protected\": req.Protected,\n\t}); err != nil {\n\t\tklog.Errorf(\"failed to protect cluster(%d): %v\", cid, err)\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (c *cluster) GetEventList(ctx context.Context, cluster string, options types.EventOptions) (*v1.EventList, error) {\n\tif options.Limit == 0 {\n\t\toptions.Limit = 500\n\t}\n\topt := metav1.ListOptions{Limit: options.Limit}\n\tfs := c.makeFieldSelector(apitypes.UID(options.Uid), options.Name, options.Namespace, options.Kind)\n\tif len(fs) != 0 {\n\t\topt.FieldSelector = fs\n\t}\n\n\tclusterSet, err := c.GetClusterSetByName(ctx, cluster)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn clusterSet.Client.CoreV1().Events(options.Namespace).List(ctx, opt)\n}\n\n// WatchPodLog streams the logs of a pod in a cluster to a websocket connection.\n//\n// Parameters:\n// - ctx: The context.Context object for the request.\n// - cluster: The name of the cluster.\n// - namespace: The namespace of the pod.\n// - podName: The name of the pod.\n// - containerName: The name of the container.\n// - tailLine: The number of lines to show from the end of the logs.\n// - w: The http.ResponseWriter object for the websocket connection.\n// - r: The *http.Request object for the websocket connection.\n//\n// Returns:\n// - error: An error if there was a problem streaming the logs.\nfunc (c *cluster) WatchPodLog(ctx context.Context, cluster string, namespace string, podName string, containerName string, tailLine int64, w http.ResponseWriter, r *http.Request) error {\n\tclusterSet, err := c.GetClusterSetByName(ctx, cluster)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get cluster(%s) clientSet: %v\", cluster, err)\n\t\treturn err\n\t}\n\n\treq := clusterSet.Client.CoreV1().Pods(namespace).GetLogs(podName, &v1.PodLogOptions{\n\t\tContainer:  containerName,\n\t\tFollow:     true,\n\t\tTailLines:  &tailLine,\n\t\tTimestamps: false,\n\t})\n\tif req == nil {\n\t\tklog.Errorf(\"failed to get stream\")\n\t\treturn fmt.Errorf(\"failed to get stream\")\n\t}\n\n\twithTimeout, cancelFunc := context.WithTimeout(ctx, time.Minute*10)\n\tdefer cancelFunc()\n\n\treader, err := req.Stream(withTimeout)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get stream: %v\", err)\n\t\treturn err\n\t}\n\tdefer reader.Close()\n\n\tconn, err := util.BuildWebSocketConnection(w, r)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to build websocket connection: %v\", err)\n\t\treturn err\n\t}\n\tdefer conn.Close()\n\n\tfor {\n\t\tbuf := make([]byte, 1024)\n\t\tn, err := reader.Read(buf)\n\t\tif err != nil && err != io.EOF {\n\t\t\tbreak\n\t\t}\n\t\terr = conn.WriteMessage(websocket.TextMessage, buf[0:n])\n\t\tif err != nil {\n\t\t\tklog.Errorf(\"failed to write message: %v ,this websocket connection will be closed\", err)\n\t\t\tbreak\n\t\t}\n\t}\n\treturn nil\n}\n\nconst Retries = 3\n\n// ReRunJob 重新运行(创建)任务，通过先删除在创建的方式实现，极端情况下可能导致 job 丢失\nfunc (c *cluster) ReRunJob(ctx context.Context, cluster string, namespace string, jobName string, resourceVersion string) error {\n\tcs, err := c.GetClusterSetByName(ctx, cluster)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tjob, err := cs.Client.BatchV1().Jobs(namespace).Get(ctx, jobName, metav1.GetOptions{})\n\tif err != nil {\n\t\treturn err\n\t}\n\tif job.ResourceVersion != resourceVersion {\n\t\treturn fmt.Errorf(\"please apply your changes to the latest and re-run\")\n\t}\n\n\tnewJob := *job\n\t// 重置不必要字段\n\tnewJob.ResourceVersion = \"\"\n\tnewJob.ObjectMeta.UID = \"\"\n\tnewJob.Status = batchv1.JobStatus{}\n\t// 重置 uid 和 label\n\tdelete(newJob.Spec.Selector.MatchLabels, \"controller-uid\")\n\tdelete(newJob.Spec.Selector.MatchLabels, \"batch.kubernetes.io/controller-uid\")\n\tdelete(newJob.Spec.Template.ObjectMeta.Labels, \"controller-uid\")\n\tdelete(newJob.Spec.Template.ObjectMeta.Labels, \"batch.kubernetes.io/controller-uid\")\n\tdelete(newJob.Spec.Template.ObjectMeta.Labels, \"batch.kubernetes.io/job-name\")\n\tdelete(newJob.Spec.Template.ObjectMeta.Labels, \"job-name\")\n\n\t// TODO: 备份一次job，避免失败job丢失\n\t// 2. 删除job\n\tif err = cs.Client.BatchV1().Jobs(namespace).Delete(ctx, jobName, metav1.DeleteOptions{}); err != nil {\n\t\treturn fmt.Errorf(\"failed to rerun job(%s) %v\", jobName, err)\n\t}\n\n\tvar jobErr error\n\t// 3. 新建job，最多重试 3 次\n\tfor i := 0; i < Retries; i++ {\n\t\t_, jobErr = cs.Client.BatchV1().Jobs(namespace).Create(ctx, &newJob, metav1.CreateOptions{})\n\t\tif jobErr != nil {\n\t\t\ttime.Sleep(time.Second)\n\t\t\tcontinue\n\t\t}\n\t\tbreak\n\t}\n\tif jobErr != nil {\n\t\treturn fmt.Errorf(\"failed to rerun job(%s) %v\", jobName, err)\n\t}\n\n\treturn nil\n}\n\n// AggregateEvents 聚合 k8s 资源的所有 events，比如 kind 为 deployment 时，则聚合 deployment，所属 rs 以及 pod 的事件\nfunc (c *cluster) AggregateEvents(ctx context.Context, cluster string, namespace string, name string, kind string) (*v1.EventList, error) {\n\tclusterSet, err := c.GetClusterSetByName(ctx, cluster)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar fieldSelectors []string\n\n\tswitch kind {\n\tcase \"deployment\":\n\t\t// TODO: 临时聚合方式，后续继续优化（简化）\n\t\t// 获取 deployment\n\t\tdeployment, err := clusterSet.Client.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{})\n\t\tif err != nil {\n\t\t\tklog.Errorf(\"failed to get deployment (%s/%s), err: %v\", namespace, name, err)\n\t\t\treturn nil, err\n\t\t}\n\t\tfieldSelectors = append(fieldSelectors, c.makeFieldSelector(deployment.UID, deployment.Name, deployment.Namespace, \"Deployment\"))\n\n\t\tvar labels []string\n\t\tfor k, v := range deployment.Spec.Selector.MatchLabels {\n\t\t\tlabels = append(labels, fmt.Sprintf(\"%s=%s\", k, v))\n\t\t}\n\t\tlabelSelector := strings.Join(labels, \",\")\n\n\t\tkubeObject, err := c.GetKubeObjectByLabel(clusterSet.Client, namespace, labelSelector, \"ReplicaSet\", \"Pod\")\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// 获取 rs\n\t\tallReplicaSets := kubeObject.GetReplicaSets()\n\t\tvar replicaSetUIDs []apitypes.UID\n\t\tfor _, rs := range allReplicaSets {\n\t\t\tfor _, ownerReference := range rs.OwnerReferences {\n\t\t\t\tif ownerReference.Kind == \"Deployment\" && ownerReference.UID == deployment.UID {\n\t\t\t\t\tfieldSelectors = append(fieldSelectors, c.makeFieldSelector(rs.UID, rs.Name, rs.Namespace, \"ReplicaSet\"))\n\t\t\t\t\treplicaSetUIDs = append(replicaSetUIDs, rs.UID)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// 获取 pods\n\t\tallPods := kubeObject.GetPods()\n\t\tfor _, p := range allPods {\n\t\t\tfor _, ownerReference := range p.OwnerReferences {\n\t\t\t\tfor _, replicaSetUID := range replicaSetUIDs {\n\t\t\t\t\tif ownerReference.UID == replicaSetUID && ownerReference.Kind == \"ReplicaSet\" {\n\t\t\t\t\t\tfieldSelectors = append(fieldSelectors, c.makeFieldSelector(p.UID, p.Name, p.Namespace, \"Pod\"))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported kubernetes object kind %s\", kind)\n\t}\n\n\tdiff := len(fieldSelectors)\n\terrCh := make(chan error, diff)\n\teventCh := make(chan *v1.EventList, diff)\n\n\tvar wg sync.WaitGroup\n\twg.Add(diff)\n\tfor _, fieldSelector := range fieldSelectors {\n\t\tgo func(fs string) {\n\t\t\tdefer wg.Done()\n\t\t\tevents, err := clusterSet.Client.CoreV1().Events(namespace).List(context.TODO(), metav1.ListOptions{\n\t\t\t\tFieldSelector: fs,\n\t\t\t\tLimit:         500,\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tklog.Errorf(\"failed to get object(%s) events: %v\", namespace, err)\n\t\t\t\terrCh <- err\n\t\t\t}\n\t\t\teventCh <- events\n\t\t}(fieldSelector)\n\t}\n\twg.Wait()\n\n\tselect {\n\tcase err := <-errCh:\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tdefault:\n\t}\n\n\teventList := &v1.EventList{Items: []v1.Event{}}\n\tfor i := 0; i < diff; i++ {\n\t\tes := <-eventCh\n\t\tif es == nil {\n\t\t\tcontinue\n\t\t}\n\t\teventList.Items = append(eventList.Items, es.Items...)\n\t}\n\n\treturn eventList, nil\n}\n\n// GetKubeObjectByLabel\n// TODO: 并发优化\nfunc (c *cluster) GetKubeObjectByLabel(Client *kubernetes.Clientset, namespace string, labelSelector string, kinds ...string) (*types.KubeObject, error) {\n\tobject := &types.KubeObject{}\n\n\tkindSet := sets.NewString(kinds...)\n\terrCh := make(chan error, kindSet.Len())\n\n\tvar wg sync.WaitGroup\n\twg.Add(kindSet.Len())\n\n\t// 后续优化\n\tif kindSet.Has(\"ReplicaSet\") {\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tallReplicaSets, err := Client.AppsV1().ReplicaSets(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector, Limit: 500})\n\t\t\tif err != nil {\n\t\t\t\terrCh <- err\n\t\t\t} else {\n\t\t\t\tobject.SetReplicaSets(allReplicaSets.Items)\n\t\t\t}\n\t\t}()\n\t}\n\n\tif kindSet.Has(\"Pod\") {\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tallPods, err := Client.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector, Limit: 500})\n\t\t\tif err != nil {\n\t\t\t\terrCh <- err\n\t\t\t} else {\n\t\t\t\tobject.SetPods(allPods.Items)\n\t\t\t}\n\t\t}()\n\t}\n\twg.Wait()\n\n\tselect {\n\tcase err := <-errCh:\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tdefault:\n\t}\n\treturn object, nil\n}\n\nfunc (c *cluster) GetKubeConfigByName(ctx context.Context, name string) (*restclient.Config, error) {\n\tcs, err := c.GetClusterSetByName(ctx, name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn cs.Config, nil\n}\n\n// GetClusterSetByName 获取 ClusterSet， 缓存中不存在时，构建缓存再返回\nfunc (c *cluster) GetClusterSetByName(ctx context.Context, name string) (client.ClusterSet, error) {\n\tcs, ok := ClusterIndexer.Get(name)\n\tif ok {\n\t\tklog.Infof(\"Get %s clusterSet from cache\", name)\n\t\treturn cs, nil\n\t}\n\n\tklog.Infof(\"building clusterSet for %s\", name)\n\t// 缓存中不存在，则新建并重写回缓存\n\tobject, err := c.factory.Cluster().GetClusterByName(ctx, name)\n\tif err != nil {\n\t\treturn client.ClusterSet{}, err\n\t}\n\tif object == nil {\n\t\treturn client.ClusterSet{}, errors.ErrClusterNotFound\n\t}\n\tnewClusterSet, err := client.NewClusterSet(object.KubeConfig)\n\tif err != nil {\n\t\treturn client.ClusterSet{}, err\n\t}\n\n\tklog.Infof(\"set %s clusterSet into cache\", name)\n\tClusterIndexer.Set(name, *newClusterSet)\n\treturn *newClusterSet, nil\n}\n\n// GetKubernetesMeta\n// TODO：临时构造 client，后续通过 informer 的方式维护缓存\nfunc (c *cluster) GetKubernetesMeta(ctx context.Context, clusterName string) (*types.KubernetesMeta, error) {\n\tclusterSet, err := c.GetClusterSetByName(ctx, clusterName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// 获取 k8s 的节点信息\n\tnodeList, err := clusterSet.Client.CoreV1().Nodes().List(ctx, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tnodes := nodeList.Items\n\t// 在集群启动，但是没有节点加入时，命中该场景\n\tif len(nodes) == 0 {\n\t\treturn nil, fmt.Errorf(\"no nodes found\")\n\t}\n\n\t// 构造 kubernetes 资源数据格式\n\tkm := types.KubernetesMeta{\n\t\tNodes:             len(nodes),\n\t\tKubernetesVersion: nodes[0].Status.NodeInfo.KubeletVersion,\n\t}\n\n\t// TODO: 并发优化\n\t// 获取集群所有节点的资源数据，并做整合\n\t//metricList, err := clusterSet.Metric.NodeMetricses().List(ctx, metav1.ListOptions{})\n\t//if err != nil {\n\t//\treturn nil, err\n\t//}\n\t//km.Resources = c.parseKubernetesResource(metricList.Items)\n\n\treturn &km, nil\n}\n\nfunc (c *cluster) GetKubernetesMetaFromPlan(ctx context.Context, planId int64) (*types.KubernetesMeta, error) {\n\tplanConfig, err := c.factory.Plan().GetConfigByPlan(ctx, planId)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tks := &types.KubernetesSpec{}\n\tif err = ks.Unmarshal(planConfig.Kubernetes); err != nil {\n\t\treturn nil, err\n\t}\n\n\tnodes, err := c.factory.Plan().ListNodes(ctx, planId)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &types.KubernetesMeta{\n\t\tKubernetesVersion: \"v\" + ks.KubernetesVersion,\n\t\tNodes:             len(nodes),\n\t}, nil\n}\n\n// 构造事件的 FieldSelector， 如果参数为空则忽略\nfunc (c *cluster) makeFieldSelector(uid apitypes.UID, name string, namespace string, kind string) string {\n\teventFS := make([]string, 0)\n\t// 追加对象的 uid\n\tif util.IsEmptyS(string(uid)) {\n\t\teventFS = append(eventFS, \"involvedObject.uid=\"+string(uid))\n\t}\n\tif util.IsEmptyS(name) {\n\t\teventFS = append(eventFS, \"involvedObject.name=\"+name)\n\t}\n\tif util.IsEmptyS(namespace) {\n\t\teventFS = append(eventFS, \"involvedObject.namespace=\"+namespace)\n\t}\n\tif util.IsEmptyS(kind) {\n\t\teventFS = append(eventFS, \"involvedObject.kind=\"+kind)\n\t}\n\t// 构造 kubernetes 原生 FieldSelector 参数格式\n\treturn strings.Join(eventFS, \",\")\n}\n\nfunc (c *cluster) parseKubernetesResource(nodeMetrics []v1beta1.NodeMetrics) types.Resources {\n\t// 初始化集群资源\n\tresourceList := v1.ResourceList{\n\t\tv1.ResourceCPU:    resource.Quantity{},\n\t\tv1.ResourceMemory: resource.Quantity{},\n\t}\n\n\t// 遍历所有 metric 数据，算集群总和，仅计算 cpu 和 memory\n\tfor _, metric := range nodeMetrics {\n\t\t// 1. Cpu\n\t\tcpuMetric := metric.Usage.Cpu()\n\t\tif cpuMetric != nil {\n\t\t\tcpuSum := resourceList[v1.ResourceCPU]\n\t\t\tcpuSum.Add(*cpuMetric)\n\t\t\tresourceList[v1.ResourceCPU] = cpuSum\n\t\t}\n\n\t\t// 2. Memory\n\t\tmemoryMetric := metric.Usage.Memory()\n\t\tif memoryMetric != nil {\n\t\t\tmemSum := resourceList[v1.ResourceMemory]\n\t\t\tmemSum.Add(*memoryMetric)\n\t\t\tresourceList[v1.ResourceMemory] = memSum\n\t\t}\n\t}\n\n\tcpuSum := resourceList[v1.ResourceCPU]\n\tmemSum := resourceList[v1.ResourceMemory]\n\treturn types.Resources{\n\t\tCpu:    strconv.FormatFloat(parseFloat64FromString(cpuSum.String())/1000/1000/1000, 'f', 2, 64) + \" Core\",\n\t\tMemory: strconv.FormatFloat(parseFloat64FromString(memSum.String())/1024/1024, 'f', 2, 64) + \" Gi\"}\n}\n\n// parseFloat64FromString 从字符串中解析出包含的数字，并以 float64 返回。\n// 无法解析时，返回 0\n// 仅解析最先遇到的数字，效果：\n// \"666ddd\" -> 666\n// \"666ddd888\" -> 666\n// \"\" 或者 \"ddd\"- > 0\nfunc parseFloat64FromString(s string) float64 {\n\tmatcher := regexp.MustCompile(`\\d+`)\n\tfs := matcher.FindString(s)\n\tif len(fs) == 0 {\n\t\treturn 0\n\t}\n\n\tf, err := strconv.ParseFloat(fs, 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn f\n}\n\nfunc (c *cluster) model2Type(o *model.Cluster) *types.Cluster {\n\tnodes := types.KubeNode{}\n\tif strings.TrimSpace(o.Nodes) != \"\" {\n\t\tif err := nodes.Unmarshal(o.Nodes); err != nil {\n\t\t\t// 非核心数据\n\t\t\tklog.Warningf(\"failed to unmarshal cluster nodes: %v\", err)\n\t\t}\n\t}\n\n\ttc := &types.Cluster{\n\t\tPixiuMeta: types.PixiuMeta{\n\t\t\tId:              o.Id,\n\t\t\tResourceVersion: o.ResourceVersion,\n\t\t},\n\t\tTimeMeta: types.TimeMeta{\n\t\t\tGmtCreate:   o.GmtCreate,\n\t\t\tGmtModified: o.GmtModified,\n\t\t},\n\t\tName:              o.Name,\n\t\tAliasName:         o.AliasName,\n\t\tClusterType:       o.ClusterType,\n\t\tKubernetesVersion: o.KubernetesVersion,\n\t\tNodes:             nodes,\n\t\tPlanId:            o.PlanId,\n\t\tStatus:            o.ClusterStatus, // 默认是运行中状态，自建集群会根据实际任务状态修改状态\n\t\tProtected:         o.Protected,\n\t\tDescription:       o.Description,\n\t}\n\n\t//var (\n\t//\tkubernetesMeta *types.KubernetesMeta\n\t//\terr            error\n\t//)\n\t//\n\t//if o.ClusterType == model.ClusterTypeStandard {\n\t//\t// 导入的集群通过API获取相关数据\n\t//\t// 获取失败时，返回空的 kubernetes Meta, 不终止主流程\n\t//\t// TODO: 后续改成并发处理\n\t//\tkubernetesMeta, err = c.GetKubernetesMeta(context.TODO(), o.Name)\n\t//} else {\n\t//\t// 自建的集群通过plan配置获取版本信息\n\t//\tkubernetesMeta, err = c.GetKubernetesMetaFromPlan(context.TODO(), o.PlanId)\n\t//\n\t//\t// 自建的集群需要从 plan task 获取状态\n\t//\ttc.Status, _ = c.GetClusterStatusFromPlanTask(o.PlanId)\n\t//}\n\t//if err != nil {\n\t//\tklog.Warning(\"failed to get kubernetes Meta: %v\", err)\n\t//} else {\n\t//\ttc.KubernetesMeta = *kubernetesMeta\n\t//}\n\n\treturn tc\n}\n\nfunc (c *cluster) GetClusterStatusFromPlanTask(planId int64) (model.ClusterStatus, error) {\n\tstatus := model.ClusterStatusRunning\n\n\t// 尝试获取最新的任务状态\n\t// 获取失败也不中断返回\n\tif tasks, err := c.factory.Plan().ListTasks(context.TODO(), planId); err == nil {\n\t\tif len(tasks) == 0 {\n\t\t\tstatus = model.ClusterStatusUnStart\n\t\t} else {\n\t\t\tfor _, task := range tasks {\n\t\t\t\tif task.Status != model.SuccessPlanStatus {\n\t\t\t\t\tif task.Status == model.FailedPlanStatus {\n\t\t\t\t\t\tstatus = model.ClusterStatusFailed\n\t\t\t\t\t} else {\n\t\t\t\t\t\tstatus = model.ClusterStatusDeploy\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn status, nil\n}\n\nfunc (c *cluster) registerIndexers(informerResources ...InformerResource) {\n\tfor _, informerResource := range informerResources {\n\t\tc.listerFuncs[informerResource.ResourceType] = informerResource.ListerFunc\n\t\tc.getterFuncs[informerResource.ResourceType] = informerResource.GetterFunc\n\t}\n}\n\nfunc (c *cluster) Run(ctx context.Context, workers int) error {\n\tklog.Infof(\"starting cluster manager\")\n\t// 同步集群状态，节点数，版本\n\tgo wait.UntilWithContext(ctx, c.Sync, 5*time.Second)\n\n\treturn nil\n}\n\nfunc (c *cluster) Sync(ctx context.Context) {\n\t// TODO: 后续添加同步任务\n}\n\nfunc NewCluster(cfg config.Config, f db.ShareDaoFactory, e *casbin.SyncedEnforcer) *cluster {\n\tc := &cluster{\n\t\tcc:       cfg,\n\t\tfactory:  f,\n\t\tenforcer: e,\n\n\t\tlisterFuncs: make(map[string]listerFunc),\n\t\tgetterFuncs: make(map[string]getterFunc),\n\t}\n\n\t// TODO: code generation?\n\t//c.registerIndexers([]InformerResource{\n\t//\t{\n\t//\t\tResourceType: ResourcePod,\n\t//\t\tListerFunc: func(ctx context.Context, informer *client.PixiuInformer, namespace string, listOption types.ListOptions) (interface{}, error) {\n\t//\t\t\treturn c.ListPods(ctx, informer.PodsLister(), namespace, listOption)\n\t//\t\t},\n\t//\t\tGetterFunc: func(ctx context.Context, informer *client.PixiuInformer, namespace, name string) (interface{}, error) {\n\t//\t\t\treturn c.GetPod(ctx, informer.PodsLister(), namespace, name)\n\t//\t\t},\n\t//\t},\n\t//\t{\n\t//\t\tResourceType: ResourceDeployment,\n\t//\t\tListerFunc: func(ctx context.Context, informer *client.PixiuInformer, namespace string, listOption types.ListOptions) (interface{}, error) {\n\t//\t\t\treturn c.ListDeployments(ctx, informer.DeploymentsLister(), namespace, listOption)\n\t//\t\t},\n\t//\t\tGetterFunc: func(ctx context.Context, informer *client.PixiuInformer, namespace, name string) (interface{}, error) {\n\t//\t\t\treturn c.GetDeployment(ctx, informer.DeploymentsLister(), namespace, name)\n\t//\t\t},\n\t//\t},\n\t//\t{\n\t//\t\tResourceType: ResourceStatefulSet,\n\t//\t\tListerFunc: func(ctx context.Context, informer *client.PixiuInformer, namespace string, listOption types.ListOptions) (interface{}, error) {\n\t//\t\t\treturn c.ListStatefulSets(ctx, informer.StatefulSetsLister(), namespace, listOption)\n\t//\t\t},\n\t//\t\tGetterFunc: func(ctx context.Context, informer *client.PixiuInformer, namespace, name string) (interface{}, error) {\n\t//\t\t\treturn c.GetStatefulSet(ctx, informer.StatefulSetsLister(), namespace, name)\n\t//\t\t},\n\t//\t},\n\t//\t{\n\t//\t\tResourceType: ResourceDaemonSet,\n\t//\t\tListerFunc: func(ctx context.Context, informer *client.PixiuInformer, namespace string, listOption types.ListOptions) (interface{}, error) {\n\t//\t\t\treturn c.ListDaemonSets(ctx, informer.DaemonSetsLister(), namespace, listOption)\n\t//\t\t},\n\t//\t\tGetterFunc: func(ctx context.Context, informer *client.PixiuInformer, namespace, name string) (interface{}, error) {\n\t//\t\t\treturn c.GetDaemonSet(ctx, informer.DaemonSetsLister(), namespace, name)\n\t//\t\t},\n\t//\t},\n\t//\t{\n\t//\t\tResourceType: ResourceCronJob,\n\t//\t\tListerFunc: func(ctx context.Context, informer *client.PixiuInformer, namespace string, listOption types.ListOptions) (interface{}, error) {\n\t//\t\t\treturn c.ListCronJobs(ctx, informer.CronJobsLister(), namespace, listOption)\n\t//\t\t},\n\t//\t\tGetterFunc: func(ctx context.Context, informer *client.PixiuInformer, namespace, name string) (interface{}, error) {\n\t//\t\t\treturn c.GetCronJob(ctx, informer.CronJobsLister(), namespace, name)\n\t//\t\t},\n\t//\t},\n\t//\t{\n\t//\t\tResourceType: ResourceJob,\n\t//\t\tListerFunc: func(ctx context.Context, informer *client.PixiuInformer, namespace string, listOption types.ListOptions) (interface{}, error) {\n\t//\t\t\treturn c.ListJobs(ctx, informer.JobsLister(), namespace, listOption)\n\t//\t\t},\n\t//\t\tGetterFunc: func(ctx context.Context, informer *client.PixiuInformer, namespace, name string) (interface{}, error) {\n\t//\t\t\treturn c.GetJob(ctx, informer.JobsLister(), namespace, name)\n\t//\t\t},\n\t//\t},\n\t//\t{\n\t//\t\tResourceType: ResourceNode,\n\t//\t\tListerFunc: func(ctx context.Context, informer *client.PixiuInformer, namespace string, listOption types.ListOptions) (interface{}, error) {\n\t//\t\t\treturn c.ListNodes(ctx, informer.NodesLister(), namespace, listOption)\n\t//\t\t},\n\t//\t\tGetterFunc: func(ctx context.Context, informer *client.PixiuInformer, namespace, name string) (interface{}, error) {\n\t//\t\t\treturn c.GetNode(ctx, informer.NodesLister(), namespace, name)\n\t//\t\t},\n\t//\t},\n\t//\t// TODO: 补充更多资源实现\n\t//}...)\n\treturn c\n}\n"
  },
  {
    "path": "pkg/controller/cluster/informer.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cluster\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\tlistersv1 \"k8s.io/client-go/listers/apps/v1\"\n\tlistersbatchv1 \"k8s.io/client-go/listers/batch/v1\"\n\tv1 \"k8s.io/client-go/listers/core/v1\"\n\t\"k8s.io/klog/v2\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n)\n\nconst (\n\tResourceNode        = \"node\"\n\tResourcePod         = \"pod\"\n\tResourceDeployment  = \"deployment\"\n\tResourceStatefulSet = \"statefulset\"\n\tResourceDaemonSet   = \"daemonset\"\n\tResourceCronJob     = \"cronjob\"\n\tResourceJob         = \"job\"\n)\n\nfunc (c *cluster) GetIndexerResource(ctx context.Context, cluster string, resource string, namespace string, name string) (interface{}, error) {\n\tif len(namespace) == 0 || len(name) == 0 {\n\t\treturn nil, fmt.Errorf(\"namespace or name is empty\")\n\t}\n\tcs, err := c.GetClusterSetByName(ctx, cluster)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// getter functions should be registered in NewCluster function\n\tfn, ok := c.getterFuncs[resource]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"unsupported resource type %s\", resource)\n\t}\n\treturn fn(ctx, cs.Informer, namespace, name)\n}\n\nfunc (c *cluster) GetPod(ctx context.Context, podsLister v1.PodLister, namespace string, name string) (interface{}, error) {\n\tpod, err := podsLister.Pods(namespace).Get(name)\n\tif err != nil {\n\t\tklog.Error(\"failed to get pod (%s/%s) from indexer: %v\", namespace, name, err)\n\t\treturn nil, err\n\t}\n\n\treturn pod, nil\n}\n\nfunc (c *cluster) GetDeployment(ctx context.Context, deploymentsLister listersv1.DeploymentLister, namespace string, name string) (interface{}, error) {\n\tdeploy, err := deploymentsLister.Deployments(namespace).Get(name)\n\tif err != nil {\n\t\tklog.Error(\"failed to get deployment (%s/%s) from indexer: %v\", namespace, name, err)\n\t\treturn nil, err\n\t}\n\n\treturn deploy, nil\n}\n\nfunc (c *cluster) GetStatefulSet(ctx context.Context, statefulSetsLister listersv1.StatefulSetLister, namespace string, name string) (interface{}, error) {\n\tstatefulSet, err := statefulSetsLister.StatefulSets(namespace).Get(name)\n\tif err != nil {\n\t\tklog.Error(\"failed to get statefulSet (%s/%s) from indexer: %v\", namespace, name, err)\n\t\treturn nil, err\n\t}\n\n\treturn statefulSet, nil\n}\n\nfunc (c *cluster) GetDaemonSet(ctx context.Context, daemonSetsLister listersv1.DaemonSetLister, namespace string, name string) (interface{}, error) {\n\tdaemonSet, err := daemonSetsLister.DaemonSets(namespace).Get(name)\n\tif err != nil {\n\t\tklog.Error(\"failed to get daemonset (%s/%s) from indexer: %v\", namespace, name, err)\n\t\treturn nil, err\n\t}\n\n\treturn daemonSet, nil\n}\n\nfunc (c *cluster) GetCronJob(ctx context.Context, cronJobsLister listersbatchv1.CronJobLister, namespace string, name string) (interface{}, error) {\n\tcronJob, err := cronJobsLister.CronJobs(namespace).Get(name)\n\tif err != nil {\n\t\tklog.Error(\"failed to get cronjob (%s/%s) from indexer: %v\", namespace, name, err)\n\t\treturn nil, err\n\t}\n\n\treturn cronJob, nil\n}\n\nfunc (c *cluster) GetJob(ctx context.Context, cronJobsLister listersbatchv1.JobLister, namespace string, name string) (interface{}, error) {\n\tjob, err := cronJobsLister.Jobs(namespace).Get(name)\n\tif err != nil {\n\t\tklog.Error(\"failed to get job (%s/%s) from indexer: %v\", namespace, name, err)\n\t\treturn nil, err\n\t}\n\n\treturn job, nil\n}\n\nfunc (c *cluster) GetNode(ctx context.Context, nodesLister v1.NodeLister, namespace string, name string) (interface{}, error) {\n\tnode, err := nodesLister.Get(name)\n\tif err != nil {\n\t\tklog.Error(\"failed to get node (%s/%s) from indexer: %v\", namespace, name, err)\n\t\treturn nil, err\n\t}\n\n\treturn node, nil\n}\n\nfunc (c *cluster) ListIndexerResources(ctx context.Context, cluster string, resource string, namespace string, listOption types.ListOptions) (interface{}, error) {\n\t// 获取客户端缓存\n\tcs, err := c.GetClusterSetByName(ctx, cluster)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// lister functions should be registered in NewCluster function\n\tfn, ok := c.listerFuncs[resource]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"unsupported resource type %s\", resource)\n\t}\n\n\tif namespace == \"all_namespaces\" {\n\t\tnamespace = \"\"\n\t}\n\treturn fn(ctx, cs.Informer, namespace, listOption)\n}\n\nfunc (c *cluster) ListPods(ctx context.Context, podsLister v1.PodLister, namespace string, listOption types.ListOptions) (interface{}, error) {\n\tpods, err := podsLister.Pods(namespace).List(labels.Everything())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// 构造通用的 objects\n\tobjects := make([]metav1.Object, 0)\n\tfor _, pod := range pods {\n\t\tobjects = append(objects, pod)\n\t}\n\n\treturn c.listObjects(objects, namespace, listOption)\n}\n\n// ListDeployments 从缓存中获取 deployment 列表\nfunc (c *cluster) ListDeployments(ctx context.Context, deploymentsLister listersv1.DeploymentLister, namespace string, listOption types.ListOptions) (interface{}, error) {\n\tdeployments, err := deploymentsLister.Deployments(namespace).List(labels.Everything())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tobjects := make([]metav1.Object, 0)\n\tfor _, deployment := range deployments {\n\t\tobjects = append(objects, deployment)\n\t}\n\n\treturn c.listObjects(objects, namespace, listOption)\n}\n\nfunc (c *cluster) ListStatefulSets(ctx context.Context, statefulSetsLister listersv1.StatefulSetLister, namespace string, listOption types.ListOptions) (interface{}, error) {\n\tstatefulSets, err := statefulSetsLister.StatefulSets(namespace).List(labels.Everything())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tobjects := make([]metav1.Object, 0)\n\tfor _, statefulSet := range statefulSets {\n\t\tobjects = append(objects, statefulSet)\n\t}\n\n\treturn c.listObjects(objects, namespace, listOption)\n}\n\nfunc (c *cluster) ListDaemonSets(ctx context.Context, daemonSetsLister listersv1.DaemonSetLister, namespace string, listOption types.ListOptions) (interface{}, error) {\n\tdaemonSets, err := daemonSetsLister.DaemonSets(namespace).List(labels.Everything())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tobjects := make([]metav1.Object, 0)\n\tfor _, daemonSet := range daemonSets {\n\t\tobjects = append(objects, daemonSet)\n\t}\n\n\treturn c.listObjects(objects, namespace, listOption)\n}\n\nfunc (c *cluster) ListCronJobs(ctx context.Context, cronJobsLister listersbatchv1.CronJobLister, namespace string, listOption types.ListOptions) (interface{}, error) {\n\tcronJobs, err := cronJobsLister.CronJobs(namespace).List(labels.Everything())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tobjects := make([]metav1.Object, 0)\n\tfor _, cronJob := range cronJobs {\n\t\tobjects = append(objects, cronJob)\n\t}\n\n\treturn c.listObjects(objects, namespace, listOption)\n}\n\nfunc (c *cluster) ListJobs(ctx context.Context, jobsLister listersbatchv1.JobLister, namespace string, listOption types.ListOptions) (interface{}, error) {\n\tjobs, err := jobsLister.Jobs(namespace).List(labels.Everything())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tobjects := make([]metav1.Object, 0)\n\tfor _, job := range jobs {\n\t\tobjects = append(objects, job)\n\t}\n\treturn c.listObjects(objects, namespace, listOption)\n}\n\nfunc (c *cluster) ListNodes(ctx context.Context, nodesLister v1.NodeLister, namespace string, listOption types.ListOptions) (interface{}, error) {\n\tnodes, err := nodesLister.List(labels.Everything())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tobjects := make([]metav1.Object, 0)\n\tfor _, node := range nodes {\n\t\tobjects = append(objects, node)\n\t}\n\treturn c.listObjects(objects, namespace, listOption)\n}\n"
  },
  {
    "path": "pkg/controller/cluster/util.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cluster\n\nimport (\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n)\n\nfunc (c *cluster) forQuery(objects []metav1.Object, queryOption types.QueryOption) []metav1.Object {\n\tif len(queryOption.LabelSelector) == 0 && len(queryOption.NameSelector) == 0 {\n\t\treturn objects\n\t}\n\n\tqueryObjects := make([]metav1.Object, 0)\n\tfor _, object := range objects {\n\t\t// 标签搜索\n\t\t// TODO: 多个标签存在时，存在乱序时无法生效\n\t\t// 名称搜索\n\t\tif (len(queryOption.LabelSelector) != 0 && strings.Contains(labels.FormatLabels(object.GetLabels()), queryOption.LabelSelector)) || (len(queryOption.NameSelector) != 0 && strings.Contains(object.GetName(), queryOption.NameSelector)) {\n\t\t\tqueryObjects = append(queryObjects, object)\n\t\t}\n\t}\n\n\treturn queryObjects\n}\n\nfunc (c *cluster) forPage(objects []metav1.Object, pageOption types.PageRequest) []metav1.Object {\n\tif !pageOption.IsPaged() {\n\t\treturn objects\n\t}\n\toffset, end, err := pageOption.Offset(len(objects))\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\treturn objects[offset:end]\n}\n\nfunc (c *cluster) forSorted(objects []metav1.Object, namespace string) []metav1.Object {\n\tsort.SliceStable(objects, func(i, j int) bool {\n\t\treturn objects[i].GetName() < objects[j].GetName()\n\t})\n\t// 全量获取 pod 时，以命名空间排序\n\tif len(namespace) == 0 {\n\t\tsort.SliceStable(objects, func(i, j int) bool {\n\t\t\treturn objects[i].GetNamespace() < objects[j].GetNamespace()\n\t\t})\n\t}\n\n\treturn objects\n}\n\nfunc (c *cluster) listObjects(objects []metav1.Object, namespace string, listOption types.ListOptions) (types.PageResponse, error) {\n\tobjects = c.forQuery(objects, listOption.QueryOption)\n\tobjects = c.forSorted(objects, namespace)\n\treturn types.PageResponse{\n\t\tPageRequest: listOption.PageRequest,\n\t\tTotal:       len(objects),\n\t\tItems:       c.forPage(objects, listOption.PageRequest),\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/controller/cluster/ws.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cluster\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\n\t\"net/http\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/gorilla/websocket\"\n\tv1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/tools/remotecommand\"\n\t\"k8s.io/klog/v2\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n\tsshutil \"github.com/caoyingjunz/pixiu/pkg/util/ssh\"\n)\n\nfunc (c *cluster) WsHandler(ctx context.Context, opt *types.WebShellOptions, w http.ResponseWriter, r *http.Request) error {\n\tcs, err := c.GetClusterSetByName(ctx, opt.Cluster)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get cluster(%s) client set: %v\", opt.Cluster, err)\n\t\treturn err\n\t}\n\n\tsession, err := types.NewTerminalSession(w, r)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// 处理关闭\n\tdefer func() {\n\t\t_ = session.Close()\n\t}()\n\tklog.Infof(\"connecting to %s/%s,\", opt.Namespace, opt.Pod)\n\n\tcmd := opt.Command\n\tif len(cmd) == 0 {\n\t\tcmd = \"/bin/bash\"\n\t}\n\n\t// 组装 POST 请求\n\treq := cs.Client.CoreV1().RESTClient().Post().\n\t\tResource(\"pods\").\n\t\tName(opt.Pod).\n\t\tNamespace(opt.Namespace).\n\t\tSubResource(\"exec\").\n\t\tVersionedParams(&v1.PodExecOptions{\n\t\t\tContainer: opt.Container,\n\t\t\tCommand:   []string{cmd},\n\t\t\tStderr:    true,\n\t\t\tStdin:     true,\n\t\t\tStdout:    true,\n\t\t\tTTY:       true,\n\t\t}, scheme.ParameterCodec)\n\n\t// remotecommand 主要实现了http 转 SPDY 添加X-Stream-Protocol-Version相关header 并发送请求\n\texecutor, err := remotecommand.NewSPDYExecutor(cs.Config, \"POST\", req.URL())\n\tif err != nil {\n\t\treturn err\n\t}\n\t// 与 kubelet 建立 stream 连接\n\tif err = executor.Stream(remotecommand.StreamOptions{\n\t\tStdout:            session,\n\t\tStdin:             session,\n\t\tStderr:            session,\n\t\tTerminalSizeQueue: session,\n\t\tTty:               true,\n\t}); err != nil {\n\t\t_, _ = session.Write([]byte(\"exec pod command failed,\" + err.Error()))\n\t\t// 标记关闭terminal\n\t\tsession.Done()\n\t}\n\n\treturn nil\n}\n\nvar BufPool = sync.Pool{New: func() interface{} { return new(bytes.Buffer) }}\n\nfunc (c *cluster) WsNodeHandler(ctx context.Context, sshConfig *types.WebSSHRequest, w http.ResponseWriter, r *http.Request) error {\n\tupgrader := &websocket.Upgrader{\n\t\tReadBufferSize:   1024,\n\t\tWriteBufferSize:  1024 * 10,\n\t\tHandshakeTimeout: time.Second * 2,\n\t\tCheckOrigin: func(r *http.Request) bool {\n\t\t\treturn true\n\t\t},\n\t\tSubprotocols: []string{r.Header.Get(\"Sec-WebSocket-Protocol\")},\n\t}\n\tconn, err := upgrader.Upgrade(w, r, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer conn.Close()\n\n\tsshClient, err := sshutil.NewSSHClient(sshConfig)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer sshClient.Close()\n\n\tturn, err := types.NewTurn(conn, sshClient)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer turn.Close()\n\n\t// 处理连接\n\thandler(turn)\n\n\treturn nil\n}\n\nfunc handler(turn *types.Turn) {\n\tlogBuff := BufPool.Get().(*bytes.Buffer)\n\tlogBuff.Reset()\n\tdefer BufPool.Put(logBuff)\n\n\twg := &sync.WaitGroup{}\n\twg.Add(2)\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\n\tgo turn.StartLoopRead(ctx, wg, logBuff)\n\tgo turn.StartSessionWait(wg)\n\n\twg.Wait()\n}\n"
  },
  {
    "path": "pkg/controller/controller.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controller\n\nimport (\n\t\"github.com/casbin/casbin/v2\"\n\n\t\"github.com/caoyingjunz/pixiu/cmd/app/config\"\n\t\"github.com/caoyingjunz/pixiu/pkg/controller/audit\"\n\t\"github.com/caoyingjunz/pixiu/pkg/controller/auth\"\n\t\"github.com/caoyingjunz/pixiu/pkg/controller/cluster\"\n\t\"github.com/caoyingjunz/pixiu/pkg/controller/helm\"\n\t\"github.com/caoyingjunz/pixiu/pkg/controller/plan\"\n\t\"github.com/caoyingjunz/pixiu/pkg/controller/tenant\"\n\t\"github.com/caoyingjunz/pixiu/pkg/controller/user\"\n\t\"github.com/caoyingjunz/pixiu/pkg/db\"\n)\n\ntype PixiuInterface interface {\n\tcluster.ClusterGetter\n\ttenant.TenantGetter\n\tuser.UserGetter\n\tplan.PlanGetter\n\taudit.AuditGetter\n\tauth.AuthGetter\n\thelm.HelmGetter\n}\n\ntype pixiu struct {\n\tcc       config.Config\n\tfactory  db.ShareDaoFactory\n\tenforcer *casbin.SyncedEnforcer\n}\n\nfunc (p *pixiu) Cluster() cluster.Interface { return cluster.NewCluster(p.cc, p.factory, p.enforcer) }\nfunc (p *pixiu) Tenant() tenant.Interface   { return tenant.NewTenant(p.cc, p.factory) }\nfunc (p *pixiu) User() user.Interface       { return user.NewUser(p.cc, p.factory, p.enforcer) }\nfunc (p *pixiu) Plan() plan.Interface       { return plan.NewPlan(p.cc, p.factory) }\nfunc (p *pixiu) Audit() audit.Interface     { return audit.NewAudit(p.cc, p.factory) }\nfunc (p *pixiu) Auth() auth.Interface       { return auth.NewAuth(p.factory, p.enforcer) }\nfunc (p *pixiu) Helm() helm.Interface       { return helm.NewHelm(p.factory) }\n\nfunc New(cfg config.Config, f db.ShareDaoFactory, enforcer *casbin.SyncedEnforcer) PixiuInterface {\n\treturn &pixiu{\n\t\tcc:       cfg,\n\t\tfactory:  f,\n\t\tenforcer: enforcer,\n\t}\n}\n"
  },
  {
    "path": "pkg/controller/helm/helm.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helm\n\nimport (\n\t\"context\"\n\n\t\"helm.sh/helm/v3/pkg/action\"\n\t\"helm.sh/helm/v3/pkg/cli\"\n\t\"k8s.io/klog/v2\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/client\"\n\t\"github.com/caoyingjunz/pixiu/pkg/controller/cluster\"\n\t\"github.com/caoyingjunz/pixiu/pkg/db\"\n)\n\ntype HelmGetter interface {\n\tHelm() Interface\n}\n\ntype Interface interface {\n\tRelease(cluster, namespace string) ReleaseInterface\n\tRepository() RepositoryInterface\n}\n\ntype Helm struct {\n\tfactory db.ShareDaoFactory\n}\n\nfunc (h *Helm) Release(cluster, namespace string) ReleaseInterface {\n\tcs := h.MustGetClusterSetByName(context.Background(), cluster)\n\tsettings := cli.New()\n\tsettings.SetNamespace(namespace)\n\tactionConfig := new(action.Configuration)\n\tresetClientGetter := client.NewHelmRESTClientGetter(cs.Config)\n\tactionConfig.Init(\n\t\tresetClientGetter,\n\t\tsettings.Namespace(),\n\t\t\"secrets\",\n\t\tklog.Infof,\n\t)\n\treturn NewReleases(actionConfig, settings)\n}\n\nfunc (h *Helm) Repository() RepositoryInterface {\n\treturn NewRepository(h.factory)\n}\n\nfunc NewHelm(factory db.ShareDaoFactory) Interface {\n\treturn &Helm{\n\t\tfactory: factory,\n\t}\n}\n\nfunc (h *Helm) MustGetClusterSetByName(ctx context.Context, name string) client.ClusterSet {\n\tcs, ok := cluster.ClusterIndexer.Get(name)\n\tif ok {\n\t\tklog.Infof(\"Get %s clusterSet from indexer\", name)\n\t\treturn cs\n\t}\n\n\tklog.Infof(\"building clusterSet for %s\", name)\n\t// 缓存中不存在，则新建并重写回缓存\n\tobject, err := h.factory.Cluster().GetClusterByName(ctx, name)\n\tif err != nil {\n\t\treturn client.ClusterSet{}\n\t}\n\tif object == nil {\n\t\treturn client.ClusterSet{}\n\t}\n\tnewClusterSet, err := client.NewClusterSet(object.KubeConfig)\n\tif err != nil {\n\t\treturn client.ClusterSet{}\n\t}\n\n\tklog.Infof(\"set %s clusterSet into indexer\", name)\n\tcluster.ClusterIndexer.Set(name, *newClusterSet)\n\treturn *newClusterSet\n}\n"
  },
  {
    "path": "pkg/controller/helm/releases.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"helm.sh/helm/v3/pkg/action\"\n\t\"helm.sh/helm/v3/pkg/chart\"\n\t\"helm.sh/helm/v3/pkg/chart/loader\"\n\t\"helm.sh/helm/v3/pkg/cli\"\n\t\"helm.sh/helm/v3/pkg/downloader\"\n\t\"helm.sh/helm/v3/pkg/getter\"\n\t\"helm.sh/helm/v3/pkg/registry\"\n\t\"helm.sh/helm/v3/pkg/release\"\n\t\"k8s.io/klog/v2\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n)\n\ntype ReleaseInterface interface {\n\tInstall(ctx context.Context, form *types.Release) (*release.Release, error)\n\n\tGet(ctx context.Context, name string) (*release.Release, error)\n\tList(ctx context.Context) ([]*release.Release, error)\n\tUninstall(ctx context.Context, name string) (*release.UninstallReleaseResponse, error)\n\tUpgrade(ctx context.Context, form *types.Release) (*release.Release, error)\n\tHistory(ctx context.Context, name string) ([]*release.Release, error)\n\tRollback(ctx context.Context, name string, toVersion int) error\n}\n\ntype Releases struct {\n\tsettings     *cli.EnvSettings\n\tactionConfig *action.Configuration\n}\n\nfunc NewReleases(actionConfig *action.Configuration, settings *cli.EnvSettings) *Releases {\n\treturn &Releases{\n\t\tactionConfig: actionConfig,\n\t\tsettings:     settings,\n\t}\n}\n\nvar _ ReleaseInterface = &Releases{}\n\nfunc (r *Releases) Get(ctx context.Context, name string) (*release.Release, error) {\n\tclient := action.NewGet(r.actionConfig)\n\treturn client.Run(name)\n}\n\nfunc (r *Releases) List(ctx context.Context) ([]*release.Release, error) {\n\tclient := action.NewList(r.actionConfig)\n\treturn client.Run()\n}\n\n// InstallRelease install release\nfunc (r *Releases) Install(ctx context.Context, form *types.Release) (*release.Release, error) {\n\tclient := action.NewInstall(r.actionConfig)\n\tclient.ReleaseName = form.Name\n\tclient.Namespace = r.settings.Namespace()\n\tclient.Version = form.Version\n\n\tclient.DryRun = form.Preview\n\tif client.DryRun {\n\t\tclient.Description = \"server\"\n\t}\n\tchart, err := r.locateChart(client.ChartPathOptions, form.Chart, r.settings)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tout, err := client.Run(chart, form.Values)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (r *Releases) Uninstall(ctx context.Context, name string) (*release.UninstallReleaseResponse, error) {\n\tclient := action.NewUninstall(r.actionConfig)\n\treturn client.Run(name)\n}\n\n// UpgradeRelease upgrade release\nfunc (r *Releases) Upgrade(ctx context.Context, form *types.Release) (*release.Release, error) {\n\tclient := action.NewUpgrade(r.actionConfig)\n\tclient.Namespace = r.settings.Namespace()\n\tclient.DryRun = form.Preview\n\tif client.DryRun {\n\t\tclient.Description = \"server\"\n\t}\n\n\tchart, err := r.locateChart(client.ChartPathOptions, form.Chart, r.settings)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tout, err := client.Run(form.Name, chart, form.Values)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (r *Releases) History(ctx context.Context, name string) ([]*release.Release, error) {\n\tclient := action.NewHistory(r.actionConfig)\n\treturn client.Run(name)\n}\n\nfunc (r *Releases) Rollback(ctx context.Context, name string, toVersion int) error {\n\tklog.Error(\"version: \", toVersion)\n\t_, err := r.Get(ctx, name)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient := action.NewRollback(r.actionConfig)\n\tclient.Version = toVersion\n\treturn client.Run(name)\n}\n\nfunc (r *Releases) locateChart(pathOpts action.ChartPathOptions, chart string, settings *cli.EnvSettings) (*chart.Chart, error) {\n\t// from cmd/helm/install.go and cmd/helm/upgrade.go\n\tcp, err := pathOpts.LocateChart(chart, settings)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tp := getter.All(settings)\n\n\t// Check chart dependencies to make sure all are present in /charts\n\tchartRequested, err := loader.Load(cp)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := checkIfInstallable(chartRequested); err != nil {\n\t\treturn nil, err\n\t}\n\n\tregistryClient, err := registry.NewClient(\n\t\tregistry.ClientOptDebug(false),\n\t\t//registry.ClientOptWriter(out),\n\t\tregistry.ClientOptCredentialsFile(settings.RegistryConfig),\n\t)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to crete helm config object %v\", err)\n\t}\n\n\tif req := chartRequested.Metadata.Dependencies; req != nil {\n\t\t// If CheckDependencies returns an error, we have unfulfilled dependencies.\n\t\t// As of Helm 2.4.0, this is treated as a stopping condition:\n\t\t// https://github.com/helm/helm/issues/2209\n\t\tif err := action.CheckDependencies(chartRequested, req); err != nil {\n\t\t\terr = fmt.Errorf(\"an error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies: %v\", err)\n\t\t\tif true { // client.DependencyUpdate\n\t\t\t\tman := &downloader.Manager{\n\t\t\t\t\tOut:              io.Discard,\n\t\t\t\t\tChartPath:        cp,\n\t\t\t\t\tKeyring:          pathOpts.Keyring,\n\t\t\t\t\tSkipUpdate:       false,\n\t\t\t\t\tGetters:          p,\n\t\t\t\t\tRepositoryConfig: settings.RepositoryConfig,\n\t\t\t\t\tRepositoryCache:  settings.RepositoryCache,\n\t\t\t\t\tDebug:            settings.Debug,\n\t\t\t\t\tRegistryClient:   registryClient, // added on top of Helm code\n\t\t\t\t}\n\t\t\t\tif err := man.Update(); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\t// Reload the chart with the updated Chart.lock file.\n\t\t\t\tif chartRequested, err = loader.Load(cp); err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"failed reloading chart after repo update : %v\", err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn chartRequested, nil\n}\n\nfunc checkIfInstallable(ch *chart.Chart) error {\n\tswitch ch.Metadata.Type {\n\tcase \"\", \"application\":\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"%s charts are not installable\", ch.Metadata.Type)\n}\n"
  },
  {
    "path": "pkg/controller/helm/repository.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"strings\"\n\n\t\"k8s.io/klog/v2\"\n\n\t\"helm.sh/helm/v3/pkg/action\"\n\t\"helm.sh/helm/v3/pkg/cli\"\n\t\"helm.sh/helm/v3/pkg/getter\"\n\t\"helm.sh/helm/v3/pkg/repo\"\n\t\"k8s.io/apimachinery/pkg/util/yaml\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/db\"\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n)\n\ntype RepositoryGetter interface {\n\tRepository() RepositoryInterface\n}\n\ntype RepositoryInterface interface {\n\tCreate(ctx context.Context, repo *types.CreateRepository) error\n\tDelete(ctx context.Context, id int64) error\n\tGet(ctx context.Context, id int64) (*model.Repository, error)\n\tList(ctx context.Context) ([]*model.Repository, error)\n\tUpdate(ctx context.Context, id int64, update *types.UpdateRepository) error\n\n\tGetChartsById(ctx context.Context, id int64) (*model.ChartIndex, error)\n\tGetChartsByURL(ctx context.Context, repoURL string) (*model.ChartIndex, error)\n\tGetChartValues(ctx context.Context, chart, version string) (string, error)\n}\n\ntype Repository struct {\n\tsettings     *cli.EnvSettings\n\tactionConfig *action.Configuration\n\tfactory      db.ShareDaoFactory\n}\n\nfunc NewRepository(f db.ShareDaoFactory) *Repository {\n\tsettings := cli.New()\n\tactionConfig := new(action.Configuration)\n\tactionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), \"secrets\", klog.Infof)\n\treturn &Repository{factory: f, settings: settings, actionConfig: actionConfig}\n}\n\nvar _ RepositoryInterface = &Repository{}\n\nfunc (r *Repository) Create(ctx context.Context, repo *types.CreateRepository) error {\n\n\trepoModel := &model.Repository{\n\t\tName: repo.Name,\n\t\tURL:  repo.URL,\n\t}\n\tif res, _ := r.GetByName(ctx, repoModel.Name); res != nil {\n\t\treturn fmt.Errorf(\"repository %s already exists\", repoModel.Name)\n\t}\n\n\t_, err := r.factory.Repository().Create(ctx, repoModel)\n\treturn err\n}\n\nfunc (r *Repository) Delete(ctx context.Context, id int64) error {\n\treturn r.factory.Repository().Delete(ctx, id)\n}\n\nfunc (r *Repository) Get(ctx context.Context, id int64) (*model.Repository, error) {\n\treturn r.factory.Repository().Get(ctx, id)\n}\n\nfunc (r *Repository) GetByName(ctx context.Context, name string) (*model.Repository, error) {\n\treturn r.factory.Repository().GetByName(ctx, name)\n}\n\nfunc (r *Repository) List(ctx context.Context) ([]*model.Repository, error) {\n\treturn r.factory.Repository().List(ctx)\n}\n\nfunc (r *Repository) Update(ctx context.Context, id int64, update *types.UpdateRepository) error {\n\tupdates := map[string]interface{}{\n\t\t\"name\":     update.Name,\n\t\t\"url\":      update.URL,\n\t\t\"username\": update.Username,\n\t\t\"password\": update.Password,\n\t}\n\treturn r.factory.Repository().Update(ctx, id, *update.ResourceVersion, updates)\n}\n\nfunc (r *Repository) GetChartsById(ctx context.Context, id int64) (*model.ChartIndex, error) {\n\trepository, err := r.Get(ctx, id)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tentry := &repo.Entry{\n\t\tName:     repository.Name,\n\t\tURL:      repository.URL,\n\t\tUsername: repository.Username,\n\t\tPassword: repository.Password,\n\t}\n\treturn r.fetch(ctx, entry)\n}\n\nfunc (r *Repository) GetChartsByURL(ctx context.Context, repoURL string) (*model.ChartIndex, error) {\n\tentry := &repo.Entry{\n\t\tURL: repoURL,\n\t}\n\treturn r.fetch(ctx, entry)\n}\n\nfunc (r *Repository) GetChartValues(_ context.Context, chart, version string) (string, error) {\n\tclient := action.NewShowWithConfig(action.ShowValues, r.actionConfig)\n\tclient.Version = version\n\tcp, err := client.ChartPathOptions.LocateChart(chart, r.settings)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tout, err := client.Run(cp)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn out, nil\n}\n\nfunc (r *Repository) resolveReferenceURL(baseURL, refURL string) (string, error) {\n\tparsedRefURL, err := url.Parse(refURL)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to parse %s as URL, %v\", refURL, err)\n\t}\n\n\tif parsedRefURL.IsAbs() {\n\t\treturn refURL, nil\n\t}\n\n\tparsedBaseURL, err := url.Parse(baseURL)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to parse %s as URL, %v\", baseURL, err)\n\t}\n\n\tparsedBaseURL.RawPath = strings.TrimSuffix(parsedBaseURL.RawPath, \"/\") + \"/\"\n\tparsedBaseURL.Path = strings.TrimSuffix(parsedBaseURL.Path, \"/\") + \"/\"\n\n\tresolvedURL := parsedBaseURL.ResolveReference(parsedRefURL)\n\tresolvedURL.RawQuery = parsedBaseURL.RawQuery\n\treturn resolvedURL.String(), nil\n}\n\nfunc (r *Repository) fetch(_ context.Context, entry *repo.Entry) (*model.ChartIndex, error) {\n\tvar charts model.ChartIndex\n\n\trep, err := repo.NewChartRepository(entry, getter.All(r.settings))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// download index\n\tif _, err = rep.DownloadIndexFile(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tindexURL, err := r.resolveReferenceURL(rep.Config.URL, \"index.yaml\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresp, err := rep.Client.Get(indexURL,\n\t\tgetter.WithURL(rep.Config.URL),\n\t\tgetter.WithInsecureSkipVerifyTLS(rep.Config.InsecureSkipTLSverify),\n\t\tgetter.WithTLSClientConfig(rep.Config.CertFile, rep.Config.KeyFile, rep.Config.CAFile),\n\t\tgetter.WithBasicAuth(rep.Config.Username, rep.Config.Password),\n\t\tgetter.WithPassCredentialsAll(rep.Config.PassCredentialsAll),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = yaml.Unmarshal(resp.Bytes(), &charts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &charts, nil\n}\n"
  },
  {
    "path": "pkg/controller/plan/bootstrap_servers.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (phe \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plan\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/util/container\"\n)\n\ntype BootStrap struct {\n\thandlerTask\n\n\tdir    string\n\trunner string\n}\n\nfunc (b BootStrap) Name() string { return \"初始化部署环境\" }\n\n// Run 以容器的形式执行 BootStrap 任务，如果存在旧的容器，则先删除在执行\nfunc (b BootStrap) Run() error {\n\tcli, err := container.NewContainer(\"bootstrap-servers\", b.GetPlanId(), b.dir)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cli.Close()\n\n\tctx, cancel := context.WithTimeout(context.Background(), 600*time.Second)\n\tdefer cancel()\n\n\t// 启动执行容器\n\tif err = cli.StartAndWaitForContainer(ctx, b.runner); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/controller/plan/checker.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (phe \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plan\n\ntype Check struct {\n\thandlerTask\n}\n\nfunc (c Check) Name() string { return \"部署预检查\" }\nfunc (c Check) Run() error {\n\tif err := c.data.validate(); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/controller/plan/deploy.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (phe \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plan\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n\t\"github.com/caoyingjunz/pixiu/pkg/util/container\"\n)\n\ntype Deploy struct {\n\thandlerTask\n\n\tdir    string\n\trunner string\n}\n\nfunc (b Deploy) Name() string { return \"部署Master\" }\n\n// Run 以容器的形式执行 BootStrap 任务，如果存在旧的容器，则先删除在执行\nfunc (b Deploy) Run() error {\n\tcli, err := container.NewContainer(\"deploy\", b.GetPlanId(), b.dir)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cli.Close()\n\n\tctx, cancel := context.WithTimeout(context.Background(), 600*time.Second)\n\tdefer cancel()\n\n\t// 启动执行容器\n\tif err = cli.StartAndWaitForContainer(ctx, b.runner); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\ntype DeployNode struct {\n\thandlerTask\n}\n\nfunc (b DeployNode) Name() string { return \"部署Node\" }\n\n// Run 以容器的形式执行 BootStrap 任务，如果存在旧的容器，则先删除在执行\nfunc (b DeployNode) Run() error {\n\treturn nil\n}\n\ntype DeployChart struct {\n\thandlerTask\n}\n\nfunc (b DeployChart) Name() string         { return \"部署基础组件\" }\nfunc (b DeployChart) Step() model.PlanStep { return model.CompletedPlanStep }\n\n// Run 以容器的形式执行 BootStrap 任务，如果存在旧的容器，则先删除在执行\nfunc (b DeployChart) Run() error {\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/controller/plan/plan.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (phe \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plan\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"sync\"\n\n\t\"gorm.io/gorm\"\n\t\"k8s.io/client-go/util/workqueue\"\n\t\"k8s.io/klog/v2\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/errors\"\n\t\"github.com/caoyingjunz/pixiu/cmd/app/config\"\n\t\"github.com/caoyingjunz/pixiu/pkg/client\"\n\t\"github.com/caoyingjunz/pixiu/pkg/db\"\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n\t\"github.com/caoyingjunz/pixiu/pkg/util/uuid\"\n)\n\ntype PlanGetter interface {\n\tPlan() Interface\n}\n\ntype Interface interface {\n\tCreate(ctx context.Context, req *types.CreatePlanRequest) error\n\tUpdate(ctx context.Context, planID int64, req *types.UpdatePlanRequest) error\n\tDelete(ctx context.Context, pid int64) error\n\tGet(ctx context.Context, pid int64) (*types.Plan, error)\n\tList(ctx context.Context, req *types.ListPlanRequest) (*types.PageResponse, error)\n\n\tGetWithSubResources(ctx context.Context, planId int64) (*types.Plan, error)\n\n\t// Start 启动部署任务\n\tStart(ctx context.Context, pid int64) error\n\t// Stop 终止部署任务\n\tStop(ctx context.Context, pid int64) error\n\n\tCreateNode(ctx context.Context, pid int64, req *types.CreatePlanNodeRequest) error\n\tUpdateNode(ctx context.Context, pid int64, nodeId int64, req *types.UpdatePlanNodeRequest) error\n\tDeleteNode(ctx context.Context, pid int64, nodeId int64) error\n\tGetNode(ctx context.Context, pid int64, nodeId int64) (*types.PlanNode, error)\n\tListNodes(ctx context.Context, pid int64) ([]types.PlanNode, error)\n\n\tCreateConfig(ctx context.Context, planId int64, req *types.CreatePlanConfigRequest) error\n\tUpdateConfig(ctx context.Context, pid int64, cfgId int64, req *types.UpdatePlanConfigRequest) error\n\tDeleteConfig(ctx context.Context, pid int64, cfgId int64) error\n\tGetConfig(ctx context.Context, planId int64) (*types.PlanConfig, error)\n\n\t// Run 启动 plan worker 处理协程\n\tRun(ctx context.Context, workers int) error\n\n\tRunTask(ctx context.Context, planId int64, taskId int64) error\n\tListTasks(ctx context.Context, planId int64) ([]types.PlanTask, error)\n\tWatchTasks(ctx context.Context, planId int64, w http.ResponseWriter, r *http.Request)\n\tWatchTaskLog(ctx context.Context, planId int64, taskId int64, w http.ResponseWriter, r *http.Request) error\n}\n\nvar taskQueue workqueue.RateLimitingInterface\nvar taskC *client.Task\n\nfunc init() {\n\ttaskQueue = workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), \"tasks\")\n\ttaskC = client.NewTaskCache()\n}\n\ntype plan struct {\n\tcc      config.Config\n\tfactory db.ShareDaoFactory\n}\n\n// Create\n// 1. 创建部署计划\n// 2. 创建部署配置\n// 3. 创建节点列表\n// 4. 创建扩展组件\n// 5. 创建容器服务\nfunc (p *plan) Create(ctx context.Context, req *types.CreatePlanRequest) error {\n\tplanModel := &model.Plan{\n\t\tName:        req.Name,\n\t\tDescription: req.Description,\n\t}\n\n\tcreatedPlan, err := p.factory.Plan().Create(ctx, planModel, p.createPlanSubResources(ctx, req))\n\tif err != nil {\n\t\tklog.Errorf(\"failed to create plan %s: %v\", req.Name, err)\n\t\treturn errors.ErrServerInternal\n\t}\n\tplanId := createdPlan.Id\n\n\t// 如果启用pixiu注册功能，则创建容器服务\n\tkubeNode := types.KubeNode{Ready: []string{}, NotReady: []string{}}\n\tnodes, _ := kubeNode.Marshal()\n\t_, err = p.factory.Cluster().Create(ctx, &model.Cluster{\n\t\tName:        uuid.NewRandName(8),\n\t\tAliasName:   req.Name,\n\t\tDescription: req.Description,\n\t\tClusterType: model.ClusterTypeCustom,\n\t\tPlanId:      planId,\n\t\tProtected:   true,\n\t\tNodes:       nodes,\n\t})\n\tif err != nil {\n\t\tklog.Errorf(\"failed to register cluster for plan: %v\", err)\n\t\t_ = p.Delete(ctx, planId)\n\t\treturn errors.ErrServerInternal\n\t}\n\treturn nil\n}\n\nfunc (p *plan) createPlanSubResources(ctx context.Context, req *types.CreatePlanRequest) db.CreatePlanOption {\n\treturn func(planModel *model.Plan, tx *gorm.DB) (*gorm.DB, error) {\n\t\tif err := p.preCreateConfig(ctx, planModel.Id, &req.Config); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tplanConfig, err := p.buildPlanConfig(ctx, &req.Config)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tplanConfig.PlanId = planModel.Id\n\n\t\tif err := p.factory.Plan().TxCreateConfig(ctx, tx, planConfig); err != nil {\n\t\t\tklog.Errorf(\"failed to create plan(%d) config: %v\", planModel.Id, err)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tfor i := range req.Nodes {\n\t\t\tnodeReq := &req.Nodes[i]\n\t\t\tnode, err := p.buildNodeFromRequest(planModel.Id, nodeReq)\n\t\t\tif err != nil {\n\t\t\t\tklog.Errorf(\"failed to build plan(%d) node from request: %v\", planModel.Id, err)\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif err := p.factory.Plan().TxCreateNode(ctx, tx, node); err != nil {\n\t\t\t\tklog.Errorf(\"failed to create node(%s): %v\", nodeReq.Name, err)\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\n\t\treturn tx, nil\n\t}\n}\n\n// Update\n// 更新部署计划\nfunc (p *plan) Update(ctx context.Context, planId int64, req *types.UpdatePlanRequest) error {\n\toldPlan, err := p.factory.Plan().Get(ctx, planId)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get plan(%d) %v\", planId, err)\n\t\treturn errors.ErrServerInternal\n\t}\n\t// 必要时更新 plan\n\tif oldPlan.Description != req.Description {\n\t\tif err := p.factory.Plan().Update(ctx, planId, *req.ResourceVersion, map[string]interface{}{\"description\": req.Description}); err != nil {\n\t\t\tklog.Errorf(\"failed to update plan %d: %v\", planId, err)\n\t\t\treturn errors.ErrServerInternal\n\t\t}\n\t}\n\n\t// 必要时更新部署计划配置\n\tif err = p.UpdateConfigIfNeeded(ctx, planId, req); err != nil {\n\t\tklog.Errorf(\"failed to update plan(%d) config: %v\", planId, err)\n\t\treturn errors.ErrServerInternal\n\t}\n\n\t// 必要时更新部署计划 nodes\n\tif err = p.updateNodesIfNeeded(ctx, planId, req); err != nil {\n\t\tklog.Errorf(\"failed to update plan(%d) nodes: %v\", planId, err)\n\t\treturn errors.ErrServerInternal\n\t}\n\n\treturn nil\n}\n\n// 删除前检查\n// 有正在运行中的任务则不允许删除\nfunc (p *plan) preDelete(ctx context.Context, planId int64) error {\n\tisRunning, err := p.TaskIsRunning(ctx, planId)\n\tif err != nil {\n\t\treturn errors.ErrServerInternal\n\t}\n\tif isRunning {\n\t\treturn errors.ErrNotAcceptable\n\t}\n\treturn nil\n}\n\n// Delete\n// 1. 删除部署计划\n// 2. 删除关联任务\n// 3. 删除关联配置\n// 4. 删除关联节点\nfunc (p *plan) Delete(ctx context.Context, planId int64) error {\n\t// 删除前校验\n\tif err := p.preDelete(ctx, planId); err != nil {\n\t\treturn err\n\t}\n\n\t// 执行实际的删除逻辑\n\t_, err := p.factory.Plan().Delete(ctx, planId)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to delete plan %d: %v\", planId, err)\n\t\treturn errors.ErrServerInternal\n\t}\n\t// 删除 plan 关联资源\n\t// 2. 删除部署计划后，同步删除任务，删除任务失败时，可直接忽略\n\tif err = p.factory.Plan().DeleteTask(ctx, planId); err != nil {\n\t\tklog.Errorf(\"failed to delete plan(%d) task: %v\", planId, err)\n\t\treturn err\n\t}\n\t// 3. 删除关联配置\n\tif err = p.factory.Plan().DeleteConfigByPlan(ctx, planId); err != nil {\n\t\tklog.Errorf(\"failed to delete plan(%d) config: %v\", planId, err)\n\t\treturn err\n\t}\n\t// 4. 删除关联nodes\n\tif err = p.factory.Plan().DeleteNodesByPlan(ctx, planId); err != nil {\n\t\tklog.Errorf(\"failed to delete plan(%d) nodes: %v\", planId, err)\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (p *plan) Get(ctx context.Context, pid int64) (*types.Plan, error) {\n\tobject, err := p.factory.Plan().Get(ctx, pid)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get plan %d: %v\", pid, err)\n\t\treturn nil, errors.ErrServerInternal\n\t}\n\n\treturn p.model2Type(object)\n}\n\n// GetWithSubResources\n// 获取 plan\n// 获取 configs\n// 获取 nodes\nfunc (p *plan) GetWithSubResources(ctx context.Context, planId int64) (*types.Plan, error) {\n\tresult, err := p.Get(ctx, planId)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// 追加配置\n\tcfg, err := p.GetConfig(ctx, planId)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tresult.Config = *cfg\n\n\t// 追加节点\n\tresult.Nodes, err = p.ListNodes(ctx, planId)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn result, nil\n}\n\nfunc (p *plan) List(ctx context.Context, req *types.ListPlanRequest) (*types.PageResponse, error) {\n\tvar opts []db.Options\n\tif req != nil && req.NameSelector != \"\" {\n\t\topts = append(opts, db.WithPlanNameLike(req.NameSelector))\n\t}\n\n\ttotal, err := p.factory.Plan().Count(ctx, opts...)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to count plans: %v\", err)\n\t\treturn nil, errors.ErrServerInternal\n\t}\n\n\tpageReq := types.PageRequest{}\n\tif req != nil {\n\t\tpageReq = req.PageRequest\n\t\tif req.Page > 0 && req.Limit > 0 {\n\t\t\topts = append(opts, db.WithOffset((req.Page-1)*req.Limit), db.WithLimit(req.Limit))\n\t\t}\n\t}\n\n\tobjects, err := p.factory.Plan().List(ctx, opts...)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get plans: %v\", err)\n\t\treturn nil, errors.ErrServerInternal\n\t}\n\n\tps := make([]types.Plan, 0, len(objects))\n\tfor _, object := range objects {\n\t\tno, err := p.model2Type(&object)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\t// 状态过滤（在内存中过滤，因为状态来自关联表）\n\t\tif req != nil && req.Step != \"\" && string(no.Step) != req.Step {\n\t\t\tcontinue\n\t\t}\n\t\tps = append(ps, *no)\n\t}\n\n\treturn &types.PageResponse{\n\t\tPageRequest: pageReq,\n\t\tTotal:       int(total),\n\t\tItems:       ps,\n\t}, nil\n}\n\n// listAll 内部使用，返回所有计划（不带分页和过滤）\nfunc (p *plan) listAll(ctx context.Context) ([]types.Plan, error) {\n\tobjects, err := p.factory.Plan().List(ctx)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get plans: %v\", err)\n\t\treturn nil, errors.ErrServerInternal\n\t}\n\n\tvar ps []types.Plan\n\tfor _, object := range objects {\n\t\tno, err := p.model2Type(&object)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tps = append(ps, *no)\n\t}\n\treturn ps, nil\n}\n\nfunc (p *plan) SyncTaskStatus(ctx context.Context) error {\n\tplans, err := p.listAll(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar wg sync.WaitGroup\n\terrChan := make(chan error, len(plans))\n\tfor _, planP := range plans {\n\t\twg.Add(1)\n\t\tgo func(planId int64) {\n\t\t\tdefer wg.Done()\n\t\t\tif err = p.syncStatus(ctx, planId); err != nil {\n\t\t\t\terrChan <- err\n\t\t\t}\n\t\t}(planP.Id)\n\t}\n\twg.Wait()\n\n\tselect {\n\tcase err := <-errChan:\n\t\treturn err\n\tdefault:\n\t}\n\n\treturn nil\n}\n\n// 启动前校验\n// 1. 配置\n// 2. 节点\n// 3. 校验runner\n// 3. 运行任务\nfunc (p *plan) preStart(ctx context.Context, pid int64) error {\n\t// 1. 校验配置\n\tcfg, err := p.GetConfig(ctx, pid)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get plan(%d) config %v\", pid, err)\n\t}\n\t// TODO: 根据具体情况对参数\n\n\t// 2. 校验节点\n\tnodes, err := p.ListNodes(ctx, pid)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get plan(%d) nodes %v\", pid, err)\n\t}\n\tif len(nodes) == 0 {\n\t\treturn fmt.Errorf(\"部署计划暂无关联节点\")\n\t}\n\n\t// 3. 校验runner\n\trunner, err := p.GetRunner(cfg.OSImage)\n\tif err != nil {\n\t\treturn err\n\t}\n\tklog.Infof(\"plan(%d) runner is %s\", pid, runner)\n\n\t// 4. 校验运行任务\n\tisRunning, err := p.TaskIsRunning(ctx, pid)\n\tif err != nil {\n\t\treturn errors.ErrServerInternal\n\t}\n\tif isRunning {\n\t\treturn errors.ErrNotAcceptable\n\t}\n\n\treturn nil\n}\n\n// TaskIsRunning\n// 校验是否有任务正在运行\nfunc (p *plan) TaskIsRunning(ctx context.Context, planId int64) (bool, error) {\n\ttasks, err := p.factory.Plan().ListTasks(ctx, planId)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get tasks of plan %d: %v\", planId, err)\n\t\treturn false, errors.ErrServerInternal\n\t}\n\n\tfor _, task := range tasks {\n\t\tif task.Status == model.RunningPlanStatus {\n\t\t\tklog.Warningf(\"task %d of plan %d is running\", task.Id, planId)\n\t\t\treturn true, nil\n\t\t}\n\t}\n\n\treturn false, nil\n}\n\nfunc (p *plan) Start(ctx context.Context, pid int64) error {\n\t// 启动前校验\n\tif err := p.preStart(ctx, pid); err != nil {\n\t\treturn err\n\t}\n\n\ttaskQueue.Add(pid)\n\treturn nil\n}\n\nfunc (p *plan) Stop(ctx context.Context, pid int64) error {\n\treturn nil\n}\n\nfunc (p *plan) model2Type(o *model.Plan) (*types.Plan, error) {\n\tstatus := model.SuccessPlanStatus\n\n\t// 尝试获取最新的任务状态\n\t// 获取失败也不中断返回\n\tif tasks, err := p.factory.Plan().ListTasks(context.TODO(), o.Id); err == nil {\n\t\tif len(tasks) == 0 {\n\t\t\tstatus = model.UnStartPlanStatus\n\t\t} else {\n\t\t\tfor _, task := range tasks {\n\t\t\t\tif task.Status != model.SuccessPlanStatus {\n\t\t\t\t\tstatus = task.Status\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// 获取 kubernetes 版本，获取失败不中断\n\tkubernetesVersion := \"\"\n\tif cfg, err := p.factory.Plan().GetConfigByPlan(context.TODO(), o.Id); err == nil && cfg != nil {\n\t\tvar kubeSpec struct {\n\t\t\tKubernetesVersion string `json:\"kubernetes_version\"`\n\t\t}\n\t\tif err := json.Unmarshal([]byte(cfg.Kubernetes), &kubeSpec); err == nil {\n\t\t\tkubernetesVersion = kubeSpec.KubernetesVersion\n\t\t}\n\t}\n\n\t// 获取节点总数，获取失败不中断\n\tnodeCount := 0\n\tif nodes, err := p.factory.Plan().ListNodes(context.TODO(), o.Id); err == nil {\n\t\tnodeCount = len(nodes)\n\t}\n\n\treturn &types.Plan{\n\t\tPixiuMeta: types.PixiuMeta{\n\t\t\tId:              o.Id,\n\t\t\tResourceVersion: o.ResourceVersion,\n\t\t},\n\t\tTimeMeta: types.TimeMeta{\n\t\t\tGmtCreate:   o.GmtCreate,\n\t\t\tGmtModified: o.GmtModified,\n\t\t},\n\t\tName:              o.Name,\n\t\tDescription:       o.Description,\n\t\tStep:              status,\n\t\tKubernetesVersion: kubernetesVersion,\n\t\tNodeCount:         nodeCount,\n\t}, nil\n}\n\nfunc NewPlan(cfg config.Config, f db.ShareDaoFactory) *plan {\n\treturn &plan{\n\t\tcc:      cfg,\n\t\tfactory: f,\n\t}\n}\n"
  },
  {
    "path": "pkg/controller/plan/plan_config.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (phe \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plan\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"k8s.io/klog/v2\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/errors\"\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n)\n\nfunc (p *plan) preCreateConfig(ctx context.Context, planId int64, req *types.CreatePlanConfigRequest) error {\n\t_, err := p.factory.Plan().GetConfigByPlan(ctx, planId)\n\tif err == nil {\n\t\treturn fmt.Errorf(\"plan(%d) 配置已存在\", planId)\n\t}\n\n\treturn nil\n}\n\nfunc (p *plan) CreateConfig(ctx context.Context, pid int64, req *types.CreatePlanConfigRequest) error {\n\t// 创建前检查\n\tif err := p.preCreateConfig(ctx, pid, req); err != nil {\n\t\treturn err\n\t}\n\n\tplanConfig, err := p.buildPlanConfig(ctx, req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tplanConfig.PlanId = pid\n\t// 创建配置\n\tif _, err = p.factory.Plan().CreateConfig(ctx, planConfig); err != nil {\n\t\tklog.Errorf(\"failed to create plan(%s) config: %v\", pid, err)\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// UpdateConfig\n// TODO\nfunc (p *plan) UpdateConfig(ctx context.Context, pid int64, cfgId int64, req *types.UpdatePlanConfigRequest) error {\n\treturn nil\n}\n\n// UpdateConfigIfNeeded\n// 更新部署计划配置\nfunc (p *plan) UpdateConfigIfNeeded(ctx context.Context, planId int64, req *types.UpdatePlanRequest) error {\n\toldConfig, err := p.factory.Plan().GetConfigByPlan(ctx, planId)\n\tif err != nil {\n\t\treturn errors.ErrServerInternal\n\t}\n\tnewConfig := req.Config\n\n\tupdates := make(map[string]interface{})\n\tif oldConfig.Region != newConfig.Region {\n\t\tupdates[\"region\"] = newConfig.Region\n\t}\n\tif oldConfig.OSImage != newConfig.OSImage {\n\t\tupdates[\"os_image\"] = newConfig.OSImage\n\t}\n\n\tnewKubernetes, err := p.buildAndCleanKubernetesConfig(newConfig.Kubernetes)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif oldConfig.Kubernetes != newKubernetes {\n\t\tupdates[\"kubernetes\"] = newKubernetes\n\t}\n\n\tnewNetwork, err := newConfig.Network.Marshal()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif oldConfig.Network != newNetwork {\n\t\tupdates[\"network\"] = newNetwork\n\t}\n\n\tnewRuntime, err := newConfig.Runtime.Marshal()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif oldConfig.Runtime != newRuntime {\n\t\tupdates[\"runtime\"] = newRuntime\n\t}\n\n\tnewComponent, err := newConfig.Component.Marshal()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif oldConfig.Component != newComponent {\n\t\tupdates[\"component\"] = newComponent\n\t}\n\n\t// 没有更新，则直接返回\n\tif len(updates) == 0 {\n\t\treturn nil\n\t}\n\tif err = p.factory.Plan().UpdateConfig(ctx, oldConfig.Id, oldConfig.ResourceVersion, updates); err != nil {\n\t\tklog.Errorf(\"failed to update plan(%d) config: %v\", planId, err)\n\t\treturn errors.ErrServerInternal\n\t}\n\n\treturn nil\n}\n\nfunc (p *plan) DeleteConfig(ctx context.Context, pid int64, cfgId int64) error {\n\tif _, err := p.factory.Plan().DeleteConfig(ctx, cfgId); err != nil {\n\t\tklog.Errorf(\"failed to delete plan(%d) config(%d): %v\", pid, cfgId, err)\n\t\treturn errors.ErrServerInternal\n\t}\n\n\treturn nil\n}\n\nfunc (p *plan) GetConfig(ctx context.Context, pid int64) (*types.PlanConfig, error) {\n\tobject, err := p.factory.Plan().GetConfigByPlan(ctx, pid)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get plan(%d) config: %v\", pid, err)\n\t\treturn nil, errors.ErrServerInternal\n\t}\n\n\treturn p.modelConfig2Type(object)\n}\n\nfunc (p *plan) buildAndCleanKubernetesConfig(ks types.KubernetesSpec) (string, error) {\n\tif ks.EnablePublicIp {\n\t\tif len(ks.ApiServer) == 0 {\n\t\t\treturn \"\", fmt.Errorf(\"启用 ApiServer 地址，但是未配置关联 IP\")\n\t\t}\n\t} else {\n\t\tif len(ks.ApiServer) != 0 {\n\t\t\tks.ApiServer = \"\"\n\t\t}\n\t}\n\treturn ks.Marshal()\n}\n\nfunc (p *plan) buildPlanConfig(ctx context.Context, req *types.CreatePlanConfigRequest) (*model.Config, error) {\n\tkubeConfig, err := p.buildAndCleanKubernetesConfig(req.Kubernetes)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tnetworkConfig, err := req.Network.Marshal()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\truntimeConfig, err := req.Runtime.Marshal()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcomponentConfig, err := req.Component.Marshal()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &model.Config{\n\t\tRegion:     req.Region,\n\t\tOSImage:    req.OSImage,\n\t\tKubernetes: kubeConfig,\n\t\tNetwork:    networkConfig,\n\t\tRuntime:    runtimeConfig,\n\t\tComponent:  componentConfig,\n\t}, nil\n}\n\nfunc (p *plan) modelConfig2Type(o *model.Config) (*types.PlanConfig, error) {\n\tks := &types.KubernetesSpec{}\n\tif err := ks.Unmarshal(o.Kubernetes); err != nil {\n\t\treturn nil, err\n\t}\n\tns := &types.NetworkSpec{}\n\tif err := ns.Unmarshal(o.Network); err != nil {\n\t\treturn nil, err\n\t}\n\trs := &types.RuntimeSpec{}\n\tif err := rs.Unmarshal(o.Runtime); err != nil {\n\t\treturn nil, err\n\t}\n\tcs := &types.ComponentSpec{}\n\tif err := cs.Unmarshal(o.Component); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &types.PlanConfig{\n\t\tPixiuMeta: types.PixiuMeta{\n\t\t\tId:              o.Id,\n\t\t\tResourceVersion: o.ResourceVersion,\n\t\t},\n\t\tTimeMeta: types.TimeMeta{\n\t\t\tGmtCreate:   o.GmtCreate,\n\t\t\tGmtModified: o.GmtModified,\n\t\t},\n\t\tPlanId:     o.PlanId,\n\t\tRegion:     o.Region,\n\t\tOSImage:    o.OSImage,\n\t\tKubernetes: *ks,\n\t\tNetwork:    *ns,\n\t\tRuntime:    *rs,\n\t\tComponent:  *cs,\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/controller/plan/plan_node.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (phe \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plan\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"k8s.io/klog/v2\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/errors\"\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n\tutilerrors \"github.com/caoyingjunz/pixiu/pkg/util/errors\"\n)\n\n// 创建前预检查\n// 1. 创建 node 时 plan 必须存在\nfunc (p *plan) preCreateNode(ctx context.Context, pid int64, req *types.CreatePlanNodeRequest) error {\n\t_, err := p.Get(ctx, pid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (p *plan) CreateNode(ctx context.Context, pid int64, req *types.CreatePlanNodeRequest) error {\n\tif err := p.preCreateNode(ctx, pid, req); err != nil {\n\t\treturn err\n\t}\n\n\tif err := p.createNode(ctx, pid, req); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (p *plan) CreateNodes(ctx context.Context, planId int64, nodes []types.CreatePlanNodeRequest) error {\n\t_, err := p.Get(ctx, planId)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, node := range nodes {\n\t\tif err = p.createNode(ctx, planId, &node); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\nfunc (p *plan) UpdateNode(ctx context.Context, pid int64, nodeId int64, req *types.UpdatePlanNodeRequest) error {\n\treturn nil\n}\n\n// 删除多余的节点\n// 新增没有的节点\n// 更新已存在的节点\nfunc (p *plan) updateNodesIfNeeded(ctx context.Context, planId int64, req *types.UpdatePlanRequest) error {\n\toldNodes, err := p.factory.Plan().ListNodes(ctx, planId)\n\tif err != nil {\n\t\treturn err\n\t}\n\tnewNodes := req.Nodes\n\n\tnewMap := make(map[string]types.CreatePlanNodeRequest)\n\tfor _, newNode := range newNodes {\n\t\tnewMap[newNode.Name] = newNode\n\t}\n\n\t// 遍历寻找待删除节点然后执行删除\n\tvar delNodes []string\n\tfor _, oldNode := range oldNodes {\n\t\tname := oldNode.Name\n\t\t_, found := newMap[name]\n\t\tif !found {\n\t\t\tdelNodes = append(delNodes, name)\n\t\t}\n\t}\n\tif len(delNodes) != 0 {\n\t\tif err = p.factory.Plan().DeleteNodesByNames(ctx, planId, delNodes); err != nil {\n\t\t\tklog.Errorf(\"failed deleting nodes %v %v\", delNodes, err)\n\t\t\treturn err\n\t\t}\n\t}\n\n\tfor _, newNode := range newNodes {\n\t\tnode, err := p.buildNodeFromRequest(planId, &newNode)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err = p.CreateOrUpdateNode(ctx, node); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (p *plan) buildNodeFromRequest(planId int64, req *types.CreatePlanNodeRequest) (*model.Node, error) {\n\tauth, err := req.Auth.Marshal()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &model.Node{\n\t\tName:   req.Name,\n\t\tPlanId: planId,\n\t\tRole:   strings.Join(req.Role, \",\"),\n\t\tCRI:    req.CRI,\n\t\tIp:     req.Ip,\n\t\tAuth:   auth,\n\t}, nil\n}\n\nfunc (p *plan) createNode(ctx context.Context, planId int64, req *types.CreatePlanNodeRequest) error {\n\tnode, err := p.buildNodeFromRequest(planId, req)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to build plan(%d) node from request: %v\", planId, err)\n\t\treturn err\n\t}\n\tif _, err = p.factory.Plan().CreateNode(ctx, node); err != nil {\n\t\tklog.Errorf(\"failed to create node(%s): %v\", req.Name, err)\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (p *plan) DeleteNode(ctx context.Context, pid int64, nodeId int64) error {\n\tif _, err := p.factory.Plan().DeleteNode(ctx, nodeId); err != nil {\n\t\tklog.Errorf(\"failed to delete plan(%d) node(%d): %v\", pid, nodeId, err)\n\t\treturn errors.ErrServerInternal\n\t}\n\n\treturn nil\n}\n\nfunc (p *plan) GetNode(ctx context.Context, pid int64, nodeId int64) (*types.PlanNode, error) {\n\tobject, err := p.factory.Plan().GetNode(ctx, nodeId)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get plan(%d) node(%d): %v\", pid, nodeId, err)\n\t\treturn nil, errors.ErrServerInternal\n\t}\n\n\treturn p.modelNode2Type(object)\n}\n\nfunc (p *plan) ListNodes(ctx context.Context, pid int64) ([]types.PlanNode, error) {\n\tobjects, err := p.factory.Plan().ListNodes(ctx, pid)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get plan(%d) nodes: %v\", pid, err)\n\t\treturn nil, errors.ErrServerInternal\n\t}\n\n\tvar nodes []types.PlanNode\n\tfor _, object := range objects {\n\t\tn, err := p.modelNode2Type(&object)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tnodes = append(nodes, *n)\n\t}\n\treturn nodes, nil\n}\n\n// CreateOrUpdateNode\n// TODO: 优化\nfunc (p *plan) CreateOrUpdateNode(ctx context.Context, object *model.Node) error {\n\told, err := p.factory.Plan().GetNodeByName(ctx, object.PlanId, object.Name)\n\tif err != nil {\n\t\tif !utilerrors.IsRecordNotFound(err) {\n\t\t\treturn err\n\t\t}\n\t\t// 不存在则创建\n\t\tklog.Infof(\"plan(%d) node(%s) not exist, try to create it.\", object.PlanId, object.Name)\n\t\t_, err = p.factory.Plan().CreateNode(ctx, object)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t}\n\n\tklog.Infof(\"plan(%d) node(%s) already exist\", object.PlanId, object.Name)\n\t// 已存在尝试更新\n\tupdates := p.buildNodeUpdates(old, object)\n\tif len(updates) == 0 {\n\t\treturn nil\n\t}\n\tklog.Infof(\"plan(%d) node(%s) already exist and need to update %v\", object.PlanId, object.Name, updates)\n\treturn p.factory.Plan().UpdateNode(ctx, old.Id, old.ResourceVersion, updates)\n}\n\nfunc (p *plan) modelNode2Type(o *model.Node) (*types.PlanNode, error) {\n\tauth := types.PlanNodeAuth{}\n\tif err := auth.Unmarshal(o.Auth); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &types.PlanNode{\n\t\tPixiuMeta: types.PixiuMeta{\n\t\t\tId:              o.Id,\n\t\t\tResourceVersion: o.ResourceVersion,\n\t\t},\n\t\tTimeMeta: types.TimeMeta{\n\t\t\tGmtCreate:   o.GmtCreate,\n\t\t\tGmtModified: o.GmtModified,\n\t\t},\n\t\tPlanId: o.PlanId,\n\t\tName:   o.Name,\n\t\tRole:   strings.Split(o.Role, \",\"),\n\t\tIp:     o.Ip,\n\t\tAuth:   auth,\n\t}, nil\n}\n\nfunc (p *plan) buildNodeUpdates(old, object *model.Node) map[string]interface{} {\n\tupdates := make(map[string]interface{})\n\tif old.Ip != object.Ip {\n\t\tupdates[\"ip\"] = object.Ip\n\t}\n\tif old.Role != object.Role {\n\t\tupdates[\"role\"] = object.Role\n\t}\n\tif old.Auth != object.Auth {\n\t\tupdates[\"auth\"] = object.Auth\n\t}\n\n\treturn updates\n}\n"
  },
  {
    "path": "pkg/controller/plan/plan_task.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (phe \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plan\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"k8s.io/klog/v2\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n\t\"github.com/caoyingjunz/pixiu/pkg/util/container\"\n)\n\n// RunTask\n// 只运行指定的计划任务\nfunc (p *plan) RunTask(ctx context.Context, planId int64, taskId int64) error {\n\treturn nil\n}\n\nfunc (p *plan) ListTasks(ctx context.Context, planId int64) ([]types.PlanTask, error) {\n\tobjects, err := p.factory.Plan().ListTasks(ctx, planId)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get plan(%d) tasks: %v\", planId, err)\n\t\treturn nil, err\n\t}\n\n\tvar tasks []types.PlanTask\n\tfor _, object := range objects {\n\t\ttasks = append(tasks, *p.modelTask2Type(&object))\n\t}\n\n\treturn tasks, nil\n}\n\nfunc (p *plan) WatchTasks(ctx context.Context, planId int64, w http.ResponseWriter, r *http.Request) {\n\tflush, _ := w.(http.Flusher)\n\tw.Header().Set(\"Content-Type\", \"text/event-stream\")\n\tw.Header().Set(\"Cache-Control\", \"no-cache\")\n\tw.Header().Set(\"Connection\", \"keep-alive\")\n\n\t// 初始化 Lister\n\tif taskC.Lister == nil {\n\t\ttaskC.SetLister(p.factory.Plan().ListTasks)\n\t}\n\t// 等待缓存同步\n\tif err := taskC.WaitForCacheSync(planId); err != nil {\n\t\treturn\n\t}\n\n\tfor {\n\t\tselect {\n\t\tcase <-r.Context().Done():\n\t\t\tklog.Infof(\"plan(%d) watch API has been closed by client and cache will be removed after 5s\", planId)\n\t\t\treturn\n\t\tdefault:\n\t\t\ttasks, ok := taskC.Get(planId)\n\t\t\tif ok {\n\t\t\t\tvar ts []types.PlanTask\n\t\t\t\tfor _, object := range tasks {\n\t\t\t\t\tts = append(ts, *p.modelTask2Type(&object))\n\t\t\t\t}\n\t\t\t\tif err := json.NewEncoder(w).Encode(ts); err != nil {\n\t\t\t\t\tklog.Errorf(\"failed to encode tasks: %v\", err)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tflush.Flush()\n\t\t\t}\n\n\t\t\t// 同步事件间隔为 3s\n\t\t\ttime.Sleep(3 * time.Second)\n\t\t}\n\t}\n}\n\nfunc (p *plan) WatchTaskLog(ctx context.Context, planId int64, taskId int64, w http.ResponseWriter, r *http.Request) error {\n\ttask, err := p.factory.Plan().GetTaskById(ctx, taskId)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get tasks of plan %d: %v\", planId, err)\n\t\treturn err\n\t}\n\n\tif task.Status == model.UnStartPlanStatus {\n\t\treturn fmt.Errorf(\"任务尚未开始\")\n\t}\n\n\tc, err := container.NewContainer(\"WatchTaskLog\", planId, \"\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"text/event-stream\")\n\tw.Header().Set(\"Cache-Control\", \"no-cache\")\n\tw.Header().Set(\"Connection\", \"keep-alive\")\n\n\t// TODO 临时指定，后期根据步骤id去做查询判断\n\tvar step string\n\tswitch task.Name {\n\tcase \"初始化部署环境\":\n\t\tstep = \"bootstrap-servers\"\n\tcase \"部署Master\":\n\t\tstep = \"deploy\"\n\tcase \"部署Node\":\n\t\tstep = \"deploy\"\n\tcase \"部署基础组件\":\n\t\tstep = \"deploy\"\n\tdefault:\n\t\tstep = \"bootstrap-servers\"\n\t}\n\n\tcontainerId := fmt.Sprintf(\"%s-%d\", step, planId)\n\treadCloser, err := c.WatchContainerLog(ctx, containerId, \"\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer readCloser.Close()\n\n\t// 读取日志\n\tscanner := bufio.NewScanner(readCloser)\n\tflush, _ := w.(http.Flusher)\n\tfor scanner.Scan() {\n\t\tline := append(scanner.Bytes(), byte('\\n'))\n\t\t// 去掉前8不可见字符\n\t\t_, err = w.Write(line[8:])\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tflush.Flush()\n\t}\n\n\treturn nil\n}\n\nfunc (p *plan) modelTask2Type(o *model.Task) *types.PlanTask {\n\treturn &types.PlanTask{\n\t\tPixiuMeta: types.PixiuMeta{\n\t\t\tId:              o.Id,\n\t\t\tResourceVersion: o.ResourceVersion,\n\t\t},\n\t\tTimeMeta: types.TimeMeta{\n\t\t\tGmtCreate:   o.GmtCreate,\n\t\t\tGmtModified: o.GmtModified,\n\t\t},\n\t\tName:    o.Name,\n\t\tPlanId:  o.PlanId,\n\t\tStatus:  o.Status,\n\t\tMessage: o.Message,\n\t}\n}\n\nfunc (p *plan) modelTask2TypeList(o []*model.Task) []types.PlanTask {\n\tvar tasks []types.PlanTask\n\tfor _, object := range o {\n\t\ttasks = append(tasks, *p.modelTask2Type(object))\n\t}\n\treturn tasks\n}\n"
  },
  {
    "path": "pkg/controller/plan/register.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (phe \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plan\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pkg/sftp\"\n\t\"golang.org/x/crypto/ssh\"\n\t\"k8s.io/klog/v2\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/db\"\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n)\n\nconst (\n\tKubeConfigFile = \"/etc/kubernetes/admin.conf\"\n)\n\ntype Register struct {\n\thandlerTask\n\n\tfactory db.ShareDaoFactory\n}\n\nfunc (c Register) Name() string { return \"集群注册\" }\nfunc (c Register) Run() error {\n\tks := &types.KubernetesSpec{}\n\tif err := ks.Unmarshal(c.data.Config.Kubernetes); err != nil {\n\t\treturn err\n\t}\n\t// 如果未启用自注册功能，则直接跳过\n\tif !ks.Register {\n\t\tklog.Infof(\"部署计划未启用自注册功能，skipping\")\n\t}\n\n\t// 从 master 节点获取 kubeConfig 内容，注入集群服务\n\tvar masterNodes []model.Node\n\tfor _, node := range c.data.Nodes {\n\t\tif strings.Contains(node.Role, model.MasterRole) {\n\t\t\tmasterNodes = append(masterNodes, node)\n\t\t}\n\t}\n\n\tvar (\n\t\tkubeConfig []byte\n\t\terr        error\n\t)\n\tfor _, masterNode := range masterNodes {\n\t\tkubeConfig, err = getKubeConfigFromMasterNode(masterNode)\n\t\tif err == nil {\n\t\t\tbreak\n\t\t} else {\n\t\t\tklog.Warningf(\"failed to get kubeConfig from master(%s): %v, trying the other masters\", masterNode.Name, err)\n\t\t}\n\t}\n\tif len(kubeConfig) == 0 {\n\t\treturn fmt.Errorf(\"get the empty kubeconfig from master nodes\")\n\t}\n\n\tconfig64 := base64.StdEncoding.EncodeToString(kubeConfig)\n\tif err = c.factory.Cluster().UpdateByPlan(context.TODO(),\n\t\tc.data.PlanId, map[string]interface{}{\"kube_config\": config64}); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc getKubeConfigFromMasterNode(maserNode model.Node) ([]byte, error) {\n\tsftpClient, err := newSftpClient(maserNode)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer sftpClient.Close()\n\n\tsrcFile, err := sftpClient.Open(KubeConfigFile)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer srcFile.Close()\n\n\tbuf, err := io.ReadAll(srcFile)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn buf, nil\n}\n\nfunc newSftpClient(node model.Node) (*sftp.Client, error) {\n\tnodeAuth := types.PlanNodeAuth{}\n\tif err := nodeAuth.Unmarshal(node.Auth); err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar clientConfig *ssh.ClientConfig\n\n\tswitch nodeAuth.Type {\n\tcase types.PasswordAuth:\n\t\t// 1. 使用密码\n\t\tclientConfig = &ssh.ClientConfig{\n\t\t\tUser: nodeAuth.Password.User,\n\t\t\tAuth: []ssh.AuthMethod{\n\t\t\t\tssh.Password(nodeAuth.Password.Password),\n\t\t\t},\n\t\t\tTimeout: 30 * time.Second,\n\t\t\tHostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {\n\t\t\t\treturn nil\n\t\t\t},\n\t\t}\n\tcase types.KeyAuth:\n\t\t//2. 使用秘钥\n\t\tkey := []byte(nodeAuth.Key.Data)\n\t\tsigner, err := ssh.ParsePrivateKey(key)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tclientConfig = &ssh.ClientConfig{\n\t\t\tUser: \"root\", // 秘钥登陆时，默认 root\n\t\t\tAuth: []ssh.AuthMethod{\n\t\t\t\tssh.PublicKeys(signer),\n\t\t\t},\n\t\t\tTimeout:         30 * time.Second,\n\t\t\tHostKeyCallback: ssh.InsecureIgnoreHostKey(),\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported ssh auth type: %s\", nodeAuth.Type)\n\t}\n\n\taddr := fmt.Sprintf(\"%s:%d\", node.Ip, 22)\n\tsshClient, err := ssh.Dial(\"tcp\", addr, clientConfig)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn sftp.NewClient(sshClient)\n}\n"
  },
  {
    "path": "pkg/controller/plan/render.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (phe \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plan\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"text/template\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n\t\"github.com/caoyingjunz/pixiu/pkg/util\"\n\tpixiutpl \"github.com/caoyingjunz/pixiu/template\"\n)\n\n// Render 渲染 pixiu 部署配置\n// 1. 渲染 hosts\n// 2. 渲染 globals.yaml\n// 3. 渲染 multinode\n// 具体参考 https://github.com/pixiu-io/kubez-ansible\ntype Render struct {\n\thandlerTask\n\n\tdir string\n}\n\nfunc (r Render) Name() string { return \"配置渲染\" }\nfunc (r Render) Run() error {\n\t// 渲染 hosts\n\tif err := r.doRender(\"hosts\", pixiutpl.HostTemplate, r.data); err != nil {\n\t\treturn err\n\t}\n\t// 渲染 multiNode\n\tnodes, err := ParseMultinode(r.data, r.dir)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err := r.doRender(\"multinode\", pixiutpl.MultiModeTemplate, nodes); err != nil {\n\t\treturn err\n\t}\n\t// 渲染 globals\n\tcfg, err := ParseConfig(r.data)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err := r.doRender(\"globals.yml\", pixiutpl.GlobalsTemplate, cfg); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (r Render) doRender(name string, text string, data interface{}) error {\n\ttpl := template.New(name)\n\ttpl = template.Must(tpl.Parse(text))\n\n\tvar buf bytes.Buffer\n\tif err := tpl.Execute(&buf, data); err != nil {\n\t\treturn err\n\t}\n\tfilename, err := GetRenderFile(r.GetPlanId(), r.dir, name)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err = util.WriteToFile(filename, buf.Bytes()); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\ntype Multinode struct {\n\tDockerMaster     []types.PlanNode\n\tDockerNode       []types.PlanNode\n\tContainerdMaster []types.PlanNode\n\tContainerdNode   []types.PlanNode\n}\n\nfunc ParseMultinode(data TaskData, workDir string) (Multinode, error) {\n\tmultinode := Multinode{\n\t\tDockerMaster:     make([]types.PlanNode, 0),\n\t\tDockerNode:       make([]types.PlanNode, 0),\n\t\tContainerdMaster: make([]types.PlanNode, 0),\n\t\tContainerdNode:   make([]types.PlanNode, 0),\n\t}\n\n\truntime := types.RuntimeSpec{}\n\tif err := runtime.Unmarshal(data.Config.Runtime); err != nil {\n\t\treturn multinode, err\n\t}\n\n\tfor _, node := range data.Nodes {\n\t\tnodeAuth := types.PlanNodeAuth{}\n\t\terr := nodeAuth.Unmarshal(node.Auth)\n\t\tif err != nil {\n\t\t\treturn multinode, err\n\t\t}\n\t\t// 生成rsa的渲染文件\n\t\t_, err = RenderRSA(data.PlanId, node.Name, workDir, nodeAuth)\n\t\tif err != nil {\n\t\t\treturn multinode, err\n\t\t}\n\n\t\tif nodeAuth.Type == types.KeyAuth {\n\t\t\tif nodeAuth.Key == nil {\n\t\t\t\treturn multinode, fmt.Errorf(\"node(%s) key auth config is empty\", node.Name)\n\t\t\t}\n\t\t\tnodeAuth.Key.File = fmt.Sprintf(\"/configs/ssh/%s/id_rsa\", node.Name)\n\t\t}\n\t\tif nodeAuth.Type == types.PasswordAuth && nodeAuth.Password == nil {\n\t\t\treturn multinode, fmt.Errorf(\"node(%s) password auth config is empty\", node.Name)\n\t\t}\n\t\tplanNode := types.PlanNode{Name: node.Name, Auth: nodeAuth}\n\n\t\troles := strings.Split(node.Role, \",\")\n\t\tif runtime.IsDocker() {\n\t\t\tfor _, role := range roles {\n\t\t\t\tif role == model.MasterRole {\n\t\t\t\t\tmultinode.DockerMaster = append(multinode.DockerMaster, planNode)\n\t\t\t\t}\n\t\t\t\tif role == model.NodeRole {\n\t\t\t\t\tmultinode.DockerNode = append(multinode.DockerNode, planNode)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif runtime.IsContainerd() {\n\t\t\tfor _, role := range roles {\n\t\t\t\tif role == model.MasterRole {\n\t\t\t\t\tmultinode.ContainerdMaster = append(multinode.ContainerdMaster, planNode)\n\t\t\t\t}\n\t\t\t\tif role == model.NodeRole {\n\t\t\t\t\tmultinode.ContainerdNode = append(multinode.ContainerdNode, planNode)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn multinode, nil\n}\n\n// GetRenderFile\n// TODO: 后续优化\nfunc GetRenderFile(planId int64, workDir string, f string) (string, error) {\n\tplanDir := filepath.Join(workDir, fmt.Sprintf(\"%d\", planId))\n\tif err := util.EnsureDirectoryExists(planDir); err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn filepath.Join(planDir, f), nil\n}\n\nfunc RenderRSA(planId int64, name string, workDir string, auth types.PlanNodeAuth) (string, error) {\n\tif auth.Type == types.KeyAuth {\n\t\tif auth.Key == nil {\n\t\t\treturn \"\", fmt.Errorf(\"node(%s) key auth config is empty\", name)\n\t\t}\n\t\tf, err := GetRSAFile(planId, workDir, name)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tif err = util.WriteToFile(f, []byte(auth.Key.Data)); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn f, nil\n\t}\n\n\treturn \"\", nil\n}\n\nfunc GetRSAFile(planId int64, workDir string, name string) (string, error) {\n\trsaDir := filepath.Join(workDir, fmt.Sprintf(\"%d\", planId), \"ssh\", name)\n\tif err := util.EnsureDirectoryExists(rsaDir); err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn filepath.Join(rsaDir, \"id_rsa\"), nil\n}\n\nfunc ParseConfig(data TaskData) (*types.PlanConfig, error) {\n\tconfig := data.Config\n\n\tnetwork := types.NetworkSpec{}\n\tif err := network.Unmarshal(config.Network); err != nil {\n\t\treturn nil, err\n\t}\n\tkubernetes := types.KubernetesSpec{}\n\tif err := kubernetes.Unmarshal(config.Kubernetes); err != nil {\n\t\treturn nil, err\n\t}\n\tcomponent := types.ComponentSpec{}\n\tif err := component.Unmarshal(config.Component); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &types.PlanConfig{\n\t\tKubernetes: kubernetes,\n\t\tNetwork:    network,\n\t\tComponent:  component,\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/controller/plan/worker.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (phe \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plan\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"k8s.io/apimachinery/pkg/util/wait\"\n\t\"k8s.io/klog/v2\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n\t\"github.com/caoyingjunz/pixiu/pkg/util/errors\"\n)\n\ntype Handler interface {\n\tGetPlanId() int64\n\n\tName() string         // 检查项名称\n\tStep() model.PlanStep // 未开始，运行中，异常和完成\n\tRun() error           // 执行\n}\n\ntype handlerTask struct {\n\tdata TaskData\n}\n\nfunc (t handlerTask) GetPlanId() int64     { return t.data.PlanId }\nfunc (t handlerTask) Step() model.PlanStep { return model.RunningPlanStep }\n\nfunc newHandlerTask(data TaskData) handlerTask {\n\treturn handlerTask{data: data}\n}\n\nfunc (p *plan) Run(ctx context.Context, workers int) error {\n\t// 进程启动时，尝试同步任务状态\n\tklog.Infof(\"starting to sync task manager\")\n\tif err := p.SyncTaskStatus(ctx); err != nil {\n\t\treturn err\n\t}\n\n\t// 启动部署计划控制器\n\tklog.Infof(\"starting deployment manager\")\n\tfor i := 0; i < workers; i++ {\n\t\tgo wait.UntilWithContext(ctx, p.worker, time.Second)\n\t}\n\treturn nil\n}\n\nfunc (p *plan) worker(ctx context.Context) {\n\tfor p.process(ctx) {\n\t}\n}\n\nfunc (p *plan) process(ctx context.Context) bool {\n\tkey, quit := taskQueue.Get()\n\tif quit {\n\t\treturn false\n\t}\n\tdefer taskQueue.Done(key)\n\n\tp.syncHandler(ctx, key.(int64))\n\treturn true\n}\n\ntype TaskData struct {\n\tPlanId int64\n\tConfig *model.Config\n\tNodes  []model.Node\n}\n\nfunc (t TaskData) validate() error {\n\treturn nil\n}\n\nfunc (p *plan) getTaskData(ctx context.Context, planId int64) (TaskData, error) {\n\tnodes, err := p.factory.Plan().ListNodes(ctx, planId)\n\tif err != nil {\n\t\treturn TaskData{}, err\n\t}\n\tcfg, err := p.factory.Plan().GetConfigByPlan(ctx, planId)\n\tif err != nil {\n\t\treturn TaskData{}, err\n\t}\n\n\treturn TaskData{\n\t\tPlanId: planId,\n\t\tConfig: cfg,\n\t\tNodes:  nodes,\n\t}, nil\n}\n\n// 实际处理函数\n// 处理步骤:\n// 1. 检查部署参数是否符合要求\n// 2. 渲染环境\n// 3. 执行部署\n// 4. 部署后环境清理\nfunc (p *plan) syncHandler(ctx context.Context, planId int64) {\n\tklog.Infof(\"starting plan(%d) task\", planId)\n\tdefer klog.Infof(\"completed plan(%d) task\", planId)\n\n\ttaskData, err := p.getTaskData(ctx, planId)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get task data: %v\", err)\n\t\treturn\n\t}\n\trunner, err := p.GetRunner(taskData.Config.OSImage)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get image(%s) for worker: %v\", taskData.Config.OSImage, err)\n\t\treturn\n\t}\n\t// Runner的工作目录\n\tdir := p.WorkDir()\n\n\ttask := newHandlerTask(taskData)\n\thandlers := []Handler{\n\t\tCheck{handlerTask: task},\n\t\tRender{handlerTask: task, dir: dir},\n\t\tBootStrap{handlerTask: task, dir: dir, runner: runner},\n\t\tDeploy{handlerTask: task, dir: dir, runner: runner},\n\t\tDeployNode{handlerTask: task},\n\t\tRegister{handlerTask: task, factory: p.factory},\n\t\tDeployChart{handlerTask: task},\n\t}\n\tif err = p.syncTasks(handlers...); err != nil {\n\t\tklog.Errorf(\"failed to sync task: %v\", err)\n\t}\n}\n\nfunc (p *plan) createPlanTasksIfNotExist(tasks ...Handler) error {\n\tfor _, task := range tasks {\n\t\tplanId := task.GetPlanId()\n\t\tname := task.Name()\n\t\tstep := task.Step()\n\n\t\t_, err := p.factory.Plan().GetTaskByName(context.TODO(), planId, name)\n\t\t// 存在则直接返回\n\t\tif err == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\t// 非不存在报错则报异常\n\t\tif !errors.IsRecordNotFound(err) {\n\t\t\tklog.Infof(\"failed to get plan(%d) tasks(%s) for first created: %v\", planId, name, err)\n\t\t\treturn err\n\t\t}\n\n\t\t// 不存在记录则新建\n\t\tif _, err = p.factory.Plan().CreateTask(context.TODO(), &model.Task{\n\t\t\tName:   name,\n\t\t\tPlanId: planId,\n\t\t\tStep:   step,\n\t\t\tStatus: model.UnStartPlanStatus,\n\t\t}); err != nil {\n\t\t\tklog.Errorf(\"failed to init plan(%d) task(%s): %v\", planId, name, err)\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (p *plan) WorkDir() string {\n\treturn p.cc.Worker.WorkDir\n}\n\nfunc (p *plan) GetRunner(osImage string) (string, error) {\n\tengines := p.cc.Worker.Engines\n\tfor _, engine := range engines {\n\t\tfor _, os := range engine.OSSupported {\n\t\t\tif os == osImage {\n\t\t\t\treturn engine.Image, nil\n\t\t\t}\n\t\t}\n\t}\n\treturn \"\", fmt.Errorf(\"osImage(%s) runner not found\", osImage)\n}\n\n// 同步任务状态\n// 任务启动时设置为运行中，结束时同步为结束状态(成功或者失败)\n// TODO: 后续优化，判断对应部署容器是否在运行，根据容器的运行结果同步状态\nfunc (p *plan) syncStatus(ctx context.Context, planId int64) error {\n\ttasks, err := p.factory.Plan().ListTasks(ctx, planId)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, task := range tasks {\n\t\tif task.Status != model.RunningPlanStatus {\n\t\t\tcontinue\n\t\t}\n\t\tif _, err = p.factory.Plan().UpdateTask(ctx, planId, task.Name, map[string]interface{}{\n\t\t\t\"status\": model.FailedPlanStatus, \"step\": model.FailedPlanStep, \"message\": \"服务异常修正，请重新启动部署计划\", \"gmt_modified\": time.Now(),\n\t\t}); err != nil {\n\t\t\tklog.Errorf(\"failed to update plan(%d) status: %v\", planId, err)\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (p *plan) syncTasks(tasks ...Handler) error {\n\t// 初始化记录\n\tif err := p.createPlanTasksIfNotExist(tasks...); err != nil {\n\t\treturn err\n\t}\n\n\t// 执行任务并更新状态\n\tfor _, task := range tasks {\n\t\tplanId := task.GetPlanId()\n\t\tname := task.Name()\n\t\tklog.Infof(\"starting plan(%d) task(%s)\", planId, name)\n\n\t\t// TODO: 通过闭包方式优化\n\t\tstart, err := p.factory.Plan().UpdateTask(context.TODO(), planId, name, map[string]interface{}{\n\t\t\t\"status\": model.RunningPlanStatus, \"message\": \"\", \"gmt_create\": time.Now(),\n\t\t})\n\t\tif err != nil {\n\t\t\tklog.Errorf(\"failed to update plan(%d) status before run task(%s): %v\", planId, name, err)\n\t\t\treturn err\n\t\t}\n\t\ttaskC.SetByTask(planId, *start)\n\n\t\tstatus := model.SuccessPlanStatus\n\t\tstep := task.Step()\n\t\tmessage := \"\"\n\n\t\t// 执行检查\n\t\trunErr := task.Run()\n\t\tif runErr != nil {\n\t\t\tstatus = model.FailedPlanStatus\n\t\t\tstep = model.FailedPlanStep\n\t\t\tmessage = runErr.Error()\n\t\t}\n\n\t\t// 执行完成之后更新状态\n\t\tend, err := p.factory.Plan().UpdateTask(context.TODO(), planId, name, map[string]interface{}{\n\t\t\t\"status\": status, \"message\": message, \"step\": step, \"gmt_modified\": time.Now(),\n\t\t})\n\t\tif err != nil {\n\t\t\tklog.Errorf(\"failed to update plan(%d) status after run task(%s): %v\", planId, name, err)\n\t\t\treturn err\n\t\t}\n\t\ttaskC.SetByTask(planId, *end)\n\n\t\tklog.Infof(\"completed plan(%d) task(%s)\", planId, name)\n\t\tif runErr != nil {\n\t\t\treturn runErr\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/controller/tenant/tenant.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage tenant\n\nimport (\n\t\"context\"\n\n\t\"k8s.io/klog/v2\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/errors\"\n\t\"github.com/caoyingjunz/pixiu/cmd/app/config\"\n\t\"github.com/caoyingjunz/pixiu/pkg/db\"\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n)\n\ntype TenantGetter interface {\n\tTenant() Interface\n}\n\ntype Interface interface {\n\tCreate(ctx context.Context, req *types.CreateTenantRequest) error\n\tUpdate(ctx context.Context, tid int64, req *types.UpdateTenantRequest) error\n\tDelete(ctx context.Context, tid int64) error\n\tGet(ctx context.Context, tid int64) (*types.Tenant, error)\n\tList(ctx context.Context) ([]types.Tenant, error)\n}\n\ntype tenant struct {\n\tcc      config.Config\n\tfactory db.ShareDaoFactory\n}\n\nfunc (t *tenant) Create(ctx context.Context, req *types.CreateTenantRequest) error {\n\tobject, err := t.factory.Tenant().GetTenantByName(ctx, req.Name)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get tenant %s: %v\", req.Name, err)\n\t\treturn errors.ErrServerInternal\n\t}\n\tif object != nil {\n\t\treturn errors.ErrTenantExists\n\t}\n\n\ttenant := &model.Tenant{\n\t\tName: req.Name,\n\t}\n\tif req.Description != nil {\n\t\ttenant.Description = *req.Description\n\t}\n\n\tif _, err = t.factory.Tenant().Create(ctx, tenant); err != nil {\n\t\tklog.Errorf(\"failed to create tenant %s: %v\", req.Name, err)\n\t\treturn errors.ErrServerInternal\n\t}\n\n\treturn nil\n}\n\nfunc (t *tenant) Update(ctx context.Context, tid int64, req *types.UpdateTenantRequest) error {\n\tobject, err := t.factory.Tenant().Get(ctx, tid)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get tenant %d: %v\", tid, err)\n\t\treturn errors.ErrServerInternal\n\t}\n\tif object == nil {\n\t\treturn errors.ErrTenantNotFound\n\t}\n\tupdates := make(map[string]interface{})\n\tif req.Name != nil {\n\t\tupdates[\"name\"] = *req.Name\n\t}\n\tif req.Description != nil {\n\t\tupdates[\"description\"] = *req.Description\n\t}\n\tif len(updates) == 0 {\n\t\treturn errors.ErrInvalidRequest\n\t}\n\tif err := t.factory.Tenant().Update(ctx, tid, *req.ResourceVersion, updates); err != nil {\n\t\tklog.Errorf(\"failed to update tenant %d: %v\", tid, err)\n\t\treturn errors.ErrServerInternal\n\t}\n\treturn nil\n}\n\nfunc (t *tenant) Delete(ctx context.Context, tid int64) error {\n\t_, err := t.factory.Tenant().Delete(ctx, tid)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to delete tenant %d: %v\", tid, err)\n\t\treturn errors.ErrServerInternal\n\t}\n\n\treturn nil\n}\n\nfunc (t *tenant) Get(ctx context.Context, tid int64) (*types.Tenant, error) {\n\tobject, err := t.factory.Tenant().Get(ctx, tid)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get tenant %d: %v\", tid, err)\n\t\treturn nil, errors.ErrServerInternal\n\t}\n\tif object == nil {\n\t\treturn nil, errors.ErrTenantNotFound\n\t}\n\treturn t.model2Type(object), nil\n}\n\nfunc (t *tenant) List(ctx context.Context) ([]types.Tenant, error) {\n\tobjects, err := t.factory.Tenant().List(ctx)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get tenants: %v\", err)\n\t\treturn nil, errors.ErrServerInternal\n\t}\n\n\tvar ts []types.Tenant\n\tfor _, object := range objects {\n\t\tts = append(ts, *t.model2Type(&object))\n\t}\n\treturn ts, nil\n}\n\nfunc (t *tenant) model2Type(o *model.Tenant) *types.Tenant {\n\treturn &types.Tenant{\n\t\tPixiuMeta: types.PixiuMeta{\n\t\t\tId:              o.Id,\n\t\t\tResourceVersion: o.ResourceVersion,\n\t\t},\n\t\tTimeMeta: types.TimeMeta{\n\t\t\tGmtCreate:   o.GmtCreate,\n\t\t\tGmtModified: o.GmtModified,\n\t\t},\n\t\tName:        o.Name,\n\t\tDescription: o.Description,\n\t}\n}\n\nfunc NewTenant(cfg config.Config, f db.ShareDaoFactory) *tenant {\n\treturn &tenant{\n\t\tcc:      cfg,\n\t\tfactory: f,\n\t}\n}\n"
  },
  {
    "path": "pkg/controller/user/user.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage user\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/casbin/casbin/v2\"\n\t\"k8s.io/klog/v2\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/errors\"\n\t\"github.com/caoyingjunz/pixiu/api/server/httputils\"\n\t\"github.com/caoyingjunz/pixiu/cmd/app/config\"\n\t\"github.com/caoyingjunz/pixiu/pkg/client\"\n\t\"github.com/caoyingjunz/pixiu/pkg/db\"\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n\t\"github.com/caoyingjunz/pixiu/pkg/util\"\n\ttokenutil \"github.com/caoyingjunz/pixiu/pkg/util/token\"\n)\n\nvar (\n\tuserIndexer  client.UserCache\n\ttokenIndexer client.TokenCache\n)\n\nfunc init() {\n\tuserIndexer = *client.NewUserCache()\n\ttokenIndexer = *client.NewTokenCache()\n}\n\ntype UserGetter interface {\n\tUser() Interface\n}\n\ntype Interface interface {\n\tCreate(ctx context.Context, req *types.CreateUserRequest) error\n\tUpdate(ctx context.Context, userId int64, req *types.UpdateUserRequest) error\n\tDelete(ctx context.Context, userId int64) error\n\tGet(ctx context.Context, userId int64) (*types.User, error)\n\tList(ctx context.Context, req *types.ListUserRequest) (*types.PageResponse, error)\n\n\t// UpdatePassword 用户修改密码或者管理员重置密码\n\tUpdatePassword(ctx context.Context, userId int64, req *types.UpdateUserPasswordRequest) error\n\t// GetCount 仅获取用户数量\n\tGetCount(ctx context.Context, opts types.ListOptions) (int64, error)\n\t// GetStatus 获取用户状态，优先从缓存获取，如果没有则从库里获取，然后同步到缓存\n\tGetStatus(ctx context.Context, uid int64) (int, error)\n\n\tLogin(ctx context.Context, req *types.LoginRequest) (*types.LoginResponse, error)\n\tLogout(ctx context.Context, userId int64) error\n\tGetLoginToken(ctx context.Context, userId int64) (string, error)\n}\n\ntype user struct {\n\tcc       config.Config\n\tfactory  db.ShareDaoFactory\n\tenforcer *casbin.SyncedEnforcer\n}\n\nfunc (u *user) Create(ctx context.Context, req *types.CreateUserRequest) error {\n\tencrypt, err := util.EncryptUserPassword(req.Password)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to encrypt user password: %v\", err)\n\t\treturn errors.ErrServerInternal\n\t}\n\n\tobject, err := u.factory.User().GetUserByName(ctx, req.Name)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get user %s: %v\", req.Name, err)\n\t\treturn errors.ErrServerInternal\n\t}\n\tif object != nil {\n\t\terr = errors.ErrUserExists // 记录错误\n\t\treturn err\n\t}\n\n\t// 超级管理员全局只允许存在一个\n\tif req.Role == model.RoleRoot {\n\t\troot, err := u.factory.User().GetRoot(ctx)\n\t\tif err != nil {\n\t\t\tklog.Errorf(\"failed to check root user: %v\", err)\n\t\t\treturn errors.ErrServerInternal\n\t\t}\n\t\tif root != nil {\n\t\t\treturn errors.ErrRootAlreadyExists\n\t\t}\n\t}\n\n\ttxFunc := func() (err error) {\n\t\tif req.Role == model.RoleRoot {\n\t\t\tbindings := model.NewGroupBinding(req.Name, model.AdminGroup)\n\t\t\t_, err = u.enforcer.AddGroupingPolicy(bindings.Raw())\n\t\t}\n\t\treturn\n\t}\n\n\tif _, err = u.factory.User().Create(ctx, &model.User{\n\t\tName:        req.Name,\n\t\tPassword:    encrypt,\n\t\tStatus:      req.Status,\n\t\tRole:        req.Role,\n\t\tEmail:       req.Email,\n\t\tPhone:       req.Phone,\n\t\tDescription: req.Description,\n\t}, txFunc); err != nil {\n\t\tklog.Errorf(\"failed to create user %s: %v\", req.Name, err)\n\t\treturn errors.ErrServerInternal\n\t}\n\n\treturn nil\n}\n\nfunc (u *user) Update(ctx context.Context, uid int64, req *types.UpdateUserRequest) error {\n\tupdates := map[string]interface{}{\n\t\t\"status\":      req.Status,\n\t\t\"email\":       req.Email,\n\t\t\"phone\":       req.Phone,\n\t\t\"description\": req.Description,\n\t}\n\tif err := u.factory.User().Update(ctx, uid, *req.ResourceVersion, updates); err != nil {\n\t\tklog.Errorf(\"failed to update user(%d): %v\", uid, err)\n\t\treturn errors.ErrServerInternal\n\t}\n\n\tuserIndexer.Set(uid, int(req.Status))\n\treturn nil\n}\n\nfunc (u *user) preResetPassword(ctx context.Context, userId int64, operatorId int64, req *types.UpdateUserPasswordRequest) error {\n\t// 操作人必须具备管理员权限\n\toperator, err := u.Get(ctx, operatorId)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif operator.Role != model.RoleRoot {\n\t\treturn fmt.Errorf(\"非超级管理员，不允许重置用户密码\")\n\t}\n\treturn nil\n}\n\nfunc (u *user) preChangePassword(ctx context.Context, userId int64, operatorId int64, req *types.UpdateUserPasswordRequest) error {\n\tif operatorId != userId {\n\t\treturn fmt.Errorf(\"用户只能修改自己的密码\")\n\t}\n\tobject, err := u.Get(ctx, userId)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// 校验旧密码是否正确\n\tif err = util.ValidateUserPassword(object.Password, req.Old); err != nil {\n\t\tklog.Errorf(\"检验用户密码失败: %v\", err)\n\t\treturn errors.ErrInvalidPassword\n\t}\n\treturn nil\n}\n\n// UpdatePassword 支持用户修改密码和管理员重置密码\n// 修改密码: 用户只能修改自己密码\n// 重启密码: 管理员可以重置他人密码\nfunc (u *user) UpdatePassword(ctx context.Context, userId int64, req *types.UpdateUserPasswordRequest) error {\n\t// 新老密码不允许相同\n\tif req.New == req.Old {\n\t\treturn errors.ErrDuplicatedPassword\n\t}\n\n\toperatorId, err := httputils.GetUserIdFromContext(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif req.Reset {\n\t\t// 管理员重置密码前置检查\n\t\tif err = u.preResetPassword(ctx, userId, operatorId, req); err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\t// 用户修改密码前置检查\n\t\tif err = u.preChangePassword(ctx, userId, operatorId, req); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tnewPass, err := util.EncryptUserPassword(req.New)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to encrypt user password: %v\", err)\n\t\treturn errors.ErrServerInternal\n\t}\n\tif err = u.factory.User().Update(ctx, userId, *req.ResourceVersion, map[string]interface{}{\n\t\t\"password\": newPass,\n\t}); err != nil {\n\t\tklog.Errorf(\"failed to update user(%d) password: %v\", userId, err)\n\t\treturn errors.ErrServerInternal\n\t}\n\n\ttokenIndexer.Delete(userId)\n\treturn nil\n}\n\nfunc (u *user) Delete(ctx context.Context, userId int64) error {\n\tif err := u.factory.User().Delete(ctx, userId); err != nil {\n\t\tklog.Errorf(\"failed to delete user(%d): %v\", userId, err)\n\t\treturn errors.ErrServerInternal\n\t}\n\n\tuserIndexer.Delete(userId)\n\ttokenIndexer.Delete(userId)\n\treturn nil\n}\n\nfunc (u *user) Get(ctx context.Context, userId int64) (*types.User, error) {\n\tobject, err := u.factory.User().Get(ctx, userId)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get user(%d): %v\", userId, err)\n\t\treturn nil, errors.ErrServerInternal\n\t}\n\tif object == nil {\n\t\treturn nil, errors.ErrUserNotFound\n\t}\n\n\treturn model2Type(object), nil\n}\n\nfunc (u *user) List(ctx context.Context, req *types.ListUserRequest) (*types.PageResponse, error) {\n\topts := []db.Options{db.WithOrderByDesc()}\n\tif req != nil {\n\t\topts = append(opts,\n\t\t\tdb.WithUserNameLike(req.UserName),\n\t\t\tdb.WithUserPhoneLike(req.UserPhone),\n\t\t\tdb.WithUserEmailLike(req.UserEmail),\n\t\t)\n\t\tif req.Status != nil {\n\t\t\topts = append(opts, db.WithUserStatus(*req.Status))\n\t\t}\n\t}\n\n\ttotal, err := u.factory.User().Count(ctx, opts...)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get user counts: %v\", err)\n\t\treturn nil, errors.ErrServerInternal\n\t}\n\n\tpageReq := types.PageRequest{}\n\tif req != nil {\n\t\tpageReq = req.PageRequest\n\t\tif req.Page > 0 && req.Limit > 0 {\n\t\t\topts = append(opts, db.WithOffset((req.Page-1)*req.Limit), db.WithLimit(req.Limit))\n\t\t}\n\t}\n\n\tobjects, err := u.factory.User().List(ctx, opts...)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get user list: %v\", err)\n\t\treturn nil, errors.ErrServerInternal\n\t}\n\n\tvar users []types.User\n\tfor _, object := range objects {\n\t\tusers = append(users, *model2Type(&object))\n\t}\n\n\treturn &types.PageResponse{\n\t\tPageRequest: pageReq,\n\t\tTotal:       int(total),\n\t\tItems:       users,\n\t}, nil\n}\n\nfunc (u *user) GetCount(ctx context.Context, opts types.ListOptions) (int64, error) {\n\tuserCount, err := u.factory.User().Count(ctx)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get user counts: %v\", err)\n\t\treturn 0, errors.ErrServerInternal\n\t}\n\n\treturn userCount, nil\n}\n\n// GetStatus 获取用户状态，优先从缓存获取，如果没有则从库里获取，然后同步到缓存\nfunc (u *user) GetStatus(ctx context.Context, uid int64) (int, error) {\n\tstatus, ok := userIndexer.Get(uid)\n\tif ok {\n\t\treturn status, nil\n\t}\n\n\tobject, err := u.factory.User().Get(ctx, uid)\n\tif err != nil {\n\t\tklog.Errorf(\"failed to get user(%d): %v\", uid, err)\n\t\treturn 0, errors.ErrServerInternal\n\t}\n\tif object == nil {\n\t\treturn 0, errors.ErrUserNotFound\n\t}\n\n\tuserIndexer.Set(uid, int(object.Status))\n\treturn int(object.Status), nil\n}\n\nfunc (u *user) Login(ctx context.Context, req *types.LoginRequest) (*types.LoginResponse, error) {\n\tobject, err := u.factory.User().GetUserByName(ctx, req.Name)\n\tif err != nil {\n\t\treturn nil, errors.ErrServerInternal\n\t}\n\tif object == nil {\n\t\treturn nil, errors.ErrUserNotFound\n\t}\n\n\t// 如果用户已被禁用，则不允许登陆\n\tif object.Status == 2 {\n\t\treturn nil, fmt.Errorf(\"用户已被禁用\")\n\t}\n\tif err = util.ValidateUserPassword(object.Password, req.Password); err != nil {\n\t\tklog.Errorf(\"检验用户密码失败: %v\", err)\n\t\treturn nil, errors.ErrInvalidPassword\n\t}\n\n\t// 生成登陆的 token 信息\n\tkey := u.GetTokenKey()\n\ttoken, err := tokenutil.GenerateToken(object.Id, object.Name, key)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"生成用户 token 失败: %v\", err)\n\t}\n\n\ttokenIndexer.Set(object.Id, token)\n\treturn &types.LoginResponse{\n\t\tUserId:   object.Id,\n\t\tUserName: object.Name,\n\t\tToken:    token,\n\t\tRole:     object.Role,\n\t\tUser:     object,\n\t}, nil\n}\n\n// Logout\n// 允许用户登出登陆状态\n// TODO: 临时实现，后续优化\nfunc (u *user) Logout(ctx context.Context, userId int64) error {\n\ttokenIndexer.Delete(userId)\n\treturn nil\n}\n\nfunc (u *user) GetLoginToken(ctx context.Context, userId int64) (string, error) {\n\tt, exists := tokenIndexer.Get(userId)\n\tif !exists {\n\t\treturn \"\", fmt.Errorf(\"invalid empty token\")\n\t}\n\n\treturn t, nil\n}\n\nfunc (u *user) GetTokenKey() []byte {\n\tk := u.cc.Default.JWTKey\n\treturn []byte(k)\n}\n\n// 将 model user 转换成 types\nfunc model2Type(o *model.User) *types.User {\n\treturn &types.User{\n\t\tPixiuMeta: types.PixiuMeta{\n\t\t\tId:              o.Id,\n\t\t\tResourceVersion: o.ResourceVersion,\n\t\t},\n\t\tName:        o.Name,\n\t\tDescription: o.Description,\n\t\tStatus:      o.Status,\n\t\tRole:        o.Role,\n\t\tEmail:       o.Email,\n\t\tPhone:       o.Phone,\n\t\tTimeMeta: types.TimeMeta{\n\t\t\tGmtCreate:   o.GmtCreate,\n\t\t\tGmtModified: o.GmtModified,\n\t\t},\n\t}\n}\n\nfunc NewUser(cfg config.Config, f db.ShareDaoFactory, e *casbin.SyncedEnforcer) *user {\n\treturn &user{\n\t\tcc:       cfg,\n\t\tfactory:  f,\n\t\tenforcer: e,\n\t}\n}\n"
  },
  {
    "path": "pkg/controller/user/user_test.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage user\n"
  },
  {
    "path": "pkg/controller/util/util.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage util\n\nimport (\n\t\"context\"\n\n\t\"github.com/casbin/casbin/v2\"\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/caoyingjunz/pixiu/api/server/httputils\"\n\t\"github.com/caoyingjunz/pixiu/pkg/db\"\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n\t\"github.com/caoyingjunz/pixiu/pkg/util\"\n)\n\nfunc MakeDbOptions(ctx context.Context) (opts []db.Options) {\n\texists, ids := httputils.GetIdRangeFromListReq(ctx)\n\tif exists {\n\t\topts = append(opts, db.WithIDIn(ids...))\n\t}\n\treturn\n}\n\nfunc SetIdRangeContext(c *gin.Context, enforcer *casbin.SyncedEnforcer, user *model.User, obj string) error {\n\tbindings, err := GetGroupBindings(enforcer, QueryWithUserName(user.Name))\n\tif err != nil {\n\t\treturn err\n\t}\n\tif model.BindingToAdmin(bindings) {\n\t\t// This user is an admin/root, it's unnecessary to set object IDs list to context.\n\t\treturn nil\n\t}\n\n\tups, err := GetUserPolicies(enforcer, user, WithObjectType(model.ObjectType(obj)))\n\tif err != nil {\n\t\treturn err\n\t}\n\tpolicies := make([]model.Policy, len(ups))\n\tfor i, up := range ups {\n\t\tpolicies[i] = up\n\t}\n\tif all, ids := model.GetIdRangeFromPolicy(policies); !all {\n\t\t// Set a list of object IDs to context.\n\t\thttputils.SetIdRangeContext(c, ids)\n\t}\n\t// If policy with all operation(*) exists, it's unnecessary to set object IDs list to context.\n\treturn nil\n}\n\ntype BindingQueryCondition func(c *policyConditions) (index int)\n\nfunc QueryWithGroupName(name string) BindingQueryCondition {\n\treturn func(c *policyConditions) int {\n\t\tc.conds[1] = &name\n\t\treturn 1\n\t}\n}\n\nfunc QueryWithUserName(name string) BindingQueryCondition {\n\treturn func(c *policyConditions) int {\n\t\tc.conds[0] = &name\n\t\treturn 0\n\t}\n}\n\nfunc GetGroupBindings(enforcer *casbin.SyncedEnforcer, conds ...BindingQueryCondition) ([]model.GroupBinding, error) {\n\tvar index int\n\tpc := &policyConditions{\n\t\tconds: [4]*string{},\n\t}\n\tfor _, cond := range conds {\n\t\ti := cond(pc)\n\t\tindex = util.Less(i, index)\n\t}\n\n\trp, err := enforcer.GetFilteredNamedGroupingPolicy(\"g\", index, pc.get()...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tbindings := make([]model.GroupBinding, len(rp))\n\tfor i, p := range rp {\n\t\tcopy(bindings[i][:], p)\n\t}\n\treturn bindings, nil\n}\n\ntype policyConditions struct {\n\tconds [4]*string\n}\n\nfunc newPolicyConditions(name string) *policyConditions {\n\treturn &policyConditions{\n\t\tconds: [4]*string{&name},\n\t}\n}\n\nfunc (c *policyConditions) get() (conds []string) {\n\tfor _, cond := range c.conds {\n\t\tif cond != nil {\n\t\t\tconds = append(conds, *cond)\n\t\t} else {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn\n}\n\ntype PolicyCondition func(c *policyConditions)\n\nfunc WithObjectType(t model.ObjectType) PolicyCondition {\n\treturn func(c *policyConditions) {\n\t\ts := t.String()\n\t\tc.conds[1] = &s\n\t}\n}\n\nfunc WithStringID(sid string) PolicyCondition {\n\treturn func(c *policyConditions) {\n\t\tc.conds[2] = &sid\n\t}\n}\n\nfunc WithOperation(op model.Operation) PolicyCondition {\n\treturn func(c *policyConditions) {\n\t\ts := op.String()\n\t\tc.conds[3] = &s\n\t}\n}\n\nfunc GetUserPolicies(enforcer *casbin.SyncedEnforcer, user *model.User, conds ...PolicyCondition) ([]model.UserPolicy, error) {\n\tpc := newPolicyConditions(user.Name)\n\tfor _, cond := range conds {\n\t\tcond(pc)\n\t}\n\n\trp, err := enforcer.GetFilteredNamedPolicy(\"p\", 0, pc.get()...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tpolicies := make([]model.UserPolicy, len(rp))\n\tfor i, p := range rp {\n\t\t_ = copy(policies[i][:], p)\n\t}\n\treturn policies, nil\n}\n\nfunc GetGroupPolicy(enforcer *casbin.SyncedEnforcer, name string) (*model.GroupPolicy, error) {\n\trp, err := enforcer.GetFilteredNamedPolicy(\"p\", 0, name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(rp) == 0 {\n\t\treturn nil, nil\n\t}\n\tpolicy := model.GroupPolicy{}\n\t_ = copy(policy[:], rp[0])\n\treturn &policy, nil\n}\n"
  },
  {
    "path": "pkg/db/audit.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage db\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"gorm.io/gorm\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n\t\"github.com/caoyingjunz/pixiu/pkg/util/errors\"\n)\n\ntype AuditInterface interface {\n\tList(ctx context.Context, opts ...Options) ([]model.Audit, error)\n\tGet(ctx context.Context, id int64) (*model.Audit, error)\n\tCreate(ctx context.Context, object *model.Audit) (*model.Audit, error)\n\tBatchDelete(ctx context.Context, opts ...Options) (int64, error)\n\n\tCount(ctx context.Context, opts ...Options) (int64, error)\n}\n\ntype audit struct {\n\tdb *gorm.DB\n}\n\nfunc newAudit(db *gorm.DB) AuditInterface {\n\treturn &audit{db: db}\n}\n\nfunc (a *audit) Create(ctx context.Context, object *model.Audit) (*model.Audit, error) {\n\tnow := time.Now()\n\tobject.GmtCreate = now\n\tobject.GmtModified = now\n\n\tif err := a.db.WithContext(ctx).Create(object).Error; err != nil {\n\t\treturn nil, err\n\t}\n\treturn object, nil\n}\n\nfunc (a *audit) Get(ctx context.Context, aid int64) (*model.Audit, error) {\n\tvar object model.Audit\n\tif err := a.db.WithContext(ctx).Where(\"id = ?\", aid).First(&object).Error; err != nil {\n\t\tif errors.IsRecordNotFound(err) {\n\t\t\treturn nil, nil\n\t\t}\n\t\treturn nil, err\n\t}\n\treturn &object, nil\n}\n\nfunc (a *audit) List(ctx context.Context, opts ...Options) ([]model.Audit, error) {\n\tvar audits []model.Audit\n\ttx := a.db.WithContext(ctx)\n\tfor _, opt := range opts {\n\t\ttx = opt(tx)\n\t}\n\tif err := tx.Find(&audits).Error; err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn audits, nil\n}\n\nfunc (a *audit) BatchDelete(ctx context.Context, opts ...Options) (int64, error) {\n\ttx := a.db.WithContext(ctx)\n\tfor _, opt := range opts {\n\t\ttx = opt(tx)\n\t}\n\n\terr := tx.Delete(&model.Audit{}).Error\n\treturn tx.RowsAffected, err\n}\n\nfunc (a *audit) Count(ctx context.Context, opts ...Options) (int64, error) {\n\ttx := a.db.WithContext(ctx)\n\tfor _, opt := range opts {\n\t\ttx = opt(tx)\n\t}\n\n\tvar total int64\n\terr := tx.Model(&model.Audit{}).Count(&total).Error\n\treturn total, err\n}\n"
  },
  {
    "path": "pkg/db/cluster.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage db\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"gorm.io/gorm\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n\t\"github.com/caoyingjunz/pixiu/pkg/util/errors\"\n)\n\ntype ClusterInterface interface {\n\tCreate(ctx context.Context, object *model.Cluster, fns ...func(*model.Cluster) error) (*model.Cluster, error)\n\tUpdate(ctx context.Context, cid int64, resourceVersion int64, updates map[string]interface{}) error\n\tDelete(ctx context.Context, cluster *model.Cluster, fns ...func(*model.Cluster) error) error\n\tGet(ctx context.Context, cid int64, opts ...Options) (*model.Cluster, error)\n\tList(ctx context.Context, opts ...Options) ([]model.Cluster, error)\n\tCount(ctx context.Context, opts ...Options) (int64, error)\n\n\t// InternalUpdate 内部更新，不更新版本号\n\tInternalUpdate(ctx context.Context, cid int64, updates map[string]interface{}) error\n\n\tGetClusterByName(ctx context.Context, name string) (*model.Cluster, error)\n\tUpdateByPlan(ctx context.Context, planId int64, updates map[string]interface{}) error\n}\n\ntype cluster struct {\n\tdb *gorm.DB\n}\n\nfunc (c *cluster) Create(ctx context.Context, object *model.Cluster, fns ...func(*model.Cluster) error) (*model.Cluster, error) {\n\tnow := time.Now()\n\tobject.GmtCreate = now\n\tobject.GmtModified = now\n\n\tif err := c.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {\n\t\tif err := tx.Create(object).Error; err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, fn := range fns {\n\t\t\tif err := fn(object); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}); err != nil {\n\t\treturn nil, err\n\t}\n\treturn object, nil\n}\n\nfunc (c *cluster) Update(ctx context.Context, cid int64, resourceVersion int64, updates map[string]interface{}) error {\n\t// 系统维护字段\n\tupdates[\"gmt_modified\"] = time.Now()\n\tupdates[\"resource_version\"] = resourceVersion + 1\n\n\tf := c.db.WithContext(ctx).Model(&model.Cluster{}).Where(\"id = ? and resource_version = ?\", cid, resourceVersion).Updates(updates)\n\tif f.Error != nil {\n\t\treturn f.Error\n\t}\n\tif f.RowsAffected == 0 {\n\t\treturn errors.ErrRecordNotUpdate\n\t}\n\n\treturn nil\n}\n\n// InternalUpdate 程序内部更新\nfunc (c *cluster) InternalUpdate(ctx context.Context, cid int64, updates map[string]interface{}) error {\n\t// 系统维护字段\n\tupdates[\"gmt_modified\"] = time.Now()\n\tf := c.db.WithContext(ctx).Model(&model.Cluster{}).Where(\"id = ?\", cid).Updates(updates)\n\tif f.Error != nil {\n\t\treturn f.Error\n\t}\n\tif f.RowsAffected == 0 {\n\t\treturn errors.ErrRecordNotUpdate\n\t}\n\n\treturn nil\n}\n\nfunc (c *cluster) Delete(ctx context.Context, cluster *model.Cluster, fns ...func(*model.Cluster) error) error {\n\t// 仅当数据库支持回写功能时才能正常\n\t//if err := c.db.Clauses(clause.Returning{}).Where(\"id = ?\", cid).Delete(&object).Error; err != nil {\n\t//\treturn nil, err\n\t//}\n\treturn c.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {\n\t\tif err := tx.Delete(cluster).Error; err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, fn := range fns {\n\t\t\tif err := fn(cluster); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n}\n\nfunc (c *cluster) Get(ctx context.Context, cid int64, opts ...Options) (*model.Cluster, error) {\n\tvar object model.Cluster\n\ttx := c.db.WithContext(ctx)\n\tfor _, opt := range opts {\n\t\ttx = opt(tx)\n\t}\n\tif err := tx.First(&object, cid).Error; err != nil {\n\t\tif errors.IsRecordNotFound(err) {\n\t\t\treturn nil, nil\n\t\t}\n\t\treturn nil, err\n\t}\n\n\treturn &object, nil\n}\n\nfunc (c *cluster) List(ctx context.Context, opts ...Options) ([]model.Cluster, error) {\n\tvar cs []model.Cluster\n\ttx := c.db.WithContext(ctx)\n\tfor _, opt := range opts {\n\t\ttx = opt(tx)\n\t}\n\tif err := tx.Find(&cs).Error; err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn cs, nil\n}\n\nfunc (c *cluster) Count(ctx context.Context, opts ...Options) (int64, error) {\n\tvar total int64\n\ttx := c.db.WithContext(ctx).Model(&model.Cluster{})\n\tfor _, opt := range opts {\n\t\ttx = opt(tx)\n\t}\n\tif err := tx.Count(&total).Error; err != nil {\n\t\treturn 0, err\n\t}\n\treturn total, nil\n}\n\nfunc (c *cluster) GetClusterByName(ctx context.Context, name string) (*model.Cluster, error) {\n\tvar object model.Cluster\n\tif err := c.db.WithContext(ctx).Where(\"name = ?\", name).First(&object).Error; err != nil {\n\t\tif errors.IsRecordNotFound(err) {\n\t\t\treturn nil, nil\n\t\t}\n\t\treturn nil, err\n\t}\n\n\treturn &object, nil\n}\n\nfunc (c *cluster) UpdateByPlan(ctx context.Context, planId int64, updates map[string]interface{}) error {\n\tupdates[\"gmt_modified\"] = time.Now()\n\n\tf := c.db.WithContext(ctx).Model(&model.Cluster{}).Where(\"plan_id = ?\", planId).Updates(updates)\n\tif f.Error != nil {\n\t\treturn f.Error\n\t}\n\tif f.RowsAffected == 0 {\n\t\treturn errors.ErrRecordNotUpdate\n\t}\n\n\treturn nil\n}\n\nfunc newCluster(db *gorm.DB) ClusterInterface {\n\treturn &cluster{db}\n}\n"
  },
  {
    "path": "pkg/db/factory.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage db\n\nimport (\n\t\"gorm.io/gorm\"\n)\n\ntype ShareDaoFactory interface {\n\tCluster() ClusterInterface\n\tTenant() TenantInterface\n\tUser() UserInterface\n\tPlan() PlanInterface\n\tAudit() AuditInterface\n\tRepository() RepositoryInterface\n}\n\ntype shareDaoFactory struct {\n\tdb *gorm.DB\n}\n\nfunc (f *shareDaoFactory) Cluster() ClusterInterface       { return newCluster(f.db) }\nfunc (f *shareDaoFactory) Tenant() TenantInterface         { return newTenant(f.db) }\nfunc (f *shareDaoFactory) User() UserInterface             { return newUser(f.db) }\nfunc (f *shareDaoFactory) Plan() PlanInterface             { return newPlan(f.db) }\nfunc (f *shareDaoFactory) Audit() AuditInterface           { return newAudit(f.db) }\nfunc (f *shareDaoFactory) Repository() RepositoryInterface { return newRepository(f.db) }\n\nfunc NewDaoFactory(db *gorm.DB, migrate bool) (ShareDaoFactory, error) {\n\tif migrate {\n\t\t// 自动创建指定模型的数据库表结构\n\t\tif err := newMigrator(db).AutoMigrate(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn &shareDaoFactory{\n\t\tdb: db,\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/db/logger.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage db\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"gorm.io/gorm/logger\"\n)\n\ntype (\n\tSQLs []string\n\n\tDBLogger struct {\n\t\tlogger.LogLevel\n\t\tSlowThreshold time.Duration // slow SQL queries\n\t}\n)\n\nconst SQLContextKey = \"sqls\"\n\nfunc NewLogger(level logger.LogLevel, slowThreshold time.Duration) *DBLogger {\n\treturn &DBLogger{\n\t\tLogLevel:      level,\n\t\tSlowThreshold: slowThreshold,\n\t}\n}\n\nfunc (l *DBLogger) LogMode(level logger.LogLevel) logger.Interface {\n\tl.LogLevel = level\n\treturn l\n}\n\nfunc (l *DBLogger) Info(ctx context.Context, msg string, data ...interface{}) {}\n\nfunc (l *DBLogger) Warn(ctx context.Context, msg string, data ...interface{}) {}\n\nfunc (l *DBLogger) Error(ctx context.Context, msg string, data ...interface{}) {}\n\nfunc (l *DBLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {\n\tif l.LogLevel <= logger.Silent {\n\t\treturn\n\t}\n\n\tsql, _ := fc()\n\tif v := ctx.Value(SQLContextKey); v != nil {\n\t\tsqls := v.(*SQLs)\n\t\t*sqls = append(*sqls, sql)\n\t}\n}\n\nfunc WithDBContext(ctx context.Context) context.Context {\n\treturn context.WithValue(ctx, SQLContextKey, new(SQLs))\n}\n\n// GetSQLs returns all the SQL statements executed in the current context.\nfunc GetSQLs(ctx context.Context) SQLs {\n\tif v := ctx.Value(SQLContextKey); v != nil {\n\t\treturn *v.(*SQLs)\n\t}\n\treturn SQLs{}\n}\n"
  },
  {
    "path": "pkg/db/migrator.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage db\n\nimport (\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n\n\t\"gorm.io/gorm\"\n)\n\ntype migrator struct {\n\tdb *gorm.DB\n}\n\n// AutoMigrate 自动创建指定模型的数据库表结构\nfunc (m *migrator) AutoMigrate() error {\n\treturn m.CreateTables(model.GetMigrationModels()...)\n}\n\nfunc (m *migrator) CreateTables(dst ...interface{}) error {\n\tdb := m.db.Set(\"gorm:table_options\", \"AUTO_INCREMENT=20220801 DEFAULT CHARSET=utf8\")\n\n\tfor _, d := range dst {\n\t\tif db.Migrator().HasTable(d) {\n\t\t\tcontinue\n\t\t}\n\t\tif err := db.Migrator().CreateTable(d); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc newMigrator(db *gorm.DB) *migrator {\n\treturn &migrator{db}\n}\n"
  },
  {
    "path": "pkg/db/model/audit.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage model\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model/pixiu\"\n)\n\nfunc init() {\n\tregister(&Audit{})\n}\n\ntype AuditOperationStatus uint8\n\nconst (\n\tAuditOpFail    AuditOperationStatus = iota // 执行失败\n\tAuditOpSuccess                             // 执行成功\n\tAuditOpUnknown                             // 获取执行状态失败\n)\n\nfunc (s AuditOperationStatus) String() string {\n\tswitch s {\n\tcase AuditOpFail:\n\t\treturn \"failed\"\n\tcase AuditOpSuccess:\n\t\treturn \"succeed\"\n\tdefault:\n\t\treturn \"unknown\"\n\t}\n}\n\ntype Audit struct {\n\tpixiu.Model\n\n\tRequestId         string               `gorm:\"column:request_id;type:varchar(32);index\" json:\"request_id\"`            // 请求 ID\n\tIp                string               `gorm:\"type:varchar(128)\" json:\"ip\"`                                           // 客户端 IP\n\tAction            string               `gorm:\"type:varchar(255)\" json:\"action\"`                                       // HTTP 方法 [POST/DELETE/PUT/GET]\n\tOperator          string               `gorm:\"type:varchar(255)\" json:\"operator\"`                                     // 操作人 ID\n\tPath              string               `gorm:\"type:varchar(255)\" json:\"path\"`                                         // HTTP 路径\n\tObjectType        ObjectType           `gorm:\"column:resource_type;type:varchar(128)\" json:\"resource_type\"`           // 操作资源类型 [cluster/plan...]\n\tStatus            AuditOperationStatus `gorm:\"type:tinyint\" json:\"status\"`                                            // 记录操作运行结果[OperationStatus]\n\tDuration          int64                `gorm:\"column:duration;type:bigint;default:0\" json:\"duration\"`                 // 请求耗时 ms\n\tResponseCode      int                  `gorm:\"column:response_code;type:int;default:0\" json:\"response_code\"`          // HTTP 响应码\n\tCluster           string               `gorm:\"column:cluster;type:varchar(255)\" json:\"cluster\"`                       // K8s 集群名\n\tResourceName      string               `gorm:\"column:resource_name;type:varchar(255)\" json:\"resource_name\"`           // 资源名称\n\tResourceNamespace string               `gorm:\"column:resource_namespace;type:varchar(255)\" json:\"resource_namespace\"` // 资源命名空间\n}\n\nfunc (a *Audit) String() string {\n\treturn fmt.Sprintf(\"user %s(ip addr: %s) access %s with %s then %s (duration: %dms)\", a.Operator, a.Ip,\n\t\ta.Path, a.Action, a.Status.String(), a.Duration)\n}\n\nfunc (a *Audit) TableName() string {\n\treturn \"audits\"\n}\n"
  },
  {
    "path": "pkg/db/model/cluster.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage model\n\nimport \"github.com/caoyingjunz/pixiu/pkg/db/model/pixiu\"\n\nfunc init() {\n\tregister(&Cluster{})\n}\n\n// ClusterType Kubernetes 集群的类型\ntype ClusterType uint8\n\nconst (\n\tClusterTypeStandard ClusterType = iota // 标准集群\n\tClusterTypeCustom                      // 自建集群\n)\n\ntype ClusterStatus uint8\n\nconst (\n\tClusterStatusRunning ClusterStatus = iota // 运行中\n\tClusterStatusDeploy                       // 部署中\n\tClusterStatusUnStart                      // 等待部署\n\tClusterStatusFailed                       // 部署失败\n\tClusterStatusError                        // 集群失联，API不可用\n)\n\n// Cluster kubernetes 集群信息\ntype Cluster struct {\n\tpixiu.Model\n\n\t// 集群名称，全局唯一\n\tName string `gorm:\"index:idx_name,unique\" json:\"name\"`\n\t// 集群别名，可以重复，允许为中文\n\tAliasName string `json:\"alias_name\"`\n\n\t// 0：标准集群 1: 自建集群\n\tClusterType `gorm:\"type:tinyint\" json:\"cluster_type\"`\n\t// 自建集群关联的 PlanId\n\tPlanId int64\n\n\t// 集群运行状态 0: 运行中 1: 部署中 2: 等待部署 3: 部署失败 4: 运行中断 5: 所有的 node 不健康\n\tClusterStatus `gorm:\"column:status;type:tinyint\" json:\"status\"`\n\n\t// 集群的版本\n\tKubernetesVersion string `gorm:\"type:varchar(255)\" json:\"kubernetes_version,omitempty\"`\n\n\t// 集群节点健康数，json 字符串\n\tNodes string `gorm:\"type:text\" json:\"nodes\"`\n\n\t// 集群删除保护，开启集群删除保护时不允许删除集群\n\t// 0: 关闭集群删除保护 1: 开启集群删除保护\n\tProtected bool `json:\"protected\"`\n\n\t// k8s kubeConfig base64 字段\n\tKubeConfig string `json:\"kube_config\"`\n\n\t// 集群用途描述，可以为空\n\tDescription string `gorm:\"type:text\" json:\"description\"`\n}\n\nfunc (*Cluster) TableName() string {\n\treturn \"clusters\"\n}\n"
  },
  {
    "path": "pkg/db/model/model.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage model\n\nvar models = make([]interface{}, 0)\n\nfunc register(model ...interface{}) {\n\tmodels = append(models, model...)\n}\n\n// GetMigrationModels is a helper function returns all models for table initalization.\nfunc GetMigrationModels() []interface{} {\n\treturn models\n}\n"
  },
  {
    "path": "pkg/db/model/pixiu/model.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage pixiu\n\nimport (\n\t\"strconv\"\n\t\"time\"\n)\n\ntype Model struct {\n\tId              int64     `gorm:\"column:id;primaryKey;autoIncrement;not null\" json:\"id\"`\n\tGmtCreate       time.Time `gorm:\"column:gmt_create;type:datetime;default:current_timestamp;not null\" json:\"gmt_create\"`\n\tGmtModified     time.Time `gorm:\"column:gmt_modified;type:datetime;default:current_timestamp;not null\" json:\"gmt_modified\"`\n\tResourceVersion int64     `gorm:\"column:resource_version;default:0;not null\" json:\"resource_version\"`\n}\n\nfunc (m Model) GetSID() string {\n\treturn strconv.FormatInt(m.Id, 10)\n}\n"
  },
  {
    "path": "pkg/db/model/plan.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage model\n\nimport (\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model/pixiu\"\n)\n\nfunc init() {\n\tregister(&Plan{}, &Node{}, &Config{}, &Task{})\n}\n\ntype Plan struct {\n\tpixiu.Model\n\n\tName        string `gorm:\"index:idx_name,unique\" json:\"name\"`\n\tDescription string `gorm:\"type:text\" json:\"description\"`\n}\n\nfunc (plan *Plan) TableName() string {\n\treturn \"plans\"\n}\n\ntype KubeRole string\n\nconst (\n\tMasterRole string = \"master\" // kubernetes master role\n\tNodeRole   string = \"node\"   // kubernetes node role\n)\n\ntype CRI string\n\nconst (\n\tDockerCRI     CRI = \"docker\"\n\tContainerdCRI CRI = \"containerd\"\n)\n\ntype Node struct {\n\tpixiu.Model\n\n\tName   string `json:\"name\"` // 主机名，相同plan内不允许重复\n\tPlanId int64  `json:\"plan_id\"`\n\tRole   string `json:\"role\"` // k8s 节点的角色，master 和 node\n\tCRI    CRI    `json:\"cri\"`\n\tIp     string `json:\"ip\"`\n\tAuth   string `json:\"auth\"`\n}\n\nfunc (node *Node) TableName() string {\n\treturn \"nodes\"\n}\n\ntype Config struct {\n\tpixiu.Model\n\n\tPlanId     int64  `json:\"plan_id\"`\n\tRegion     string `json:\"region\"`\n\tOSImage    string `json:\"os_image\"`\n\tKubernetes string `json:\"kubernetes\"`\n\tNetwork    string `json:\"network\"`\n\tRuntime    string `json:\"runtime\"`\n\tComponent  string `json:\"component\"`\n}\n\nfunc (config *Config) TableName() string {\n\treturn \"configs\"\n}\n\ntype PlanStep int\n\nconst (\n\tUnStartedPlanStep PlanStep = iota\n\tRunningPlanStep\n\tFailedPlanStep\n\tCompletedPlanStep\n)\n\ntype TaskStatus string\n\nconst (\n\tFailedPlanStatus  TaskStatus = \"已失败\"\n\tSuccessPlanStatus TaskStatus = \"已成功\"\n\tUnStartPlanStatus TaskStatus = \"未开始\"\n\tRunningPlanStatus TaskStatus = \"运行中\"\n)\n\ntype Task struct {\n\tpixiu.Model\n\n\tName    string     `json:\"name\"`\n\tPlanId  int64      `json:\"plan_id\"`\n\tStep    PlanStep   `json:\"step\"`\n\tStatus  TaskStatus `json:\"status\"`\n\tMessage string     `json:\"message\"`\n}\n\nfunc (task *Task) TableName() string {\n\treturn \"tasks\"\n}\n"
  },
  {
    "path": "pkg/db/model/rbac.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage model\n\nimport (\n\t\"reflect\"\n\t\"strconv\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model/pixiu\"\n\t\"github.com/caoyingjunz/pixiu/pkg/util\"\n)\n\nconst (\n\tAdminGroup = \"root\"\n\tSidAll     = \"*\"\n)\n\ntype Operation string\n\nconst (\n\tOpRead   Operation = \"read\"\n\tOpCreate Operation = \"create\"\n\tOpUpdate Operation = \"update\"\n\tOpDelete Operation = \"delete\"\n\tOpAll    Operation = \"*\"\n)\n\nfunc (o Operation) String() string {\n\treturn string(o)\n}\n\nvar OperationMap = map[Operation]struct{}{\n\tOpRead:   {},\n\tOpCreate: {},\n\tOpUpdate: {},\n\tOpDelete: {},\n\tOpAll:    {},\n}\n\ntype ObjectType string\n\nconst (\n\tObjectUser    ObjectType = \"users\"\n\tObjectCluster ObjectType = \"clusters\"\n\tObjectTenant  ObjectType = \"tenants\"\n\tObjectPlan    ObjectType = \"plans\"\n\tObjectAuth    ObjectType = \"auth\"\n\tObjectAll     ObjectType = \"*\"\n)\n\nfunc (o ObjectType) String() string {\n\treturn string(o)\n}\n\nvar ObjectTypeMap = map[ObjectType]struct{}{\n\tObjectUser:    {},\n\tObjectCluster: {},\n\tObjectTenant:  {},\n\tObjectPlan:    {},\n\tObjectAuth:    {},\n\tObjectAll:     {},\n}\n\n// TODO:\ntype RBACInterface interface{}\n\n// Casbin RBAC model\n// ref: https://github.com/casbin/casbin/blob/master/examples/rbac_model.conf\nconst RBACModel = `\n[request_definition]\nr = sub, obj, id, op\n\n[policy_definition]\np = sub, obj, id, op\n\n[role_definition]\ng = _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && keyMatch(r.id, p.id) && keyMatch(r.op, p.op)`\n\n// TODO:\ntype CasbinRBACImpl struct{}\n\ntype Policy interface {\n\tRaw() []string\n}\n\n// UserPolicy is a RBAC policy for user.\n// e.g. [\"foo\", \"clusters\", \"*\", \"read\"]\ntype UserPolicy [4]string\n\n// NewUserPolicy returns a policy slice for user.\n// e.g. [\"foo\", \"clusters\", \"*\", \"read\"]: foo is a user name\nfunc NewUserPolicy(userName string, obj ObjectType, sid string, op Operation) UserPolicy {\n\treturn UserPolicy{userName, obj.String(), sid, op.String()}\n}\n\nfunc (p UserPolicy) Raw() []string {\n\treturn p[:]\n}\n\nfunc (p UserPolicy) GetUserName() string {\n\treturn p[0]\n}\n\nfunc (p UserPolicy) GetObjectType() ObjectType {\n\treturn ObjectType(p[1])\n}\n\nfunc (p UserPolicy) GetSID() string {\n\treturn p[2]\n}\n\nfunc (p UserPolicy) GetOperation() Operation {\n\treturn Operation(p[3])\n}\n\n// GroupPolicy is a RBAC policy for group.\n// e.g. [\"master\", \"clusters\", \"*\", \"*\"]\ntype GroupPolicy [4]string\n\n// NewGroupPolicy returns a policy slice for group.\n// e.g. [\"master\", \"clusters\", \"*\", \"*\"]: master is a group name\nfunc NewGroupPolicy(groupName string, obj ObjectType, sid string, op Operation) GroupPolicy {\n\treturn GroupPolicy{groupName, obj.String(), sid, op.String()}\n}\n\nfunc (p GroupPolicy) Raw() []string {\n\treturn p[:]\n}\n\nfunc (p GroupPolicy) GetGroupName() string {\n\treturn p[0]\n}\n\nfunc (p GroupPolicy) GetObjectType() ObjectType {\n\treturn ObjectType(p[1])\n}\n\nfunc (p GroupPolicy) GetSID() string {\n\treturn p[2]\n}\n\nfunc (p GroupPolicy) GetOperation() Operation {\n\treturn Operation(p[3])\n}\n\n// GroupBinding binds a user to a group.\n// e.g. [\"foo\", \"master\"]: user foo belongs to group master\ntype GroupBinding [2]string\n\n// NewGroupBinding returns a binding slice for relationship between user and group.\nfunc NewGroupBinding(userName, groupName string) GroupBinding {\n\treturn GroupBinding{userName, groupName}\n}\n\nfunc (p GroupBinding) Raw() []string {\n\treturn p[:]\n}\n\nfunc (p GroupBinding) GetUserName() string {\n\treturn p[0]\n}\n\nfunc (p GroupBinding) GetGroupName() string {\n\treturn p[1]\n}\n\n// AdminPolicy is the specific policy for admin/root user.\nvar AdminPolicy = NewGroupPolicy(AdminGroup, ObjectAll, SidAll, OpAll)\n\n// IsAdminPolicy returns true if the policy is the admin policy.\nfunc IsAdminPolicy(policy Policy) bool {\n\tswitch p := policy.(type) {\n\tcase GroupPolicy:\n\t\treturn reflect.DeepEqual(p.Raw(), AdminPolicy.Raw())\n\tdefault:\n\t\treturn false\n\t}\n}\n\n// BindingToAdmin returns true if policy binding to admin group exists.\nfunc BindingToAdmin(policies []GroupBinding) bool {\n\tfor _, policy := range policies {\n\t\tif policy.GetGroupName() == AdminGroup {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// NewPolicyFromModels returns a policy slice.\n// e.g. [\"foo\", \"clusters\", \"*\", \"*\"]\nfunc NewPolicyFromModels(user *User, obj ObjectType, model pixiu.Model, op Operation) Policy {\n\treturn NewUserPolicy(user.Name, obj, model.GetSID(), op)\n}\n\n// NOTE: GetIdRangeFromPolicy is only used for listing API request.\n// GetIdRangeFromPolicy returns true and an empty list when policy with all operation(*) are allowed exists,\n// otherwise it returns false and a list of object IDs.\nfunc GetIdRangeFromPolicy(policies []Policy) (all bool, ids []int64) {\n\tids = make([]int64, 0)\n\tfor _, policy := range policies {\n\t\tif _, ok := policy.(GroupBinding); ok {\n\t\t\tcontinue\n\t\t}\n\n\t\traw := policy.Raw() // e.g. [\"foo\", \"clusters\", \"*\", \"read\"]\n\t\tsid := raw[2]\n\t\tswitch sid {\n\t\tcase \"\":\n\t\t\tcontinue\n\t\tcase SidAll:\n\t\t\t// permit to read all\n\t\t\treturn true, []int64{}\n\t\t}\n\n\t\t// operation\n\t\tif !(raw[3] == OpRead.String() || raw[3] == OpAll.String()) {\n\t\t\tcontinue\n\t\t}\n\n\t\tid, err := strconv.ParseInt(sid, 10, 64)\n\t\tif err != nil {\n\t\t\t// invalid sid\n\t\t\tcontinue\n\t\t}\n\t\tids = append(ids, id)\n\t}\n\treturn false, util.DeduplicateIntSlice(ids)\n}\n"
  },
  {
    "path": "pkg/db/model/rbac_test.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage model\n\nimport (\n\t\"reflect\"\n\t\"strconv\"\n\t\"testing\"\n)\n\nfunc TestGetIdRangeFromPolicies(t *testing.T) {\n\ttype args struct {\n\t\tpolicies []Policy\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twantAll bool\n\t\twantIds []int64\n\t}{\n\t\t{\n\t\t\tname: \"case 1\",\n\t\t\targs: args{\n\t\t\t\tpolicies: []Policy{},\n\t\t\t},\n\t\t\twantAll: false,\n\t\t\twantIds: []int64{},\n\t\t},\n\t\t{\n\t\t\tname: \"case 2\",\n\t\t\targs: args{\n\t\t\t\tpolicies: []Policy{\n\t\t\t\t\tNewUserPolicy(\"foo\", ObjectCluster, strconv.Itoa(1), OpRead),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAll: false,\n\t\t\twantIds: []int64{1},\n\t\t},\n\t\t{\n\t\t\tname: \"case 3\",\n\t\t\targs: args{\n\t\t\t\tpolicies: []Policy{\n\t\t\t\t\tNewUserPolicy(\"foo\", ObjectCluster, strconv.Itoa(1), OpRead),\n\t\t\t\t\tNewUserPolicy(\"foo\", ObjectCluster, strconv.Itoa(2), OpRead),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAll: false,\n\t\t\twantIds: []int64{1, 2},\n\t\t},\n\t\t{\n\t\t\tname: \"case 4\",\n\t\t\targs: args{\n\t\t\t\tpolicies: []Policy{\n\t\t\t\t\tNewUserPolicy(\"foo\", ObjectCluster, \"\", OpRead),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAll: false,\n\t\t\twantIds: []int64{},\n\t\t},\n\t\t{\n\t\t\tname: \"case 5\",\n\t\t\targs: args{\n\t\t\t\tpolicies: []Policy{\n\t\t\t\t\tNewUserPolicy(\"foo\", ObjectCluster, \"*\", OpRead),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAll: true,\n\t\t\twantIds: []int64{},\n\t\t},\n\t\t{\n\t\t\tname: \"case 6\",\n\t\t\targs: args{\n\t\t\t\tpolicies: []Policy{\n\t\t\t\t\tNewUserPolicy(\"foo\", ObjectCluster, strconv.Itoa(1), OpRead),\n\t\t\t\t\tNewUserPolicy(\"foo\", ObjectCluster, \"*\", OpRead),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAll: true,\n\t\t\twantIds: []int64{},\n\t\t},\n\t\t{\n\t\t\tname: \"case 7\",\n\t\t\targs: args{\n\t\t\t\tpolicies: []Policy{\n\t\t\t\t\tNewUserPolicy(\"foo\", ObjectCluster, strconv.Itoa(1), OpRead),\n\t\t\t\t\tNewUserPolicy(\"foo\", ObjectCluster, strconv.Itoa(1), OpRead),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAll: false,\n\t\t\twantIds: []int64{1},\n\t\t},\n\t\t{\n\t\t\tname: \"case 8\",\n\t\t\targs: args{\n\t\t\t\tpolicies: []Policy{\n\t\t\t\t\tNewUserPolicy(\"foo\", ObjectCluster, strconv.Itoa(1), OpRead),\n\t\t\t\t\tNewUserPolicy(\"foo\", ObjectCluster, strconv.Itoa(1), OpRead),\n\t\t\t\t\tNewUserPolicy(\"foo\", ObjectCluster, \"*\", OpRead),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAll: true,\n\t\t\twantIds: []int64{},\n\t\t},\n\t\t{\n\t\t\tname: \"case 9\",\n\t\t\targs: args{\n\t\t\t\tpolicies: []Policy{\n\t\t\t\t\tNewUserPolicy(\"foo\", ObjectCluster, strconv.Itoa(1), OpDelete),\n\t\t\t\t\tNewUserPolicy(\"foo\", ObjectCluster, strconv.Itoa(2), OpUpdate),\n\t\t\t\t\tNewUserPolicy(\"foo\", ObjectCluster, strconv.Itoa(3), OpRead),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAll: false,\n\t\t\twantIds: []int64{3},\n\t\t},\n\t\t{\n\t\t\tname: \"case 10\",\n\t\t\targs: args{\n\t\t\t\tpolicies: []Policy{\n\t\t\t\t\tNewUserPolicy(\"foo\", ObjectCluster, strconv.Itoa(1), OpDelete),\n\t\t\t\t\tNewUserPolicy(\"foo\", ObjectCluster, strconv.Itoa(2), OpUpdate),\n\t\t\t\t\tNewUserPolicy(\"foo\", ObjectCluster, strconv.Itoa(3), OpRead),\n\t\t\t\t\tNewUserPolicy(\"foo\", ObjectCluster, \"*\", OpRead),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAll: true,\n\t\t\twantIds: []int64{},\n\t\t},\n\t\t{\n\t\t\tname: \"case 11\",\n\t\t\targs: args{\n\t\t\t\tpolicies: []Policy{\n\t\t\t\t\tNewUserPolicy(\"foo\", ObjectCluster, strconv.Itoa(1), OpDelete),\n\t\t\t\t\tNewUserPolicy(\"foo\", ObjectCluster, strconv.Itoa(2), OpUpdate),\n\t\t\t\t\tNewUserPolicy(\"foo\", ObjectCluster, strconv.Itoa(3), OpRead),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAll: false,\n\t\t\twantIds: []int64{3},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgotAll, gotIds := GetIdRangeFromPolicy(tt.args.policies)\n\t\t\tif gotAll != tt.wantAll {\n\t\t\t\tt.Errorf(\"GetIdRangeFromPolicies() gotAll = %v, want %v\", gotAll, tt.wantAll)\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(gotIds, tt.wantIds) {\n\t\t\t\tt.Errorf(\"GetIdRangeFromPolicies() gotIds = %v, want %v\", gotIds, tt.wantIds)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIsAdminPolicy(t *testing.T) {\n\ttype args struct {\n\t\tpolicy Policy\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"case 1\",\n\t\t\targs: args{\n\t\t\t\tpolicy: NewUserPolicy(\"foo\", ObjectCluster, \"*\", OpRead),\n\t\t\t},\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"case 1\",\n\t\t\targs: args{\n\t\t\t\tpolicy: AdminPolicy,\n\t\t\t},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"case 2\",\n\t\t\targs: args{\n\t\t\t\tpolicy: GroupPolicy{},\n\t\t\t},\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"case 3\",\n\t\t\targs: args{\n\t\t\t\tpolicy: NewGroupPolicy(\"*\", \"*\", \"*\", \"*\"),\n\t\t\t},\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"case 4\",\n\t\t\targs: args{\n\t\t\t\tpolicy: NewGroupPolicy(\"*\", \"root\", \"*\", \"*\"),\n\t\t\t},\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"case 5\",\n\t\t\targs: args{\n\t\t\t\tpolicy: NewGroupPolicy(\"*\", \"*\", \"root\", \"*\"),\n\t\t\t},\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"case 6\",\n\t\t\targs: args{\n\t\t\t\tpolicy: NewGroupPolicy(\"*\", \"*\", \"*\", \"root\"),\n\t\t\t},\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := IsAdminPolicy(tt.args.policy); got != tt.want {\n\t\t\t\tt.Errorf(\"IsAdminPolicy() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBindingToAdmin(t *testing.T) {\n\ttype args struct {\n\t\tpolicies []GroupBinding\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"case 1\",\n\t\t\targs: args{},\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"case 2\",\n\t\t\targs: args{\n\t\t\t\tpolicies: []GroupBinding{\n\t\t\t\t\tNewGroupBinding(\"foo\", \"bar\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"case 3\",\n\t\t\targs: args{\n\t\t\t\tpolicies: []GroupBinding{\n\t\t\t\t\tNewGroupBinding(\"foo\", AdminGroup),\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"case 4\",\n\t\t\targs: args{\n\t\t\t\tpolicies: []GroupBinding{\n\t\t\t\t\tNewGroupBinding(\"foo\", \"bar\"),\n\t\t\t\t\tNewGroupBinding(\"foo\", AdminGroup),\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"case 5\",\n\t\t\targs: args{\n\t\t\t\tpolicies: []GroupBinding{\n\t\t\t\t\tNewGroupBinding(\"foo\", \"bar\"),\n\t\t\t\t\tNewGroupBinding(\"foo\", \"baz\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := BindingToAdmin(tt.args.policies); got != tt.want {\n\t\t\t\tt.Errorf(\"HasAdminGroupPolicy() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/db/model/repository.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage model\n\nimport (\n\t\"time\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model/pixiu\"\n)\n\nfunc init() {\n\tregister(&Repository{})\n}\n\ntype Repository struct {\n\tpixiu.Model\n\tName     string `gorm:\"column:name; index:idx_name,unique; not null\" json:\"name\"`\n\tURL      string `gorm:\"column:url;not null\" json:\"url\"`\n\tUsername string `gorm:\"column:username\" json:\"username\"`\n\tPassword string `gorm:\"column:password\" json:\"password\"`\n}\n\nfunc (*Repository) TableName() string {\n\treturn \"repositories\"\n}\n\ntype ChartIndex struct {\n\tAPIVersion string  `json:\"apiVersion\"`\n\tEntries    Entries `json:\"entries\"`\n}\n\ntype Entries map[string][]ChartVersion\n\ntype ChartVersion struct {\n\tAnnotations  map[string]string `json:\"annotations\"`\n\tAPIVersion   string            `json:\"apiVersion\"`\n\tAppVersion   string            `json:\"appVersion\"`\n\tCreated      time.Time         `json:\"created\"`\n\tDependencies []Dependency      `json:\"dependencies\"`\n\tDescription  string            `json:\"description\"`\n\tDigest       string            `json:\"digest\"`\n\tIcon         string            `json:\"icon\"`\n\tMaintainers  []Maintainer      `json:\"maintainers\"`\n\tName         string            `json:\"name\"`\n\tSources      []string          `json:\"sources\"`\n\tType         string            `json:\"type\"`\n\tURLs         []string          `json:\"urls\"`\n\tVersion      string            `json:\"version\"`\n}\n\ntype Dependency struct {\n\tCondition  string `json:\"condition\"`\n\tName       string `json:\"name\"`\n\tRepository string `json:\"repository\"`\n\tVersion    string `json:\"version\"`\n\tAlias      string `json:\"alias,omitempty\"`\n}\n\ntype Maintainer struct {\n\tName string `json:\"name\"`\n}\n"
  },
  {
    "path": "pkg/db/model/tenant.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage model\n\nimport \"github.com/caoyingjunz/pixiu/pkg/db/model/pixiu\"\n\nfunc init() {\n\tregister(&Tenant{})\n}\n\ntype Tenant struct {\n\tpixiu.Model\n\n\tName        string `gorm:\"index:idx_name,unique\" json:\"name\"`\n\tDescription string `gorm:\"type:text\" json:\"description\"`\n\tExtension   string `gorm:\"type:text\" json:\"extension,omitempty\"`\n}\n\nfunc (tenant *Tenant) TableName() string {\n\treturn \"tenants\"\n}\n"
  },
  {
    "path": "pkg/db/model/user.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage model\n\nimport \"github.com/caoyingjunz/pixiu/pkg/db/model/pixiu\"\n\nfunc init() {\n\tregister(&User{})\n}\n\ntype UserRole uint8\n\nconst (\n\tRoleUser  UserRole = iota // 普通用户\n\tRoleAdmin                 // 管理员\n\tRoleRoot                  // 超级管理员\n)\n\ntype UserStatus uint8 // TODO\n\ntype User struct {\n\tpixiu.Model\n\n\tName        string     `gorm:\"index:idx_name,unique\" json:\"name\"`\n\tPassword    string     `gorm:\"type:varchar(256)\" json:\"-\"`\n\tStatus      UserStatus `gorm:\"type:tinyint\" json:\"status\"`\n\tRole        UserRole   `gorm:\"type:tinyint\" json:\"role\"`\n\tEmail       string     `gorm:\"type:varchar(128)\" json:\"email\"`\n\tPhone       string     `gorm:\"column:phone;type:varchar(32)\" json:\"phone\"`\n\tDescription string     `gorm:\"type:text\" json:\"description\"`\n\tExtension   string     `gorm:\"type:text\" json:\"extension,omitempty\"`\n}\n\nfunc (user *User) TableName() string {\n\treturn \"users\"\n}\n"
  },
  {
    "path": "pkg/db/options.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage db\n\nimport (\n\t\"time\"\n\n\t\"gorm.io/gorm\"\n)\n\ntype Options func(*gorm.DB) *gorm.DB\n\nfunc WithOrderByASC() Options {\n\treturn func(tx *gorm.DB) *gorm.DB {\n\t\treturn tx.Order(\"id ASC\")\n\t}\n}\n\nfunc WithOrderByDesc() Options {\n\treturn func(tx *gorm.DB) *gorm.DB {\n\t\treturn tx.Order(\"id DESC\")\n\t}\n}\n\nfunc WithOffset(offset int) Options {\n\treturn func(tx *gorm.DB) *gorm.DB {\n\t\treturn tx.Offset(offset)\n\t}\n}\n\nfunc WithCreatedBefore(t time.Time) Options {\n\treturn func(tx *gorm.DB) *gorm.DB {\n\t\treturn tx.Where(\"gmt_create < ?\", t)\n\t}\n}\n\nfunc WithLimit(limit int) Options {\n\treturn func(tx *gorm.DB) *gorm.DB {\n\t\tif limit == 0 {\n\t\t\t// `LIMIT 0` statement will return 0 rows, it's meaningless.\n\t\t\treturn tx\n\t\t}\n\t\treturn tx.Limit(limit)\n\t}\n}\n\nfunc WithIDIn(ids ...int64) Options {\n\treturn func(tx *gorm.DB) *gorm.DB {\n\t\t// e.g. `WHERE id IN (1, 2, 3)`\n\t\treturn tx.Where(\"id IN ?\", ids)\n\t}\n}\n\nfunc WithAliasNameLike(name string) Options {\n\treturn func(tx *gorm.DB) *gorm.DB {\n\t\tif name == \"\" {\n\t\t\treturn tx\n\t\t}\n\t\treturn tx.Where(\"alias_name like ?\", \"%\"+name+\"%\")\n\t}\n}\n\nfunc WithClusterStatus(status int) Options {\n\treturn func(tx *gorm.DB) *gorm.DB {\n\t\treturn tx.Where(\"status = ?\", status)\n\t}\n}\n\nfunc WithUserNameLike(name string) Options {\n\treturn func(tx *gorm.DB) *gorm.DB {\n\t\tif name == \"\" {\n\t\t\treturn tx\n\t\t}\n\t\treturn tx.Where(\"name like ?\", \"%\"+name+\"%\")\n\t}\n}\n\nfunc WithUserPhoneLike(phone string) Options {\n\treturn func(tx *gorm.DB) *gorm.DB {\n\t\tif phone == \"\" {\n\t\t\treturn tx\n\t\t}\n\t\treturn tx.Where(\"phone like ?\", \"%\"+phone+\"%\")\n\t}\n}\n\nfunc WithUserEmailLike(email string) Options {\n\treturn func(tx *gorm.DB) *gorm.DB {\n\t\tif email == \"\" {\n\t\t\treturn tx\n\t\t}\n\t\treturn tx.Where(\"email like ?\", \"%\"+email+\"%\")\n\t}\n}\n\nfunc WithUserStatus(status int) Options {\n\treturn func(tx *gorm.DB) *gorm.DB {\n\t\treturn tx.Where(\"status = ?\", status)\n\t}\n}\n\nfunc WithAuditOperatorLike(operator string) Options {\n\treturn func(tx *gorm.DB) *gorm.DB {\n\t\tif operator == \"\" {\n\t\t\treturn tx\n\t\t}\n\t\treturn tx.Where(\"operator like ?\", \"%\"+operator+\"%\")\n\t}\n}\n\nfunc WithAuditAction(action string) Options {\n\treturn func(tx *gorm.DB) *gorm.DB {\n\t\tif action == \"\" {\n\t\t\treturn tx\n\t\t}\n\t\treturn tx.Where(\"action = ?\", action)\n\t}\n}\n\nfunc WithAuditObjectType(ot string) Options {\n\treturn func(tx *gorm.DB) *gorm.DB {\n\t\tif ot == \"\" {\n\t\t\treturn tx\n\t\t}\n\t\treturn tx.Where(\"resource_type = ?\", ot)\n\t}\n}\n\nfunc WithAuditStatus(status uint8) Options {\n\treturn func(tx *gorm.DB) *gorm.DB {\n\t\treturn tx.Where(\"status = ?\", status)\n\t}\n}\n\nfunc WithAuditCreatedAfter(t time.Time) Options {\n\treturn func(tx *gorm.DB) *gorm.DB {\n\t\tif t.IsZero() {\n\t\t\treturn tx\n\t\t}\n\t\treturn tx.Where(\"gmt_create >= ?\", t)\n\t}\n}\n\nfunc WithAuditCluster(cluster string) Options {\n\treturn func(tx *gorm.DB) *gorm.DB {\n\t\treturn tx.Where(\"cluster = ?\", cluster)\n\t}\n}\n\nfunc WithPlanNameLike(name string) Options {\n\treturn func(tx *gorm.DB) *gorm.DB {\n\t\tif name == \"\" {\n\t\t\treturn tx\n\t\t}\n\t\treturn tx.Where(\"name like ?\", \"%\"+name+\"%\")\n\t}\n}\n"
  },
  {
    "path": "pkg/db/plan.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage db\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"gorm.io/gorm\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n\t\"github.com/caoyingjunz/pixiu/pkg/util/errors\"\n)\n\ntype PlanInterface interface {\n\tCreate(ctx context.Context, object *model.Plan, opts ...CreatePlanOption) (*model.Plan, error)\n\tUpdate(ctx context.Context, pid int64, resourceVersion int64, updates map[string]interface{}) error\n\tDelete(ctx context.Context, pid int64) (*model.Plan, error)\n\tGet(ctx context.Context, pid int64) (*model.Plan, error)\n\tList(ctx context.Context, opts ...Options) ([]model.Plan, error)\n\tCount(ctx context.Context, opts ...Options) (int64, error)\n\n\tCreateNode(ctx context.Context, object *model.Node) (*model.Node, error)\n\tTxCreateNode(ctx context.Context, tx *gorm.DB, object *model.Node) error\n\tUpdateNode(ctx context.Context, nodeId int64, resourceVersion int64, updates map[string]interface{}) error\n\tDeleteNode(ctx context.Context, nodeId int64) (*model.Node, error)\n\tGetNode(ctx context.Context, nodeId int64) (*model.Node, error)\n\tListNodes(ctx context.Context, pid int64, opts ...Options) ([]model.Node, error)\n\n\tDeleteNodesByPlan(ctx context.Context, planId int64) error\n\tGetNodeByName(ctx context.Context, planId int64, name string) (*model.Node, error)\n\n\tDeleteNodesByNames(ctx context.Context, planId int64, names []string) error\n\n\tCreateConfig(ctx context.Context, object *model.Config) (*model.Config, error)\n\tTxCreateConfig(ctx context.Context, tx *gorm.DB, object *model.Config) error\n\tUpdateConfig(ctx context.Context, cfgId int64, resourceVersion int64, updates map[string]interface{}) error\n\tDeleteConfig(ctx context.Context, cfgId int64) (*model.Config, error)\n\tGetConfig(ctx context.Context, cfgId int64) (*model.Config, error)\n\tListConfigs(ctx context.Context, opts ...Options) ([]model.Config, error)\n\n\tDeleteConfigByPlan(ctx context.Context, planId int64) error\n\tGetConfigByPlan(ctx context.Context, planId int64) (*model.Config, error)\n\n\tCreateTask(ctx context.Context, object *model.Task) (*model.Task, error)\n\tUpdateTask(ctx context.Context, pid int64, name string, updates map[string]interface{}) (*model.Task, error)\n\tDeleteTask(ctx context.Context, pid int64) error\n\tListTasks(ctx context.Context, pid int64, opts ...Options) ([]model.Task, error)\n\n\tGetNewestTask(ctx context.Context, pid int64) (*model.Task, error)\n\tGetTaskByName(ctx context.Context, planId int64, name string) (*model.Task, error)\n\tGetTaskById(ctx context.Context, taskId int64) (*model.Task, error)\n}\n\ntype plan struct {\n\tdb *gorm.DB\n}\n\ntype CreatePlanOption func(plan *model.Plan, tx *gorm.DB) (*gorm.DB, error)\n\nfunc (p *plan) Create(ctx context.Context, object *model.Plan, opts ...CreatePlanOption) (*model.Plan, error) {\n\tnow := time.Now()\n\tobject.GmtCreate = now\n\tobject.GmtModified = now\n\n\tif len(opts) == 0 {\n\t\t// no transaction\n\t\tif err := p.db.WithContext(ctx).Create(object).Error; err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn object, nil\n\t}\n\n\tif err := p.db.WithContext(ctx).Transaction(func(tx *gorm.DB) (err error) {\n\t\tif err = tx.Create(object).Error; err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tfor _, opt := range opts {\n\t\t\tif tx, err = opt(object, tx); err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\treturn\n\t}); err != nil {\n\t\treturn nil, err\n\t}\n\treturn object, nil\n}\n\nfunc (p *plan) Update(ctx context.Context, pid int64, resourceVersion int64, updates map[string]interface{}) error {\n\t// 系统维护字段\n\tupdates[\"gmt_modified\"] = time.Now()\n\tupdates[\"resource_version\"] = resourceVersion + 1\n\n\tf := p.db.WithContext(ctx).Model(&model.Plan{}).Where(\"id = ? and resource_version = ?\", pid, resourceVersion).Updates(updates)\n\tif f.Error != nil {\n\t\treturn f.Error\n\t}\n\n\tif f.RowsAffected == 0 {\n\t\treturn errors.ErrRecordNotFound\n\t}\n\n\treturn nil\n}\n\nfunc (p *plan) Delete(ctx context.Context, pid int64) (*model.Plan, error) {\n\tobject, err := p.Get(ctx, pid)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err = p.db.WithContext(ctx).Where(\"id = ?\", pid).Delete(&model.Plan{}).Error; err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn object, nil\n}\n\nfunc (p *plan) Get(ctx context.Context, pid int64) (*model.Plan, error) {\n\tvar object model.Plan\n\tif err := p.db.WithContext(ctx).Where(\"id = ?\", pid).First(&object).Error; err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &object, nil\n}\n\nfunc (p *plan) List(ctx context.Context, opts ...Options) ([]model.Plan, error) {\n\tvar objects []model.Plan\n\ttx := p.db.WithContext(ctx)\n\tfor _, opt := range opts {\n\t\ttx = opt(tx)\n\t}\n\tif err := tx.Find(&objects).Error; err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn objects, nil\n}\n\nfunc (p *plan) Count(ctx context.Context, opts ...Options) (int64, error) {\n\tvar count int64\n\ttx := p.db.WithContext(ctx).Model(&model.Plan{})\n\tfor _, opt := range opts {\n\t\ttx = opt(tx)\n\t}\n\tif err := tx.Count(&count).Error; err != nil {\n\t\treturn 0, err\n\t}\n\treturn count, nil\n}\n\nfunc (p *plan) CreateNode(ctx context.Context, object *model.Node) (*model.Node, error) {\n\tnow := time.Now()\n\tobject.GmtCreate = now\n\tobject.GmtModified = now\n\n\tif err := p.db.WithContext(ctx).Create(object).Error; err != nil {\n\t\treturn nil, err\n\t}\n\treturn object, nil\n}\n\n// TxCreateNode creates a node object in the given transaction.\nfunc (p *plan) TxCreateNode(ctx context.Context, tx *gorm.DB, object *model.Node) error {\n\tnow := time.Now()\n\tobject.GmtCreate = now\n\tobject.GmtModified = now\n\n\treturn tx.WithContext(ctx).Create(object).Error\n}\n\nfunc (p *plan) UpdateNode(ctx context.Context, nodeId int64, resourceVersion int64, updates map[string]interface{}) error {\n\t// 系统维护字段\n\tupdates[\"gmt_modified\"] = time.Now()\n\tupdates[\"resource_version\"] = resourceVersion + 1\n\n\tf := p.db.WithContext(ctx).Model(&model.Node{}).Where(\"id = ? and resource_version = ?\", nodeId, resourceVersion).Updates(updates)\n\tif f.Error != nil {\n\t\treturn f.Error\n\t}\n\tif f.RowsAffected == 0 {\n\t\treturn errors.ErrRecordNotFound\n\t}\n\n\treturn nil\n}\n\nfunc (p *plan) DeleteNode(ctx context.Context, nodeId int64) (*model.Node, error) {\n\tobject, err := p.GetNode(ctx, nodeId)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err = p.db.WithContext(ctx).Where(\"id = ?\", nodeId).Delete(&model.Node{}).Error; err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn object, nil\n}\n\nfunc (p *plan) DeleteNodesByPlan(ctx context.Context, planId int64) error {\n\tif err := p.db.WithContext(ctx).Where(\"plan_id = ?\", planId).Delete(&model.Node{}).Error; err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (p *plan) DeleteNodesByNames(ctx context.Context, planId int64, names []string) error {\n\tif err := p.db.WithContext(ctx).Where(\"plan_id = ? and name in (?)\", planId, names).Delete(&model.Node{}).Error; err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (p *plan) GetNodeByName(ctx context.Context, planId int64, name string) (*model.Node, error) {\n\tvar object model.Node\n\tif err := p.db.WithContext(ctx).Where(\"plan_id = ? and name = ?\", planId, name).First(&object).Error; err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &object, nil\n}\n\nfunc (p *plan) GetNode(ctx context.Context, nodeId int64) (*model.Node, error) {\n\tvar object model.Node\n\tif err := p.db.WithContext(ctx).Where(\"id = ?\", nodeId).First(&object).Error; err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &object, nil\n}\n\nfunc (p *plan) ListNodes(ctx context.Context, pid int64, opts ...Options) ([]model.Node, error) {\n\tvar objects []model.Node\n\ttx := p.db.WithContext(ctx).Where(\"plan_id = ?\", pid)\n\tfor _, opt := range opts {\n\t\ttx = opt(tx)\n\t}\n\tif err := tx.Find(&objects).Error; err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn objects, nil\n}\n\nfunc (p *plan) CreateConfig(ctx context.Context, object *model.Config) (*model.Config, error) {\n\tnow := time.Now()\n\tobject.GmtCreate = now\n\tobject.GmtModified = now\n\n\tif err := p.db.WithContext(ctx).Create(object).Error; err != nil {\n\t\treturn nil, err\n\t}\n\treturn object, nil\n}\n\n// TxCreateConfig creates a config object in the given transaction.\nfunc (p *plan) TxCreateConfig(ctx context.Context, tx *gorm.DB, object *model.Config) error {\n\tnow := time.Now()\n\tobject.GmtCreate = now\n\tobject.GmtModified = now\n\n\treturn tx.WithContext(ctx).Create(object).Error\n}\n\nfunc (p *plan) UpdateConfig(ctx context.Context, cid int64, resourceVersion int64, updates map[string]interface{}) error {\n\t// 系统维护字段\n\tupdates[\"gmt_modified\"] = time.Now()\n\tupdates[\"resource_version\"] = resourceVersion + 1\n\n\tf := p.db.WithContext(ctx).Model(&model.Config{}).Where(\"id = ? and resource_version = ?\", cid, resourceVersion).Updates(updates)\n\tif f.Error != nil {\n\t\treturn f.Error\n\t}\n\tif f.RowsAffected == 0 {\n\t\treturn errors.ErrRecordNotFound\n\t}\n\n\treturn nil\n}\n\nfunc (p *plan) DeleteConfig(ctx context.Context, cid int64) (*model.Config, error) {\n\tobject, err := p.GetConfig(ctx, cid)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err = p.db.WithContext(ctx).Where(\"id = ?\", cid).Delete(&model.Config{}).Error; err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn object, nil\n}\n\nfunc (p *plan) DeleteConfigByPlan(ctx context.Context, planId int64) error {\n\tif err := p.db.WithContext(ctx).Where(\"plan_id = ?\", planId).Delete(&model.Config{}).Error; err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (p *plan) GetConfig(ctx context.Context, cid int64) (*model.Config, error) {\n\tvar object model.Config\n\tif err := p.db.WithContext(ctx).Where(\"id = ?\", cid).First(&object).Error; err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &object, nil\n}\n\nfunc (p *plan) ListConfigs(ctx context.Context, opts ...Options) ([]model.Config, error) {\n\tvar objects []model.Config\n\ttx := p.db.WithContext(ctx)\n\tfor _, opt := range opts {\n\t\ttx = opt(tx)\n\t}\n\tif err := tx.Find(&objects).Error; err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn objects, nil\n}\n\nfunc (p *plan) GetConfigByPlan(ctx context.Context, planId int64) (*model.Config, error) {\n\tvar object model.Config\n\tif err := p.db.WithContext(ctx).Where(\"plan_id = ?\", planId).First(&object).Error; err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &object, nil\n}\n\nfunc (p *plan) CreateTask(ctx context.Context, object *model.Task) (*model.Task, error) {\n\tnow := time.Now()\n\tobject.GmtCreate = now\n\tobject.GmtModified = now\n\n\tif err := p.db.WithContext(ctx).Create(object).Error; err != nil {\n\t\treturn nil, err\n\t}\n\treturn object, nil\n}\n\nfunc (p *plan) UpdateTask(ctx context.Context, pid int64, name string, updates map[string]interface{}) (*model.Task, error) {\n\tf := p.db.WithContext(ctx).Model(&model.Task{}).Where(\"plan_id = ? and name = ?\", pid, name).Updates(updates)\n\tif f.Error != nil {\n\t\treturn nil, f.Error\n\t}\n\tif f.RowsAffected == 0 {\n\t\treturn nil, errors.ErrRecordNotFound\n\t}\n\n\treturn p.GetTaskByName(ctx, pid, name)\n}\n\nfunc (p *plan) DeleteTask(ctx context.Context, pid int64) error {\n\tif err := p.db.WithContext(ctx).Where(\"plan_id = ?\", pid).Delete(&model.Task{}).Error; err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (p *plan) ListTasks(ctx context.Context, pid int64, opts ...Options) ([]model.Task, error) {\n\tvar objects []model.Task\n\ttx := p.db.WithContext(ctx).Where(\"plan_id = ?\", pid)\n\tfor _, opt := range opts {\n\t\ttx = opt(tx)\n\t}\n\tif err := tx.Find(&objects).Error; err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn objects, nil\n}\n\nfunc (p *plan) GetNewestTask(ctx context.Context, pid int64) (*model.Task, error) {\n\tvar objects []model.Task\n\tif err := p.db.WithContext(ctx).Where(\"plan_id = ?\", pid).Order(\"id DESC\").Limit(1).Find(&objects).Error; err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(objects) == 0 {\n\t\treturn nil, errors.ErrRecordNotFound\n\t}\n\treturn &objects[0], nil\n}\n\nfunc (p *plan) GetTaskById(ctx context.Context, taskId int64) (*model.Task, error) {\n\tvar object model.Task\n\tif err := p.db.WithContext(ctx).Where(\"id = ?\", taskId).First(&object).Error; err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &object, nil\n}\n\nfunc (p *plan) GetTaskByName(ctx context.Context, planId int64, name string) (*model.Task, error) {\n\tvar object model.Task\n\tif err := p.db.WithContext(ctx).Where(\"plan_id = ? and name = ?\", planId, name).First(&object).Error; err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &object, nil\n}\n\nfunc newPlan(db *gorm.DB) *plan {\n\treturn &plan{db}\n}\n"
  },
  {
    "path": "pkg/db/repository.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage db\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n\t\"github.com/caoyingjunz/pixiu/pkg/util/errors\"\n\t\"gorm.io/gorm\"\n)\n\ntype RepositoryInterface interface {\n\tCreate(ctx context.Context, object *model.Repository) (*model.Repository, error)\n\tUpdate(ctx context.Context, id int64, resourceVersion int64, updates map[string]interface{}) error\n\tDelete(ctx context.Context, id int64) error\n\tGet(ctx context.Context, id int64) (*model.Repository, error)\n\tGetByName(ctx context.Context, name string) (*model.Repository, error)\n\tList(ctx context.Context) ([]*model.Repository, error)\n}\n\ntype repository struct {\n\tdb *gorm.DB\n}\n\nfunc newRepository(db *gorm.DB) RepositoryInterface {\n\treturn &repository{db}\n}\n\nvar _ RepositoryInterface = &repository{}\n\nfunc (r *repository) Create(ctx context.Context, object *model.Repository) (*model.Repository, error) {\n\tnow := time.Now()\n\tobject.GmtCreate = now\n\tobject.GmtModified = now\n\n\tif err := r.db.WithContext(ctx).Create(object).Error; err != nil {\n\t\treturn nil, err\n\t}\n\treturn object, nil\n}\n\nfunc (r *repository) Update(ctx context.Context, id int64, resourceVersion int64, updates map[string]interface{}) error {\n\tupdates[\"gmt_modified\"] = time.Now()\n\tupdates[\"resource_version\"] = resourceVersion + 1\n\n\tf := r.db.WithContext(ctx).Model(&model.Repository{}).Where(\"id = ? and resource_version = ? \", id, resourceVersion).Updates(updates)\n\tif f.Error != nil {\n\t\treturn f.Error\n\t}\n\n\tif f.RowsAffected == 0 {\n\t\treturn errors.ErrRecordNotFound\n\t}\n\n\treturn nil\n\n}\n\nfunc (r *repository) Delete(ctx context.Context, id int64) error {\n\tf := r.db.WithContext(ctx).Where(\"id = ?\", id).Delete(&model.Repository{})\n\tif f.Error != nil {\n\t\treturn f.Error\n\t}\n\n\tif f.RowsAffected == 0 {\n\t\treturn errors.ErrRecordNotFound\n\t}\n\n\treturn nil\n}\n\nfunc (r *repository) Get(ctx context.Context, id int64) (*model.Repository, error) {\n\tvar repo model.Repository\n\tif err := r.db.WithContext(ctx).Where(\"id = ?\", id).First(&repo).Error; err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &repo, nil\n}\n\nfunc (r *repository) GetByName(ctx context.Context, name string) (*model.Repository, error) {\n\tvar repo model.Repository\n\tif err := r.db.WithContext(ctx).Where(\"name = ?\", name).First(&repo).Error; err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &repo, nil\n}\n\nfunc (r *repository) List(ctx context.Context) ([]*model.Repository, error) {\n\tvar repos []*model.Repository\n\tif err := r.db.WithContext(ctx).Find(&repos).Error; err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn repos, nil\n}\n"
  },
  {
    "path": "pkg/db/tenant.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage db\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"gorm.io/gorm\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n\t\"github.com/caoyingjunz/pixiu/pkg/util/errors\"\n)\n\ntype TenantInterface interface {\n\tCreate(ctx context.Context, object *model.Tenant) (*model.Tenant, error)\n\tUpdate(ctx context.Context, cid int64, resourceVersion int64, updates map[string]interface{}) error\n\tDelete(ctx context.Context, cid int64) (*model.Tenant, error)\n\tGet(ctx context.Context, cid int64) (*model.Tenant, error)\n\tList(ctx context.Context, opts ...Options) ([]model.Tenant, error)\n\n\tGetTenantByName(ctx context.Context, name string) (*model.Tenant, error)\n}\n\ntype tenant struct {\n\tdb *gorm.DB\n}\n\nfunc (t *tenant) Create(ctx context.Context, object *model.Tenant) (*model.Tenant, error) {\n\tnow := time.Now()\n\tobject.GmtCreate = now\n\tobject.GmtModified = now\n\n\tif err := t.db.WithContext(ctx).Create(object).Error; err != nil {\n\t\treturn nil, err\n\t}\n\treturn object, nil\n}\n\nfunc (t *tenant) Update(ctx context.Context, tid int64, resourceVersion int64, updates map[string]interface{}) error {\n\t// 系统维护字段\n\tupdates[\"gmt_modified\"] = time.Now()\n\tupdates[\"resource_version\"] = resourceVersion + 1\n\n\tf := t.db.WithContext(ctx).Model(&model.Tenant{}).Where(\"id = ? and resource_version = ?\", tid, resourceVersion).Updates(updates)\n\tif f.Error != nil {\n\t\treturn f.Error\n\t}\n\n\tif f.RowsAffected == 0 {\n\t\treturn errors.ErrRecordNotFound\n\t}\n\n\treturn nil\n}\n\nfunc (t *tenant) Delete(ctx context.Context, tid int64) (*model.Tenant, error) {\n\tobject, err := t.Get(ctx, tid)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif object == nil {\n\t\treturn nil, nil\n\t}\n\tif err = t.db.WithContext(ctx).Where(\"id = ?\", tid).Delete(&model.Tenant{}).Error; err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn object, nil\n}\n\nfunc (t *tenant) Get(ctx context.Context, tid int64) (*model.Tenant, error) {\n\tvar object model.Tenant\n\tif err := t.db.WithContext(ctx).Where(\"id = ?\", tid).First(&object).Error; err != nil {\n\t\tif errors.IsRecordNotFound(err) {\n\t\t\treturn nil, nil\n\t\t}\n\t\treturn nil, err\n\t}\n\n\treturn &object, nil\n}\n\nfunc (t *tenant) List(ctx context.Context, opts ...Options) ([]model.Tenant, error) {\n\tvar objects []model.Tenant\n\ttx := t.db.WithContext(ctx)\n\tfor _, opt := range opts {\n\t\ttx = opt(tx)\n\t}\n\tif err := tx.Find(&objects).Error; err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn objects, nil\n}\n\nfunc (t *tenant) GetTenantByName(ctx context.Context, name string) (*model.Tenant, error) {\n\tvar object model.Tenant\n\tif err := t.db.WithContext(ctx).Where(\"name = ?\", name).First(&object).Error; err != nil {\n\t\tif errors.IsRecordNotFound(err) {\n\t\t\treturn nil, nil\n\t\t}\n\t\treturn nil, err\n\t}\n\n\treturn &object, nil\n}\n\nfunc newTenant(db *gorm.DB) *tenant {\n\treturn &tenant{db}\n}\n"
  },
  {
    "path": "pkg/db/user.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage db\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"gorm.io/gorm\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n\t\"github.com/caoyingjunz/pixiu/pkg/util/errors\"\n)\n\ntype UserInterface interface {\n\tCreate(ctx context.Context, object *model.User, fns ...func() error) (*model.User, error)\n\tUpdate(ctx context.Context, uid int64, resourceVersion int64, updates map[string]interface{}) error\n\tDelete(ctx context.Context, uid int64) error\n\tGet(ctx context.Context, uid int64) (*model.User, error)\n\tGetRoot(ctx context.Context) (*model.User, error)\n\tList(ctx context.Context, opts ...Options) ([]model.User, error)\n\n\tCount(ctx context.Context, opts ...Options) (int64, error)\n\n\tGetUserByName(ctx context.Context, userName string) (*model.User, error)\n}\n\ntype user struct {\n\tdb *gorm.DB\n}\n\nfunc (u *user) Create(ctx context.Context, object *model.User, fns ...func() error) (*model.User, error) {\n\tnow := time.Now()\n\tobject.GmtCreate = now\n\tobject.GmtModified = now\n\n\tif err := u.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {\n\t\tif err := tx.Create(object).Error; err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, fn := range fns {\n\t\t\tif err := fn(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn object, nil\n}\n\nfunc (u *user) Update(ctx context.Context, uid int64, resourceVersion int64, updates map[string]interface{}) error {\n\t// 系统维护字段\n\tupdates[\"gmt_modified\"] = time.Now()\n\tupdates[\"resource_version\"] = resourceVersion + 1\n\n\tf := u.db.WithContext(ctx).Model(&model.User{}).Where(\"id = ? and resource_version = ?\", uid, resourceVersion).Updates(updates)\n\tif f.Error != nil {\n\t\treturn f.Error\n\t}\n\tif f.RowsAffected == 0 {\n\t\treturn errors.ErrRecordNotUpdate\n\t}\n\treturn nil\n}\n\nfunc (u *user) Delete(ctx context.Context, uid int64) error {\n\treturn u.db.WithContext(ctx).Where(\"id = ?\", uid).Delete(&model.User{}).Error\n}\n\nfunc (u *user) Get(ctx context.Context, uid int64) (*model.User, error) {\n\tvar object model.User\n\tif err := u.db.WithContext(ctx).Where(\"id = ?\", uid).First(&object).Error; err != nil {\n\t\tif errors.IsRecordNotFound(err) {\n\t\t\treturn nil, nil\n\t\t}\n\t\treturn nil, err\n\t}\n\n\treturn &object, nil\n}\n\nfunc (u *user) GetRoot(ctx context.Context) (*model.User, error) {\n\tvar object model.User\n\tif err := u.db.WithContext(ctx).Where(\"role = ?\", model.RoleRoot).First(&object).Error; err != nil {\n\t\tif errors.IsRecordNotFound(err) {\n\t\t\treturn nil, nil\n\t\t}\n\t\treturn nil, err\n\t}\n\n\treturn &object, nil\n}\n\n// List 获取用户列表\n// TODO: 暂时不做分页考虑\nfunc (u *user) List(ctx context.Context, opts ...Options) ([]model.User, error) {\n\tvar objects []model.User\n\ttx := u.db.WithContext(ctx)\n\tfor _, opt := range opts {\n\t\ttx = opt(tx)\n\t}\n\tif err := tx.Find(&objects).Error; err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn objects, nil\n}\n\nfunc (u *user) Count(ctx context.Context, opts ...Options) (int64, error) {\n\tvar total int64\n\ttx := u.db.WithContext(ctx).Model(&model.User{})\n\tfor _, opt := range opts {\n\t\ttx = opt(tx)\n\t}\n\tif err := tx.Count(&total).Error; err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn total, nil\n}\n\nfunc (u *user) GetUserByName(ctx context.Context, userName string) (*model.User, error) {\n\tvar object model.User\n\tif err := u.db.WithContext(ctx).Where(\"name = ?\", userName).First(&object).Error; err != nil {\n\t\tif errors.IsRecordNotFound(err) {\n\t\t\treturn nil, nil\n\t\t}\n\t\treturn nil, err\n\t}\n\n\treturn &object, nil\n}\n\nfunc newUser(db *gorm.DB) *user {\n\treturn &user{db}\n}\n"
  },
  {
    "path": "pkg/jobmanager/audit_cleaner.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage jobmanager\n\nimport (\n\t\"time\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/db\"\n\tlogutil \"github.com/caoyingjunz/pixiu/pkg/util/log\"\n)\n\nconst (\n\tDefaultSchedule     = \"0 0 * * 6\" // 每周六 0 点执行\n\tDefaultDaysReserved = 30          // 保留 30 天的审计日志\n)\n\ntype AuditsCleaner struct {\n\tcfg AuditOptions\n\tdao db.ShareDaoFactory\n}\n\ntype AuditOptions struct {\n\tSchedule     string `yaml:\"schedule\"`\n\tDaysReserved int    `yaml:\"days_reserved\"`\n}\n\nfunc DefaultOptions() AuditOptions {\n\treturn AuditOptions{\n\t\tSchedule:     DefaultSchedule,\n\t\tDaysReserved: DefaultDaysReserved,\n\t}\n}\n\nfunc NewAuditsCleaner(cfg AuditOptions, dao db.ShareDaoFactory) *AuditsCleaner {\n\treturn &AuditsCleaner{\n\t\tcfg: cfg,\n\t\tdao: dao,\n\t}\n}\n\nfunc (ac *AuditsCleaner) Name() string {\n\treturn \"audits-cleaner\"\n}\n\nfunc (ac *AuditsCleaner) CronSpec() string {\n\treturn ac.cfg.Schedule\n}\n\nfunc (ac *AuditsCleaner) LogLevel() logutil.LogLevel {\n\treturn logutil.InfoLevel\n}\n\nfunc (ac *AuditsCleaner) Do(ctx *JobContext) (err error) {\n\tresv := ac.cfg.DaysReserved\n\tbefore := time.Now().AddDate(0, 0, -resv)\n\tentries := map[string]interface{}{\n\t\t\"days_reserved\": resv,\n\t\t\"deadline\":      before,\n\t}\n\tentries[\"records_deleted\"], err = ac.dao.Audit().BatchDelete(ctx, db.WithCreatedBefore(before))\n\tctx.WithLogFields(entries)\n\n\treturn\n}\n\nfunc (a *AuditOptions) Valid() error {\n\t// TODO\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/jobmanager/cluster_syncer.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage jobmanager\n\nimport (\n\t\"context\"\n\t\"sync\"\n\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/klog/v2\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/client\"\n\t\"github.com/caoyingjunz/pixiu/pkg/db\"\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n\tlogutil \"github.com/caoyingjunz/pixiu/pkg/util/log\"\n)\n\nconst (\n\tDefaultSyncInterval = \"@every 5s\"\n)\n\ntype ClusterSyncer struct {\n\tfactory db.ShareDaoFactory\n}\n\nfunc NewClusterSyncer(f db.ShareDaoFactory) *ClusterSyncer {\n\treturn &ClusterSyncer{\n\t\tfactory: f,\n\t}\n}\n\nfunc (cs *ClusterSyncer) Name() string {\n\treturn \"cluster-syncer\"\n}\n\nfunc (cs *ClusterSyncer) CronSpec() string {\n\treturn DefaultSyncInterval\n}\n\nfunc (cs *ClusterSyncer) LogLevel() logutil.LogLevel {\n\treturn logutil.DebugLevel\n}\n\nfunc (cs *ClusterSyncer) Do(ctx *JobContext) (err error) {\n\tclusters, err := cs.factory.Cluster().List(ctx)\n\tif err != nil {\n\t\tklog.Error(\"[ClusterSyncer] failed to get clusters: %v\", err)\n\t\treturn err\n\t}\n\n\tdiff := len(clusters)\n\terrCh := make(chan error, diff)\n\tvar wg sync.WaitGroup\n\twg.Add(diff)\n\tfor _, cluster := range clusters {\n\t\tgo func(c model.Cluster) {\n\t\t\tdefer wg.Done()\n\t\t\tif err = doSync(cs.factory, c); err != nil {\n\t\t\t\terrCh <- err\n\t\t\t}\n\t\t}(cluster)\n\t}\n\twg.Wait()\n\n\tselect {\n\tcase err = <-errCh:\n\t\tif err != nil {\n\t\t\tklog.Error(\"failed to sync cluster status: %v\", err)\n\t\t}\n\tdefault:\n\t}\n\n\treturn nil\n}\n\nfunc doSync(f db.ShareDaoFactory, cluster model.Cluster) error {\n\t// 处理自建集群正在部署的集群\n\tif cluster.ClusterType == model.ClusterTypeCustom {\n\t\t// 自建环境，状态是部署未完成时，则直接不做同步，包含：部署中，等待部署，部署失败\n\t\tif cluster.ClusterStatus == model.ClusterStatusUnStart ||\n\t\t\tcluster.ClusterStatus == model.ClusterStatusDeploy ||\n\t\t\tcluster.ClusterStatus == model.ClusterStatusFailed {\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tvar (\n\t\tkubernetesVersion string\n\t\tnodeData          string\n\t\terr               error\n\t)\n\tstatus := model.ClusterStatusRunning\n\tnodeData, kubernetesVersion, err = getNewestKubeStatus(cluster)\n\tif err != nil {\n\t\tstatus = model.ClusterStatusError\n\t}\n\n\tupdates := make(map[string]interface{})\n\tparseStatus(updates, status, kubernetesVersion, nodeData, cluster)\n\tif len(updates) == 0 {\n\t\treturn nil\n\t}\n\n\tif err = f.Cluster().InternalUpdate(context.TODO(), cluster.Id, updates); err != nil {\n\t\tklog.Error(\"failed to update cluster(%s) status: %v\", cluster.Name, err)\n\t}\n\treturn nil\n}\n\nfunc parseStatus(update map[string]interface{}, status model.ClusterStatus, kubernetesVersion string, nodeData string, cluster model.Cluster) {\n\tif status != cluster.ClusterStatus {\n\t\tupdate[\"status\"] = status\n\t}\n\tif kubernetesVersion != cluster.KubernetesVersion {\n\t\tupdate[\"kubernetes_version\"] = kubernetesVersion\n\t}\n\tif nodeData != cluster.Nodes {\n\t\tupdate[\"nodes\"] = nodeData\n\t}\n}\n\nfunc getNewestKubeStatus(cluster model.Cluster) (string, string, error) {\n\tclusterSet, err := client.NewClusterSet(cluster.KubeConfig)\n\tif err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\n\tnodeList, err := clusterSet.Client.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{})\n\tif err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\tnodes := nodeList.Items\n\n\tkubeNode := &types.KubeNode{Ready: make([]string, 0), NotReady: make([]string, 0)}\n\t// 获取存储状态\n\tfor _, node := range nodes {\n\t\tnodeStatus := parseKubeNodeStatus(&node)\n\t\tswitch nodeStatus {\n\t\tcase \"Ready\":\n\t\t\tkubeNode.Ready = append(kubeNode.Ready, node.Name)\n\t\tcase \"NotReady\":\n\t\t\tkubeNode.NotReady = append(kubeNode.NotReady, node.Name)\n\t\t}\n\t}\n\n\tnodeData, err := kubeNode.Marshal()\n\tif err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\n\tvar kubernetesVersion string\n\tif len(nodes) != 0 {\n\t\tkubernetesVersion = nodes[0].Status.NodeInfo.KubeletVersion\n\t}\n\n\treturn nodeData, kubernetesVersion, nil\n}\n\nfunc parseKubeNodeStatus(node *v1.Node) string {\n\tstatus := \"Ready\"\n\tfor _, condition := range node.Status.Conditions {\n\t\tif condition.Type != v1.NodeReady {\n\t\t\tcontinue\n\t\t}\n\t\tif condition.Status != \"True\" {\n\t\t\tstatus = \"NotReady\"\n\t\t}\n\t}\n\n\treturn status\n}\n"
  },
  {
    "path": "pkg/jobmanager/context.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage jobmanager\n\nimport (\n\t\"context\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/db\"\n\tlogutil \"github.com/caoyingjunz/pixiu/pkg/util/log\"\n)\n\ntype JobContext struct {\n\tcontext.Context\n\t*logutil.Logger\n}\n\nfunc NewJobContext(name string, cfg *logutil.LogOptions) *JobContext {\n\tjc := &JobContext{\n\t\tContext: db.WithDBContext(context.Background()),\n\t\tLogger:  logutil.NewLogger(cfg),\n\t}\n\tjc.WithLogField(\"job\", name)\n\treturn jc\n}\n\nfunc (c *JobContext) Log(level logutil.LogLevel, err error) {\n\tc.Logger.Log(c.Context, level, err)\n}\n"
  },
  {
    "path": "pkg/jobmanager/manager.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage jobmanager\n\nimport (\n\t\"github.com/robfig/cron/v3\"\n\n\tlogutil \"github.com/caoyingjunz/pixiu/pkg/util/log\"\n)\n\ntype Job interface {\n\t// Name returns the job name\n\tName() string\n\n\t// CronSpec returns the cron expression of the job\n\t// e.g. \"* * * * *\"\n\tCronSpec() string\n\n\t// LogLevel returns the log level of the job\n\tLogLevel() logutil.LogLevel\n\n\t// Do is the job handler\n\tDo(ctx *JobContext) error\n}\n\ntype Manager struct {\n\tcron *cron.Cron\n}\n\nfunc NewManager(lc *logutil.LogOptions, jobs ...Job) *Manager {\n\tc := cron.New()\n\tfor _, job := range jobs {\n\t\tc.AddFunc(job.CronSpec(), func() {\n\t\t\tctx := NewJobContext(job.Name(), lc)\n\t\t\tctx.Log(job.LogLevel(), job.Do(ctx))\n\t\t})\n\t}\n\treturn &Manager{\n\t\tc,\n\t}\n}\n\nfunc (m *Manager) Run() {\n\tm.cron.Start()\n}\n\nfunc (m *Manager) Stop() {\n\tctx := m.cron.Stop()\n\t<-ctx.Done()\n}\n"
  },
  {
    "path": "pkg/static/localfile.go",
    "content": "/*\nCopyright 2025 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage static\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"net/http\"\n\t\"os\"\n\t\"path\"\n\t\"strings\"\n)\n\ntype localFileSystem struct {\n\thttp.FileSystem\n\troot    string\n\tindexes bool\n}\n\nfunc LocalFile(root string, indexes bool) *localFileSystem {\n\treturn &localFileSystem{\n\t\tFileSystem: gin.Dir(root, indexes),\n\t\troot:       root,\n\t\tindexes:    indexes,\n\t}\n}\nfunc (l *localFileSystem) Exists(prefix string, file string) bool {\n\tif p := strings.TrimPrefix(file, prefix); len(p) < len(file) {\n\t\tname := path.Join(l.root, path.Clean(p))\n\t\tif strings.Contains(name, \"\\\\\") || strings.Contains(name, \"..\") {\n\t\t\treturn false\n\t\t}\n\t\tstats, err := os.Stat(name)\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\tif stats.IsDir() {\n\t\t\tif !l.indexes {\n\t\t\t\t_, err := os.Stat(path.Join(name, \"index.html\"))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/static/static.go",
    "content": "/*\nCopyright 2025 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage static\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype ServeFileSystem interface {\n\thttp.FileSystem\n\tExists(prefix string, path string) bool\n}\n\nfunc ServeRoot(urlPrefix, root string) gin.HandlerFunc {\n\treturn Serve(urlPrefix, LocalFile(root, false))\n}\n\n// Serve returns a middleware handler that serves static files in the given directory.\nfunc Serve(urlPrefix string, fs ServeFileSystem) gin.HandlerFunc {\n\treturn ServeCached(urlPrefix, fs, 0)\n}\n\n// ServeCached returns a middleware handler that similar as Serve\n// but with the Cache-Control Header set as passed in the cacheAge parameter\nfunc ServeCached(urlPrefix string, fs ServeFileSystem, cacheAge uint) gin.HandlerFunc {\n\tfileserver := http.FileServer(fs)\n\tif urlPrefix != \"\" {\n\t\tfileserver = http.StripPrefix(urlPrefix, fileserver)\n\t}\n\treturn func(c *gin.Context) {\n\t\tif fs.Exists(urlPrefix, c.Request.URL.Path) {\n\t\t\tif cacheAge != 0 {\n\t\t\t\tc.Writer.Header().Add(\"Cache-Control\", fmt.Sprintf(\"max-age=%d\", cacheAge))\n\t\t\t}\n\t\t\tfileserver.ServeHTTP(c.Writer, c.Request)\n\t\t\tc.Abort()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/types/helm.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage types\n\ntype Release struct {\n\tName    string                 `json:\"name\" binding:\"required\"`\n\tChart   string                 `json:\"chart\" binding:\"required\"`\n\tVersion string                 `json:\"version\" binding:\"required\"`\n\tValues  map[string]interface{} `json:\"values\"`\n\tPreview bool                   `json:\"preview\"`\n}\n\ntype RepoId struct {\n\tId int64 `uri:\"id\" binding:\"required\"`\n}\n\ntype RepoName struct {\n\tCluster string `uri:\"cluster\" binding:\"required\"`\n\tName    string `uri:\"name\" binding:\"required\"`\n}\n\ntype RepoURL struct {\n\tUrl string `form:\"url\" binding:\"required\"`\n}\ntype ChartValues struct {\n\tChart   string `form:\"chart\" binding:\"required\"`\n\tVersion string `form:\"version\" binding:\"required\"`\n}\n\ntype ReleaseHistory struct {\n\tVersion int `form:\"version\"`\n}\n\ntype CreateRepository struct {\n\tName     string `json:\"name\" binding:\"required\"`\n\tURL      string `json:\"url\" binding:\"required\"`\n\tUsername string `json:\"username\"`\n\tPassword string `json:\"password\"`\n}\n\ntype UpdateRepository struct {\n\tName            string `json:\"name\" binding:\"required\"`\n\tURL             string `json:\"url\" binding:\"required\"`\n\tUsername        string `json:\"username\"`\n\tPassword        string `json:\"password\"`\n\tResourceVersion *int64 `json:\"resource_version\" binding:\"required\"`\n}\n"
  },
  {
    "path": "pkg/types/meta.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage types\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/gorilla/websocket\"\n\t\"golang.org/x/crypto/ssh\"\n\tappv1 \"k8s.io/api/apps/v1\"\n\t\"k8s.io/api/core/v1\"\n\t\"k8s.io/client-go/tools/remotecommand\"\n\t\"k8s.io/klog/v2\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n)\n\nconst (\n\ttimeLayout = \"2006-01-02 15:04:05.999999999\"\n\n\tMsgData   = '1'\n\tMsgResize = '2'\n)\n\nfunc (c *Cluster) SetId(i int64) {\n\tc.Id = i\n}\n\nfunc (o *KubeObject) SetReplicaSets(replicaSets []appv1.ReplicaSet) {\n\to.lock.Lock()\n\tdefer o.lock.Unlock()\n\n\to.ReplicaSets = replicaSets\n}\n\nfunc (o *KubeObject) GetReplicaSets() []appv1.ReplicaSet {\n\to.lock.Lock()\n\tdefer o.lock.Unlock()\n\n\treturn o.ReplicaSets\n}\n\nfunc (o *KubeObject) SetPods(pods []v1.Pod) {\n\to.lock.Lock()\n\tdefer o.lock.Unlock()\n\n\to.Pods = pods\n}\n\nfunc (o *KubeObject) GetPods() []v1.Pod {\n\to.lock.Lock()\n\tdefer o.lock.Unlock()\n\n\treturn o.Pods\n}\n\nfunc FormatTime(GmtCreate time.Time, GmtModified time.Time) TimeSpec {\n\treturn TimeSpec{\n\t\tGmtCreate:   GmtCreate.Format(timeLayout),\n\t\tGmtModified: GmtModified.Format(timeLayout),\n\t}\n}\n\n// NewTerminalSession 该方法用于升级 http 协议至 websocket，并new一个 TerminalSession 类型的对象返回\nfunc NewTerminalSession(w http.ResponseWriter, r *http.Request) (*TerminalSession, error) {\n\t// 初始化 Upgrader 类型的对象，用于http协议升级为 websocket 协议\n\tupgrader := &websocket.Upgrader{\n\t\tHandshakeTimeout: time.Second * 2,\n\t\t// 检测请求来源\n\t\tCheckOrigin: func(r *http.Request) bool {\n\t\t\treturn true\n\t\t},\n\t\tSubprotocols: []string{r.Header.Get(\"Sec-WebSocket-Protocol\")},\n\t}\n\tconn, err := upgrader.Upgrade(w, r, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := &TerminalSession{\n\t\twsConn:   conn,\n\t\tsizeChan: make(chan remotecommand.TerminalSize),\n\t\tdoneChan: make(chan struct{}),\n\t}\n\n\treturn session, nil\n}\n\n// 用于读取web端的输入，接收web端输入的指令内容\nfunc (t *TerminalSession) Read(p []byte) (int, error) {\n\t_, message, err := t.wsConn.ReadMessage()\n\tif err != nil {\n\t\treturn copy(p, \"\\u0004\"), err\n\t}\n\t// 反序列化\n\tvar msg TerminalMessage\n\tif err = json.Unmarshal(message, &msg); err != nil {\n\t\treturn copy(p, \"\\u0004\"), err\n\t}\n\t// 逻辑判断\n\tswitch msg.Operation {\n\t// 如果是标准输入\n\tcase \"stdin\":\n\t\treturn copy(p, msg.Data), nil\n\t// 窗口调整大小\n\tcase \"resize\":\n\t\tt.sizeChan <- remotecommand.TerminalSize{Width: msg.Cols, Height: msg.Rows}\n\t\treturn 0, nil\n\t// ping\t无内容交互\n\tcase \"ping\":\n\t\treturn 0, nil\n\tdefault:\n\t\treturn copy(p, \"\\u0004\"), fmt.Errorf(\"unknown message type\")\n\t}\n}\n\n// 写数据的方法，拿到 api-server 的返回内容，向web端输出\nfunc (t *TerminalSession) Write(p []byte) (int, error) {\n\tmsg, err := json.Marshal(TerminalMessage{\n\t\tOperation: \"stdout\",\n\t\tData:      string(p),\n\t})\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif err = t.wsConn.WriteMessage(websocket.TextMessage, msg); err != nil {\n\t\treturn 0, err\n\t}\n\treturn len(p), nil\n}\n\n// Done 标记关闭doneChan,关闭后触发退出终端\nfunc (t *TerminalSession) Done() {\n\tclose(t.doneChan)\n}\n\n// Close 用于关闭websocket连接\nfunc (t *TerminalSession) Close() error {\n\treturn t.wsConn.Close()\n}\n\n// Next 获取web端是否resize,以及是否退出终端\nfunc (t *TerminalSession) Next() *remotecommand.TerminalSize {\n\tselect {\n\tcase size := <-t.sizeChan:\n\t\treturn &size\n\tcase <-t.doneChan:\n\t\treturn nil\n\t}\n}\n\nfunc NewTurn(wsConn *websocket.Conn, sshClient *ssh.Client) (*Turn, error) {\n\tsession, err := sshClient.NewSession()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstdinPipe, err := session.StdinPipe()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tturn := &Turn{StdinPipe: stdinPipe, Session: session, WsConn: wsConn}\n\tsession.Stdout = turn\n\tsession.Stderr = turn\n\n\tmodes := ssh.TerminalModes{\n\t\tssh.ECHO:          1,     // disable echo\n\t\tssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud\n\t\tssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud\n\t}\n\tif err = session.RequestPty(\"xterm\", 150, 30, modes); err != nil {\n\t\treturn nil, err\n\t}\n\tif err = session.Shell(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn turn, nil\n}\n\nfunc (t *Turn) Write(p []byte) (n int, err error) {\n\twriter, err := t.WsConn.NextWriter(websocket.BinaryMessage)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tdefer writer.Close()\n\n\treturn writer.Write(p)\n}\n\nfunc (t *Turn) Close() error {\n\tif t.Session != nil {\n\t\tt.Session.Close()\n\t}\n\treturn t.WsConn.Close()\n}\n\nfunc (t *Turn) Read(p []byte) (n int, err error) {\n\tfor {\n\t\tmsgType, reader, err := t.WsConn.NextReader()\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tif msgType != websocket.BinaryMessage {\n\t\t\tcontinue\n\t\t}\n\t\treturn reader.Read(p)\n\t}\n}\n\nfunc (t *Turn) StartLoopRead(ctx context.Context, wg *sync.WaitGroup, logBuff *bytes.Buffer) {\n\tdefer wg.Done()\n\terr := t.loopRead(logBuff, ctx)\n\tif err != nil {\n\t\tklog.Errorf(\"LoopRead exit, err:%s\", err)\n\t}\n}\n\nfunc (t *Turn) loopRead(logBuff *bytes.Buffer, context context.Context) error {\n\tfor {\n\t\tselect {\n\t\tcase <-context.Done():\n\t\t\treturn fmt.Errorf(\"LoopRead exit\")\n\t\tdefault:\n\t\t\t_, wsData, err := t.WsConn.ReadMessage()\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"reading webSocket message err:%s\", err)\n\t\t\t}\n\t\t\tbody := decode(wsData[1:])\n\n\t\t\tswitch wsData[0] {\n\t\t\tcase MsgResize:\n\t\t\t\tif err := t.resizeDo(body); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\tcase MsgData:\n\t\t\t\tif err := t.dataDo(body, logBuff); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (t *Turn) dataDo(body []byte, logBuff *bytes.Buffer) error {\n\tif _, err := t.StdinPipe.Write(body); err != nil {\n\t\treturn fmt.Errorf(\"StdinPipe write err:%s\", err)\n\t}\n\n\tif _, err := logBuff.Write(body); err != nil {\n\t\treturn fmt.Errorf(\"logBuff write err:%s\", err)\n\t}\n\treturn nil\n}\n\ntype Resize struct {\n\tColumns int\n\tRows    int\n}\n\nfunc (t *Turn) resizeDo(body []byte) error {\n\tvar args Resize\n\terr := json.Unmarshal(body, &args)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"ssh pty resize windows err:%s\", err)\n\t}\n\n\tif args.Columns > 0 && args.Rows > 0 {\n\t\tif err := t.Session.WindowChange(args.Rows, args.Columns); err != nil {\n\t\t\treturn fmt.Errorf(\"ssh pty resize windows err:%s\", err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (t *Turn) sessionWait() error {\n\tif err := t.Session.Wait(); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (t *Turn) StartSessionWait(wg *sync.WaitGroup) {\n\tdefer wg.Done()\n\terr := t.sessionWait()\n\tif err != nil {\n\t\tklog.Errorf(\"SessionWait exit, err:%s\", err)\n\t}\n}\n\nfunc decode(p []byte) []byte {\n\tdecodeString, _ := base64.StdEncoding.DecodeString(string(p))\n\treturn decodeString\n}\n\nfunc (a *PlanNodeAuth) Marshal() (string, error) {\n\tdata, err := json.Marshal(a)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn string(data), nil\n}\n\nfunc (a *PlanNodeAuth) Unmarshal(s string) error {\n\tif err := json.Unmarshal([]byte(s), a); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (ks *KubernetesSpec) Marshal() (string, error) {\n\tdata, err := json.Marshal(ks)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn string(data), nil\n}\n\nfunc (ks *KubernetesSpec) Unmarshal(s string) error {\n\tif err := json.Unmarshal([]byte(s), ks); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (ns *NetworkSpec) Marshal() (string, error) {\n\tdata, err := json.Marshal(ns)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn string(data), nil\n}\n\nfunc (ns *NetworkSpec) Unmarshal(s string) error {\n\tif err := json.Unmarshal([]byte(s), ns); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (rs *RuntimeSpec) Marshal() (string, error) {\n\tdata, err := json.Marshal(rs)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn string(data), nil\n}\n\nfunc (rs *RuntimeSpec) Unmarshal(s string) error {\n\tif err := json.Unmarshal([]byte(s), rs); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (cs ComponentSpec) Marshal() (string, error) {\n\tdata, err := json.Marshal(cs)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn string(data), nil\n}\n\nfunc (cs *ComponentSpec) Unmarshal(s string) error {\n\tif err := json.Unmarshal([]byte(s), cs); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (rs *RuntimeSpec) IsDocker() bool {\n\treturn rs.Runtime == string(model.DockerCRI)\n}\n\nfunc (rs *RuntimeSpec) IsContainerd() bool {\n\treturn rs.Runtime == string(model.ContainerdCRI)\n}\n\nfunc (p PageRequest) IsPaged() bool {\n\treturn p.Page != 0 && p.Limit != 0\n}\n\nfunc (p PageRequest) Offset(total int) (int, int, error) {\n\toffset := (p.Page - 1) * p.Limit\n\tif offset > total {\n\t\treturn 0, 0, fmt.Errorf(\"invaild offset\")\n\t}\n\n\tend := offset + p.Limit\n\tif end > total {\n\t\tend = total\n\t}\n\n\treturn offset, end, nil\n}\n\nfunc (node *KubeNode) Marshal() (string, error) {\n\tdata, err := json.Marshal(node)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn string(data), nil\n}\n\nfunc (node *KubeNode) Unmarshal(s string) error {\n\tif err := json.Unmarshal([]byte(s), node); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/types/request.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage types\n\nimport \"github.com/caoyingjunz/pixiu/pkg/db/model\"\n\nconst AllNamespace = \"all_namespaces\"\n\ntype (\n\t// LoginRequest is the request body struct for user login.\n\tLoginRequest struct {\n\t\tName     string `json:\"name\" binding:\"required\"`     // required\n\t\tPassword string `json:\"password\" binding:\"required\"` // required\n\t}\n\n\tCreateUserRequest struct {\n\t\tName        string           `json:\"name\" binding:\"required\"`              // required\n\t\tPassword    string           `json:\"password\" binding:\"required,password\"` // required\n\t\tRole        model.UserRole   `json:\"role\" binding:\"omitempty,oneof=0 1 2\"` // optional\n\t\tStatus      model.UserStatus `json:\"status\" binding:\"omitempty\"`\n\t\tEmail       string           `json:\"email\" binding:\"omitempty,email\"` // optional\n\t\tPhone       string           `json:\"phone\" binding:\"omitempty\"`       // optional\n\t\tDescription string           `json:\"description\" binding:\"omitempty\"` // optional\n\t}\n\n\t// UpdateUserRequest\n\t// !Note: if you want to update description only, email also must be provided with current value\n\tUpdateUserRequest struct {\n\t\tRole            model.UserRole   `json:\"role\" binding:\"omitempty,oneof=0 1 2\"`   // required\n\t\tStatus          model.UserStatus `json:\"status\" binding:\"omitempty,oneof=0 1 2\"` // required\n\t\tEmail           string           `json:\"email\" binding:\"omitempty,email\"`        // optional\n\t\tPhone           string           `json:\"phone\" binding:\"omitempty\"`              // optional\n\t\tDescription     string           `json:\"description\" binding:\"omitempty\"`        // optional\n\t\tResourceVersion *int64           `json:\"resource_version\" binding:\"required\"`    // required\n\t}\n\n\tUpdateUserPasswordRequest struct {\n\t\tNew             string `json:\"new\" binding:\"required,password\"`     // required\n\t\tOld             string `json:\"old\" binding:\"required\"`              // required\n\t\tResourceVersion *int64 `json:\"resource_version\" binding:\"required\"` // required\n\t\tReset           bool   `json:\"reset\"`\n\t}\n\n\tCreateClusterRequest struct {\n\t\tName        string            `json:\"name\" binding:\"omitempty\"`                   // optional\n\t\tAliasName   string            `json:\"alias_name\" binding:\"omitempty\"`             // optional\n\t\tType        model.ClusterType `json:\"cluster_type\" binding:\"omitempty,oneof=0 1\"` // optional\n\t\tKubeConfig  string            `json:\"kube_config\" binding:\"required\"`             // required\n\t\tDescription string            `json:\"description\" binding:\"omitempty\"`            // optional\n\t\tProtected   bool              `json:\"protected\" binding:\"omitempty\"`              // optional\n\t}\n\n\tUpdateClusterRequest struct {\n\t\tAliasName   *string `json:\"alias_name\" binding:\"omitempty\"`  // optional\n\t\tDescription *string `json:\"description\" binding:\"omitempty\"` // optional\n\t\t// TODO: put resource version in a common struct for updating request only\n\t\tResourceVersion *int64 `json:\"resource_version\" binding:\"required\"` // required\n\t}\n\n\tProtectClusterRequest struct {\n\t\tResourceVersion *int64 `json:\"resource_version\" binding:\"required\"` // required\n\t\tProtected       bool   `json:\"protected\" binding:\"omitempty\"`       // optional\n\t}\n\n\tCreateTenantRequest struct {\n\t\tName        string  `json:\"name\" binding:\"required\"`         // required\n\t\tDescription *string `json:\"description\" binding:\"omitempty\"` // optional\n\t}\n\n\tUpdateTenantRequest struct {\n\t\tName            *string `json:\"name\" binding:\"omitempty\"`            // optional\n\t\tDescription     *string `json:\"description\" binding:\"omitempty\"`     // optional\n\t\tResourceVersion *int64  `json:\"resource_version\" binding:\"required\"` // required\n\t}\n\n\tCreatePlanRequest struct {\n\t\tName        string `json:\"name\" binding:\"required\"`         // required\n\t\tDescription string `json:\"description\" binding:\"omitempty\"` // optional\n\n\t\tConfig CreatePlanConfigRequest `json:\"config\"`\n\t\tNodes  []CreatePlanNodeRequest `json:\"nodes\"`\n\t}\n\n\tUpdatePlanRequest struct {\n\t\tName            string `json:\"name\" binding:\"required\"`             // required\n\t\tResourceVersion *int64 `json:\"resource_version\" binding:\"required\"` // required\n\t\tDescription     string `json:\"description\" binding:\"omitempty\"`     // optional\n\n\t\tConfig CreatePlanConfigRequest `json:\"config\"`\n\t\tNodes  []CreatePlanNodeRequest `json:\"nodes\"`\n\t}\n\n\tCreatePlanNodeRequest struct {\n\t\tName   string       `json:\"name\" binding:\"omitempty\"` // required\n\t\tPlanId int64        `json:\"plan_id\"`\n\t\tRole   []string     `json:\"role\"` // k8s 节点的角色，master 和 node\n\t\tCRI    model.CRI    `json:\"cri\"`\n\t\tIp     string       `json:\"ip\"`\n\t\tAuth   PlanNodeAuth `json:\"auth\"`\n\t}\n\n\tUpdatePlanNodeRequest struct {\n\t\tResourceVersion int64        `json:\"resource_version\" binding:\"required\"` // required\n\t\tName            string       `json:\"name\" binding:\"omitempty\"`            // required\n\t\tPlanId          int64        `json:\"plan_id\"`\n\t\tRole            []string     `json:\"role\"` // k8s 节点的角色，master 为 1 和 node 为 0\n\t\tCRI             model.CRI    `json:\"cri\"`\n\t\tIp              string       `json:\"ip\"`\n\t\tAuth            PlanNodeAuth `json:\"auth\"`\n\t}\n\n\tCreatePlanConfigRequest struct {\n\t\tPlanId      int64  `json:\"plan_id\"`\n\t\tRegion      string `json:\"region\"`\n\t\tOSImage     string `json:\"os_image\" binding:\"required\"`     // 操作系统\n\t\tDescription string `json:\"description\" binding:\"omitempty\"` // optional\n\n\t\tKubernetes KubernetesSpec `json:\"kubernetes\"`\n\t\tNetwork    NetworkSpec    `json:\"network\"`\n\t\tRuntime    RuntimeSpec    `json:\"runtime\"`\n\t\tComponent  ComponentSpec  `json:\"component\"` // 支持的扩展组件配置\n\t}\n\n\tUpdatePlanConfigRequest struct {\n\t\t// TODO:\n\t}\n\n\tRBACPolicyRequest struct {\n\t\t// user ID or group name is required\n\t\tUserId     *int64           `json:\"user_id\" binding:\"required_without=GroupName,excluded_with=GroupName\"`\n\t\tGroupName  *string          `json:\"group_name\" binding:\"required_without=UserId,excluded_with=UserId\"`\n\t\tObjectType model.ObjectType `json:\"object_type\" binding:\"required,rbac_object\"`\n\t\tSID        string           `json:\"sid\" binding:\"omitempty,rbac_sid\"`\n\t\tOperation  model.Operation  `json:\"operation\" binding:\"required,rbac_operation\"`\n\t}\n\n\tListRBACPolicyRequest struct {\n\t\tUserId     int64             `form:\"user_id\" binding:\"required\"`\n\t\tObjectType *model.ObjectType `form:\"object_type\" binding:\"omitempty,required_with=UserId,rbac_object\"`\n\t\tSID        *string           `form:\"sid\" binding:\"omitempty,required_with=ObjectType,rbac_sid\"`\n\t\tOperation  *model.Operation  `form:\"operation\" binding:\"omitempty,required_with=SID,rbac_operation\"`\n\t}\n\n\tGroupBindingRequest struct {\n\t\tUserId    int64  `json:\"user_id\" binding:\"required\"`\n\t\tGroupName string `json:\"group_name\" binding:\"required\"`\n\t}\n\n\tListGroupBindingRequest struct {\n\t\tUserId    *int64  `form:\"user_id\" binding:\"omitempty\"`\n\t\tGroupName *string `form:\"group_name\" binding:\"omitempty\"`\n\t}\n\n\t// PageRequest 分页配置\n\tPageRequest struct {\n\t\tPage  int `form:\"page\" json:\"page\"`   // 页数，表示第几页\n\t\tLimit int `form:\"limit\" json:\"limit\"` // 每页数量\n\t}\n\t// QueryOption 搜索配置\n\tQueryOption struct {\n\t\tLabelSelector string `form:\"labelSelector\" json:\"labelSelector\"` // 标签搜索\n\t\tNameSelector  string `form:\"nameSelector\" json:\"nameSelector\"`   // 名称搜索\n\t}\n\n\t// ListClusterRequest 集群列表查询参数\n\tListClusterRequest struct {\n\t\tPageRequest `form:\",inline\"`\n\t\tQueryOption `form:\",inline\"`\n\t\tStatus      *int `form:\"status\" json:\"status\"` // 集群状态过滤，不传则不过滤\n\t}\n\n\t// ListPlanRequest 部署计划列表查询参数\n\tListPlanRequest struct {\n\t\tPageRequest  `form:\",inline\"`\n\t\tNameSelector string `form:\"nameSelector\" json:\"nameSelector\"` // 名称模糊搜索\n\t\tStep         string `form:\"step\" json:\"step\"`                 // 状态过滤，不传则不过滤\n\t}\n\n\t// ListUserRequest 用户列表查询参数\n\tListUserRequest struct {\n\t\tPageRequest `form:\",inline\"`\n\t\tUserName    string `form:\"userName\" json:\"userName\"`\n\t\tUserPhone   string `form:\"userPhone\" json:\"userPhone\"`\n\t\tUserEmail   string `form:\"userEmail\" json:\"userEmail\"`\n\t\tStatus      *int   `form:\"status\" json:\"status\"`\n\t}\n\n\t// WebSSHRequest 主机 ssh 跳转请求\n\tWebSSHRequest struct {\n\t\tHost     string `form:\"host\" json:\"host\" binding:\"required\"`\n\t\tPort     int    `form:\"port\" json:\"port\"`\n\t\tUser     string `form:\"user\" json:\"user\" binding:\"required\"`\n\t\tPassword string `form:\"password\" json:\"password\"`\n\t}\n)\n\ntype (\n\tLoginResponse struct {\n\t\tUserId      int64          `json:\"user_id\"`\n\t\tUserName    string         `json:\"user_name\"`\n\t\tToken       string         `json:\"token\"`\n\t\tRole        model.UserRole `json:\"role\"`\n\t\t*model.User `json:\"-\"`\n\t}\n\n\t// PageResponse 分页查询返回值\n\tPageResponse struct {\n\t\tPageRequest `json:\",inline\"` // 分页请求属性\n\n\t\tTotal int         `json:\"total\"` // 分页总数\n\t\tItems interface{} `json:\"items\"` // 指定页的元素列表\n\t}\n)\n"
  },
  {
    "path": "pkg/types/types.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage types\n\nimport (\n\t\"io\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/gorilla/websocket\"\n\t\"golang.org/x/crypto/ssh\"\n\tappv1 \"k8s.io/api/apps/v1\"\n\tv1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/client-go/tools/remotecommand\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/db/model\"\n)\n\ntype PixiuObjectMeta struct {\n\tCluster   string `uri:\"cluster\" binding:\"required\"`\n\tNamespace string `uri:\"namespace\" binding:\"required\"`\n\tName      string `uri:\"name\"`\n}\n\ntype PixiuMeta struct {\n\t// pixiu 对象 ID\n\tId int64 `json:\"id\"`\n\t// Pixiu 对象版本号\n\tResourceVersion int64 `json:\"resource_version\"`\n}\n\ntype TimeMeta struct {\n\t// pixiu 对象创建时间\n\tGmtCreate time.Time `json:\"gmt_create\"`\n\t// pixiu 对象修改时间\n\tGmtModified time.Time `json:\"gmt_modified\"`\n}\n\ntype KubeNode struct {\n\tReady    []string `json:\"ready\"`\n\tNotReady []string `json:\"not_ready\"`\n}\n\ntype Cluster struct {\n\tPixiuMeta `json:\",inline\"`\n\n\tName      string              `json:\"name\"`\n\tAliasName string              `json:\"alias_name\"`\n\tStatus    model.ClusterStatus `json:\"status\"` // 0: 运行中 1: 部署中 2: 等待部署 3: 部署失败 4: 集群失联，API不可用\n\n\t// 0: 标准集群 1: 自建集群\n\tClusterType model.ClusterType `json:\"cluster_type\"`\n\tPlanId      int64             `json:\"plan_id\"` // 自建集群关联的 PlanId，如果是自建的集群，planId 不为 0\n\n\t// kubernetes 集群的版本和状态\n\tKubernetesVersion string   `json:\"kubernetes_version\"`\n\tNodes             KubeNode `json:\"nodes\"`\n\n\t// 集群删除保护，开启集群删除保护时不允许删除集群\n\t// 0: 关闭集群删除保护 1: 开启集群删除保护\n\tProtected bool `json:\"protected\"`\n\n\t// k8s kubeConfig base64 字段\n\tKubeConfig string `json:\"kube_config,omitempty\"`\n\n\t// 集群用途描述，可以为空\n\tDescription string `json:\"description\"`\n\n\tKubernetesMeta `json:\",inline\"`\n\tTimeMeta       `json:\",inline\"`\n}\n\n// KubernetesMeta 记录 kubernetes 集群的数据\ntype KubernetesMeta struct {\n\t// 集群的版本\n\tKubernetesVersion string `json:\"kubernetes_version,omitempty\"`\n\t// 节点数量\n\tNodes int `json:\"nodes\"`\n\t// The memory and cpu usage\n\tResources Resources `json:\"resources\"`\n}\n\n// Resources kubernetes 的资源信息\n// The memory and cpu usage\ntype Resources struct {\n\tCpu    string `json:\"cpu\"`\n\tMemory string `json:\"memory\"`\n}\n\ntype User struct {\n\tPixiuMeta `json:\",inline\"`\n\n\tName        string           `json:\"name\"`                                 // 用户名称\n\tPassword    string           `json:\"password\" binding:\"required,password\"` // 用户密码\n\tStatus      model.UserStatus `json:\"status\"`                               // 用户状态标识\n\tRole        model.UserRole   `json:\"role\"`                                 // 用户角色，目前只实现管理员，0: 普通用户 1: 管理员 2: 超级管理员\n\tEmail       string           `json:\"email\"`                                // 用户注册邮件\n\tPhone       string           `json:\"phone\"`                                // 用户手机号\n\tDescription string           `json:\"description\"`                          // 用户描述信息\n\n\tTimeMeta `json:\",inline\"`\n}\n\ntype Tenant struct {\n\tPixiuMeta `json:\",inline\"`\n\tTimeMeta  `json:\",inline\"`\n\n\tName        string `json:\"name\"`        // 用户名称\n\tDescription string `json:\"description\"` // 用户描述信息\n}\n\ntype Plan struct {\n\tPixiuMeta `json:\",inline\"`\n\tTimeMeta  `json:\",inline\"`\n\n\tName              string           `json:\"name\"` // 用户名称\n\tStep              model.TaskStatus `json:\"step\"`\n\tDescription       string           `json:\"description\"`        // 用户描述信息\n\tKubernetesVersion string           `json:\"kubernetes_version\"` // k8s 版本\n\tNodeCount         int              `json:\"node_count\"`         // 节点总数\n\n\tConfig PlanConfig `json:\"config\"`\n\tNodes  []PlanNode `json:\"nodes\"`\n}\n\ntype PlanNode struct {\n\tPixiuMeta `json:\",inline\"`\n\tTimeMeta  `json:\",inline\"`\n\n\tName   string       `json:\"name\"` // required\n\tPlanId int64        `json:\"plan_id,omitempty\"`\n\tRole   []string     `json:\"role\"` // k8s 节点的角色，master 和 node\n\tCRI    model.CRI    `json:\"cri\"`\n\tIp     string       `json:\"ip\"`\n\tAuth   PlanNodeAuth `json:\"auth,omitempty\"`\n}\n\ntype Audit struct {\n\tPixiuMeta `json:\",inline\"`\n\tTimeMeta  `json:\",inline\"`\n\n\tIp                string                     `json:\"ip\"`\n\tAction            string                     `json:\"action\"`             // 操作动作\n\tStatus            model.AuditOperationStatus `json:\"status\"`             // 操作状态\n\tOperator          string                     `json:\"operator\"`           // 操作人\n\tPath              string                     `json:\"path\"`               // 操作路径\n\tObjectType        model.ObjectType           `json:\"resource_type\"`      // 资源类型\n\tDuration          int64                      `json:\"duration\"`           // 请求耗时 ms\n\tResponseCode      int                        `json:\"response_code\"`      // HTTP 响应码\n\tCluster           string                     `json:\"cluster\"`            // K8s 集群名\n\tResourceName      string                     `json:\"resource_name\"`      // 资源名称\n\tResourceNamespace string                     `json:\"resource_namespace\"` // 资源命名空间\n}\n\ntype AuthType string\n\nconst (\n\tNoneAuth     AuthType = \"none\"     // 已开启密码\n\tKeyAuth      AuthType = \"key\"      // 密钥\n\tPasswordAuth AuthType = \"password\" // 密码\n)\n\ntype PlanNodeAuth struct {\n\tType     AuthType      `json:\"type\"` // 节点认证模式，支持 key 和 password\n\tKey      *KeySpec      `json:\"key,omitempty\"`\n\tPassword *PasswordSpec `json:\"password,omitempty\"`\n}\n\ntype PlanTask struct {\n\tPixiuMeta `json:\",inline\"`\n\tTimeMeta  `json:\",inline\"`\n\n\tName    string           `json:\"name\"`\n\tPlanId  int64            `json:\"plan_id\" binding:\"required\"`\n\tStatus  model.TaskStatus `json:\"status\"`\n\tMessage string           `json:\"message\"`\n}\n\ntype KeySpec struct {\n\tData string `json:\"data,omitempty\"`\n\tFile string `json:\"-\"`\n}\n\ntype PasswordSpec struct {\n\tUser     string `json:\"user,omitempty\"`\n\tPassword string `json:\"password,omitempty\"`\n}\n\ntype PlanConfig struct {\n\tPixiuMeta `json:\",inline\"`\n\tTimeMeta  `json:\",inline\"`\n\n\tPlanId     int64          `json:\"plan_id,omitempty\"` // required\n\tRegion     string         `json:\"region\"`\n\tOSImage    string         `json:\"os_image\"` // 操作系统\n\tKubernetes KubernetesSpec `json:\"kubernetes\"`\n\tNetwork    NetworkSpec    `json:\"network\"`\n\tRuntime    RuntimeSpec    `json:\"runtime\"`\n\tComponent  ComponentSpec  `json:\"component\"` // 支持的扩展组件配置\n\n}\n\n// TimeSpec 通用时间规格\ntype TimeSpec struct {\n\tGmtCreate   interface{} `json:\"gmt_create,omitempty\"`\n\tGmtModified interface{} `json:\"gmt_modified,omitempty\"`\n}\n\ntype KubeObject struct {\n\tlock sync.RWMutex\n\n\tReplicaSets []appv1.ReplicaSet\n\tPods        []v1.Pod\n}\n\n// WebShellOptions ws API 参数定义\ntype WebShellOptions struct {\n\tCluster   string `form:\"cluster\"`\n\tNamespace string `form:\"namespace\"`\n\tPod       string `form:\"pod\"`\n\tContainer string `form:\"container\"`\n\tCommand   string `form:\"command\"`\n}\n\n// TerminalMessage 定义了终端和容器 shell 交互内容的格式 Operation 是操作类型\n// Data 是具体数据内容 Rows和Cols 可以理解为终端的行数和列数，也就是宽、高\ntype TerminalMessage struct {\n\tOperation string `json:\"operation\"`\n\tData      string `json:\"data\"`\n\tRows      uint16 `json:\"rows\"`\n\tCols      uint16 `json:\"cols\"`\n}\n\n// TerminalSession 定义 TerminalSession 结构体，实现 PtyHandler 接口\n// wsConn 是 websocket 连接\n// sizeChan 用来定义终端输入和输出的宽和高\n// doneChan 用于标记退出终端\ntype TerminalSession struct {\n\twsConn   *websocket.Conn\n\tsizeChan chan remotecommand.TerminalSize\n\tdoneChan chan struct{}\n}\n\ntype Turn struct {\n\tStdinPipe io.WriteCloser\n\tSession   *ssh.Session\n\tWsConn    *websocket.Conn\n}\n\n// ListOptions is the query options to a standard REST list call.\ntype ListOptions struct {\n\tCount bool  `form:\"count\"`\n\tLimit int64 `form:\"limit\"`\n\n\tPageRequest `json:\",inline\"` // 分页请求属性\n\tQueryOption `json:\",inline\"` // 搜索内容\n}\n\ntype EventOptions struct {\n\tUid        string `form:\"uid\"`\n\tNamespace  string `form:\"namespace\"`\n\tName       string `form:\"name\"`\n\tKind       string `form:\"kind\"`\n\tNamespaced bool   `form:\"namespaced\"`\n\tLimit      int64  `form:\"limit\"`\n}\n\ntype PodLogOptions struct {\n\tContainer string `form:\"container\"`\n\tTailLines int64  `form:\"tailLines\"`\n}\n\ntype KubernetesSpec struct {\n\tEnablePublicIp    bool   `json:\"enable_public_ip\"`\n\tApiServer         string `json:\"api_server\"`\n\tApiPort           string `json:\"api_port\"`\n\tKubernetesVersion string `json:\"kubernetes_version\"`\n\tEnableHA          bool   `json:\"enable_ha\"`\n\tRegister          bool   `json:\"register\"`\n}\n\ntype NetworkSpec struct {\n\tNetworkInterface string `json:\"network_interface\"` // 网口，默认 eth0\n\tCni              string `json:\"cni\"`\n\tPodNetwork       string `json:\"pod_network\"`\n\tServiceNetwork   string `json:\"service_network\"`\n\tKubeProxy        string `json:\"kube_proxy\"`\n}\n\ntype RuntimeSpec struct {\n\tRuntime string `json:\"runtime\"`\n}\n\ntype ComponentSpec struct {\n\tHelm       *Helm       `json:\"helm,omitempty\"` // 忽略，则使用默认值\n\tPrometheus *Prometheus `json:\"prometheus,omitempty\"`\n\tGrafana    *Grafana    `json:\"grafana,omitempty\"`\n\tHaproxy    *Haproxy    `json:\"haproxy,omitempty\"`\n}\n\ntype Helm struct {\n\tEnable      bool   `json:\"enable\"`\n\tHelmRelease string `json:\"helm_release\"`\n}\n\ntype Prometheus struct {\n\tEnablePrometheus string `json:\"enable_prometheus\"`\n\tEnable           bool   `json:\"enable\"`\n}\n\ntype Grafana struct {\n\tEnable               bool   `json:\"enable\"`\n\tGrafanaAdminUser     string `json:\"grafana_admin_user\"`\n\tGrafanaAdminPassword string `json:\"grafana_admin_password\"`\n}\n\n// Haproxy Options\n// This configuration is usually enabled when self-created VMs require high availability.\ntype Haproxy struct {\n\tEnable                    bool   `json:\"enable\"`                       // Enable haproxy and keepalived,\n\tKeepalivedVirtualRouterId string `json:\"keepalived_virtual_router_id\"` // Arbitrary unique number from 0..255\n}\n\ntype RBACPolicy struct {\n\tUserName   string           `json:\"username,omitempty\"`\n\tGroupName  string           `json:\"groupname,omitempty\"`\n\tObjectType model.ObjectType `json:\"resource_type,omitempty\"`\n\tStringID   string           `json:\"sid,omitempty\"`\n\tOperation  model.Operation  `json:\"operation,omitempty\"`\n}\n\n// AuditListOptions 审计列表查询选项，支持过滤\ntype AuditListOptions struct {\n\tListOptions `json:\",inline\"`\n\tOperator    string `form:\"operator\"`    // 模糊匹配操作人\n\tAction      string `form:\"action\"`      // 精确匹配 HTTP 方法（POST/PUT/DELETE/PATCH）\n\tObjectType  string `form:\"object_type\"` // 资源类型\n\tCluster     string `form:\"cluster\"`     // 集群名称\n\tStatus      *uint8 `form:\"status\"`      // 操作状态（0:失败 1:成功 2:未知）\n\tStartTime   string `form:\"start_time\"`  // 时间范围起（RFC3339，留空忽略）\n\tEndTime     string `form:\"end_time\"`    // 时间范围止（RFC3339，留空忽略）\n}\n"
  },
  {
    "path": "pkg/util/container/container.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/docker/docker/api/types\"\n\t\"github.com/docker/docker/api/types/container\"\n\t\"github.com/docker/docker/api/types/network\"\n\t\"github.com/docker/docker/client\"\n\t\"k8s.io/klog/v2\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/util/errors\"\n)\n\ntype Container struct {\n\tclient *client.Client\n\taction string\n\tname   string\n\tplanId int64\n\tdir    string\n}\n\nfunc NewContainer(action string, planId int64, dir string) (*Container, error) {\n\tclient, err := client.NewClientWithOpts(client.FromEnv)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &Container{\n\t\tclient: client,\n\t\taction: action,\n\t\tname:   fmt.Sprintf(\"%s-%d\", action, planId),\n\t\tplanId: planId,\n\t\tdir:    dir}, nil\n}\n\n// StartAndWaitForContainer 创建，启动容器，并等待容器退出\nfunc (c *Container) StartAndWaitForContainer(ctx context.Context, image string) error {\n\t// 已经存在，则先删除运行的容器\n\tif err := c.ClearContainer(ctx); err != nil {\n\t\treturn err\n\t}\n\n\tconfig := &container.Config{\n\t\tLabels: map[string]string{\n\t\t\t\"author\":    \"caoyingjunz\",\n\t\t\t\"pixiuName\": c.name,\n\t\t},\n\t\tImage: image,\n\t\tEnv:   []string{fmt.Sprintf(\"COMMAND=%s\", c.action)},\n\t}\n\thostConfig := &container.HostConfig{\n\t\tBinds: []string{fmt.Sprintf(\"%s/%d:/configs\", c.dir, c.planId)},\n\t}\n\tnetConfig := &network.NetworkingConfig{}\n\tresp, err := c.client.ContainerCreate(ctx, config, hostConfig, netConfig, nil, c.name)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// 启动容器\n\tif err = c.client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {\n\t\treturn err\n\t}\n\t// 等待容器运行完成退出\n\treturn c.WaitContainer(ctx, resp.ID, 180)\n}\n\nfunc (c *Container) Close() error {\n\treturn c.client.Close()\n}\n\n// ClearContainer 清理已存在的老容器\nfunc (c *Container) ClearContainer(ctx context.Context) error {\n\told, err := c.GetContainer(ctx, c.name)\n\tif err != nil {\n\t\t// 如果不存在则直接返回\n\t\tif err == errors.ErrContainerNotFound {\n\t\t\treturn nil\n\t\t}\n\t\treturn err\n\t}\n\n\tcontainerId := old.ID\n\ttimeout := 5 * time.Second\n\tif err = c.client.ContainerStop(ctx, containerId, &timeout); err != nil {\n\t\treturn err\n\t}\n\n\treturn c.client.ContainerRemove(ctx, containerId, types.ContainerRemoveOptions{Force: true})\n}\n\nfunc (c *Container) ListContainers(ctx context.Context) ([]types.Container, error) {\n\tcs, err := c.client.ContainerList(ctx, types.ContainerListOptions{All: true})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn cs, nil\n}\n\nfunc (c *Container) GetContainer(ctx context.Context, containerName string) (*types.Container, error) {\n\tcontainers, err := c.ListContainers(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, container := range containers {\n\t\tfor _, name := range container.Names {\n\t\t\tif name == \"/\"+containerName {\n\t\t\t\treturn &container, nil\n\t\t\t}\n\t\t}\n\t}\n\treturn nil, errors.ErrContainerNotFound\n}\n\n// WaitContainer\n// 等待容器运行退出\n// 官方的客户端实现有问题，先通过探针的方式规避，后续优化\n// 循环检查容器状态，直到出现异常或符合预期\nfunc (c *Container) WaitContainer(ctx context.Context, containerId string, times int) error {\n\t//_, errCh := c.client.ContainerWait(ctx, resp.ID, container.WaitConditionNextExit)\n\t//if err = <-errCh; err != nil {\n\t//\tfmt.Println(\"结束\", err)\n\t//\n\t//\treturn err\n\t//}\n\n\tfor i := 0; i < times; i++ {\n\t\tklog.Infof(\"waiting for container at %d times\", i+1)\n\t\t// 先等待 5s 再执行，开始等待符合业务场景，且后续的逻辑处理不受影响\n\t\ttime.Sleep(5 * time.Second)\n\n\t\t// 实际开始检查\n\t\tcontainerInfo, err := c.client.ContainerInspect(ctx, containerId)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif containerInfo.State != nil {\n\t\t\t// Can be one of \"created\", \"running\", \"paused\", \"restarting\", \"removing\", \"exited\", or \"dead\"\n\t\t\tstate := containerInfo.State\n\t\t\t// 容器还在运行，等待下一次检查\n\t\t\tif state.Status == \"running\" && state.Running {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// 状态异常，直接退出\n\t\t\tif state.Status == \"paused\" || state.Status == \"removing\" || state.Status == \"dead\" {\n\t\t\t\treturn fmt.Errorf(\"容器状态异常(%s)，退出等待\", state.Status)\n\t\t\t}\n\n\t\t\t// 容器已经退出\n\t\t\tif state.Status == \"exited\" {\n\t\t\t\tif state.ExitCode == 0 {\n\t\t\t\t\t// 正常退出\n\t\t\t\t\treturn nil\n\t\t\t\t} else {\n\t\t\t\t\t// 异常退出返回错误信息\n\t\t\t\t\treturn fmt.Errorf(state.Error)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 其他状态，继续等待\n\t\t}\n\t}\n\n\treturn fmt.Errorf(\"等待容器(%s)运行完成超时\", containerId)\n}\n\nfunc (c *Container) WatchContainerLog(ctx context.Context, containerId, since string) (io.ReadCloser, error) {\n\treturn c.client.ContainerLogs(ctx, containerId, types.ContainerLogsOptions{\n\t\tShowStdout: true,\n\t\tShowStderr: true,\n\t\tFollow:     true,\n\t\tTimestamps: false,\n\t})\n}\n"
  },
  {
    "path": "pkg/util/errors/errors.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage errors\n\nimport (\n\t\"errors\"\n\n\t\"gorm.io/gorm\"\n\n\t\"github.com/go-sql-driver/mysql\"\n)\n\nvar (\n\tErrRecordNotFound     = gorm.ErrRecordNotFound\n\tErrRecordNotUpdate    = errors.New(\"record not updated\")\n\tErrBusySystem         = errors.New(\"系统繁忙，请稍后再试\")\n\tErrReqParams          = errors.New(\"请求参数错误\")\n\tErrCloudNotRegister   = errors.New(\"cloud 集群未注册\")\n\tErrUserNotFound       = errors.New(\"用户不存在\")\n\tErrNotAcceptable      = errors.New(\"有任务正在执行，请稍后再试\")\n\tErrClusterNotFound    = errors.New(\"集群不存在\")\n\tErrUserPassword       = errors.New(\"密码错误\")\n\tErrInternal           = errors.New(\"服务器内部错误\")\n\tErrTenantNotFound     = errors.New(\"租户不存在\")\n\tErrDuplicatedPassword = errors.New(\"新密码与旧密码相同\")\n\tErrAuditNotFound      = errors.New(\"审计记录不存在\")\n\n\tErrContainerNotFound = errors.New(\"容器不存在\")\n\n\tParamsError         = errors.New(\"参数错误\")\n\tOperateFailed       = errors.New(\"操作失败\")\n\tNoPermission        = errors.New(\"无权限\")\n\tInnerError          = errors.New(\"内部错误\")\n\tNoUserIdError       = errors.New(\"请登录\")\n\tUserExistError      = errors.New(\"用户已存在\")\n\tRoleExistError      = errors.New(\"角色已存在\")\n\tRoleNotExistError   = errors.New(\"角色不存在\")\n\tPolicyExistError    = errors.New(\"策略已存在\")\n\tPolicyNotExistError = errors.New(\"策略不存在\")\n\tTenantExistError    = errors.New(\"租户已存在\")\n\tErrAuditExists      = errors.New(\"审计记录已存在\")\n\n\tErrRootAlreadyExists = errors.New(\"超级管理员已存在\")\n)\n\nfunc IsRecordNotFound(err error) bool {\n\treturn errors.Is(err, gorm.ErrRecordNotFound)\n}\n\nfunc IsNotUpdated(err error) bool {\n\treturn errors.Is(err, ErrRecordNotUpdate)\n}\n\nfunc IsUniqueConstraintError(err error) bool {\n\tmysqlErr, ok := err.(*mysql.MySQLError)\n\tif !ok {\n\t\treturn false\n\t}\n\n\t// 数据库的 1062 错误码为固定的主键冲突号\n\treturn mysqlErr.Number == 1062\n}\n"
  },
  {
    "path": "pkg/util/log/log.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage log\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\tklog \"github.com/sirupsen/logrus\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/db\"\n)\n\nvar once sync.Once\n\ntype LogFormat string\n\nconst (\n\tLogFormatJson LogFormat = \"json\"\n\tLogFormatText LogFormat = \"text\"\n)\n\nvar ErrInvalidLogFormat = errors.New(\"invalid log format\")\n\ntype LogLevel = klog.Level\n\n// Providing 3 log levels now.\nconst (\n\tErrorLevel LogLevel = klog.ErrorLevel\n\tInfoLevel  LogLevel = klog.InfoLevel\n\tDebugLevel LogLevel = klog.DebugLevel\n)\n\ntype LogOptions struct {\n\tLogFormat `yaml:\"log_format\"`\n\tLogSQL    bool `yaml:\"log_sql\"`\n\tLogLevel  `yaml:\"log_level\"`\n}\n\n// DefaultLogOptions returns the default configs.\nfunc DefaultLogOptions() *LogOptions {\n\treturn &LogOptions{\n\t\tLogFormat: LogFormatJson,\n\t\tLogSQL:    false,\n\t\tLogLevel:  InfoLevel,\n\t}\n}\n\nfunc (o *LogOptions) Valid() error {\n\tswitch o.LogFormat {\n\tcase LogFormatJson, LogFormatText:\n\t\treturn nil\n\tdefault:\n\t\treturn ErrInvalidLogFormat\n\t}\n}\n\n// Init sets the log format only once.\nfunc (o *LogOptions) Init() {\n\tonce.Do(func() {\n\t\tklog.SetLevel(o.LogLevel)\n\t\tswitch o.LogFormat {\n\t\tcase LogFormatJson:\n\t\t\tklog.SetFormatter(&klog.JSONFormatter{\n\t\t\t\tTimestampFormat: time.RFC3339Nano,\n\t\t\t})\n\t\tdefault:\n\t\t\tklog.SetFormatter(&klog.TextFormatter{\n\t\t\t\tFullTimestamp:   true,\n\t\t\t\tTimestampFormat: time.RFC3339Nano,\n\t\t\t})\n\t\t}\n\t})\n}\n\nconst (\n\tSuccessMsg = \"SUCCESS\"\n\tErrorMsg   = \"ERROR\"\n\tFailMsg    = \"FAIL\"\n)\n\ntype Logger struct {\n\tstartTime time.Time\n\tlogSQL    bool\n\tlogEntry  *klog.Entry\n}\n\nfunc NewLogger(cfg *LogOptions) *Logger {\n\treturn &Logger{\n\t\tstartTime: time.Now(),\n\t\tlogSQL:    cfg.LogSQL,\n\t\tlogEntry:  klog.NewEntry(klog.StandardLogger()),\n\t}\n}\n\nfunc (l *Logger) WithLogField(key string, value interface{}) {\n\tl.logEntry = l.logEntry.WithField(key, value)\n}\n\nfunc (l *Logger) WithLogFields(fields map[string]interface{}) {\n\tl.logEntry = l.logEntry.WithFields(fields)\n}\n\nfunc (l *Logger) Log(ctx context.Context, level LogLevel, err error) {\n\tfields := make(map[string]interface{})\n\tif l.logSQL {\n\t\tif sqls := db.GetSQLs(ctx); len(sqls) > 0 {\n\t\t\tfields[\"sqls\"] = sqls\n\t\t}\n\t}\n\tfields[\"latency\"] = fmt.Sprintf(\"%dµs\", time.Since(l.startTime).Microseconds())\n\n\tif err != nil {\n\t\tfields[\"error\"] = err\n\t\tl.logEntry.WithFields(fields).Error(FailMsg)\n\t\treturn\n\t}\n\n\tswitch level {\n\tcase DebugLevel:\n\t\tl.logEntry.WithFields(fields).Debug(SuccessMsg)\n\tcase InfoLevel:\n\t\tl.logEntry.WithFields(fields).Info(SuccessMsg)\n\t}\n}\n"
  },
  {
    "path": "pkg/util/lru/lru.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage lru\n\nimport (\n\t\"container/list\"\n\t\"sync\"\n)\n\ntype LRUCache struct {\n\tcap       int\n\tevictList *list.List\n\titems     map[interface{}]*list.Element\n\n\tmu sync.RWMutex\n}\n\ntype entry struct {\n\tkey   interface{}\n\tvalue interface{}\n}\n\nfunc NewLRUCache(cap int) *LRUCache {\n\treturn &LRUCache{\n\t\tcap:       cap,\n\t\tevictList: list.New(),\n\t\titems:     make(map[interface{}]*list.Element),\n\t}\n}\n\nfunc (c *LRUCache) Contains(key interface{}) bool {\n\t_, exists := c.items[key]\n\treturn exists\n}\n\nfunc (c *LRUCache) Add(key, value interface{}) {\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\n\t// TODO: 重复代码优化\n\tif ent, ok := c.items[key]; ok {\n\t\t// 当前元素存在, 覆盖当前 value, 并移动到 list 头部\n\t\tent.Value.(*entry).value = value\n\t\tc.evictList.MoveToFront(ent)\n\t} else {\n\t\t// 当前元素不存在, 并移动到 list 头部\n\t\tc.items[key] = c.evictList.PushFront(&entry{key, value})\n\t}\n\n\t// 超出 LRUCache 的容量, 删除 list 尾部元素\n\tif c.evictList.Len() > c.cap {\n\t\tlastElement := c.evictList.Back()\n\t\tc.evictList.Remove(lastElement)\n\t\tdelete(c.items, lastElement.Value.(*entry).key)\n\t}\n}\n\nfunc (c *LRUCache) Get(key interface{}) (value interface{}) {\n\tc.mu.RLock()\n\tdefer c.mu.RUnlock()\n\n\tif ent, ok := c.items[key]; ok {\n\t\tvalue = ent.Value.(*entry).value\n\t\tc.evictList.MoveToFront(ent)\n\t}\n\treturn\n}\n\nfunc (c *LRUCache) Len() int { return c.evictList.Len() }\n"
  },
  {
    "path": "pkg/util/ssh/ssh.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage ssh\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"golang.org/x/crypto/ssh\"\n\n\t\"github.com/caoyingjunz/pixiu/pkg/types\"\n)\n\nfunc NewSSHClient(sshConfig *types.WebSSHRequest) (*ssh.Client, error) {\n\t// TODO：利用 gin 的解析，直接设置默认值\n\tport := sshConfig.Port\n\tif port == 0 {\n\t\tport = 22\n\t}\n\n\t// TODO 补充支持 PrivateKey 场景\n\treturn ssh.Dial(\"tcp\", fmt.Sprintf(\"%s:%d\", sshConfig.Host, port), &ssh.ClientConfig{\n\t\tTimeout:         time.Second * 5,\n\t\tUser:            sshConfig.User,\n\t\tAuth:            []ssh.AuthMethod{ssh.Password(sshConfig.Password)},\n\t\tHostKeyCallback: ssh.InsecureIgnoreHostKey(), // 忽略 know_hosts 检查\n\t})\n}\n"
  },
  {
    "path": "pkg/util/token/token.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage token\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/golang-jwt/jwt/v4\"\n)\n\ntype Claims struct {\n\tjwt.RegisteredClaims\n\n\tId   int64  `json:\"id\"`\n\tName string `json:\"name\"`\n\tRole string `json:\"role\"`\n}\n\n// GenerateToken 生成 token\nfunc GenerateToken(uid int64, name string, jwtKey []byte) (string, error) {\n\t// Generate jwt, 临时有效期 360 分钟\n\tnowTime := time.Now()\n\texpiresTime := nowTime.Add(360 * time.Minute)\n\tclaims := &Claims{\n\t\tRegisteredClaims: jwt.RegisteredClaims{\n\t\t\tExpiresAt: jwt.NewNumericDate(expiresTime), // 过期时间\n\t\t\tIssuedAt:  jwt.NewNumericDate(nowTime),     // 签发时间\n\t\t\tNotBefore: jwt.NewNumericDate(nowTime),     // 生效时间\n\t\t},\n\t\tId:   uid,\n\t\tName: name,\n\t}\n\n\ttoken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)\n\treturn token.SignedString(jwtKey)\n}\n\nfunc ParseToken(tokenStr string, jwtKey []byte) (*Claims, error) {\n\ttoken, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(t *jwt.Token) (interface{}, error) {\n\t\treturn jwtKey, nil\n\t})\n\tif err != nil {\n\t\tif ve, ok := err.(*jwt.ValidationError); ok {\n\t\t\tif ve.Errors == jwt.ValidationErrorExpired {\n\t\t\t\treturn nil, fmt.Errorf(\"登录已过期，请重新登录\")\n\t\t\t}\n\t\t}\n\t\treturn nil, err\n\t}\n\n\tif claims, ok := token.Claims.(*Claims); ok && token.Valid {\n\t\treturn claims, nil\n\t}\n\n\treturn nil, fmt.Errorf(\"failed to parse token\")\n}\n"
  },
  {
    "path": "pkg/util/util.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage util\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net/http\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/gorilla/websocket\"\n\t\"golang.org/x/crypto/bcrypt\"\n)\n\nfunc init() {\n\trand.Seed(time.Now().UnixNano())\n}\n\n// EncryptUserPassword 生成加密密码\n// 前端传的密码为明文，需要加密存储\n// TODO: 后续确认是否有必要在前端加密\nfunc EncryptUserPassword(origin string) (string, error) {\n\tpwd, err := bcrypt.GenerateFromPassword([]byte(origin), bcrypt.DefaultCost)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn string(pwd), nil\n}\n\n// ValidateUserPassword 验证用户的密码是否正确\nfunc ValidateUserPassword(old, new string) error {\n\treturn bcrypt.CompareHashAndPassword([]byte(old), []byte(new))\n}\n\n// ValidateStrongPassword validates the password is strong enough.\nfunc ValidateStrongPassword(password string) bool {\n\tif len(password) < 8 {\n\t\treturn false\n\t}\n\n\tvar oneUpper bool\n\tvar oneLower bool\n\tvar oneNumber bool\n\tfor _, l := range password {\n\t\tif oneUpper && oneLower && oneNumber {\n\t\t\treturn true\n\t\t}\n\n\t\tif !oneUpper && l >= 'A' && l <= 'Z' {\n\t\t\toneUpper = true\n\t\t\tcontinue\n\t\t}\n\t\tif !oneLower && l >= 'a' && l <= 'z' {\n\t\t\toneLower = true\n\t\t\tcontinue\n\t\t}\n\t\tif !oneNumber && l >= '0' && l <= '9' {\n\t\t\toneNumber = true\n\t\t\tcontinue\n\t\t}\n\t}\n\treturn oneUpper && oneLower && oneNumber\n}\n\n// GenerateRequestID return a request ID string with random suffix.\nfunc GenerateRequestID() string {\n\treturn fmt.Sprintf(\"%s-%06d\", time.Now().Format(\"20060102150405\"), rand.Intn(1000000))\n}\n\nfunc IsEmptyS(s string) bool {\n\treturn len(s) != 0\n}\n\nfunc IsDirectoryExists(path string) bool {\n\tstat, err := os.Stat(path)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tif stat.IsDir() {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc IsFileExists(path string) bool {\n\tstat, err := os.Stat(path)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tif stat.IsDir() {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc EnsureDirectoryExists(path string) error {\n\tif !IsDirectoryExists(path) {\n\t\tif err := os.MkdirAll(path, 0755); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc WriteToFile(filename string, data []byte) error {\n\treturn os.WriteFile(filename, data, 0600)\n}\n\nfunc BuildWebSocketConnection(w http.ResponseWriter, r *http.Request) (*websocket.Conn, error) {\n\tupgrader := websocket.Upgrader{\n\t\tReadBufferSize:  1024,\n\t\tWriteBufferSize: 1024,\n\t\tCheckOrigin: func(r *http.Request) bool {\n\t\t\treturn true\n\t\t}}\n\tupgrader.Subprotocols = []string{r.Header.Get(\"Sec-WebSocket-Protocol\")}\n\treturn upgrader.Upgrade(w, r, nil)\n}\n\n// DeduplicateIntSlice returns a new slice with duplicated elements removed.\nfunc DeduplicateIntSlice(s []int64) (ret []int64) {\n\tret = make([]int64, 0)\n\tm := make(map[int64]struct{})\n\tfor _, v := range s {\n\t\tif _, ok := m[v]; ok {\n\t\t\tcontinue\n\t\t}\n\t\tm[v] = struct{}{}\n\t\tret = append(ret, v)\n\t}\n\n\treturn\n}\n\n// More returns the larger one.\nfunc More(a, b int) int {\n\tif a > b {\n\t\treturn a\n\t}\n\treturn b\n}\n\n// Less returns the smaller one.\nfunc Less(a, b int) int {\n\tif a < b {\n\t\treturn a\n\t}\n\treturn b\n}\n"
  },
  {
    "path": "pkg/util/util_test.go",
    "content": "/*\nCopyright 2024 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage util\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestValidateStrongPassword(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tpassword string\n\t\twant     bool\n\t}{\n\t\t{\n\t\t\tname:     \"case1\",\n\t\t\tpassword: \"123456\",\n\t\t\twant:     false,\n\t\t},\n\t\t{\n\t\t\tname:     \"case2\",\n\t\t\tpassword: \"12345678\",\n\t\t\twant:     false,\n\t\t},\n\t\t{\n\t\t\tname:     \"case3\",\n\t\t\tpassword: \"12345678a\",\n\t\t\twant:     false,\n\t\t},\n\t\t{\n\t\t\tname:     \"case4\",\n\t\t\tpassword: \"12345678A\",\n\t\t\twant:     false,\n\t\t},\n\t\t{\n\t\t\tname:     \"case5\",\n\t\t\tpassword: \"12345678aA\",\n\t\t\twant:     true,\n\t\t},\n\t\t{\n\t\t\tname:     \"case6\",\n\t\t\tpassword: \"123456Aa\",\n\t\t\twant:     true,\n\t\t},\n\t\t{\n\t\t\tname:     \"case7\",\n\t\t\tpassword: \"abcdefgh\",\n\t\t\twant:     false,\n\t\t},\n\t\t{\n\t\t\tname:     \"case8\",\n\t\t\tpassword: \"ABCDEFGH\",\n\t\t\twant:     false,\n\t\t},\n\t\t{\n\t\t\tname:     \"case9\",\n\t\t\tpassword: \"abcdef12\",\n\t\t\twant:     false,\n\t\t},\n\t\t{\n\t\t\tname:     \"case10\",\n\t\t\tpassword: \"ABCDEF12\",\n\t\t\twant:     false,\n\t\t},\n\t\t{\n\t\t\tname:     \"case11\",\n\t\t\tpassword: \"Abcdef12\",\n\t\t\twant:     true,\n\t\t},\n\t\t{\n\t\t\tname:     \"case12\",\n\t\t\tpassword: \"aBCDEF12\",\n\t\t\twant:     true,\n\t\t},\n\t\t{\n\t\t\tname:     \"case13\",\n\t\t\tpassword: \"$$$$$$$$\",\n\t\t\twant:     false,\n\t\t},\n\t\t{\n\t\t\tname:     \"case14\",\n\t\t\tpassword: \"$$$$$aA1\",\n\t\t\twant:     true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := ValidateStrongPassword(tt.password); got != tt.want {\n\t\t\t\tt.Errorf(\"ValidateStrongPassword() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDeduplicateIntSlice(t *testing.T) {\n\ttype args struct {\n\t\ts []int64\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant []int64\n\t}{\n\t\t{\n\t\t\tname: \"case 1\",\n\t\t\targs: args{\n\t\t\t\ts: []int64{1, 2, 3, 4, 5},\n\t\t\t},\n\t\t\twant: []int64{1, 2, 3, 4, 5},\n\t\t},\n\t\t{\n\t\t\tname: \"case 2\",\n\t\t\targs: args{\n\t\t\t\ts: []int64{1, 1, 1, 1, 1},\n\t\t\t},\n\t\t\twant: []int64{1},\n\t\t},\n\t\t{\n\t\t\tname: \"case 3\",\n\t\t\targs: args{\n\t\t\t\ts: []int64{1, 1, 2, 3, 4, 5},\n\t\t\t},\n\t\t\twant: []int64{1, 2, 3, 4, 5},\n\t\t},\n\t\t{\n\t\t\tname: \"case 4\",\n\t\t\targs: args{\n\t\t\t\ts: []int64{1, 1, 2, 1, 3, 4, 5},\n\t\t\t},\n\t\t\twant: []int64{1, 2, 3, 4, 5},\n\t\t},\n\t\t{\n\t\t\tname: \"case 5\",\n\t\t\targs: args{\n\t\t\t\ts: []int64{1, 2, 3, 4, 5, 5, 5, 5},\n\t\t\t},\n\t\t\twant: []int64{1, 2, 3, 4, 5},\n\t\t},\n\t\t{\n\t\t\tname: \"case 6\",\n\t\t\targs: args{\n\t\t\t\ts: []int64{5, 1, 4, 2, 3, 3, 2, 4, 1, 5},\n\t\t\t},\n\t\t\twant: []int64{5, 1, 4, 2, 3},\n\t\t},\n\t\t{\n\t\t\tname: \"case 7\",\n\t\t\targs: args{\n\t\t\t\ts: []int64{1, 1, 1, 2, 3, 3, 4, 4, 5, 5, 5},\n\t\t\t},\n\t\t\twant: []int64{1, 2, 3, 4, 5},\n\t\t},\n\t\t{\n\t\t\tname: \"case 8\",\n\t\t\targs: args{\n\t\t\t\ts: []int64{1, 2, 3, 4, 5, 5, 5, 5, 4, 3, 2, 1},\n\t\t\t},\n\t\t\twant: []int64{1, 2, 3, 4, 5},\n\t\t},\n\t\t{\n\t\t\tname: \"case 9\",\n\t\t\targs: args{\n\t\t\t\ts: []int64{1, 2, 3, 4, 5, 5, 5, 5, 4, 3, 2, 1, 1, 1},\n\t\t\t},\n\t\t\twant: []int64{1, 2, 3, 4, 5},\n\t\t},\n\t\t{\n\t\t\tname: \"case 10\",\n\t\t\targs: args{\n\t\t\t\ts: []int64{1, 2, 3, 4, 5, 5, 5, 5, 4, 3, 2, 1, 1, 1, 2, 3, 4, 5},\n\t\t\t},\n\t\t\twant: []int64{1, 2, 3, 4, 5},\n\t\t},\n\t\t{\n\t\t\tname: \"case 11\",\n\t\t\targs: args{\n\t\t\t\ts: []int64{},\n\t\t\t},\n\t\t\twant: []int64{},\n\t\t},\n\t\t{\n\t\t\tname: \"case 12\",\n\t\t\targs: args{\n\t\t\t\ts: nil,\n\t\t\t},\n\t\t\twant: []int64{},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := DeduplicateIntSlice(tt.args.s); !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"DeduplicateIntSlice() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/util/uuid/uuid.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage uuid\n\nimport (\n\t\"math/rand\"\n\n\t\"github.com/google/uuid\"\n)\n\nconst (\n\tnamePrefix = \"pixiu-\"\n)\n\nfunc NewUUID() string {\n\treturn uuid.New().String()\n}\n\nfunc NewRandName(length int) string {\n\tchars := []rune(\"abcdefghijklmnopqrstuvwxyz\")\n\n\trs := make([]rune, length)\n\tfor i := 0; i < length; i++ {\n\t\trs[i] = chars[rand.Intn(len(chars))]\n\t}\n\n\treturn namePrefix + string(rs)\n}\n"
  },
  {
    "path": "template/globals.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (phe \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage template\n\nconst GlobalsTemplate = `# Render below by Pixiu\n---\n{{- if .Kubernetes.EnableHA }}\nenable_kubernetes_ha: \"yes\"\n{{- end }}\n\n{{- if .Kubernetes.EnablePublicIp }}\nkube_vip_address: \"{{ .Kubernetes.ApiServer }}\"\n{{- end }}\n\n{{- if .Kubernetes.ApiPort }}\nkube_vip_port: \"{{ .Kubernetes.ApiPort }}\"\n{{- end }}\n\nkube_release: {{ .Kubernetes.KubernetesVersion }}\n\ncluster_cidr: \"{{ .Network.PodNetwork }}\"\nservice_cidr: \"{{ .Network.ServiceNetwork }}\"\n\nnetwork_interface: \"{{ .Network.NetworkInterface }}\"\n\n{{- if and .Component.Haproxy .Component.Haproxy.Enable }}\nenable_haproxy: \"yes\"\n{{- if .Component.Haproxy.KeepalivedVirtualRouterId }}\nkeepalived_virtual_router_id: \"{{ .Component.Haproxy.KeepalivedVirtualRouterId }}\"\n{{- end }}\n{{- end }}\n\n{{- if eq .Network.Cni \"calico\" }}\nenable_calico: \"yes\"\n{{- end }}\n\nenable_nfs: \"no\"\n`\n"
  },
  {
    "path": "template/hosts.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (phe \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage template\n\nconst HostTemplate = `# Render below by Pixiu engine\n127.0.0.1\tlocalhost\n::1         localhost localhost.localdomain localhost6 localhost6.localdomain6\n{{- range .Nodes }}\n{{ .Ip }}  {{ .Name }}\n{{- end }}\n`\n"
  },
  {
    "path": "template/multinode.go",
    "content": "/*\nCopyright 2021 The Pixiu Authors.\n\nLicensed under the Apache License, Version 2.0 (phe \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage template\n\nconst MultiModeTemplate = `# Render below by Pixiu engine\n[docker-master]\n{{- range .DockerMaster }}\n{{- if eq .Auth.Type \"password\" }}\n{{ .Name }} ansible_ssh_user={{ .Auth.Password.User }} ansible_ssh_pass={{ .Auth.Password.Password }}\n{{- end }}\n{{- if eq .Auth.Type \"key\" }}\n{{ .Name }} ansible_ssh_user=root ansible_ssh_private_key_file={{ .Auth.Key.File }}\n{{- end }}\n{{- end }}\n\n[docker-node]\n{{- range .DockerNode }}\n{{- if eq .Auth.Type \"password\" }}\n{{ .Name }} ansible_ssh_user={{ .Auth.Password.User }} ansible_ssh_pass={{ .Auth.Password.Password }}\n{{- end }}\n{{- if eq .Auth.Type \"key\" }}\n{{ .Name }} ansible_ssh_user=root ansible_ssh_private_key_file={{ .Auth.Key.File }}\n{{- end }}\n{{- end }}\n\n[containerd-master]\n{{- range .ContainerdMaster }}\n{{- if eq .Auth.Type \"password\" }}\n{{ .Name }} ansible_ssh_user={{ .Auth.Password.User }} ansible_ssh_pass={{ .Auth.Password.Password }}\n{{- end }}\n{{- if eq .Auth.Type \"key\" }}\n{{ .Name }} ansible_ssh_user=root ansible_ssh_private_key_file={{ .Auth.Key.File }}\n{{- end }}\n{{- end }}\n\n[containerd-node]\n{{- range .ContainerdNode }}\n{{- if eq .Auth.Type \"password\" }}\n{{ .Name }} ansible_ssh_user={{ .Auth.Password.User }} ansible_ssh_pass={{ .Auth.Password.Password }}\n{{- end }}\n{{- if eq .Auth.Type \"key\" }}\n{{ .Name }} ansible_ssh_user=root ansible_ssh_private_key_file={{ .Auth.Key.File }}\n{{- end }}\n{{- end }}\n\n[storage]\n\n# Don't change the bellow groups\n[kube-master:children]\ndocker-master\ncontainerd-master\n\n[kube-node:children]\ndocker-node\ncontainerd-node\n\n[baremetal:children]\nkube-master\nkube-node\nstorage\n\n[kubernetes:children]\nkube-master\nkube-node\n\n[nfs-server:children]\nstorage\n\n[haproxy:children]\nkube-master\n`\n"
  }
]