Repository: caoyingjunz/gopixiu
Branch: master
Commit: 5fd5f7d45c3f
Files: 158
Total size: 746.1 KB
Directory structure:
gitextract__nbx67ym/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── workflows/
│ ├── build-webshell-image.yml
│ ├── ci.yml
│ └── docker-image.yml
├── .gitignore
├── Makefile
├── README.md
├── api/
│ ├── docs/
│ │ ├── docs.go
│ │ ├── swagger.json
│ │ └── swagger.yaml
│ └── server/
│ ├── errors/
│ │ └── errors.go
│ ├── httpstatus/
│ │ └── status.go
│ ├── httputils/
│ │ ├── docs.go
│ │ ├── httputils.go
│ │ └── httputils_test.go
│ ├── middleware/
│ │ ├── admission.go
│ │ ├── audit.go
│ │ ├── authentication.go
│ │ ├── authorization.go
│ │ ├── cors.go
│ │ ├── limiter.go
│ │ ├── log.go
│ │ └── middleware.go
│ ├── router/
│ │ ├── audit/
│ │ │ ├── audit.go
│ │ │ └── audit_routes.go
│ │ ├── auth/
│ │ │ ├── auth.go
│ │ │ └── auth_routes.go
│ │ ├── cluster/
│ │ │ ├── cluster.go
│ │ │ ├── cluster_routes.go
│ │ │ ├── helper.go
│ │ │ ├── informer.go
│ │ │ ├── proxy.go
│ │ │ └── ws.go
│ │ ├── helm/
│ │ │ ├── helm.go
│ │ │ ├── release_routes.go
│ │ │ └── respository_routes.go
│ │ ├── plan/
│ │ │ ├── config_routes.go
│ │ │ ├── node_routes.go
│ │ │ ├── plan.go
│ │ │ ├── plan_routes.go
│ │ │ └── task_routes.go
│ │ ├── proxy/
│ │ │ ├── helper.go
│ │ │ └── proxy.go
│ │ ├── router.go
│ │ ├── static/
│ │ │ └── index.html
│ │ ├── tenant/
│ │ │ ├── tenant.go
│ │ │ └── tenant_routes.go
│ │ └── user/
│ │ ├── user.go
│ │ └── user_routes.go
│ └── validator/
│ ├── helper.go
│ ├── password.go
│ ├── rbac.go
│ └── validator.go
├── cmd/
│ ├── app/
│ │ ├── config/
│ │ │ └── config.go
│ │ ├── options/
│ │ │ └── options.go
│ │ └── server.go
│ └── pixiuserver.go
├── config.yaml
├── deploy/
│ ├── README.md
│ └── pixiu/
│ ├── .helmignore
│ ├── Chart.yaml
│ ├── templates/
│ │ ├── NOTES.txt
│ │ ├── _helpers.tpl
│ │ ├── configmap.yaml
│ │ ├── deployment.yaml
│ │ ├── hpa.yaml
│ │ ├── ingress.yaml
│ │ ├── service.yaml
│ │ └── serviceaccount.yaml
│ └── values.yaml
├── docker/
│ ├── Dockerfile
│ ├── Dockerfile-toolbox
│ └── start.sh
├── docs/
│ ├── OWNERS
│ ├── README.md
│ ├── apis.md
│ └── sql.md
├── go.mod
├── go.sum
├── hack/
│ ├── tools/
│ │ └── licfmt/
│ │ └── licfmt.go
│ ├── update-gofmt.sh
│ ├── update-image.sh
│ └── verify-gofmt.sh
├── install.md
├── pkg/
│ ├── client/
│ │ ├── cache.go
│ │ ├── client.go
│ │ ├── helm.go
│ │ ├── task.go
│ │ ├── token_cache.go
│ │ └── user_cache.go
│ ├── controller/
│ │ ├── audit/
│ │ │ └── audit.go
│ │ ├── auth/
│ │ │ └── auth.go
│ │ ├── cluster/
│ │ │ ├── cluster.go
│ │ │ ├── informer.go
│ │ │ ├── util.go
│ │ │ └── ws.go
│ │ ├── controller.go
│ │ ├── helm/
│ │ │ ├── helm.go
│ │ │ ├── releases.go
│ │ │ └── repository.go
│ │ ├── plan/
│ │ │ ├── bootstrap_servers.go
│ │ │ ├── checker.go
│ │ │ ├── deploy.go
│ │ │ ├── plan.go
│ │ │ ├── plan_config.go
│ │ │ ├── plan_node.go
│ │ │ ├── plan_task.go
│ │ │ ├── register.go
│ │ │ ├── render.go
│ │ │ └── worker.go
│ │ ├── tenant/
│ │ │ └── tenant.go
│ │ ├── user/
│ │ │ ├── user.go
│ │ │ └── user_test.go
│ │ └── util/
│ │ └── util.go
│ ├── db/
│ │ ├── audit.go
│ │ ├── cluster.go
│ │ ├── factory.go
│ │ ├── logger.go
│ │ ├── migrator.go
│ │ ├── model/
│ │ │ ├── audit.go
│ │ │ ├── cluster.go
│ │ │ ├── model.go
│ │ │ ├── pixiu/
│ │ │ │ └── model.go
│ │ │ ├── plan.go
│ │ │ ├── rbac.go
│ │ │ ├── rbac_test.go
│ │ │ ├── repository.go
│ │ │ ├── tenant.go
│ │ │ └── user.go
│ │ ├── options.go
│ │ ├── plan.go
│ │ ├── repository.go
│ │ ├── tenant.go
│ │ └── user.go
│ ├── jobmanager/
│ │ ├── audit_cleaner.go
│ │ ├── cluster_syncer.go
│ │ ├── context.go
│ │ └── manager.go
│ ├── static/
│ │ ├── localfile.go
│ │ └── static.go
│ ├── types/
│ │ ├── helm.go
│ │ ├── meta.go
│ │ ├── request.go
│ │ └── types.go
│ └── util/
│ ├── container/
│ │ └── container.go
│ ├── errors/
│ │ └── errors.go
│ ├── log/
│ │ └── log.go
│ ├── lru/
│ │ └── lru.go
│ ├── ssh/
│ │ └── ssh.go
│ ├── token/
│ │ └── token.go
│ ├── util.go
│ ├── util_test.go
│ └── uuid/
│ └── uuid.go
└── template/
├── globals.go
├── hosts.go
└── multinode.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
labels: ["kind/bug"]
about: Create a report to help us improve
---
**Describe the Bug**
A clear and concise description of what the bug is.
For UI issues please also add a screenshot that shows the issue.
**Versions Used**
KubeSphere:
Kubernetes: (If KubeSphere installer used, you can skip this)
**Environment**
How many nodes and their hardware configuration:
For example: CentOS 7.5 / 3 masters: 8cpu/8g; 3 nodes: 8cpu/16g
(and other info are welcomed to help us debugging)
**How To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature Request
about: Have a good idea? Please don't hesitate to write it down, describe the new feature.
---
**What's it about?**
**What's the reason why we need it?**
I believe this is an important feature for Pixiu. There're a few use cases:
* case one
* case two
* ...
Please leave your comments below if there's anyone agrees with me. Or just give me a thumb up.
**Area Suggestion**
/kind feature-request
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
#### What type of PR is this?
#### What this PR does / why we need it:
#### Which issue(s) this PR fixes:
Fixes #
#### Special notes for your reviewer:
#### Does this PR introduce a user-facing change?
```release-note
```
#### Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.:
```docs
```
================================================
FILE: .github/workflows/build-webshell-image.yml
================================================
name: Publish webshell image
on:
push:
branches:
- "master"
paths:
- 'docker/Dockerfile-toolbox'
- 'Makefile'
env:
K8S_VERSION: v1.23.6 # the same as Makefile
HELM_VERSION: v3.7.1 # the same as Makefile
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Get short commit hash
run: echo "COMMIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login in dockerhub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_NAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push webshell image
uses: docker/build-push-action@v5
with:
context: .
file: ./docker/Dockerfile-toolbox
platforms: linux/amd64,linux/arm64
build-args: |
K8S_VERSION=${{ env.K8S_VERSION }}
HELM_VERSION=${{ env.HELM_VERSION }}
push: true
tags: |
${{ secrets.DOCKER_NAME }}/pixiu-webshell:latest
${{ secrets.DOCKER_NAME }}/pixiu-webshell:v0.1
${{ secrets.DOCKER_NAME }}/pixiu-webshell:${{ env.COMMIT_HASH }}
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
push:
branches:
- master
pull_request:
branches:
- master
env:
GO_VERSION: '1.17.5'
jobs:
# markdown-lint:
# runs-on: ubuntu-latest
# container: node:14
# steps:
# - name: Checkout
# uses: actions/checkout@v4
#
# - name: Install mdl
# run: apt update && apt-get install ruby-full -y && gem install chef-utils -v 16.6.14 && gem install mdl -v 0.11.0
#
# - name: Lint markdown files
# 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
golang-lint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: true
- name: Set up Golang
uses: actions/setup-go@v2
with:
go-version: ${{ env.GO_VERSION }}
- name: Run go fmt test
run: hack/verify-gofmt.sh
env:
GO111MODULE: auto
- name: Build the pixiu binariy
run: go build -v ./...
- name: Run pixiu unit test
run: go test -v ./...
================================================
FILE: .github/workflows/docker-image.yml
================================================
name: Publish pixiu image
on:
push:
branches:
- "master"
paths-ignore:
- 'docs/**'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Get short commit hash
run: |
echo "COMMIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
echo "TIMESTAMP=$(date +%Y%m%d%H%M%S)" >> $GITHUB_ENV
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# - name: Login harbor
# uses: docker/login-action@v3
# with:
# registry: crpi-0ecikjs9ylb2hqyo.cn-hangzhou.personal.cr.aliyuncs.com
# username: ${{ secrets.HARBOR_USERNAME }}
# password: ${{ secrets.HARBOR_PASSWORD }}
# - name: Build and push the pixiu image
# uses: docker/build-push-action@v5
# with:
# context: .
# file: ./docker/Dockerfile
# platforms: linux/amd64,linux/arm64
# build-args: |
# VERSION=${{ env.COMMIT_HASH }}-${{ env.TIMESTAMP }}
# push: true
# tags: |
# crpi-0ecikjs9ylb2hqyo.cn-hangzhou.personal.cr.aliyuncs.com/pixiu-public/pixiu:latest
# crpi-0ecikjs9ylb2hqyo.cn-hangzhou.personal.cr.aliyuncs.com/pixiu-public/pixiu:v1.0.1
# crpi-0ecikjs9ylb2hqyo.cn-hangzhou.personal.cr.aliyuncs.com/pixiu-public/pixiu:${{ env.COMMIT_HASH }}
================================================
FILE: .gitignore
================================================
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
bin
testbin/*
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Kubernetes Generated files - skip generated files, except for vendored files
!vendor/**/zz_generated.*
# editor and IDE paraphernalia
.idea
.vscode
.gitcommits
*.swp
*.swo
# output
dist
# licensefmt
hack/tools/licfmt/licfmt
vendor
================================================
FILE: Makefile
================================================
.PHONY: run build image push clean
tag = v0.1
releaseName = pixiu
dockerhubUser ?= jacky06
k8sVersion ?= v1.23.6
helmVersion ?= v3.7.1
targetDir ?= dist
commitHash = $(shell git rev-parse --short HEAD)
# e.g. 1862ce5-20240203180617
version = $(commitHash)-$(shell date +%Y%m%d%H%M%S)
ALL: run
run: build
./pixiu --configfile ./config.yaml
build:
go build -o $(targetDir)/$(releaseName) -ldflags "-X 'main.version=$(version)'" ./cmd/
image:
docker build -t $(dockerhubUser)/$(releaseName):$(tag) --build-arg VERSION=$(version) .
push: image
docker push $(dockerhubUser)/$(releaseName):$(tag)
webshell-image:
docker build --build-arg K8S_VERSION=$(k8sVersion) \
--build-arg HELM_VERSION=$(helmVersion) \
-t $(dockerhubUser)/pixiu-webshell:$(tag) -f docker/Dockerfile .
push-webshell-image: webshell-image
docker push $(dockerhubUser)/pixiu-webshell:$(tag)
licfmt:
go run hack/tools/licfmt/licfmt.go -v ./*
clean:
-rm -f ./$(releaseName)
.PHONY: api-docs
api-docs: ## generate the api docs
swag init --generalInfo ./cmd/pixiuserver.go --output ./api/docs
================================================
FILE: README.md
================================================
# Pixiu Overview
Pixiu is an open source container platform for cloud-native application management.
![Build Status][build-url]
[![Release][release-image]][release-url]
[![License][license-image]][license-url]
## 安装手册
- [安装手册](install.md)
## 页面展示
### 首页
### 部署计划
- 创建部署计划
```text
通过新建部署计划,可以实现通过页面 `点点点` 的方式创建 `kubernetes` 集群, 如同各大云厂商一样
```

- 新建节点
```text
1. 添加各个节点的信息,节点的角色,用户名,密码等
2. 各大组件支持高度的自定义,例如:calico,fannel
2. kubernetes 版本自主选择
```

- 部署详情
```text
可以看到部署计划在每个部署的运行状态,以及详细日志
```

### 集群管理
- 集群概览
```text
cpu状态,内存状态,集群的基本信息,网络信息,集群服务
```

- 集群管理

- 集群工作负载deployment

- 集群工作负载pod

### 审计功能
- 审计管理

## 学习分享
- [go-learning](https://github.com/caoyingjunz/go-learning)
## 沟通交流
- 搜索微信号 `yingjuncz`, 备注(pixiu), 验证通过会加入群聊
- [bilibili](https://space.bilibili.com/3493104248162809?spm_id_from=333.1007.0.0) 技术分享
Copyright 2019 caoyingjun (cao.yingjunz@gmail.com) Apache License 2.0
[build-url]: https://github.com/caoyingjunz/pixiu/actions/workflows/ci.yml/badge.svg
[release-image]: https://img.shields.io/badge/release-download-orange.svg
[release-url]: https://www.apache.org/licenses/LICENSE-2.0.html
[license-image]: https://img.shields.io/badge/license-Apache%202-4EB1BA.svg
[license-url]: https://www.apache.org/licenses/LICENSE-2.0.html
================================================
FILE: api/docs/docs.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package docs GENERATED BY SWAG; DO NOT EDIT
// This file was generated by swaggo/swag
package docs
import "github.com/swaggo/swag"
const docTemplate = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"termsOfService": "https://github.com/caoyingjunz/pixiu",
"contact": {
"name": "API Support",
"url": "https://github.com/caoyingjunz/pixiu",
"email": "support@pixiu.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/pixiu/clusters": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "List clusters",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Clusters"
],
"summary": "List clusters",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"allOf": [
{
"$ref": "#/definitions/httputils.Response"
},
{
"type": "object",
"properties": {
"result": {
"type": "array",
"items": {
"$ref": "#/definitions/types.Cluster"
}
}
}
}
]
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
}
}
}
},
"/pixiu/clusters/": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "Create by a json cluster",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Clusters"
],
"summary": "Create a cluster",
"parameters": [
{
"description": "Create cluster",
"name": "cluster",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/types.Cluster"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
}
}
}
},
"/pixiu/clusters/{clusterId}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "Get by cloud cluster ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Clusters"
],
"summary": "Get Cluster by clusterId",
"parameters": [
{
"type": "integer",
"description": "Cluster ID",
"name": "clusterId",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/httputils.Response"
},
{
"type": "object",
"properties": {
"result": {
"$ref": "#/definitions/types.Cluster"
}
}
}
]
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
}
}
},
"put": {
"security": [
{
"Bearer": []
}
],
"description": "Update by json cluster",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Clusters"
],
"summary": "Update an cluster",
"parameters": [
{
"type": "integer",
"description": "Cluster ID",
"name": "clusterId",
"in": "path",
"required": true
},
{
"description": "Update cluster",
"name": "cluster",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/types.Cluster"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
}
}
},
"delete": {
"security": [
{
"Bearer": []
}
],
"description": "Delete by cloud cluster ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Clusters"
],
"summary": "Delete cluster by clusterId",
"parameters": [
{
"type": "integer",
"description": "Cluster ID",
"name": "clusterId",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
}
}
}
},
"/pixiu/clusters/{clusterId}/ping": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "Do ping",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Clusters"
],
"summary": "Ping cluster",
"parameters": [
{
"type": "integer",
"description": "Cluster ID",
"name": "clusterId",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/httputils.Response"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
}
}
}
},
"/pixiu/users": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "List users",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Users"
],
"summary": "List users",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"allOf": [
{
"$ref": "#/definitions/httputils.Response"
},
{
"type": "object",
"properties": {
"result": {
"type": "array",
"items": {
"$ref": "#/definitions/types.User"
}
}
}
}
]
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
}
}
}
},
"/pixiu/users/": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "Create by a json user",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Users"
],
"summary": "Create a user",
"parameters": [
{
"description": "Create user",
"name": "user",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/types.User"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
}
}
}
},
"/pixiu/users/login": {
"post": {
"description": "Login by a json user",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Login"
],
"summary": "User login",
"parameters": [
{
"description": "User login",
"name": "user",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/types.User"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
}
}
}
},
"/pixiu/users/{userId}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "Get by user ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Users"
],
"summary": "Get user by userId",
"parameters": [
{
"type": "integer",
"description": "User ID",
"name": "userId",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/httputils.Response"
},
{
"type": "object",
"properties": {
"result": {
"$ref": "#/definitions/types.User"
}
}
}
]
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
}
}
},
"put": {
"security": [
{
"Bearer": []
}
],
"description": "Update by json user",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Users"
],
"summary": "Update an user",
"parameters": [
{
"type": "integer",
"description": "User ID",
"name": "userId",
"in": "path",
"required": true
},
{
"description": "Update user",
"name": "user",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/types.User"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
}
}
},
"delete": {
"security": [
{
"Bearer": []
}
],
"description": "Delete by userID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Users"
],
"summary": "Delete user by userId",
"parameters": [
{
"type": "integer",
"description": "User ID",
"name": "userId",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
}
}
}
}
},
"definitions": {
"httputils.Response": {
"type": "object",
"properties": {
"code": {
"description": "返回的状态码",
"type": "integer"
},
"message": {
"description": "异常返回时的错误信息",
"type": "string"
},
"result": {
"description": "正常返回时的数据,可以为任意数据结构"
}
}
},
"types.Cluster": {
"type": "object",
"properties": {
"alias_name": {
"type": "string"
},
"cluster_type": {
"description": "0:标准集群 1: 自建集群",
"type": "integer"
},
"description": {
"description": "集群用途描述,可以为空",
"type": "string"
},
"gmt_create": {
"description": "pixiu 对象创建时间",
"type": "string"
},
"gmt_modified": {
"description": "pixiu 对象修改时间",
"type": "string"
},
"id": {
"description": "pixiu 对象 ID",
"type": "integer"
},
"kube_config": {
"description": "k8s kubeConfig base64 字段",
"type": "string"
},
"kubernetes_version": {
"description": "集群的版本",
"type": "string"
},
"name": {
"type": "string"
},
"nodes": {
"description": "节点数量",
"type": "integer"
},
"resource_version": {
"description": "Pixiu 对象版本号",
"type": "integer"
},
"resources": {
"description": "The memory and cpu usage",
"$ref": "#/definitions/types.Resources"
}
}
},
"types.Resources": {
"type": "object",
"properties": {
"cpu": {
"type": "string"
},
"memory": {
"type": "string"
}
}
},
"types.User": {
"type": "object",
"properties": {
"description": {
"description": "用户描述信息",
"type": "string"
},
"email": {
"description": "用户注册邮件",
"type": "string"
},
"gmt_create": {
"description": "pixiu 对象创建时间",
"type": "string"
},
"gmt_modified": {
"description": "pixiu 对象修改时间",
"type": "string"
},
"id": {
"description": "pixiu 对象 ID",
"type": "integer"
},
"name": {
"description": "用户名称",
"type": "string"
},
"password": {
"description": "用户密码",
"type": "string"
},
"resource_version": {
"description": "Pixiu 对象版本号",
"type": "integer"
},
"role": {
"description": "用户角色,目前只实现管理员,0: 普通用户 1: 管理员 2: 超级管理员",
"type": "string"
},
"status": {
"description": "用户状态标识",
"type": "integer"
}
}
}
},
"securityDefinitions": {
"Bearer": {
"description": "Type \"Bearer\" followed by a space and JWT token",
"type": "apiKey",
"name": "Authorization",
"in": "header"
}
}
}`
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "1.0",
Host: "localhost:8090",
BasePath: "",
Schemes: []string{"http", "https"},
Title: "Pixiu API Documentation",
Description: "",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
}
func init() {
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}
================================================
FILE: api/docs/swagger.json
================================================
{
"schemes": [
"http",
"https"
],
"swagger": "2.0",
"info": {
"title": "Pixiu API Documentation",
"termsOfService": "https://github.com/caoyingjunz/pixiu",
"contact": {
"name": "API Support",
"url": "https://github.com/caoyingjunz/pixiu",
"email": "support@pixiu.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "1.0"
},
"host": "localhost:8090",
"paths": {
"/pixiu/clusters": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "List clusters",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Clusters"
],
"summary": "List clusters",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"allOf": [
{
"$ref": "#/definitions/httputils.Response"
},
{
"type": "object",
"properties": {
"result": {
"type": "array",
"items": {
"$ref": "#/definitions/types.Cluster"
}
}
}
}
]
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
}
}
}
},
"/pixiu/clusters/": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "Create by a json cluster",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Clusters"
],
"summary": "Create a cluster",
"parameters": [
{
"description": "Create cluster",
"name": "cluster",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/types.Cluster"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
}
}
}
},
"/pixiu/clusters/{clusterId}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "Get by cloud cluster ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Clusters"
],
"summary": "Get Cluster by clusterId",
"parameters": [
{
"type": "integer",
"description": "Cluster ID",
"name": "clusterId",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/httputils.Response"
},
{
"type": "object",
"properties": {
"result": {
"$ref": "#/definitions/types.Cluster"
}
}
}
]
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
}
}
},
"put": {
"security": [
{
"Bearer": []
}
],
"description": "Update by json cluster",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Clusters"
],
"summary": "Update an cluster",
"parameters": [
{
"type": "integer",
"description": "Cluster ID",
"name": "clusterId",
"in": "path",
"required": true
},
{
"description": "Update cluster",
"name": "cluster",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/types.Cluster"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
}
}
},
"delete": {
"security": [
{
"Bearer": []
}
],
"description": "Delete by cloud cluster ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Clusters"
],
"summary": "Delete cluster by clusterId",
"parameters": [
{
"type": "integer",
"description": "Cluster ID",
"name": "clusterId",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
}
}
}
},
"/pixiu/clusters/{clusterId}/ping": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "Do ping",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Clusters"
],
"summary": "Ping cluster",
"parameters": [
{
"type": "integer",
"description": "Cluster ID",
"name": "clusterId",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/httputils.Response"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
}
}
}
},
"/pixiu/users": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "List users",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Users"
],
"summary": "List users",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"allOf": [
{
"$ref": "#/definitions/httputils.Response"
},
{
"type": "object",
"properties": {
"result": {
"type": "array",
"items": {
"$ref": "#/definitions/types.User"
}
}
}
}
]
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
}
}
}
},
"/pixiu/users/": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "Create by a json user",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Users"
],
"summary": "Create a user",
"parameters": [
{
"description": "Create user",
"name": "user",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/types.User"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
}
}
}
},
"/pixiu/users/login": {
"post": {
"description": "Login by a json user",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Login"
],
"summary": "User login",
"parameters": [
{
"description": "User login",
"name": "user",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/types.User"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
}
}
}
},
"/pixiu/users/{userId}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "Get by user ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Users"
],
"summary": "Get user by userId",
"parameters": [
{
"type": "integer",
"description": "User ID",
"name": "userId",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/httputils.Response"
},
{
"type": "object",
"properties": {
"result": {
"$ref": "#/definitions/types.User"
}
}
}
]
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
}
}
},
"put": {
"security": [
{
"Bearer": []
}
],
"description": "Update by json user",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Users"
],
"summary": "Update an user",
"parameters": [
{
"type": "integer",
"description": "User ID",
"name": "userId",
"in": "path",
"required": true
},
{
"description": "Update user",
"name": "user",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/types.User"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
}
}
},
"delete": {
"security": [
{
"Bearer": []
}
],
"description": "Delete by userID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Users"
],
"summary": "Delete user by userId",
"parameters": [
{
"type": "integer",
"description": "User ID",
"name": "userId",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/httputils.Response"
}
}
}
}
}
},
"definitions": {
"httputils.Response": {
"type": "object",
"properties": {
"code": {
"description": "返回的状态码",
"type": "integer"
},
"message": {
"description": "异常返回时的错误信息",
"type": "string"
},
"result": {
"description": "正常返回时的数据,可以为任意数据结构"
}
}
},
"types.Cluster": {
"type": "object",
"properties": {
"alias_name": {
"type": "string"
},
"cluster_type": {
"description": "0:标准集群 1: 自建集群",
"type": "integer"
},
"description": {
"description": "集群用途描述,可以为空",
"type": "string"
},
"gmt_create": {
"description": "pixiu 对象创建时间",
"type": "string"
},
"gmt_modified": {
"description": "pixiu 对象修改时间",
"type": "string"
},
"id": {
"description": "pixiu 对象 ID",
"type": "integer"
},
"kube_config": {
"description": "k8s kubeConfig base64 字段",
"type": "string"
},
"kubernetes_version": {
"description": "集群的版本",
"type": "string"
},
"name": {
"type": "string"
},
"nodes": {
"description": "节点数量",
"type": "integer"
},
"resource_version": {
"description": "Pixiu 对象版本号",
"type": "integer"
},
"resources": {
"description": "The memory and cpu usage",
"$ref": "#/definitions/types.Resources"
}
}
},
"types.Resources": {
"type": "object",
"properties": {
"cpu": {
"type": "string"
},
"memory": {
"type": "string"
}
}
},
"types.User": {
"type": "object",
"properties": {
"description": {
"description": "用户描述信息",
"type": "string"
},
"email": {
"description": "用户注册邮件",
"type": "string"
},
"gmt_create": {
"description": "pixiu 对象创建时间",
"type": "string"
},
"gmt_modified": {
"description": "pixiu 对象修改时间",
"type": "string"
},
"id": {
"description": "pixiu 对象 ID",
"type": "integer"
},
"name": {
"description": "用户名称",
"type": "string"
},
"password": {
"description": "用户密码",
"type": "string"
},
"resource_version": {
"description": "Pixiu 对象版本号",
"type": "integer"
},
"role": {
"description": "用户角色,目前只实现管理员,0: 普通用户 1: 管理员 2: 超级管理员",
"type": "string"
},
"status": {
"description": "用户状态标识",
"type": "integer"
}
}
}
},
"securityDefinitions": {
"Bearer": {
"description": "Type \"Bearer\" followed by a space and JWT token",
"type": "apiKey",
"name": "Authorization",
"in": "header"
}
}
}
================================================
FILE: api/docs/swagger.yaml
================================================
definitions:
httputils.Response:
properties:
code:
description: 返回的状态码
type: integer
message:
description: 异常返回时的错误信息
type: string
result:
description: 正常返回时的数据,可以为任意数据结构
type: object
types.Cluster:
properties:
alias_name:
type: string
cluster_type:
description: '0:标准集群 1: 自建集群'
type: integer
description:
description: 集群用途描述,可以为空
type: string
gmt_create:
description: pixiu 对象创建时间
type: string
gmt_modified:
description: pixiu 对象修改时间
type: string
id:
description: pixiu 对象 ID
type: integer
kube_config:
description: k8s kubeConfig base64 字段
type: string
kubernetes_version:
description: 集群的版本
type: string
name:
type: string
nodes:
description: 节点数量
type: integer
resource_version:
description: Pixiu 对象版本号
type: integer
resources:
$ref: '#/definitions/types.Resources'
description: The memory and cpu usage
type: object
types.Resources:
properties:
cpu:
type: string
memory:
type: string
type: object
types.User:
properties:
description:
description: 用户描述信息
type: string
email:
description: 用户注册邮件
type: string
gmt_create:
description: pixiu 对象创建时间
type: string
gmt_modified:
description: pixiu 对象修改时间
type: string
id:
description: pixiu 对象 ID
type: integer
name:
description: 用户名称
type: string
password:
description: 用户密码
type: string
resource_version:
description: Pixiu 对象版本号
type: integer
role:
description: '用户角色,目前只实现管理员,0: 普通用户 1: 管理员 2: 超级管理员'
type: string
status:
description: 用户状态标识
type: integer
type: object
host: localhost:8090
info:
contact:
email: support@pixiu.io
name: API Support
url: https://github.com/caoyingjunz/pixiu
license:
name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
termsOfService: https://github.com/caoyingjunz/pixiu
title: Pixiu API Documentation
version: "1.0"
paths:
/pixiu/clusters:
get:
consumes:
- application/json
description: List clusters
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
allOf:
- $ref: '#/definitions/httputils.Response'
- properties:
result:
items:
$ref: '#/definitions/types.Cluster'
type: array
type: object
type: array
"400":
description: Bad Request
schema:
$ref: '#/definitions/httputils.Response'
"404":
description: Not Found
schema:
$ref: '#/definitions/httputils.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/httputils.Response'
security:
- Bearer: []
summary: List clusters
tags:
- Clusters
/pixiu/clusters/:
post:
consumes:
- application/json
description: Create by a json cluster
parameters:
- description: Create cluster
in: body
name: cluster
required: true
schema:
$ref: '#/definitions/types.Cluster'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/httputils.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/httputils.Response'
"404":
description: Not Found
schema:
$ref: '#/definitions/httputils.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/httputils.Response'
security:
- Bearer: []
summary: Create a cluster
tags:
- Clusters
/pixiu/clusters/{clusterId}:
delete:
consumes:
- application/json
description: Delete by cloud cluster ID
parameters:
- description: Cluster ID
in: path
name: clusterId
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/httputils.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/httputils.Response'
"404":
description: Not Found
schema:
$ref: '#/definitions/httputils.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/httputils.Response'
security:
- Bearer: []
summary: Delete cluster by clusterId
tags:
- Clusters
get:
consumes:
- application/json
description: Get by cloud cluster ID
parameters:
- description: Cluster ID
in: path
name: clusterId
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/httputils.Response'
- properties:
result:
$ref: '#/definitions/types.Cluster'
type: object
"400":
description: Bad Request
schema:
$ref: '#/definitions/httputils.Response'
"404":
description: Not Found
schema:
$ref: '#/definitions/httputils.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/httputils.Response'
security:
- Bearer: []
summary: Get Cluster by clusterId
tags:
- Clusters
put:
consumes:
- application/json
description: Update by json cluster
parameters:
- description: Cluster ID
in: path
name: clusterId
required: true
type: integer
- description: Update cluster
in: body
name: cluster
required: true
schema:
$ref: '#/definitions/types.Cluster'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/httputils.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/httputils.Response'
"404":
description: Not Found
schema:
$ref: '#/definitions/httputils.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/httputils.Response'
security:
- Bearer: []
summary: Update an cluster
tags:
- Clusters
/pixiu/clusters/{clusterId}/ping:
get:
consumes:
- application/json
description: Do ping
parameters:
- description: Cluster ID
in: path
name: clusterId
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/httputils.Response'
type: array
"400":
description: Bad Request
schema:
$ref: '#/definitions/httputils.Response'
"404":
description: Not Found
schema:
$ref: '#/definitions/httputils.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/httputils.Response'
security:
- Bearer: []
summary: Ping cluster
tags:
- Clusters
/pixiu/users:
get:
consumes:
- application/json
description: List users
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
allOf:
- $ref: '#/definitions/httputils.Response'
- properties:
result:
items:
$ref: '#/definitions/types.User'
type: array
type: object
type: array
"400":
description: Bad Request
schema:
$ref: '#/definitions/httputils.Response'
"404":
description: Not Found
schema:
$ref: '#/definitions/httputils.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/httputils.Response'
security:
- Bearer: []
summary: List users
tags:
- Users
/pixiu/users/:
post:
consumes:
- application/json
description: Create by a json user
parameters:
- description: Create user
in: body
name: user
required: true
schema:
$ref: '#/definitions/types.User'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/httputils.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/httputils.Response'
"404":
description: Not Found
schema:
$ref: '#/definitions/httputils.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/httputils.Response'
security:
- Bearer: []
summary: Create a user
tags:
- Users
/pixiu/users/{userId}:
delete:
consumes:
- application/json
description: Delete by userID
parameters:
- description: User ID
in: path
name: userId
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/httputils.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/httputils.Response'
"404":
description: Not Found
schema:
$ref: '#/definitions/httputils.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/httputils.Response'
security:
- Bearer: []
summary: Delete user by userId
tags:
- Users
get:
consumes:
- application/json
description: Get by user ID
parameters:
- description: User ID
in: path
name: userId
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/httputils.Response'
- properties:
result:
$ref: '#/definitions/types.User'
type: object
"400":
description: Bad Request
schema:
$ref: '#/definitions/httputils.Response'
"404":
description: Not Found
schema:
$ref: '#/definitions/httputils.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/httputils.Response'
security:
- Bearer: []
summary: Get user by userId
tags:
- Users
put:
consumes:
- application/json
description: Update by json user
parameters:
- description: User ID
in: path
name: userId
required: true
type: integer
- description: Update user
in: body
name: user
required: true
schema:
$ref: '#/definitions/types.User'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/httputils.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/httputils.Response'
"404":
description: Not Found
schema:
$ref: '#/definitions/httputils.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/httputils.Response'
security:
- Bearer: []
summary: Update an user
tags:
- Users
/pixiu/users/login:
post:
consumes:
- application/json
description: Login by a json user
parameters:
- description: User login
in: body
name: user
required: true
schema:
$ref: '#/definitions/types.User'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/httputils.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/httputils.Response'
"404":
description: Not Found
schema:
$ref: '#/definitions/httputils.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/httputils.Response'
summary: User login
tags:
- Login
schemes:
- http
- https
securityDefinitions:
Bearer:
description: Type "Bearer" followed by a space and JWT token
in: header
name: Authorization
type: apiKey
swagger: "2.0"
================================================
FILE: api/server/errors/errors.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package errors
import (
"net/http"
"github.com/caoyingjunz/pixiu/pkg/util/errors"
)
type Error struct {
Code int
Err error
}
func (e Error) Error() string {
return e.Err.Error()
}
func NewError(err error, code int) Error {
return Error{
Code: code,
Err: err,
}
}
var (
ErrUnauthorized = Error{
Code: http.StatusUnauthorized,
Err: errors.NoUserIdError,
}
ErrForbidden = Error{
Code: http.StatusForbidden,
Err: errors.NoPermission,
}
ErrInvalidRequest = Error{
Code: http.StatusBadRequest,
Err: errors.ErrReqParams,
}
ErrServerInternal = Error{
Code: http.StatusInternalServerError,
Err: errors.ErrInternal,
}
ErrUserNotFound = Error{
Code: http.StatusNotFound,
Err: errors.ErrUserNotFound,
}
ErrNotAcceptable = Error{
Code: http.StatusNotAcceptable,
Err: errors.ErrNotAcceptable,
}
ErrUserExists = Error{
Code: http.StatusConflict,
Err: errors.UserExistError,
}
ErrInvalidPassword = Error{
Code: http.StatusUnauthorized,
Err: errors.ErrUserPassword,
}
ErrDuplicatedPassword = Error{
Code: http.StatusConflict,
Err: errors.ErrDuplicatedPassword,
}
ErrClusterNotFound = Error{
Code: http.StatusNotFound,
Err: errors.ErrClusterNotFound,
}
ErrTenantExists = Error{
Code: http.StatusConflict,
Err: errors.TenantExistError,
}
ErrTenantNotFound = Error{
Code: http.StatusNotFound,
Err: errors.ErrTenantNotFound,
}
ErrAuditNotFound = Error{
Code: http.StatusNotFound,
Err: errors.ErrAuditNotFound,
}
ErrAuditExists = Error{
Code: http.StatusConflict,
Err: errors.ErrAuditExists,
}
ErrRBACPolicyExists = Error{
Code: http.StatusConflict,
Err: errors.PolicyExistError,
}
ErrRBACPolicyNotFound = Error{
Code: http.StatusNotFound,
Err: errors.PolicyNotExistError,
}
ErrGroupBindingExists = Error{
Code: http.StatusConflict,
Err: errors.PolicyExistError,
}
ErrGroupBindingNotFound = Error{
Code: http.StatusNotFound,
Err: errors.PolicyNotExistError,
}
ErrRootAlreadyExists = Error{
Code: http.StatusConflict,
Err: errors.ErrRootAlreadyExists,
}
)
================================================
FILE: api/server/httpstatus/status.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package httpstatus
// TODO: 自定义状态码
================================================
FILE: api/server/httputils/docs.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package httputils
// HttpOK 正常返回
type HttpOK struct {
Code int `json:"code" example:"200"`
Result string `json:"result" example:"any result"`
}
// HttpError 异常返回
type HttpError struct {
Code int `json:"code" example:"400"`
Message string `json:"message" example:"status bad request"`
}
================================================
FILE: api/server/httputils/httputils.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package httputils
import (
"context"
goerrors "errors"
"fmt"
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"github.com/caoyingjunz/pixiu/api/server/errors"
validatorutil "github.com/caoyingjunz/pixiu/api/server/validator"
"github.com/caoyingjunz/pixiu/pkg/db/model"
)
type Response struct {
Code int `json:"code"` // 返回的状态码
Result interface{} `json:"result,omitempty"` // 正常返回时的数据,可以为任意数据结构
Message string `json:"message,omitempty"` // 异常返回时的错误信息
}
func (r *Response) SetCode(c int) {
r.Code = c
}
func (r *Response) SetMessage(m interface{}) {
switch msg := m.(type) {
case error:
r.Message = msg.Error()
case string:
r.Message = msg
}
}
func (r *Response) SetMessageWithCode(m interface{}, c int) {
r.SetCode(c)
r.SetMessage(m)
}
func (r *Response) Error() string {
return r.Message
}
func (r *Response) String() string {
//data, _ := json.Marshal(r)
//return string(data)
return ""
}
// NewResponse 构造 http 返回值
// SetSuccess 时设置 code 为 200 并追加 success 的标识
// SetFailed 时设置 code 为 400,也可以自定义设置错误码,并追加报错信息
func NewResponse() *Response {
return &Response{}
}
// SetSuccess 设置成功返回值
func SetSuccess(c *gin.Context, r *Response) {
_ = contextBind(c).withResponseCode(http.StatusOK)
r.SetMessageWithCode("success", http.StatusOK)
c.JSON(http.StatusOK, r)
}
// SetFailed 设置错误返回值
func SetFailed(c *gin.Context, r *Response, err error) {
switch e := err.(type) {
case errors.Error:
setFailedWithCode(c, r, e.Code, e)
case validator.ValidationErrors:
setFailedWithValidationError(c, r, validatorutil.TranslateError(e))
default:
setFailedWithCode(c, r, http.StatusBadRequest, err)
}
}
// SetFailedWithCode 设置错误返回值
func setFailedWithCode(c *gin.Context, r *Response, code int, err error) {
_ = contextBind(c).withResponseCode(code).withRawError(err)
r.SetMessageWithCode(err, code)
c.JSON(http.StatusOK, r)
}
func setFailedWithValidationError(c *gin.Context, r *Response, e string) {
_ = contextBind(c).withResponseCode(http.StatusBadRequest).withRawError(goerrors.New(e))
r.SetMessageWithCode(e, http.StatusBadRequest)
c.JSON(http.StatusOK, r)
}
// AbortFailedWithCode 设置错误,code 返回值并终止请求
func AbortFailedWithCode(c *gin.Context, code int, err error) {
r := NewResponse()
_ = contextBind(c).withResponseCode(code).withRawError(err)
r.SetMessageWithCode(err, code)
c.JSON(http.StatusOK, r)
c.Abort()
}
func ShouldBindAny(c *gin.Context, jsonObject interface{}, uriObject interface{}, queryObject interface{}) error {
var err error
if jsonObject != nil {
if err = c.ShouldBindJSON(jsonObject); err != nil {
return err
}
}
if uriObject != nil {
if err = c.ShouldBindUri(uriObject); err != nil {
return err
}
}
if queryObject != nil {
if err = c.ShouldBindQuery(queryObject); err != nil {
return err
}
}
return nil
}
const userKey = "user"
func GetUserFromRequest(ctx context.Context) (*model.User, error) {
val := ctx.Value(userKey)
if val == nil {
return nil, fmt.Errorf("get nil user")
}
user, ok := val.(*model.User)
if !ok {
return nil, fmt.Errorf("failed to assert user")
}
return user, nil
}
func GetUserIdFromContext(ctx context.Context) (int64, error) {
user, err := GetUserFromRequest(ctx)
if err != nil {
return 0, err
}
return user.Id, nil
}
func SetUserToContext(c *gin.Context, user *model.User) {
c.Set(userKey, user)
}
func GetObjectFromRequest(c *gin.Context) (string, string, bool) {
return getObjectFromRequest(c.Request.URL.Path)
}
// getObjectFromRequest cuts and returns the object from the request path.
// e.g. /pixiu/clusters/1 -> "clusters" "1" true
func getObjectFromRequest(path string) (obj, sid string, ok bool) {
// must start with /
l := len(path)
if l == 0 || path[0] != '/' {
return
}
subs := strings.Split(path[1:l], "/")
l = len(subs)
if l < 2 || subs[0] != "pixiu" {
return
}
if l == 2 {
// e.g. /pixiu/clusters -> "clusters" "" true
return subs[1], "", subs[1] != ""
}
return subs[1], subs[2], subs[1] != "" && subs[2] != ""
}
const (
objIDsKey = "objIDs"
)
func SetIdRangeContext(c *gin.Context, ids []int64) {
c.Set(objIDsKey, ids)
}
func GetIdRangeFromListReq(ctx context.Context) (exists bool, ids []int64) {
val := ctx.Value(objIDsKey)
if val == nil {
return
}
ids, exists = val.([]int64)
return
}
const (
ResponseCodeKey = "response_code"
RawErrorKey = "raw_error"
)
type ctxBind struct {
*gin.Context
}
func contextBind(c *gin.Context) *ctxBind {
return &ctxBind{c}
}
// withResponseCode puts the response code into the HTTP context.
func (cb *ctxBind) withResponseCode(code int) *ctxBind {
cb.Set(ResponseCodeKey, code)
return cb
}
// withRawError puts the raw error into the HTTP context.
func (cb *ctxBind) withRawError(err error) *ctxBind {
cb.Set(RawErrorKey, err)
return cb
}
// GetResponseCode gets the response code from the HTTP context.
func GetResponseCode(ctx context.Context) (code int) {
val := ctx.Value(ResponseCodeKey)
if val == nil {
return
}
code = val.(int)
return
}
// GetRawError gets the raw error from the HTTP context.
func GetRawError(ctx context.Context) (err error) {
val := ctx.Value(RawErrorKey)
if val == nil {
return
}
err = val.(error)
return
}
================================================
FILE: api/server/httputils/httputils_test.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package httputils
import "testing"
func Test_getObjectFromRequest(t *testing.T) {
tests := []struct {
name string
path string
wantObj string
wantSid string
wantOk bool
}{
{
name: "test0",
path: "",
wantObj: "",
wantOk: false,
},
{
name: "test1",
path: "/",
wantObj: "",
wantOk: false,
},
{
name: "test2",
path: "//",
wantObj: "",
wantOk: false,
},
{
name: "test3",
path: "pixiu",
wantObj: "",
wantOk: false,
},
{
name: "test4",
path: "/pixiu",
wantObj: "",
wantOk: false,
},
{
name: "test5",
path: "/pixiu/",
wantObj: "",
wantOk: false,
},
{
name: "test6",
path: "/pixiu/users",
wantObj: "users",
wantSid: "",
wantOk: true,
},
{
name: "test7",
path: "/pixiu/users/",
wantObj: "users",
wantOk: false,
},
{
name: "test8",
path: "/pixiu/users/1",
wantObj: "users",
wantSid: "1",
wantOk: true,
},
{
name: "test9",
path: "/pixiu//",
wantObj: "",
wantOk: false,
},
{
name: "test10",
path: "///",
wantObj: "",
wantOk: false,
},
{
name: "test11",
path: "/pixiu/users/1/password",
wantObj: "users",
wantSid: "1",
wantOk: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotObj, gotSid, gotOk := getObjectFromRequest(tt.path)
if gotObj != tt.wantObj {
t.Errorf("getObjectFromRequest() gotObj = %v, want %v", gotObj, tt.wantObj)
}
if gotSid != tt.wantSid {
t.Errorf("getObjectFromRequest() gotSid = %v, want %v", gotSid, tt.wantSid)
}
if gotOk != tt.wantOk {
t.Errorf("getObjectFromRequest() gotOk = %v, want %v", gotOk, tt.wantOk)
}
})
}
}
================================================
FILE: api/server/middleware/admission.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package middleware
import "github.com/gin-gonic/gin"
// Admission 准入控制
func Admission() gin.HandlerFunc {
return func(c *gin.Context) {}
}
================================================
FILE: api/server/middleware/audit.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package middleware
import (
"context"
"net/http"
"strings"
"sync"
"time"
"github.com/gin-contrib/requestid"
"github.com/gin-gonic/gin"
"k8s.io/klog/v2"
"github.com/caoyingjunz/pixiu/api/server/httputils"
"github.com/caoyingjunz/pixiu/cmd/app/options"
"github.com/caoyingjunz/pixiu/pkg/db"
"github.com/caoyingjunz/pixiu/pkg/db/model"
)
const (
defaultAuditQueueSize = 2048
defaultAuditWorkers = 2
auditWriteTimeout = 3 * time.Second
)
type auditRecorder struct {
factory db.ShareDaoFactory
queue chan *model.Audit
}
var (
recorderOnce sync.Once
recorderInst *auditRecorder
)
func getAuditRecorder(o *options.Options) *auditRecorder {
recorderOnce.Do(func() {
recorderInst = &auditRecorder{
factory: o.Factory,
queue: make(chan *model.Audit, defaultAuditQueueSize),
}
for i := 0; i < defaultAuditWorkers; i++ {
go recorderInst.run()
}
})
return recorderInst
}
func (r *auditRecorder) run() {
for record := range r.queue {
r.write(record)
}
}
func (r *auditRecorder) write(record *model.Audit) {
ctx, cancel := context.WithTimeout(context.Background(), auditWriteTimeout)
defer cancel()
if _, err := r.factory.Audit().Create(ctx, record); err != nil {
klog.Errorf("failed to create audit record [%s]: %v", record.String(), err)
}
}
func (r *auditRecorder) enqueue(record *model.Audit) {
select {
case r.queue <- record:
default:
// 队列满时降级同步写入,确保非 GET 请求都能落库
klog.Warningf("audit queue is full, fallback to direct write: %s", record.Path)
r.write(record)
}
}
func Audit(o *options.Options) gin.HandlerFunc {
recorder := getAuditRecorder(o)
return func(c *gin.Context) {
if !shouldAudit(c) {
c.Next()
return
}
startTime := time.Now()
c.Next()
recorder.enqueue(buildAuditRecord(c, startTime))
}
}
func buildAuditRecord(c *gin.Context, startTime time.Time) *model.Audit {
userName := "unknown"
if user, err := httputils.GetUserFromRequest(c); err == nil && user != nil {
userName = user.Name
}
cluster, resourceName, resourceNamespace := parseK8sProxyPath(c.Request.URL.Path)
return &model.Audit{
RequestId: requestid.Get(c),
Action: c.Request.Method,
Ip: c.ClientIP(),
Operator: userName,
Path: c.Request.RequestURI,
ObjectType: detectObjectType(c),
Status: getAuditStatus(c),
Duration: time.Since(startTime).Milliseconds(),
ResponseCode: c.Writer.Status(),
Cluster: cluster,
ResourceName: resourceName,
ResourceNamespace: resourceNamespace,
}
}
// parseK8sProxyPath 从 K8s proxy URL 路径中解析集群、资源名称和命名空间。
// 支持以下路径格式:
// - /pixiu/proxy/{cluster}/api/v1/namespaces/{namespace}/{resource}/{name}
// - /pixiu/proxy/{cluster}/apis/{group}/{version}/namespaces/{namespace}/{resource}/{name}
// - /pixiu/proxy/{cluster}/api/v1/{resource}/{name}(集群级资源)
func parseK8sProxyPath(path string) (cluster, resourceName, resourceNamespace string) {
// /pixiu/proxy/{cluster}/...
const proxyPrefix = "/pixiu/proxy/"
if !strings.HasPrefix(path, proxyPrefix) {
return
}
rest := path[len(proxyPrefix):]
parts := strings.SplitN(rest, "/", 2)
if len(parts) < 1 || parts[0] == "" {
return
}
cluster = parts[0]
if len(parts) < 2 {
return
}
segments := strings.Split(parts[1], "/")
// 查找 namespaces/{ns} 段
for i := 0; i < len(segments)-1; i++ {
if segments[i] == "namespaces" {
resourceNamespace = segments[i+1]
// namespace 之后还有 {resource}/{name}
if i+3 < len(segments) {
resourceName = segments[i+3]
}
return
}
}
// 无 namespaces 段,集群级资源:api/v1/{resource}/{name} 或 apis/.../.../{resource}/{name}
if len(segments) > 0 {
resourceName = segments[len(segments)-1]
}
return
}
func shouldAudit(c *gin.Context) bool {
if c.Request.Method == http.MethodGet || c.Request.Method == http.MethodOptions {
return false
}
return strings.HasPrefix(c.Request.URL.Path, "/pixiu")
}
func detectObjectType(c *gin.Context) model.ObjectType {
obj, _, ok := httputils.GetObjectFromRequest(c)
if !ok {
return model.ObjectAll
}
ot := model.ObjectType(obj)
if _, exists := model.ObjectTypeMap[ot]; exists {
return ot
}
return model.ObjectAll
}
// getAuditStatus returns the status of operation.
func getAuditStatus(c *gin.Context) model.AuditOperationStatus {
respCode := httputils.GetResponseCode(c)
if respCode == 0 {
respCode = c.Writer.Status()
if respCode == 0 {
return model.AuditOpUnknown
}
}
if responseOK(respCode) {
return model.AuditOpSuccess
}
return model.AuditOpFail
}
func responseOK(code int) bool {
return code == http.StatusOK ||
code == http.StatusCreated ||
code == http.StatusAccepted
}
================================================
FILE: api/server/middleware/authentication.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package middleware
import (
"fmt"
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/caoyingjunz/pixiu/api/server/errors"
"github.com/caoyingjunz/pixiu/api/server/httputils"
"github.com/caoyingjunz/pixiu/cmd/app/options"
tokenutil "github.com/caoyingjunz/pixiu/pkg/util/token"
)
// Authentication 身份认证
func Authentication(o *options.Options) gin.HandlerFunc {
keyBytes := []byte(o.ComponentConfig.Default.JWTKey)
return func(c *gin.Context) {
if o.ComponentConfig.Default.Mode.InDebug() {
// Considered all as root user when running in debug mode.
root, err := o.Factory.User().GetRoot(c)
if err != nil {
httputils.AbortFailedWithCode(c, http.StatusInternalServerError, err)
return
}
httputils.SetUserToContext(c, root)
return
}
if alwaysAllowPath.Has(c.Request.URL.Path) || allowCustomRequest(c) {
return
}
if err := validate(c, o, keyBytes); err != nil {
httputils.AbortFailedWithCode(c, http.StatusUnauthorized, err)
return
}
}
}
func validate(c *gin.Context, o *options.Options, keyBytes []byte) error {
token, err := extractToken(c, false)
if err != nil {
return err
}
claim, err := tokenutil.ParseToken(token, keyBytes)
if err != nil {
return err
}
existToken, err := o.Controller.User().GetLoginToken(c, claim.Id)
if err != nil {
return fmt.Errorf("未登陆或者密码被修改,请重新登陆")
}
if token != existToken {
return fmt.Errorf("已被他人登陆")
}
user, err := o.Factory.User().Get(c, claim.Id)
if err != nil {
return err
}
if user == nil {
return errors.ErrUnauthorized
}
httputils.SetUserToContext(c, user)
return nil
}
// 从请求头中获取 token
func extractToken(c *gin.Context, ws bool) (string, error) {
emptyFunc := func(t string) bool { return len(t) == 0 }
if ws {
wsToken := c.GetHeader("Sec-WebSocket-Protocol")
if emptyFunc(wsToken) {
return "", fmt.Errorf("authorization header is not provided")
}
return wsToken, nil
}
token := c.GetHeader("Authorization")
if emptyFunc(token) {
return "", fmt.Errorf("authorization header is not provided")
}
fields := strings.Fields(token)
if len(fields) != 2 {
return "", fmt.Errorf("invalid authorization header format")
}
if fields[0] != "Bearer" {
return "", fmt.Errorf("unsupported authorization type")
}
return fields[1], nil
}
================================================
FILE: api/server/middleware/authorization.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package middleware
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"github.com/caoyingjunz/pixiu/api/server/httputils"
"github.com/caoyingjunz/pixiu/api/server/router/cluster"
"github.com/caoyingjunz/pixiu/api/server/router/proxy"
"github.com/caoyingjunz/pixiu/cmd/app/options"
ctrlutil "github.com/caoyingjunz/pixiu/pkg/controller/util"
"github.com/caoyingjunz/pixiu/pkg/db/model"
)
// HTTP method to operation
var operationsMap = map[string]model.Operation{
http.MethodGet: model.OpRead,
http.MethodPost: model.OpCreate,
http.MethodPatch: model.OpUpdate,
http.MethodPut: model.OpUpdate,
http.MethodDelete: model.OpDelete,
}
// Authorization 鉴权
func Authorization(o *options.Options) gin.HandlerFunc {
return func(c *gin.Context) {
// 允许请求直接通过
if o.ComponentConfig.Default.Mode.InDebug() || alwaysAllowPath.Has(c.Request.URL.Path) || allowCustomRequest(c) {
return
}
user, err := httputils.GetUserFromRequest(c)
if err != nil {
httputils.AbortFailedWithCode(c, http.StatusMethodNotAllowed, err)
return
}
switch user.Status {
case 1:
// status 为 1,表示用户只读模式, 只读模式只允许查询请求
if c.Request.Method != http.MethodGet && c.Request.Method != http.MethodOptions {
httputils.AbortFailedWithCode(c, http.StatusForbidden, fmt.Errorf("无操作权限"))
return
}
case 2:
// 禁用用户无法进行任何操作
httputils.AbortFailedWithCode(c, http.StatusForbidden, fmt.Errorf("用户已被禁用"))
return
}
// Proxy path should be skipped now.
// TODO: get object and ID from proxy path
if proxy.IsProxyPath(c) || cluster.IsKubeProxyPath(c) || cluster.IsHelmPath(c) {
return
}
obj, id, ok := httputils.GetObjectFromRequest(c)
if !ok {
return
}
op := operationsMap[c.Request.Method]
// load policy for consistency
// ref: https://github.com/casbin/casbin/issues/679#issuecomment-761525328
if err := o.Enforcer.LoadPolicy(); err != nil {
httputils.AbortFailedWithCode(c, http.StatusInternalServerError, err)
return
}
ok, err = o.Enforcer.Enforce(user.Name, obj, id, op.String())
if err != nil {
httputils.AbortFailedWithCode(c, http.StatusMethodNotAllowed, err)
return
}
if !ok {
httputils.AbortFailedWithCode(c, http.StatusForbidden, fmt.Errorf("无操作权限"))
}
if id != "" {
return
}
// this is a list API
if err := ctrlutil.SetIdRangeContext(c, o.Enforcer, user, obj); err != nil {
httputils.AbortFailedWithCode(c, http.StatusInternalServerError, err)
}
}
}
================================================
FILE: api/server/middleware/cors.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package middleware
import (
"time"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
func Cors() gin.HandlerFunc {
c := cors.Config{
AllowAllOrigins: true,
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "PATCH"},
AllowHeaders: []string{"Content-Type", "Access-Token", "Authorization"},
MaxAge: 6 * time.Hour,
}
return cors.New(c)
}
================================================
FILE: api/server/middleware/limiter.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package middleware
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/juju/ratelimit"
"golang.org/x/time/rate"
"github.com/caoyingjunz/pixiu/api/server/httputils"
"github.com/caoyingjunz/pixiu/pkg/util/errors"
"github.com/caoyingjunz/pixiu/pkg/util/lru"
)
const (
capacity = 100
quantum = 20
cap = 200
)
// UserRateLimiter 针对每个用户的请求进行限速
// TODO 限速大小从配置中读取
func UserRateLimiter() gin.HandlerFunc {
cache := lru.NewLRUCache(cap)
return func(c *gin.Context) {
clientIP := c.ClientIP()
if !cache.Contains(clientIP) {
cache.Add(clientIP, ratelimit.NewBucketWithQuantum(time.Second, capacity, quantum))
return
}
// 通过 ClientIP 取出 bucket
val := cache.Get(clientIP)
if val == nil {
return
}
// 判断是否还有可用的 bucket
bucket := val.(*ratelimit.Bucket)
if bucket.TakeAvailable(1) == 0 {
httputils.AbortFailedWithCode(c, http.StatusForbidden, errors.ErrBusySystem)
}
}
}
func Limiter() gin.HandlerFunc {
// 初始化一个限速器,每秒产生 1000 个令牌,桶的大小为 1000 个
// 初始化状态桶是满的
// TODO: 限速的值从配置或者环境变量中获取
limiter := rate.NewLimiter(1000, 1000)
return func(c *gin.Context) {
if !limiter.Allow() {
httputils.AbortFailedWithCode(c, http.StatusForbidden, errors.ErrBusySystem)
}
}
}
================================================
FILE: api/server/middleware/log.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package middleware
import (
"github.com/gin-contrib/requestid"
"github.com/gin-gonic/gin"
"github.com/caoyingjunz/pixiu/api/server/httputils"
"github.com/caoyingjunz/pixiu/pkg/db"
logutil "github.com/caoyingjunz/pixiu/pkg/util/log"
)
func Logger(cfg *logutil.LogOptions) gin.HandlerFunc {
return func(c *gin.Context) {
l := logutil.NewLogger(cfg)
c.Set(db.SQLContextKey, new(db.SQLs)) // set SQL context key
// 处理请求操作
c.Next()
l.WithLogFields(map[string]interface{}{
"request_id": requestid.Get(c),
"method": c.Request.Method,
"uri": c.Request.RequestURI,
httputils.ResponseCodeKey: httputils.GetResponseCode(c),
"client_ip": c.ClientIP(),
})
l.Log(c, logutil.InfoLevel, httputils.GetRawError(c))
}
}
================================================
FILE: api/server/middleware/middleware.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package middleware
import (
"github.com/gin-contrib/requestid"
"github.com/gin-gonic/gin"
"k8s.io/apimachinery/pkg/util/sets"
"github.com/caoyingjunz/pixiu/cmd/app/options"
"github.com/caoyingjunz/pixiu/pkg/util"
)
var alwaysAllowPath sets.String
func init() {
alwaysAllowPath = sets.NewString("/pixiu/users/login")
}
// 允许特定请求不经过验证
func allowCustomRequest(c *gin.Context) bool {
// TODO: 其他请求
return false
}
func InstallMiddlewares(o *options.Options) {
// 依次进行跨域,日志,单用户限速,总量限速,验证,鉴权和审计
o.HttpEngine.Use(
requestid.New(requestid.WithGenerator(func() string {
return util.GenerateRequestID()
})),
Cors(),
Logger(&o.ComponentConfig.Default.LogOptions),
UserRateLimiter(),
Limiter(),
Authentication(o),
Authorization(o),
Admission(),
Audit(o),
)
}
================================================
FILE: api/server/router/audit/audit.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package audit
import (
"github.com/gin-gonic/gin"
"github.com/caoyingjunz/pixiu/cmd/app/options"
"github.com/caoyingjunz/pixiu/pkg/controller"
)
type auditRouter struct {
c controller.PixiuInterface
}
func NewRouter(o *options.Options) {
router := &auditRouter{
c: o.Controller,
}
router.initRoutes(o.HttpEngine)
}
func (a *auditRouter) initRoutes(httpEngine *gin.Engine) {
auditRoute := httpEngine.Group("/pixiu/audits")
{
// get 日志
auditRoute.GET("/:auditId", a.getAudit)
auditRoute.GET("", a.listAudits)
}
}
================================================
FILE: api/server/router/audit/audit_routes.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package audit
import (
"github.com/gin-gonic/gin"
"github.com/caoyingjunz/pixiu/api/server/httputils"
"github.com/caoyingjunz/pixiu/pkg/types"
)
type AuditMeta struct {
AuditId int64 `uri:"auditId" binding:"required"`
}
func (a *auditRouter) getAudit(c *gin.Context) {
r := httputils.NewResponse()
var (
opt AuditMeta
err error
)
if err = c.ShouldBindUri(&opt); err != nil {
httputils.SetFailed(c, r, err)
return
}
if r.Result, err = a.c.Audit().Get(c, opt.AuditId); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
func (a *auditRouter) listAudits(c *gin.Context) {
r := httputils.NewResponse()
var (
listOption types.AuditListOptions
err error
)
if err = httputils.ShouldBindAny(c, nil, nil, &listOption); err != nil {
httputils.SetFailed(c, r, err)
return
}
if r.Result, err = a.c.Audit().List(c, listOption); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
================================================
FILE: api/server/router/auth/auth.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package auth
import (
"github.com/gin-gonic/gin"
"github.com/caoyingjunz/pixiu/cmd/app/options"
"github.com/caoyingjunz/pixiu/pkg/controller"
)
const (
AuthBasePath = "/pixiu/auth"
PolicySubPath = "/policy"
BindingSubPath = "/binding"
)
type authRouter struct {
c controller.PixiuInterface
}
func NewRouter(o *options.Options) {
router := &authRouter{
c: o.Controller,
}
router.initRoutes(o.HttpEngine)
}
func (a *authRouter) initRoutes(ge *gin.Engine) {
authRoute := ge.Group(AuthBasePath)
{
policyRoute := authRoute.Group(PolicySubPath)
policyRoute.POST("", a.createPolicy)
policyRoute.DELETE("", a.deletePolicy)
policyRoute.GET("", a.listPolicies)
}
{
bindingRoute := authRoute.Group(BindingSubPath)
bindingRoute.POST("", a.createBinding)
bindingRoute.DELETE("", a.deleteBinding)
bindingRoute.GET("", a.listBindings)
}
}
================================================
FILE: api/server/router/auth/auth_routes.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package auth
import (
"github.com/gin-gonic/gin"
"github.com/caoyingjunz/pixiu/api/server/httputils"
"github.com/caoyingjunz/pixiu/pkg/types"
)
type IdMeta struct {
PolicyId int64 `uri:"policyId" binding:"required"`
}
func (a *authRouter) listPolicies(c *gin.Context) {
r := httputils.NewResponse()
var (
req types.ListRBACPolicyRequest
err error
)
if err = c.ShouldBindQuery(&req); err != nil {
httputils.SetFailed(c, r, err)
return
}
if r.Result, err = a.c.Auth().ListRBACPolicies(c, &req); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
func (a *authRouter) createPolicy(c *gin.Context) {
r := httputils.NewResponse()
var req types.RBACPolicyRequest
if err := c.ShouldBindJSON(&req); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err := a.c.Auth().CreateRBACPolicy(c, &req); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
func (a *authRouter) deletePolicy(c *gin.Context) {
r := httputils.NewResponse()
var req types.RBACPolicyRequest
if err := c.ShouldBindJSON(&req); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err := a.c.Auth().DeleteRBACPolicy(c, &req); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
func (a *authRouter) listBindings(c *gin.Context) {
r := httputils.NewResponse()
var (
req types.ListGroupBindingRequest
err error
)
if err = c.ShouldBindQuery(&req); err != nil {
httputils.SetFailed(c, r, err)
return
}
if r.Result, err = a.c.Auth().ListGroupBindings(c, &req); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
func (a *authRouter) createBinding(c *gin.Context) {
r := httputils.NewResponse()
var req types.GroupBindingRequest
if err := c.ShouldBindJSON(&req); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err := a.c.Auth().CreateGroupBinding(c, &req); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
func (a *authRouter) deleteBinding(c *gin.Context) {
r := httputils.NewResponse()
var req types.GroupBindingRequest
if err := c.ShouldBindJSON(&req); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err := a.c.Auth().DeleteGroupBinding(c, &req); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
================================================
FILE: api/server/router/cluster/cluster.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cluster
import (
"github.com/gin-gonic/gin"
"github.com/caoyingjunz/pixiu/cmd/app/options"
"github.com/caoyingjunz/pixiu/pkg/controller"
)
const (
kubeProxyBaseURL = "/pixiu/kubeproxy"
helmBaseURL = "/pixiu/helms"
indexerBaseURL = "/pixiu/indexer"
)
// clusterRouter is a router to talk with the cluster controller
type clusterRouter struct {
c controller.PixiuInterface
}
// NewRouter initializes a new cluster router
func NewRouter(o *options.Options) {
s := &clusterRouter{
c: o.Controller,
}
s.initRoutes(o.HttpEngine)
}
func (cr *clusterRouter) initRoutes(httpEngine *gin.Engine) {
clusterRoute := httpEngine.Group("/pixiu/clusters")
{
clusterRoute.POST("", cr.createCluster)
clusterRoute.PUT("/:clusterId", cr.updateCluster)
clusterRoute.DELETE("/:clusterId", cr.deleteCluster)
clusterRoute.GET("/:clusterId", cr.getCluster)
clusterRoute.GET("", cr.listClusters)
// 检查 kubernetes 的连通性
clusterRoute.POST("/ping", cr.pingCluster)
// 设置集群的删除保护模式
clusterRoute.POST("/protect/:clusterId", cr.protectCluster)
}
// 调用 kubernetes 对象
kubeRoute := httpEngine.Group(kubeProxyBaseURL)
{
// 获取指定对象的日志
kubeRoute.GET("/clusters/:cluster/namespaces/:namespace/pods/:pod/log", cr.watchPodLog)
// Deprecated 聚合 events
kubeRoute.GET("/clusters/:cluster/namespaces/:namespace/name/:name/kind/:kind/events", cr.aggregateEvents)
// 获取指定对象的 events,支持事件聚合
kubeRoute.GET("/clusters/:cluster/api/v1/events", cr.getEventList)
// pod ws
kubeRoute.GET("/ws", cr.webShell)
// node ws
kubeRoute.GET("/nodes/ws", cr.nodeWebShell)
// 重启Job action=rerun
kubeRoute.POST("/clusters/:cluster/namespaces/:namespace/jobs/:name", cr.ReRunJob)
}
// 从 pixiu 缓存中获取 kubernetes 对象
//indexerRoute := httpEngine.Group(indexerBaseURL)
//{
// // 从缓存中获取指定对象
// indexerRoute.GET("/clusters/:cluster/resources/:resource/namespaces/:namespace/name/:name", cr.getIndexerResource)
// // 从缓存中获取对象列表
// indexerRoute.GET("/clusters/:cluster/resources/:resource/namespaces/:namespace", cr.listIndexerResources)
//}
}
================================================
FILE: api/server/router/cluster/cluster_routes.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cluster
import (
"github.com/gin-gonic/gin"
"github.com/caoyingjunz/pixiu/api/server/httputils"
"github.com/caoyingjunz/pixiu/pkg/types"
)
type IdMeta struct {
ClusterId int64 `uri:"clusterId" binding:"required"`
}
// CreateCluster godoc
//
// @Summary Create a cluster
// @Description Create by a json cluster
// @Tags Clusters
// @Accept json
// @Produce json
// @Param cluster body types.Cluster true "Create cluster"
// @Success 200 {object} httputils.Response
// @Failure 400 {object} httputils.Response
// @Failure 404 {object} httputils.Response
// @Failure 500 {object} httputils.Response
// @Router /pixiu/clusters/ [post]
// @Security Bearer
func (cr *clusterRouter) createCluster(c *gin.Context) {
r := httputils.NewResponse()
var req types.CreateClusterRequest
if err := c.ShouldBindJSON(&req); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err := cr.c.Cluster().Create(c, &req); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
// UpdateCluster godoc
//
// @Summary Update an cluster
// @Description Update by json cluster
// @Tags Clusters
// @Accept json
// @Produce json
// @Param clusterId path int true "Cluster ID"
// @Param cluster body types.Cluster true "Update cluster"
// @Success 200 {object} httputils.Response
// @Failure 400 {object} httputils.Response
// @Failure 404 {object} httputils.Response
// @Failure 500 {object} httputils.Response
// @Router /pixiu/clusters/{clusterId} [put]
// @Security Bearer
func (cr *clusterRouter) updateCluster(c *gin.Context) {
r := httputils.NewResponse()
var (
idMeta IdMeta
err error
)
if err = c.ShouldBindUri(&idMeta); err != nil {
httputils.SetFailed(c, r, err)
return
}
var req types.UpdateClusterRequest
if err = c.ShouldBindJSON(&req); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err = cr.c.Cluster().Update(c, idMeta.ClusterId, &req); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
// DeleteCluster godoc
//
// @Summary Delete cluster by clusterId
// @Description Delete by cloud cluster ID
// @Tags Clusters
// @Accept json
// @Produce json
// @Param clusterId path int true "Cluster ID"
// @Success 200 {object} httputils.Response
// @Failure 400 {object} httputils.Response
// @Failure 404 {object} httputils.Response
// @Failure 500 {object} httputils.Response
// @Router /pixiu/clusters/{clusterId} [delete]
// @Security Bearer
func (cr *clusterRouter) deleteCluster(c *gin.Context) {
r := httputils.NewResponse()
var (
idMeta IdMeta
err error
)
if err = c.ShouldBindUri(&idMeta); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err = cr.c.Cluster().Delete(c, idMeta.ClusterId); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
// GetCluster godoc
//
// @Summary Get Cluster by clusterId
// @Description Get by cloud cluster ID
// @Tags Clusters
// @Accept json
// @Produce json
// @Param clusterId path int true "Cluster ID"
// @Success 200 {object} httputils.Response{result=types.Cluster}
// @Failure 400 {object} httputils.Response
// @Failure 404 {object} httputils.Response
// @Failure 500 {object} httputils.Response
// @Router /pixiu/clusters/{clusterId} [get]
// @Security Bearer
func (cr *clusterRouter) getCluster(c *gin.Context) {
r := httputils.NewResponse()
var (
idMeta IdMeta
err error
)
if err = c.ShouldBindUri(&idMeta); err != nil {
httputils.SetFailed(c, r, err)
return
}
if r.Result, err = cr.c.Cluster().Get(c, idMeta.ClusterId); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
// ListClusters godoc
//
// @Summary List clusters
// @Description List clusters with pagination and filter
// @Tags Clusters
// @Accept json
// @Produce json
// @Param page query int false "Page number (1-based)"
// @Param limit query int false "Page size"
// @Param nameSelector query string false "Fuzzy match on alias_name"
// @Param status query int false "Cluster status (0-4)"
// @Success 200 {object} httputils.Response{result=types.PageResponse}
// @Failure 400 {object} httputils.Response
// @Failure 500 {object} httputils.Response
// @Router /pixiu/clusters [get]
// @Security Bearer
func (cr *clusterRouter) listClusters(c *gin.Context) {
r := httputils.NewResponse()
var req types.ListClusterRequest
if err := c.ShouldBindQuery(&req); err != nil {
httputils.SetFailed(c, r, err)
return
}
var err error
if r.Result, err = cr.c.Cluster().List(c, &req); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
// PingCluster godoc
//
// @Summary Ping cluster
// @Description Do ping
// @Tags Clusters
// @Accept json
// @Produce json
// @Param clusterId path int true "Cluster ID"
// @Success 200 {array} httputils.Response
// @Failure 400 {object} httputils.Response
// @Failure 404 {object} httputils.Response
// @Failure 500 {object} httputils.Response
// @Router /pixiu/clusters/{clusterId}/ping [get]
// @Security Bearer
func (cr *clusterRouter) pingCluster(c *gin.Context) {
r := httputils.NewResponse()
var (
cluster types.Cluster
err error
)
if err = c.ShouldBindJSON(&cluster); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err = cr.c.Cluster().Ping(c, cluster.KubeConfig); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
func (cr *clusterRouter) protectCluster(c *gin.Context) {
r := httputils.NewResponse()
var (
idMeta IdMeta
req types.ProtectClusterRequest
err error
)
if err = httputils.ShouldBindAny(c, &req, &idMeta, nil); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err = cr.c.Cluster().Protect(c, idMeta.ClusterId, &req); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
func (cr *clusterRouter) aggregateEvents(c *gin.Context) {
r := httputils.NewResponse()
var (
optMeta struct {
Cluster string `uri:"cluster" binding:"required"`
Namespace string `uri:"namespace" binding:"required"`
Name string `uri:"name" binding:"required"`
Kind string `uri:"kind" binding:"required"`
}
err error
)
if err = c.ShouldBindUri(&optMeta); err != nil {
httputils.SetFailed(c, r, err)
return
}
if r.Result, err = cr.c.Cluster().AggregateEvents(c, optMeta.Cluster, optMeta.Namespace, optMeta.Name, optMeta.Kind); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
func (cr *clusterRouter) getEventList(c *gin.Context) {
r := httputils.NewResponse()
var (
opts struct {
Cluster string `uri:"cluster" binding:"required"`
}
eventOpt types.EventOptions
err error
)
if err = httputils.ShouldBindAny(c, nil, &opts, &eventOpt); err != nil {
httputils.SetFailed(c, r, err)
return
}
if r.Result, err = cr.c.Cluster().GetEventList(c, opts.Cluster, eventOpt); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
func (cr *clusterRouter) watchPodLog(c *gin.Context) {
r := httputils.NewResponse()
var (
opts struct {
Cluster string `uri:"cluster" binding:"required"`
Namespace string `uri:"namespace" binding:"required"`
Pod string `uri:"pod" binding:"required"` //pod name
}
logOpt types.PodLogOptions
err error
)
if err = httputils.ShouldBindAny(c, nil, &opts, &logOpt); err != nil {
httputils.SetFailed(c, r, err)
return
}
// websocket
if err = cr.c.Cluster().WatchPodLog(c, opts.Cluster, opts.Namespace, opts.Pod, logOpt.Container, logOpt.TailLines, c.Writer, c.Request); err != nil {
httputils.SetFailed(c, r, err)
return
}
}
================================================
FILE: api/server/router/cluster/helper.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cluster
import (
"strings"
"github.com/gin-gonic/gin"
)
func IsKubeProxyPath(c *gin.Context) bool {
return strings.HasPrefix(c.Request.URL.Path, kubeProxyBaseURL)
}
func IsHelmPath(c *gin.Context) bool {
return strings.HasPrefix(c.Request.URL.Path, helmBaseURL)
}
================================================
FILE: api/server/router/cluster/informer.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cluster
import (
"github.com/gin-gonic/gin"
"github.com/caoyingjunz/pixiu/api/server/httputils"
"github.com/caoyingjunz/pixiu/pkg/types"
)
type ResourceMeta struct {
Cluster string `uri:"cluster" binding:"required"`
Resource string `uri:"resource" binding:"required"`
Namespace string `uri:"namespace"`
Name string `uri:"name"`
}
func (cr *clusterRouter) getIndexerResource(c *gin.Context) {
r := httputils.NewResponse()
var (
resourceMeta ResourceMeta
err error
)
if err = httputils.ShouldBindAny(c, nil, &resourceMeta, nil); err != nil {
httputils.SetFailed(c, r, err)
return
}
if r.Result, err = cr.c.Cluster().GetIndexerResource(c, resourceMeta.Cluster, resourceMeta.Resource, resourceMeta.Namespace, resourceMeta.Name); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
func (cr *clusterRouter) listIndexerResources(c *gin.Context) {
r := httputils.NewResponse()
var (
resourceMeta ResourceMeta
listOption types.ListOptions // 分页设置
err error
)
if err = httputils.ShouldBindAny(c, nil, &resourceMeta, &listOption); err != nil {
httputils.SetFailed(c, r, err)
return
}
if r.Result, err = cr.c.Cluster().ListIndexerResources(c, resourceMeta.Cluster, resourceMeta.Resource, resourceMeta.Namespace, listOption); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
================================================
FILE: api/server/router/cluster/proxy.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cluster
import (
"github.com/gin-gonic/gin"
"github.com/caoyingjunz/pixiu/api/server/httputils"
"github.com/caoyingjunz/pixiu/pkg/types"
)
type Action struct {
Act string `form:"action" binding:"required"`
ResourceVersion string `form:"resourceVersion" binding:"required"`
}
func (cr *clusterRouter) ReRunJob(c *gin.Context) {
r := httputils.NewResponse()
var (
jobMeta types.PixiuObjectMeta
action Action
err error
)
if err = httputils.ShouldBindAny(c, nil, &jobMeta, &action); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err = cr.c.Cluster().ReRunJob(c, jobMeta.Cluster, jobMeta.Namespace, jobMeta.Name, action.ResourceVersion); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
================================================
FILE: api/server/router/cluster/ws.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cluster
import (
"github.com/gin-gonic/gin"
"github.com/caoyingjunz/pixiu/api/server/httputils"
"github.com/caoyingjunz/pixiu/pkg/types"
)
func (cr *clusterRouter) webShell(c *gin.Context) {
r := httputils.NewResponse()
var (
err error
opt types.WebShellOptions
)
if err = c.ShouldBindQuery(&opt); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err = cr.c.Cluster().WsHandler(c, &opt, c.Writer, c.Request); err != nil {
httputils.SetFailed(c, r, err)
return
}
}
func (cr *clusterRouter) nodeWebShell(c *gin.Context) {
r := httputils.NewResponse()
var (
sshConfig types.WebSSHRequest
err error
)
if err = httputils.ShouldBindAny(c, nil, nil, &sshConfig); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err = cr.c.Cluster().WsNodeHandler(c, &sshConfig, c.Writer, c.Request); err != nil {
httputils.SetFailed(c, r, err)
return
}
}
================================================
FILE: api/server/router/helm/helm.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package helm
import (
"github.com/gin-gonic/gin"
"github.com/caoyingjunz/pixiu/cmd/app/options"
"github.com/caoyingjunz/pixiu/pkg/controller"
)
const (
helmBaseURL = "/pixiu/helms"
)
type helmRouter struct {
c controller.PixiuInterface
}
func NewRouter(o *options.Options) {
hr := &helmRouter{
c: o.Controller,
}
hr.initRoutes(o.HttpEngine)
}
func (hr *helmRouter) initRoutes(httpEngine *gin.Engine) {
helmRoute := httpEngine.Group(helmBaseURL)
{
// helm Repository
helmRoute.POST("/repositories", hr.createRepository)
helmRoute.PUT("/repositories/:id", hr.updateRepository)
helmRoute.DELETE("/repositories/:id", hr.deleteRepository)
helmRoute.GET("/repositories/:id", hr.getRepository)
helmRoute.GET("/repositories", hr.listRepositories)
helmRoute.GET("/repositories/:id/charts", hr.getRepoCharts)
helmRoute.GET("/repositories/charts", hr.getRepoChartsByURL)
helmRoute.GET("/repositories/values", hr.getChartValues)
// Helm Release
helmRoute.POST("/clusters/:cluster/namespaces/:namespace/releases", hr.InstallRelease)
helmRoute.PUT("/clusters/:cluster/namespaces/:namespace/releases", hr.UpgradeRelease)
helmRoute.DELETE("/clusters/:cluster/namespaces/:namespace/releases/:name", hr.UninstallRelease)
helmRoute.GET("/clusters/:cluster/namespaces/:namespace/releases/:name", hr.GetRelease)
helmRoute.GET("/clusters/:cluster/namespaces/:namespace/releases", hr.ListReleases)
helmRoute.GET("/clusters/:cluster/namespaces/:namespace/releases/:name/history", hr.GetReleaseHistory)
helmRoute.POST("/clusters/:cluster/namespaces/:namespace/releases/:name/rollback", hr.RollbackRelease)
}
}
================================================
FILE: api/server/router/helm/release_routes.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package helm
import (
"github.com/gin-gonic/gin"
"github.com/caoyingjunz/pixiu/api/server/httputils"
"github.com/caoyingjunz/pixiu/pkg/types"
)
// GetRelease retrieves a release by its name in the specified namespace and cluster
//
// @Summary get a release
// @Description retrieves a release from the specified namespace and cluster
// @Tags helm
// @Accept json
// @Produce json
// @Param cluster path string true "Kubernetes cluster name"
// @Param namespace path string true "Kubernetes namespace"
// @Param name path string true "Release name"
// @Success 200 {object} httputils.Response{result=types.Release}
// @Failure 400 {object} httputils.Response
// @Failure 404 {object} httputils.Response
// @Failure 500 {object} httputils.Response
// @Router /helm/releases/{cluster}/{namespace}/{name} [get]
func (hr *helmRouter) GetRelease(c *gin.Context) {
r := httputils.NewResponse()
var (
err error
helmMeta types.PixiuObjectMeta
)
if err = c.ShouldBindUri(&helmMeta); err != nil {
httputils.SetFailed(c, r, err)
return
}
if r.Result, err = hr.c.Helm().Release(helmMeta.Cluster, helmMeta.Namespace).Get(c, helmMeta.Name); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
// ListReleases lists all releases in the specified namespace and cluster
//
// @Summary list releases
// @Description lists all releases in the specified namespace and cluster
// @Tags helm
// @Accept json
// @Produce json
// @Param cluster path string true "Kubernetes cluster name"
// @Param namespace path string true "Kubernetes namespace"
// @Success 200 {object} httputils.Response{result=[]types.Release}
// @Failure 400 {object} httputils.Response
// @Failure 404 {object} httputils.Response
// @Failure 500 {object} httputils.Response
// @Router /helm/releases/{cluster}/{namespace} [get]
func (hr *helmRouter) ListReleases(c *gin.Context) {
r := httputils.NewResponse()
var (
err error
helmMeta types.PixiuObjectMeta
)
if err = c.ShouldBindUri(&helmMeta); err != nil {
httputils.SetFailed(c, r, err)
return
}
if r.Result, err = hr.c.Helm().Release(helmMeta.Cluster, helmMeta.Namespace).List(c); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
// InstallRelease installs a new release in the specified namespace and cluster
//
// @Summary install a release
// @Description installs a release in the specified Kubernetes namespace and cluster
// @Tags helm
// @Accept json
// @Produce json
// @Param cluster path string true "Kubernetes cluster name"
// @Param namespace path string true "Kubernetes namespace"
// @Param body body types.ReleaseForm true "Release information"
// @Success 200 {object} httputils.Response
// @Failure 400 {object} httputils.Response
// @Failure 500 {object} httputils.Response
// @Router /helm/releases/{cluster}/{namespace} [post]
func (hr *helmRouter) InstallRelease(c *gin.Context) {
r := httputils.NewResponse()
var (
err error
helmMeta types.PixiuObjectMeta
releaseOpt types.Release
)
if err = httputils.ShouldBindAny(c, &releaseOpt, &helmMeta, nil); err != nil {
httputils.SetFailed(c, r, err)
return
}
if r.Result, err = hr.c.Helm().Release(helmMeta.Cluster, helmMeta.Namespace).Install(c, &releaseOpt); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
// UninstallRelease uninstalls a release from the specified namespace and cluster
//
// @Summary uninstall a release
// @Description uninstalls a release from the specified Kubernetes namespace and cluster
// @Tags helm
// @Accept json
// @Produce json
// @Param cluster path string true "Kubernetes cluster name"
// @Param namespace path string true "Kubernetes namespace"
// @Param name path string true "Release name"
// @Success 200 {object} httputils.Response
// @Failure 400 {object} httputils.Response
// @Failure 404 {object} httputils.Response
// @Failure 500 {object} httputils.Response
// @Router /helm/releases/{cluster}/{namespace}/{name} [delete]
func (hr *helmRouter) UninstallRelease(c *gin.Context) {
r := httputils.NewResponse()
var (
err error
helmMeta types.PixiuObjectMeta
)
if err = c.ShouldBindUri(&helmMeta); err != nil {
httputils.SetFailed(c, r, err)
return
}
if r.Result, err = hr.c.Helm().Release(helmMeta.Cluster, helmMeta.Namespace).Uninstall(c, helmMeta.Name); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
// UpgradeRelease upgrades a release in the specified namespace and cluster
//
// @Summary upgrade a release
// @Description upgrades a release in the specified Kubernetes namespace and cluster
// @Tags helm
// @Accept json
// @Produce json
// @Param cluster path string true "Kubernetes cluster name"
// @Param namespace path string true "Kubernetes namespace"
// @Param name path string true "Release name"
// @Param body body types.ReleaseForm true "Release information"
// @Success 200 {object} httputils.Response
// @Failure 400 {object} httputils.Response
// @Failure 500 {object} httputils.Response
// @Router /helm/releases/{cluster}/{namespace}/{name} [put]
func (hr *helmRouter) UpgradeRelease(c *gin.Context) {
r := httputils.NewResponse()
var (
err error
helmMeta types.PixiuObjectMeta
releaseOpt types.Release
)
if err = httputils.ShouldBindAny(c, &releaseOpt, &helmMeta, nil); err != nil {
httputils.SetFailed(c, r, err)
return
}
if r.Result, err = hr.c.Helm().Release(helmMeta.Cluster, helmMeta.Namespace).Upgrade(c, &releaseOpt); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
// GetReleaseHistory retrieves the history of a release in the specified namespace and cluster
//
// @Summary get a release history
// @Description retrieves the history of a release from the specified Kubernetes namespace and cluster
// @Tags helm
// @Accept json
// @Produce json
// @Param cluster path string true "Kubernetes cluster name"
// @Param namespace path string true "Kubernetes namespace"
// @Param name path string true "Release name"
// @Success 200 {object} httputils.Response{result=types.ReleaseHistory}
// @Failure 400 {object} httputils.Response
// @Failure 404 {object} httputils.Response
// @Failure 500 {object} httputils.Response
// @Router /helm/releases/history/{cluster}/{namespace}/{name} [get]
func (hr *helmRouter) GetReleaseHistory(c *gin.Context) {
r := httputils.NewResponse()
var (
err error
helmMeta types.PixiuObjectMeta
)
if err = c.ShouldBindUri(&helmMeta); err != nil {
httputils.SetFailed(c, r, err)
return
}
if r.Result, err = hr.c.Helm().Release(helmMeta.Cluster, helmMeta.Namespace).History(c, helmMeta.Name); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
// RollbackRelease rolls back a release in the specified namespace and cluster to the specified revision
//
// @Summary rollback a release
// @Description rolls back a release from the specified Kubernetes namespace and cluster to the specified revision
// @Tags helm
// @Accept json
// @Produce json
// @Param cluster path string true "Kubernetes cluster name"
// @Param namespace path string true "Kubernetes namespace"
// @Param name path string true "Release name"
// @Param version query int true "Release revision"
// @Success 200 {object} httputils.Response
// @Failure 400 {object} httputils.Response
// @Failure 404 {object} httputils.Response
// @Failure 500 {object} httputils.Response
// @Router /helm/releases/rollback/{cluster}/{namespace}/{name} [post]
func (hr *helmRouter) RollbackRelease(c *gin.Context) {
r := httputils.NewResponse()
var (
err error
helmMeta types.PixiuObjectMeta
reverionMeta types.ReleaseHistory
)
if err = httputils.ShouldBindAny(c, nil, &helmMeta, &reverionMeta); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err = hr.c.Helm().Release(helmMeta.Cluster, helmMeta.Namespace).Rollback(c, helmMeta.Name, reverionMeta.Version); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
================================================
FILE: api/server/router/helm/respository_routes.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package helm
import (
"github.com/gin-gonic/gin"
"github.com/caoyingjunz/pixiu/api/server/httputils"
"github.com/caoyingjunz/pixiu/pkg/types"
)
// createRepository creates a new repository in the specified cluster
//
// @Summary create a repository
// @Description creates a new repository in the specified Kubernetes cluster
// @Tags repositories
// @Accept json
// @Produce json
// @Param cluster query string true "Kubernetes cluster name"
// @Param body body types.RepoForm true "Repository information"
// @Success 200 {object} httputils.Response
// @Failure 400 {object} httputils.Response
// @Failure 500 {object} httputils.Response
// @Router /repositories [post]
func (hr *helmRouter) createRepository(c *gin.Context) {
r := httputils.NewResponse()
var (
err error
req types.CreateRepository
)
if err = httputils.ShouldBindAny(c, &req, nil, nil); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err = hr.c.Helm().Repository().Create(c, &req); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
// deleteRepository deletes a repository by its ID
//
// @Summary delete a repository by ID
// @Description deletes a repository from the system using the provided ID
// @Tags repositories
// @Accept json
// @Produce json
// @Param id path int true "Repository ID"
// @Success 200 {object} httputils.Response
// @Failure 400 {object} httputils.Response
// @Failure 404 {object} httputils.Response
// @Failure 500 {object} httputils.Response
// @Router /repositories/{id} [delete]
func (hr *helmRouter) deleteRepository(c *gin.Context) {
r := httputils.NewResponse()
var (
err error
repoMeta types.RepoId
)
if err = c.ShouldBindUri(&repoMeta); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err = hr.c.Helm().Repository().Delete(c, repoMeta.Id); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
// updateRepository updates a repository by its ID
//
// @Summary update a repository by ID
// @Description updates a repository in the system using the provided ID and update information
// @Tags repositories
// @Accept json
// @Produce json
// @Param id path int true "Repository ID"
// @Param body body types.RepoUpdateForm true "Repository update information"
// @Success 200 {object} httputils.Response
// @Failure 400 {object} httputils.Response
// @Failure 404 {object} httputils.Response
// @Failure 500 {object} httputils.Response
func (hr *helmRouter) updateRepository(c *gin.Context) {
r := httputils.NewResponse()
var (
err error
repoMeta types.RepoId
formData types.UpdateRepository
)
if err = httputils.ShouldBindAny(c, &formData, &repoMeta, nil); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err = hr.c.Helm().Repository().Update(c, repoMeta.Id, &formData); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
// getRepository retrieves a repository by its ID
//
// @Summary get a repository by ID
// @Description retrieves a repository from the system using the provided ID
// @Tags repositories
// @Accept json
// @Produce json
// @Param id path int true "Repository ID"
// @Success 200 {object} httputils.Response{result=types.Repository}
// @Failure 400 {object} httputils.Response
// @Failure 404 {object} httputils.Response
// @Failure 500 {object} httputils.Response
// @Router /repositories/{id} [get]
func (hr *helmRouter) getRepository(c *gin.Context) {
r := httputils.NewResponse()
var (
err error
repoMeta types.RepoId
)
if err = c.ShouldBindUri(&repoMeta); err != nil {
httputils.SetFailed(c, r, err)
return
}
if r.Result, err = hr.c.Helm().Repository().Get(c, repoMeta.Id); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
// listRepositories retrieves a list of all repositories
//
// @Summary list repositories
// @Description retrieves a list of all repositories in the system
// @Tags repositories
// @Accept json
// @Produce json
// @Success 200 {object} httputils.Response{result=[]types.Repository}
// @Failure 400 {object} httputils.Response
// @Failure 500 {object} httputils.Response
// @Router /repositories [get]
func (hr *helmRouter) listRepositories(c *gin.Context) {
r := httputils.NewResponse()
var err error
if r.Result, err = hr.c.Helm().Repository().List(c); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
// getRepoCharts retrieves charts of a repository by its ID
//
// @Summary get repository charts by ID
// @Description retrieves charts associated with a repository from the system using the provided ID
// @Tags repositories
// @Accept json
// @Produce json
// @Param id path int true "Repository ID"
// @Success 200 {object} httputils.Response{result=model.ChartIndex}
// @Failure 400 {object} httputils.Response
// @Failure 404 {object} httputils.Response
// @Failure 500 {object} httputils.Response
// @Router /repositories/{id}/charts [get]
func (hr *helmRouter) getRepoCharts(c *gin.Context) {
r := httputils.NewResponse()
var (
err error
repoMeta types.RepoId
)
if err = c.ShouldBindUri(&repoMeta); err != nil {
httputils.SetFailed(c, r, err)
return
}
if r.Result, err = hr.c.Helm().Repository().GetChartsById(c, repoMeta.Id); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
// getRepoChartsByURL retrieves charts of a repository by its URL
//
// @Summary get repository charts by URL
// @Description retrieves charts associated with a repository from the system using the provided URL
// @Tags repositories
// @Accept json
// @Produce json
// @Param url query string true "Repository URL"
// @Success 200 {object} httputils.Response{result=model.ChartIndex}
// @Failure 400 {object} httputils.Response
// @Failure 404 {object} httputils.Response
// @Failure 500 {object} httputils.Response
// @Router /repositories/charts [get]
func (hr *helmRouter) getRepoChartsByURL(c *gin.Context) {
r := httputils.NewResponse()
var (
err error
repoMeta types.RepoURL
)
if err = httputils.ShouldBindAny(c, nil, nil, &repoMeta); err != nil {
httputils.SetFailed(c, r, err)
return
}
if r.Result, err = hr.c.Helm().Repository().GetChartsByURL(c, repoMeta.Url); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
// getChartValues retrieves the values of a specific chart version
//
// @Summary get chart values
// @Description retrieves values for a specific chart version using the provided chart name and version
// @Tags charts
// @Accept json
// @Produce json
// @Param chart query string true "Chart name"
// @Param version query string true "Chart version"
// @Success 200 {object} httputils.Response{result=types.ChartValues}
// @Failure 400 {object} httputils.Response
// @Failure 404 {object} httputils.Response
// @Failure 500 {object} httputils.Response
// @Router /repositories/chartvalues [get]
func (hr *helmRouter) getChartValues(c *gin.Context) {
r := httputils.NewResponse()
var (
err error
repoMeta types.ChartValues
)
if err = httputils.ShouldBindAny(c, nil, nil, &repoMeta); err != nil {
httputils.SetFailed(c, r, err)
return
}
if r.Result, err = hr.c.Helm().Repository().GetChartValues(c, repoMeta.Chart, repoMeta.Version); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
================================================
FILE: api/server/router/plan/config_routes.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plan
import (
"github.com/gin-gonic/gin"
"github.com/caoyingjunz/pixiu/api/server/httputils"
"github.com/caoyingjunz/pixiu/pkg/types"
)
type planConfigMeta struct {
planMeta
ConfigId int64 `uri:"configId" binding:"required"`
}
func (t *planRouter) createPlanConfig(c *gin.Context) {
r := httputils.NewResponse()
var (
opt planMeta
req types.CreatePlanConfigRequest
err error
)
if err = httputils.ShouldBindAny(c, &req, &opt, nil); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err = t.c.Plan().CreateConfig(c, opt.PlanId, &req); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
func (t *planRouter) updatePlanConfig(c *gin.Context) {
r := httputils.NewResponse()
var (
opt planConfigMeta
req types.UpdatePlanConfigRequest
err error
)
if err = httputils.ShouldBindAny(c, &req, &opt, nil); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err = t.c.Plan().UpdateConfig(c, opt.PlanId, opt.ConfigId, &req); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
func (t *planRouter) deletePlanConfig(c *gin.Context) {
r := httputils.NewResponse()
var (
opt planConfigMeta
err error
)
if err = httputils.ShouldBindAny(c, nil, &opt, nil); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err = t.c.Plan().DeleteConfig(c, opt.PlanId, opt.ConfigId); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
func (t *planRouter) getPlanConfig(c *gin.Context) {
r := httputils.NewResponse()
var (
opt planMeta
err error
)
if err = httputils.ShouldBindAny(c, nil, &opt, nil); err != nil {
httputils.SetFailed(c, r, err)
return
}
if r.Result, err = t.c.Plan().GetConfig(c, opt.PlanId); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
================================================
FILE: api/server/router/plan/node_routes.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plan
import (
"github.com/gin-gonic/gin"
"github.com/caoyingjunz/pixiu/api/server/httputils"
"github.com/caoyingjunz/pixiu/pkg/types"
)
type planNodeMeta struct {
planMeta `json:",inline"`
NodeId int64 `uri:"nodeId" binding:"required"`
}
func (t *planRouter) createPlanNode(c *gin.Context) {
r := httputils.NewResponse()
var (
opt planMeta
req types.CreatePlanNodeRequest
err error
)
if err = httputils.ShouldBindAny(c, &req, &opt, nil); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err = t.c.Plan().CreateNode(c, opt.PlanId, &req); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
func (t *planRouter) updatePlanNode(c *gin.Context) {
r := httputils.NewResponse()
var (
opt planNodeMeta
req types.UpdatePlanNodeRequest
err error
)
if err = httputils.ShouldBindAny(c, &req, &opt, nil); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err = t.c.Plan().UpdateNode(c, opt.PlanId, opt.NodeId, &req); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
func (t *planRouter) deletePlanNode(c *gin.Context) {
r := httputils.NewResponse()
var (
opt planNodeMeta
err error
)
if err = httputils.ShouldBindAny(c, nil, &opt, nil); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err = t.c.Plan().DeleteNode(c, opt.PlanId, opt.NodeId); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
func (t *planRouter) getPlanNode(c *gin.Context) {
r := httputils.NewResponse()
var (
opt planNodeMeta
err error
)
if err = httputils.ShouldBindAny(c, nil, &opt, nil); err != nil {
httputils.SetFailed(c, r, err)
return
}
if r.Result, err = t.c.Plan().GetNode(c, opt.PlanId, opt.NodeId); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
func (t *planRouter) listPlanNodes(c *gin.Context) {
r := httputils.NewResponse()
var (
opt planMeta
err error
)
if err = httputils.ShouldBindAny(c, nil, &opt, nil); err != nil {
httputils.SetFailed(c, r, err)
return
}
if r.Result, err = t.c.Plan().ListNodes(c, opt.PlanId); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
================================================
FILE: api/server/router/plan/plan.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plan
import (
"github.com/gin-gonic/gin"
"github.com/caoyingjunz/pixiu/cmd/app/options"
"github.com/caoyingjunz/pixiu/pkg/controller"
)
type planRouter struct {
c controller.PixiuInterface
}
func NewRouter(o *options.Options) {
router := &planRouter{
c: o.Controller,
}
router.initRoutes(o.HttpEngine)
}
func (t *planRouter) initRoutes(ginEngine *gin.Engine) {
planRoute := ginEngine.Group("/pixiu/plans")
{
planRoute.POST("", t.createPlan)
planRoute.PUT("/:planId", t.updatePlan)
planRoute.DELETE("/:planId", t.deletePlan)
planRoute.GET("/:planId", t.getPlan)
planRoute.GET("", t.listPlans)
planRoute.GET("/:planId/resources", t.getPlanWithSubResources)
// 启动部署任务
planRoute.POST("/:planId/start", t.startPlan)
// 终止部署任务
planRoute.POST("/:planId/stop", t.stopPlan)
// 部署计划的节点API
planRoute.POST("/:planId/nodes", t.createPlanNode)
planRoute.PUT("/:planId/nodes/:nodeId", t.updatePlanNode)
planRoute.DELETE("/:planId/nodes/:nodeId", t.deletePlanNode)
planRoute.GET("/:planId/nodes/:nodeId", t.getPlanNode)
planRoute.GET("/:planId/nodes", t.listPlanNodes)
// 部署计划的部署配置
planRoute.POST("/:planId/configs", t.createPlanConfig)
planRoute.PUT("/:planId/configs/:configId", t.updatePlanConfig)
planRoute.DELETE("/:planId/configs/:configId", t.deletePlanConfig)
planRoute.GET("/:planId/configs", t.getPlanConfig)
// 执行指定任务
planRoute.POST("/:planId/tasks/:taskId", t.runTasks)
// 查询任务列表
planRoute.GET("/:planId/tasks", t.listTasks)
// 实时查询任务进度
planRoute.GET("/:planId/tasks/:taskId/logs", t.watchTaskLog)
// 获取 os 与 os version
planRoute.GET("/distributions", t.getDistributions)
}
}
================================================
FILE: api/server/router/plan/plan_routes.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plan
import (
"github.com/gin-gonic/gin"
"github.com/caoyingjunz/pixiu/api/server/httputils"
"github.com/caoyingjunz/pixiu/pkg/types"
)
type planMeta struct {
PlanId int64 `uri:"planId" binding:"required"`
}
type watchTaskLogMeta struct {
PlanId int64 `uri:"planId" binding:"required"`
TaskId int64 `uri:"taskId" binding:"required"`
}
type WatchMeta struct {
Watch bool `form:"watch"`
}
// 创建部署计划,同时创建配置和节点
func (t *planRouter) createPlan(c *gin.Context) {
r := httputils.NewResponse()
var req types.CreatePlanRequest
if err := c.ShouldBindJSON(&req); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err := t.c.Plan().Create(c, &req); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
func (t *planRouter) updatePlan(c *gin.Context) {
r := httputils.NewResponse()
var (
opt planMeta
req types.UpdatePlanRequest
err error
)
if err = httputils.ShouldBindAny(c, &req, &opt, nil); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err = t.c.Plan().Update(c, opt.PlanId, &req); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
func (t *planRouter) deletePlan(c *gin.Context) {
r := httputils.NewResponse()
var (
opt planMeta
err error
)
if err = c.ShouldBindUri(&opt); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err = t.c.Plan().Delete(c, opt.PlanId); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
func (t *planRouter) getPlan(c *gin.Context) {
r := httputils.NewResponse()
var (
opt planMeta
err error
)
if err = c.ShouldBindUri(&opt); err != nil {
httputils.SetFailed(c, r, err)
return
}
if r.Result, err = t.c.Plan().Get(c, opt.PlanId); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
// getPlanWithSubResources
// 获取 plan
// 获取 configs
// 获取 nodes
func (t *planRouter) getPlanWithSubResources(c *gin.Context) {
r := httputils.NewResponse()
var (
opt planMeta
err error
)
if err = c.ShouldBindUri(&opt); err != nil {
httputils.SetFailed(c, r, err)
return
}
if r.Result, err = t.c.Plan().GetWithSubResources(c, opt.PlanId); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
func (t *planRouter) listPlans(c *gin.Context) {
r := httputils.NewResponse()
var (
req types.ListPlanRequest
err error
)
if err = c.ShouldBindQuery(&req); err != nil {
httputils.SetFailed(c, r, err)
return
}
if r.Result, err = t.c.Plan().List(c, &req); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
func (t *planRouter) startPlan(c *gin.Context) {
r := httputils.NewResponse()
var (
opt planMeta
err error
)
if err = c.ShouldBindUri(&opt); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err = t.c.Plan().Start(c, opt.PlanId); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
func (t *planRouter) stopPlan(c *gin.Context) {
r := httputils.NewResponse()
var (
opt planMeta
err error
)
if err = c.ShouldBindUri(&opt); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err = t.c.Plan().Stop(c, opt.PlanId); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
type DistributionsMeta struct {
Centos []string `json:"centos,omitempty"`
Ubuntu []string `json:"ubuntu,omitempty"`
Debian []string `json:"debian,omitempty"`
OpenEuler []string `json:"openEuler,omitempty"`
Rocky []string `json:"rocky,omitempty"`
}
func (t *planRouter) getDistributions(c *gin.Context) {
r := httputils.NewResponse()
r.Result = &DistributionsMeta{
Centos: []string{"centos7"},
Ubuntu: []string{"ubuntu18.04", "ubuntu20.04", "ubuntu22.04"},
Debian: []string{"debian10", "debian11"},
OpenEuler: []string{"openEuler22.03"},
Rocky: []string{"rocky8.5", "rocky9.2", "rocky9.3"},
}
httputils.SetSuccess(c, r)
}
================================================
FILE: api/server/router/plan/task_routes.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plan
import (
"github.com/gin-gonic/gin"
"github.com/caoyingjunz/pixiu/api/server/httputils"
)
type taskNodeMeta struct {
planMeta `json:",inline"`
TaskId int64 `uri:"taskId" binding:"required"`
}
func (t *planRouter) runTasks(c *gin.Context) {
r := httputils.NewResponse()
httputils.SetSuccess(c, r)
}
func (t *planRouter) listTasks(c *gin.Context) {
r := httputils.NewResponse()
var (
opt planMeta
watch WatchMeta
err error
)
if err = httputils.ShouldBindAny(c, nil, &opt, &watch); err != nil {
httputils.SetFailed(c, r, err)
return
}
// 不是长连接请求则直接返回
if !watch.Watch {
if r.Result, err = t.c.Plan().ListTasks(c, opt.PlanId); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
return
}
// 长连接请求
t.c.Plan().WatchTasks(c, opt.PlanId, c.Writer, c.Request)
}
func (t *planRouter) watchTaskLog(c *gin.Context) {
r := httputils.NewResponse()
var (
opt watchTaskLogMeta
err error
)
if err = httputils.ShouldBindAny(c, nil, &opt, nil); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err = t.c.Plan().WatchTaskLog(c, opt.PlanId, opt.TaskId, c.Writer, c.Request); err != nil {
httputils.SetFailed(c, r, err)
return
}
}
================================================
FILE: api/server/router/proxy/helper.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package proxy
import (
"strings"
"github.com/gin-gonic/gin"
)
// IsProxyPath returns true when the request path is a proxy one.
func IsProxyPath(c *gin.Context) bool {
return strings.HasPrefix(c.Request.URL.Path, proxyBaseURL)
}
================================================
FILE: api/server/router/proxy/proxy.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package proxy
import (
"context"
"fmt"
"net/url"
"github.com/gin-gonic/gin"
"k8s.io/apimachinery/pkg/util/proxy"
"k8s.io/client-go/rest"
"github.com/caoyingjunz/pixiu/api/server/httputils"
"github.com/caoyingjunz/pixiu/cmd/app/options"
"github.com/caoyingjunz/pixiu/pkg/controller"
)
const (
proxyBaseURL = "/pixiu/proxy"
)
type proxyRouter struct {
c controller.PixiuInterface
}
func NewRouter(o *options.Options) {
s := &proxyRouter{
c: o.Controller,
}
s.initRoutes(o.HttpEngine)
}
func (p *proxyRouter) initRoutes(ginEngine *gin.Engine) {
proxyRoute := ginEngine.Group("/pixiu/")
{
proxyRoute.Any("/proxy/:clusterName/*act", p.proxyHandler)
}
}
func (p *proxyRouter) proxyHandler(c *gin.Context) {
resp := httputils.NewResponse()
var cluster struct {
Name string `uri:"clusterName" binding:"required"`
}
if err := c.ShouldBindUri(&cluster); err != nil {
httputils.SetFailed(c, resp, err)
return
}
name := cluster.Name
config, err := p.c.Cluster().GetKubeConfigByName(context.TODO(), name)
if err != nil {
httputils.SetFailed(c, resp, fmt.Errorf("failed to get cluster %q kubeconfig", name))
return
}
transport, err := rest.TransportFor(config)
if err != nil {
httputils.SetFailed(c, resp, err)
return
}
target, err := p.parseTarget(*c.Request.URL, config.Host, name)
if err != nil {
httputils.SetFailed(c, resp, err)
return
}
httpProxy := proxy.NewUpgradeAwareHandler(target, transport, false, false, nil)
httpProxy.UpgradeTransport = proxy.NewUpgradeRequestRoundTripper(transport, transport)
httpProxy.ServeHTTP(c.Writer, c.Request)
}
func (p *proxyRouter) parseTarget(target url.URL, host string, name string) (*url.URL, error) {
kubeURL, err := url.Parse(host)
if err != nil {
return nil, err
}
// TODO: 检查 URL 是否规范
target.Path = target.Path[len(proxyBaseURL+"/"+name):]
target.Host = kubeURL.Host
target.Scheme = kubeURL.Scheme
return &target, nil
}
================================================
FILE: api/server/router/router.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package router
import (
"embed"
"net/http"
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
// 导入 docs.json 文件
_ "github.com/caoyingjunz/pixiu/api/docs"
_ "github.com/caoyingjunz/pixiu/api/server/validator"
"github.com/caoyingjunz/pixiu/api/server/middleware"
"github.com/caoyingjunz/pixiu/api/server/router/audit"
"github.com/caoyingjunz/pixiu/api/server/router/auth"
"github.com/caoyingjunz/pixiu/api/server/router/cluster"
"github.com/caoyingjunz/pixiu/api/server/router/helm"
"github.com/caoyingjunz/pixiu/api/server/router/plan"
"github.com/caoyingjunz/pixiu/api/server/router/proxy"
"github.com/caoyingjunz/pixiu/api/server/router/tenant"
"github.com/caoyingjunz/pixiu/api/server/router/user"
"github.com/caoyingjunz/pixiu/cmd/app/options"
"github.com/caoyingjunz/pixiu/pkg/static"
)
type RegisterFunc func(o *options.Options)
//go:embed static
var EmbedFS embed.FS
func InstallRouters(o *options.Options) {
fs := []RegisterFunc{
middleware.InstallMiddlewares,
cluster.NewRouter,
helm.NewRouter,
proxy.NewRouter,
tenant.NewRouter,
user.NewRouter,
plan.NewRouter,
audit.NewRouter,
auth.NewRouter,
}
install(o, fs...)
// StaticFiles 启用前端集成
o.HttpEngine.Use(static.Serve("/", static.LocalFile(o.ComponentConfig.Default.StaticFiles, true)))
// 启动健康检查
o.HttpEngine.GET("/healthz", func(c *gin.Context) { c.String(http.StatusOK, "ok") })
// 启动 APIs 服务
o.HttpEngine.GET("/api-ref/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}
func install(o *options.Options, fs ...RegisterFunc) {
for _, f := range fs {
f(o)
}
}
================================================
FILE: api/server/router/static/index.html
================================================
欢迎使用 Pixiu
欢迎使用 Pixiu
================================================
FILE: api/server/router/tenant/tenant.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tenant
import (
"github.com/gin-gonic/gin"
"github.com/caoyingjunz/pixiu/cmd/app/options"
"github.com/caoyingjunz/pixiu/pkg/controller"
)
type tenantRouter struct {
c controller.PixiuInterface
}
func NewRouter(o *options.Options) {
router := &tenantRouter{
c: o.Controller,
}
router.initRoutes(o.HttpEngine)
}
func (t *tenantRouter) initRoutes(ginEngine *gin.Engine) {
tenantRoute := ginEngine.Group("/pixiu/tenants")
{
tenantRoute.POST("", t.createTenant)
tenantRoute.PUT("/:tenantId", t.updateTenant)
tenantRoute.DELETE("/:tenantId", t.deleteTenant)
tenantRoute.GET("/:tenantId", t.getTenant)
tenantRoute.GET("", t.listTenants)
}
}
================================================
FILE: api/server/router/tenant/tenant_routes.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tenant
import (
"github.com/gin-gonic/gin"
"github.com/caoyingjunz/pixiu/api/server/httputils"
"github.com/caoyingjunz/pixiu/pkg/types"
)
type TenantMeta struct {
TenantId int64 `uri:"tenantId" binding:"required"`
}
func (t *tenantRouter) createTenant(c *gin.Context) {
r := httputils.NewResponse()
var req types.CreateTenantRequest
if err := c.ShouldBindJSON(&req); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err := t.c.Tenant().Create(c, &req); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
func (t *tenantRouter) updateTenant(c *gin.Context) {
r := httputils.NewResponse()
var (
opt TenantMeta
err error
)
if err = c.ShouldBindUri(&opt); err != nil {
httputils.SetFailed(c, r, err)
return
}
var req types.UpdateTenantRequest
if err = c.ShouldBindJSON(&req); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err = t.c.Tenant().Update(c, opt.TenantId, &req); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
func (t *tenantRouter) deleteTenant(c *gin.Context) {
r := httputils.NewResponse()
var (
opt TenantMeta
err error
)
if err = c.ShouldBindUri(&opt); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err = t.c.Tenant().Delete(c, opt.TenantId); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
func (t *tenantRouter) getTenant(c *gin.Context) {
r := httputils.NewResponse()
var (
opt TenantMeta
err error
)
if err = c.ShouldBindUri(&opt); err != nil {
httputils.SetFailed(c, r, err)
return
}
if r.Result, err = t.c.Tenant().Get(c, opt.TenantId); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
func (t *tenantRouter) listTenants(c *gin.Context) {
r := httputils.NewResponse()
var err error
if r.Result, err = t.c.Tenant().List(c); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
================================================
FILE: api/server/router/user/user.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package user
import (
"github.com/gin-gonic/gin"
"github.com/caoyingjunz/pixiu/cmd/app/options"
"github.com/caoyingjunz/pixiu/pkg/controller"
)
type userRouter struct {
c controller.PixiuInterface
}
func NewRouter(o *options.Options) {
router := &userRouter{
c: o.Controller,
}
router.initRoutes(o.HttpEngine)
}
func (u *userRouter) initRoutes(httpEngine *gin.Engine) {
// TODO: Base pixiu 后续作为常量定义
userRoute := httpEngine.Group("/pixiu/users")
{
userRoute.POST("", u.createUser)
userRoute.PUT("/:userId", u.updateUser)
userRoute.DELETE("/:userId", u.deleteUser)
userRoute.GET("/:userId", u.getUser)
userRoute.GET("", u.listUsers)
// 用户修改密码或者管理员重置密码
userRoute.PUT("/:userId/password", u.updatePassword)
// 用户的登陆或者退出
userRoute.POST("/login", u.login)
userRoute.POST("/:userId/logout", u.logout)
}
}
================================================
FILE: api/server/router/user/user_routes.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package user
import (
"github.com/gin-gonic/gin"
"github.com/caoyingjunz/pixiu/api/server/httputils"
"github.com/caoyingjunz/pixiu/pkg/types"
)
type IdMeta struct {
UserId int64 `uri:"userId" binding:"required"`
}
// CreateUser godoc
//
// @Summary Create a user
// @Description Create by a json user
// @Tags Users
// @Accept json
// @Produce json
// @Param user body types.CreateUserRequest true "Create user"
// @Success 200 {object} httputils.Response
// @Failure 400 {object} httputils.Response
// @Failure 404 {object} httputils.Response
// @Failure 500 {object} httputils.Response
// @Router /pixiu/users/ [post]
// @Security Bearer
func (u *userRouter) createUser(c *gin.Context) {
r := httputils.NewResponse()
var (
req types.CreateUserRequest
err error
)
if err = c.ShouldBindJSON(&req); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err = u.c.User().Create(c, &req); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
// UpdateUser godoc
//
// @Summary Update an user
// @Description Update by json user
// @Tags Users
// @Accept json
// @Produce json
// @Param userId path int true "User ID"
// @Param user body types.UpdateUserRequest true "Update user"
// @Success 200 {object} httputils.Response
// @Failure 400 {object} httputils.Response
// @Failure 404 {object} httputils.Response
// @Failure 500 {object} httputils.Response
// @Router /pixiu/users/{userId} [put]
// @Security Bearer
func (u *userRouter) updateUser(c *gin.Context) {
r := httputils.NewResponse()
var (
idMeta IdMeta
req types.UpdateUserRequest
err error
)
if err = httputils.ShouldBindAny(c, &req, &idMeta, nil); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err = u.c.User().Update(c, idMeta.UserId, &req); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
// UpdateUserPassword godoc
//
// @Summary Update user password
// @Description Update by json user
// @Tags Users
// @Accept json
// @Produce json
// @Param userId path int true "User ID"
// @Param user body types.UpdateUserPasswordRequest true "Update user password"
// @Success 200 {object} httputils.Response
// @Failure 400 {object} httputils.Response
// @Failure 404 {object} httputils.Response
// @Failure 500 {object} httputils.Response
// @Router /pixiu/users/password [put]
// @Security Bearer
func (u *userRouter) updatePassword(c *gin.Context) {
r := httputils.NewResponse()
var (
idMeta IdMeta
req types.UpdateUserPasswordRequest
err error
)
if err = httputils.ShouldBindAny(c, &req, &idMeta, nil); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err = u.c.User().UpdatePassword(c, idMeta.UserId, &req); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
// DeleteUser godoc
//
// @Summary Delete user by userId
// @Description Delete by userID
// @Tags Users
// @Accept json
// @Produce json
// @Param userId path int true "User ID"
// @Success 200 {object} httputils.Response
// @Failure 400 {object} httputils.Response
// @Failure 404 {object} httputils.Response
// @Failure 500 {object} httputils.Response
// @Router /pixiu/users/{userId} [delete]
// @Security Bearer
func (u *userRouter) deleteUser(c *gin.Context) {
r := httputils.NewResponse()
var (
idMeta IdMeta
err error
)
if err = c.ShouldBindUri(&idMeta); err != nil {
httputils.SetFailed(c, r, err)
return
}
if err = u.c.User().Delete(c, idMeta.UserId); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
// Getuser godoc
//
// @Summary Get user by userId
// @Description Get by user ID
// @Tags Users
// @Accept json
// @Produce json
// @Param userId path int true "User ID"
// @Success 200 {object} httputils.Response{result=types.User}
// @Failure 400 {object} httputils.Response
// @Failure 404 {object} httputils.Response
// @Failure 500 {object} httputils.Response
// @Router /pixiu/users/{userId} [get]
// @Security Bearer
func (u *userRouter) getUser(c *gin.Context) {
r := httputils.NewResponse()
var (
idMeta IdMeta
err error
)
if err = c.ShouldBindUri(&idMeta); err != nil {
httputils.SetFailed(c, r, err)
return
}
if r.Result, err = u.c.User().Get(c, idMeta.UserId); err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
// Listusers godoc
//
// @Summary List users
// @Description List users
// @Tags Users
// @Accept json
// @Produce json
// @Success 200 {array} httputils.Response{result=[]types.User}
// @Failure 400 {object} httputils.Response
// @Failure 404 {object} httputils.Response
// @Failure 500 {object} httputils.Response
// @Router /pixiu/users [get]
// @Security Bearer
func (u *userRouter) listUsers(c *gin.Context) {
r := httputils.NewResponse()
var req types.ListUserRequest
if err := c.ShouldBindQuery(&req); err != nil {
httputils.SetFailed(c, r, err)
return
}
var err error
r.Result, err = u.c.User().List(c, &req)
if err != nil {
httputils.SetFailed(c, r, err)
return
}
httputils.SetSuccess(c, r)
}
// Login godoc
//
// @Summary User login
// @Description Login by a json user
// @Tags Login
// @Accept json
// @Produce json
// @Param user body types.LoginRequest true "User login"
// @Success 200 {object} httputils.Response
// @Failure 400 {object} httputils.Response
// @Failure 404 {object} httputils.Response
// @Failure 500 {object} httputils.Response
// @Router /pixiu/users/login [post]
func (u *userRouter) login(c *gin.Context) {
r := httputils.NewResponse()
var (
req types.LoginRequest
err error
)
if err = c.ShouldBindJSON(&req); err != nil {
httputils.SetFailed(c, r, err)
return
}
loginResp, err := u.c.User().Login(c, &req)
if err != nil {
httputils.SetFailed(c, r, err)
return
}
r.Result = loginResp
httputils.SetUserToContext(c, loginResp.User)
httputils.SetSuccess(c, r)
}
// TODO
func (u *userRouter) logout(c *gin.Context) {
r := httputils.NewResponse()
httputils.SetSuccess(c, r)
}
================================================
FILE: api/server/validator/helper.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package validator
import (
"strings"
"github.com/go-playground/validator/v10"
)
// TranslateError returns the translated message of the validation error.
func TranslateError(errs validator.ValidationErrors) string {
messages := make([]string, len(errs))
for i, err := range errs {
messages[i] = err.Translate(tran)
}
return strings.Join(messages, "; ")
}
================================================
FILE: api/server/validator/password.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package validator
import (
"github.com/go-playground/validator/v10"
"github.com/caoyingjunz/pixiu/pkg/util"
)
func init() {
register(
&passwordValidator{pixiuValidator: newPixiuValidator("password", "密码不符合要求,至少包含一个大写字母、一个小写字母、一个数字")},
)
}
// passwordValidator is a customized validator for validating user password.
type passwordValidator struct {
pixiuValidator
}
// validate validates the password in request.
func (pv *passwordValidator) validate(fl validator.FieldLevel) bool {
return util.ValidateStrongPassword(fl.Field().String())
}
================================================
FILE: api/server/validator/rbac.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package validator
import (
"strconv"
"github.com/caoyingjunz/pixiu/pkg/db/model"
"github.com/go-playground/validator/v10"
)
func init() {
register(
&objectValidator{pixiuValidator: newPixiuValidator("rbac_object", "对象类型不支持")},
&operationValidator{pixiuValidator: newPixiuValidator("rbac_operation", "操作不支持")},
&stringIDValidator{pixiuValidator: newPixiuValidator("rbac_sid", "不合法")},
)
}
type objectValidator struct {
pixiuValidator
}
func (ov *objectValidator) validate(fl validator.FieldLevel) bool {
obj := fl.Field().Interface().(model.ObjectType)
_, ok := model.ObjectTypeMap[obj]
return ok
}
type operationValidator struct {
pixiuValidator
}
func (ov *operationValidator) validate(fl validator.FieldLevel) bool {
op := fl.Field().Interface().(model.Operation)
_, ok := model.OperationMap[op]
return ok
}
type stringIDValidator struct {
pixiuValidator
}
func (sv *stringIDValidator) validate(fl validator.FieldLevel) bool {
sid := fl.Field().String()
if sid == "*" {
return true
}
_, err := strconv.Atoi(sid)
return err == nil
}
================================================
FILE: api/server/validator/validator.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package validator
import (
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
zt "github.com/go-playground/validator/v10/translations/zh"
)
type customValidator interface {
getTag() string
translateError(ut ut.Translator) error
translate(ut ut.Translator, fe validator.FieldError) string
// Should be implemented by the custom validator.
validate(fl validator.FieldLevel) bool
}
var tran ut.Translator
var customValidators []customValidator
// register adds a new custom validator to the validator list
func register(validators ...customValidator) {
customValidators = append(customValidators, validators...)
}
func init() {
_zh := zh.New() // default is Chinese
uni := ut.New(_zh, _zh)
tran, _ = uni.GetTranslator("zh")
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
_ = zt.RegisterDefaultTranslations(v, tran)
for _, c := range customValidators {
_ = v.RegisterValidation(c.getTag(), c.validate)
_ = v.RegisterTranslation(c.getTag(), tran, c.translateError, c.translate)
}
}
}
type pixiuValidator struct {
tag string
err string
}
func newPixiuValidator(tag, err string) pixiuValidator {
return pixiuValidator{
tag: tag,
err: err,
}
}
func (c pixiuValidator) getTag() string {
return c.tag
}
func (c pixiuValidator) translateError(ut ut.Translator) error {
return ut.Add(c.tag, "{0}"+c.err, true)
}
func (c pixiuValidator) translate(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T(c.tag, fe.Field())
return t
}
================================================
FILE: cmd/app/config/config.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"fmt"
"github.com/caoyingjunz/pixiu/pkg/jobmanager"
logutil "github.com/caoyingjunz/pixiu/pkg/util/log"
)
type Mode string
const (
DebugMode Mode = "debug"
ReleaseMode Mode = "release"
)
func (m Mode) InDebug() bool {
return m == DebugMode
}
type Config struct {
Default DefaultOptions `yaml:"default"`
Mysql MysqlOptions `yaml:"mysql"`
Worker WorkerOptions `yaml:"worker"`
Audit jobmanager.AuditOptions `yaml:"audit"`
TLS *TLS `yaml:"tls"`
}
type DefaultOptions struct {
Mode Mode `yaml:"mode"`
Listen int `yaml:"listen"`
JWTKey string `yaml:"jwt_key"`
// 自动创建指定模型的数据库表结构,不会更新已存在的数据库表
AutoMigrate bool `yaml:"auto_migrate"`
logutil.LogOptions `yaml:",inline"`
// 静态文件路径
StaticFiles string `yaml:"static_files"`
// 超级管理员初始化配置,留空则使用默认值
AdminUser string `yaml:"admin_user"`
AdminPassword string `yaml:"admin_password"`
}
func (o DefaultOptions) Valid() error {
if err := o.LogOptions.Valid(); err != nil {
return err
}
return nil
}
// MysqlOptions 数据库具体配置
type MysqlOptions struct {
Host string `yaml:"host"`
User string `yaml:"user"`
Password string `yaml:"password"`
Port int `yaml:"port"`
Name string `yaml:"name"`
}
func (o MysqlOptions) Valid() error {
// TODO
return nil
}
type WorkerOptions struct {
WorkDir string `yaml:"work_dir"`
Engines []Engine `yaml:"engines"`
}
type Engine struct {
Image string `yaml:"image"`
OSSupported []string `yaml:"os_supported"`
}
func (w WorkerOptions) Valid() error {
// TODO
return nil
}
type TLS struct {
CertFile string `yaml:"cert_file"`
KeyFile string `yaml:"key_file"`
}
func (t *TLS) Valid() error {
if t != nil {
if len(t.CertFile) == 0 {
return fmt.Errorf("listen on tls, no cert_file found")
}
if len(t.KeyFile) == 0 {
return fmt.Errorf("listen on tls, no key_file found")
}
}
return nil
}
func (c *Config) Valid() (err error) {
if err = c.Default.Valid(); err != nil {
return
}
if err = c.Mysql.Valid(); err != nil {
return
}
if err = c.Worker.Valid(); err != nil {
return
}
if err = c.TLS.Valid(); err != nil {
return err
}
return
}
================================================
FILE: cmd/app/options/options.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package options
import (
"context"
"fmt"
"os"
"time"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
gormadapter "github.com/casbin/gorm-adapter/v3"
"github.com/gin-gonic/gin"
"github.com/spf13/cobra"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"k8s.io/klog/v2"
"github.com/caoyingjunz/pixiu/cmd/app/config"
"github.com/caoyingjunz/pixiu/pkg/controller"
pixiudb "github.com/caoyingjunz/pixiu/pkg/db"
pixiuModel "github.com/caoyingjunz/pixiu/pkg/db/model"
"github.com/caoyingjunz/pixiu/pkg/jobmanager"
"github.com/caoyingjunz/pixiu/pkg/types"
logutil "github.com/caoyingjunz/pixiu/pkg/util/log"
pixiuConfig "github.com/caoyingjunz/pixiulib/config"
)
const (
maxIdleConns = 10
maxOpenConns = 100
defaultListen = 8080
defaultTokenKey = "pixiu"
defaultConfigFile = "/etc/pixiu/config.yaml"
defaultLogFormat = logutil.LogFormatJson
defaultWorkDir = "/etc/pixiu"
defaultStaticDir = "/static"
defaultAdminUser = "admin"
defaultAdminPassword = "Pixiu123456!"
defaultSlowSQLDuration = 1 * time.Second
rulesTableName = "rules"
)
// Options has all the params needed to run a pixiu
type Options struct {
// The default values.
ComponentConfig config.Config
HttpEngine *gin.Engine
// 数据库接口
db *gorm.DB
Factory pixiudb.ShareDaoFactory
// 貔貅主控制接口
Controller controller.PixiuInterface
// ConfigFile is the location of the pixiu server's configuration file.
ConfigFile string
// Authorization enforcement and policy management
Enforcer *casbin.SyncedEnforcer
JobManager *jobmanager.Manager
}
func NewOptions() (*Options, error) {
return &Options{
HttpEngine: gin.Default(), // 初始化默认 api 路由
ConfigFile: defaultConfigFile,
}, nil
}
// Complete completes all the required options
func (o *Options) Complete() error {
// 配置文件优先级: 默认配置,环境变量,命令行
if len(o.ConfigFile) == 0 {
// Try to read config file path from env.
if cfgFile := os.Getenv("ConfigFile"); cfgFile != "" {
o.ConfigFile = cfgFile
} else {
o.ConfigFile = defaultConfigFile
}
}
c := pixiuConfig.New()
c.SetConfigFile(o.ConfigFile)
c.SetConfigType("yaml")
if err := c.Binding(&o.ComponentConfig); err != nil {
return err
}
// TODO: move to config initialization?
if o.ComponentConfig.Default.Listen == 0 {
o.ComponentConfig.Default.Listen = defaultListen
}
if len(o.ComponentConfig.Default.JWTKey) == 0 {
o.ComponentConfig.Default.JWTKey = defaultTokenKey
}
if o.ComponentConfig.Default.LogFormat == "" {
o.ComponentConfig.Default.LogFormat = defaultLogFormat
}
if o.ComponentConfig.Worker.WorkDir == "" {
o.ComponentConfig.Worker.WorkDir = defaultWorkDir
}
if len(o.ComponentConfig.Default.StaticFiles) == 0 {
o.ComponentConfig.Default.StaticFiles = defaultStaticDir
}
if o.ComponentConfig.Audit.Schedule == "" {
o.ComponentConfig.Audit.Schedule = jobmanager.DefaultSchedule
}
if o.ComponentConfig.Audit.DaysReserved == 0 {
o.ComponentConfig.Audit.DaysReserved = jobmanager.DefaultDaysReserved
}
if len(o.ComponentConfig.Default.AdminUser) == 0 {
o.ComponentConfig.Default.AdminUser = defaultAdminUser
}
if len(o.ComponentConfig.Default.AdminPassword) == 0 {
o.ComponentConfig.Default.AdminPassword = defaultAdminPassword
}
if err := o.ComponentConfig.Valid(); err != nil {
return err
}
o.ComponentConfig.Default.LogOptions.Init()
// 注册依赖组件
if err := o.register(); err != nil {
return err
}
o.Controller = controller.New(o.ComponentConfig, o.Factory, o.Enforcer)
if err := o.bootstrapRootUser(); err != nil {
return err
}
o.JobManager = jobmanager.NewManager(
&o.ComponentConfig.Default.LogOptions,
jobmanager.NewAuditsCleaner(o.ComponentConfig.Audit, o.Factory),
jobmanager.NewClusterSyncer(o.Factory),
)
return nil
}
// BindFlags binds the pixiu Configuration struct fields
func (o *Options) BindFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.ConfigFile, "configfile", defaultConfigFile, "The location of the pixiu configuration file")
}
func (o *Options) register() error {
// 注册数据库
if err := o.registerDatabase(); err != nil {
return err
}
// TODO: 注册其他依赖
if err := o.registerEnforcer(); err != nil {
return err
}
return nil
}
// This panics if o.db is nil.
func (o *Options) registerEnforcer() error {
// Casbin
a, err := gormadapter.NewAdapterByDBUseTableName(o.db, "", rulesTableName)
if err != nil {
return err
}
m, err := model.NewModelFromString(pixiuModel.RBACModel)
if err != nil {
return err
}
if o.Enforcer, err = casbin.NewSyncedEnforcer(m, a); err != nil {
return err
}
// Add an super admin policy.
_, err = o.Enforcer.AddPolicy(pixiuModel.AdminPolicy.Raw())
return err
}
func (o *Options) registerDatabase() error {
sqlConfig := o.ComponentConfig.Mysql
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local",
sqlConfig.User,
sqlConfig.Password,
sqlConfig.Host,
sqlConfig.Port,
sqlConfig.Name)
opt := &gorm.Config{
Logger: pixiudb.NewLogger(logger.Info, defaultSlowSQLDuration),
}
db, err := gorm.Open(mysql.Open(dsn), opt)
if err != nil {
return err
}
o.db = db
// 设置数据库连接池
sqlDB, err := db.DB()
if err != nil {
return err
}
sqlDB.SetMaxIdleConns(maxIdleConns)
sqlDB.SetMaxOpenConns(maxOpenConns)
o.Factory, err = pixiudb.NewDaoFactory(db, o.ComponentConfig.Default.AutoMigrate)
return err
}
// Validate validates all the required options.
func (o *Options) Validate() error {
// TODO
return nil
}
// bootstrapRootUser 启动时自动初始化超级管理员账户
// 若超管已存在则跳过,若不存在则使用配置文件中的用户名和密码创建
// 密码经由 Controller.User().Create() 内部调用 util.EncryptUserPassword() 加密后入库
func (o *Options) bootstrapRootUser() error {
ctx := context.Background()
root, err := o.Factory.User().GetRoot(ctx)
if err != nil {
return fmt.Errorf("failed to check root user: %v", err)
}
if root != nil {
klog.Info("root user already exists, skipping")
return nil
}
adminUser := o.ComponentConfig.Default.AdminUser
adminPassword := o.ComponentConfig.Default.AdminPassword
klog.Infof("initializing root user: %s", adminUser)
return o.Controller.User().Create(ctx, &types.CreateUserRequest{
Name: adminUser,
Password: adminPassword,
Role: pixiuModel.RoleRoot,
})
}
================================================
FILE: cmd/app/server.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package app
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/spf13/cobra"
"k8s.io/klog/v2"
"github.com/caoyingjunz/pixiu/api/server/router"
"github.com/caoyingjunz/pixiu/cmd/app/options"
)
func NewServerCommand(version string) *cobra.Command {
opts, err := options.NewOptions()
if err != nil {
klog.Fatalf("unable to initialize command options: %v", err)
}
cmd := &cobra.Command{
Use: "pixiu-server",
Long: "The pixiu server controller is a daemon that embeds the core control loops.",
Run: func(cmd *cobra.Command, args []string) {
if err = opts.Complete(); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
if err = opts.Validate(); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
if err = Run(opts); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
},
Args: func(cmd *cobra.Command, args []string) error {
for _, arg := range args {
if len(arg) > 0 {
return fmt.Errorf("%q does not take any arguments, got %q", cmd.CommandPath(), args)
}
}
return nil
},
}
// 绑定命令行参数
opts.BindFlags(cmd)
verCmd := &cobra.Command{
Use: "version",
Short: "Print the version",
Long: "Print version and exit.",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println(version)
},
}
cmd.AddCommand(verCmd)
return cmd
}
// Run 优雅启动貔貅服务
func Run(opt *options.Options) error {
srv := &http.Server{
Addr: fmt.Sprintf(":%d", opt.ComponentConfig.Default.Listen),
Handler: opt.HttpEngine,
}
// TODO: 暂未设置优雅退出
// 启动集群相关控制器
runers := []func(context.Context, int) error{opt.Controller.Plan().Run, opt.Controller.Cluster().Run}
for _, runner := range runers {
if err := runner(context.TODO(), 5); err != nil {
klog.Fatal("failed to start manager: ", err)
}
}
// 安装 http 路由
router.InstallRouters(opt)
// Initializing the server in a goroutine so that it won't block the graceful shutdown handling below
go func() {
var err error
if opt.ComponentConfig.TLS != nil {
klog.Info("starting pixiu server with TLS")
err = srv.ListenAndServeTLS(opt.ComponentConfig.TLS.CertFile, opt.ComponentConfig.TLS.KeyFile)
} else {
klog.Info("starting pixiu server with no TLS")
err = srv.ListenAndServe()
}
if err != nil && err != http.ErrServerClosed {
klog.Fatal("failed to listen pixiu server: ", err)
}
}()
klog.Info("starting job manager")
opt.JobManager.Run()
// Wait for interrupt signal to gracefully shut down the server with a timeout of 5 seconds.
quit := make(chan os.Signal)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
klog.Info("shutting pixiu server down ...")
// The context is used to inform the server it has 5 seconds to finish the request
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
klog.Fatalf("pixiu server forced to shutdown: %v", err)
}
klog.Info("shutting job manager down ...")
opt.JobManager.Stop()
return nil
}
================================================
FILE: cmd/pixiuserver.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"io"
"math/rand"
"os"
"time"
"github.com/gin-gonic/gin"
"k8s.io/klog/v2"
"github.com/caoyingjunz/pixiu/cmd/app"
)
var version string
// @title Pixiu API Documentation
// @version 1.0
// @termsOfService https://github.com/caoyingjunz/pixiu
// @contact.name API Support
// @contact.url https://github.com/caoyingjunz/pixiu
// @contact.email support@pixiu.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @schemes http https
// @host localhost:8090
// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
// @description Use the Pixiu APIs to your cloud
// @description Type "Bearer" followed by a space and JWT token
func main() {
klog.InitFlags(nil)
rand.Seed(time.Now().UnixNano())
gin.SetMode(gin.ReleaseMode)
gin.DefaultWriter = io.Discard
cmd := app.NewServerCommand(version)
if err := cmd.Execute(); err != nil {
os.Exit(1)
}
}
================================================
FILE: config.yaml
================================================
default:
# 运行模式,可选 debug 和 release
mode: debug
# 服务监听端口
listen: 8091
# jwt 签名的 key
jwt_key: pixiu
# 自动创建指定模型的数据库表结构,不会更新已存在的数据库表
auto_migrate: true
# 日志的格式,可选 text 和 json
log_format: json
log_level: info
# 静态文件路径
static_files: /static
# 超级管理初始化用户名和密码,默认为 admin/Pixiu123456!
admin_user: pixiu
admin_password: 123456
# 配置前端请求地址
dashboard:
url: http://localhost:8080
#tls:
# cert_file: test.pem
# key_file: test.key
# 数据库地址信息
mysql:
host: peng
user: root
password: Pixiu868686
port: 3306
name: pixiu
worker:
work_dir: /tmp/pixiu
engines:
- image: ccr.ccs.tencentyun.com/pixiucloud/kubez-ansible:v2.0.1
os_supported:
- centos7
- debian10
- ubuntu18.04
- image: ccr.ccs.tencentyun.com/pixiucloud/kubez-ansible:v3.0.1
os_supported:
- debian11
- ubuntu20.04
- ubuntu22.04
- rocky8.5
- rocky9.2
- rocky9.3
- openEuler22.03
================================================
FILE: deploy/README.md
================================================
# Pixiu
Pixiu是貔貅的服务端, 貔貅是一个广泛使用的、具有web-ui的、实现多集群管理的、非常好用的kubernetes(k8s)容器管理平台.
本 chart 使用 [Helm](https://helm.sh) 包管理器实现在[Kubernetes](https://kubernetes.io) (k8s)集群上部署pixiu
## 安装Chart
注意: 暂时只支持helm3
- 通过源码安装(本示例默认源码安装)
```
helm install [RELEASE_NAME] pixiu/
```
- 修改默认参数后, 打包上传到repos后, 例如https://artifacthub.io/, 再行安装
```
helm repo add pixiu https://xxxx.xxx.xx/pixiu
helm repo update
helm install [RELEASE_NAME] ./pixiu
```
## 卸载Chart
通过以下命令卸载:
```console
helm delete pixiu
```
## 更新Chart
```
helm upgrade [RELEASE_NAME] [CHART] --install
```
## 配置
- 详情请参考: [values.yaml](./values.yaml)
- 自定义参数:
```
helm install pixiu pixiu/ --set=service.externalPort=8080,resources.limits.cpu=300m
```
================================================
FILE: deploy/pixiu/.helmignore
================================================
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
================================================
FILE: deploy/pixiu/Chart.yaml
================================================
apiVersion: v2
name: pixiu
description: A Helm chart for Kubernetes
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "1.16.0"
================================================
FILE: deploy/pixiu/templates/NOTES.txt
================================================
1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
{{- range .paths }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
{{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "pixiu.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "pixiu.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "pixiu.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
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}")
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
{{- end }}
================================================
FILE: deploy/pixiu/templates/_helpers.tpl
================================================
{{/*
Expand the name of the chart.
*/}}
{{- define "pixiu.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "pixiu.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "pixiu.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "pixiu.labels" -}}
helm.sh/chart: {{ include "pixiu.chart" . }}
{{ include "pixiu.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "pixiu.selectorLabels" -}}
app.kubernetes.io/name: {{ include "pixiu.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "pixiu.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "pixiu.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
================================================
FILE: deploy/pixiu/templates/configmap.yaml
================================================
# Copyright 2021 The Pixiu Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
apiVersion: v1
kind: ConfigMap
metadata:
labels:
{{- include "pixiu.labels" . | nindent 4 }}
{{- if .Values.commonLabels }}
{{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }}
{{- end }}
annotations:
{{- if .Values.commonAnnotations }}
{{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }}
{{- end }}
name: {{ template "pixiu.fullname" . }}-config
namespace: {{ .Release.Namespace }}
data:
config.yaml: |
default:
listen: {{ .Values.default.listen }}
log_type: {{ .Values.default.log_type }}
log_level: {{ .Values.default.log_level }}
log_dir: {{ .Values.default.log_dir }}
jwt_key: {{ .Values.default.jwt_key }}
mysql:
host: {{ .Values.mysql.host }}
user: {{ .Values.mysql.user }}
password: {{ .Values.mysql.password }}
port: {{ .Values.mysql.port }}
name: {{ .Values.mysql.name }}
cicd:
driver: {{ .Values.cicd.driver }}
jenkins:
host: {{ .Values.cicd.host }}
user: {{ .Values.cicd.user }}
password: {{ .Values.cicd.password }}
================================================
FILE: deploy/pixiu/templates/deployment.yaml
================================================
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "pixiu.fullname" . }}
labels:
{{- include "pixiu.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "pixiu.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "pixiu.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "pixiu.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.pixiu }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
volumeMounts:
- mountPath: /etc/pixiu
name: config-volume
readOnly: true
resources:
{{- toYaml .Values.resources | nindent 12 }}
- name: dashboard
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.dashboard }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 80
protocol: TCP
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
- configMap:
defaultMode: 420
name: pixiu-config
name: config-volume
================================================
FILE: deploy/pixiu/templates/hpa.yaml
================================================
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "pixiu.fullname" . }}
labels:
{{- include "pixiu.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "pixiu.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}
================================================
FILE: deploy/pixiu/templates/ingress.yaml
================================================
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "pixiu.fullname" . -}}
{{- $svcPort := .Values.service.port -}}
{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1beta1
{{- else -}}
apiVersion: extensions/v1beta1
{{- end }}
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
{{- include "pixiu.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
backend:
serviceName: {{ $fullName }}
servicePort: {{ $svcPort }}
{{- end }}
{{- end }}
{{- end }}
================================================
FILE: deploy/pixiu/templates/service.yaml
================================================
apiVersion: v1
kind: Service
metadata:
name: {{ include "pixiu.fullname" . }}
labels:
{{- include "pixiu.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "pixiu.selectorLabels" . | nindent 4 }}
================================================
FILE: deploy/pixiu/templates/serviceaccount.yaml
================================================
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "pixiu.serviceAccountName" . }}
labels:
{{- include "pixiu.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}
================================================
FILE: deploy/pixiu/values.yaml
================================================
# Default values for pixiu.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
default:
# 服务监听端口
listen: 8090
# 日志类型,支持 stdout, stderr 和 file, 默认为 stdout
log_type: stdout
# 日志级别,支持 INFO, WARN 和 ERROR, 默认为 INFO
log_level: INFO
# 日志路径,在日志类型为 file 的时候生效
log_dir: /var/log/pixiu
# jwt 签名的 key
jwt_key: pixiu
# 数据库地址信息
mysql:
host: pixiu
user: root
password: GoPixiu868686
port: 3306
name: pixiu
cicd:
enable: false
driver: jenkins
jenkins:
host: http://127.0.0.1:9090
user: adminops
password: adminops@321
replicaCount: 1
image:
pixiu: jacky06/pixiu:v0.1
dashboard: jacky06/pixiu-dashboard
pullPolicy: IfNotPresent
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
# Specifies whether a service account should be created
create: false
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
podAnnotations: {}
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
service:
type: ClusterIP
port: 80
ingress:
enabled: false
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- host: chart-example.local
paths:
- path: /
backend:
serviceName: chart-example.local
servicePort: 80
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 100
targetCPUUtilizationPercentage: 80
# targetMemoryUtilizationPercentage: 80
nodeSelector: {}
tolerations: []
affinity: {}
================================================
FILE: docker/Dockerfile
================================================
FROM golang:1.17-alpine as builder
WORKDIR /app
ARG VERSION
ENV GOPROXY=https://goproxy.cn
COPY ../go.mod ./
COPY ../go.sum ./
#RUN go mod download
COPY .. .
RUN CGO_ENABLED=0 go build -ldflags "-s -w -X 'main.version=${VERSION}'" -o pixiu ./cmd
FROM node:16.18.0-alpine as dashboard-builder
WORKDIR /build
RUN apk add git
RUN git clone https://github.com/pixiu-io/dashboard.git
RUN cd dashboard && npm install && npm run build
FROM busybox as runner
LABEL MAINTAINER="PIXIU"
COPY --from=builder /app/pixiu /app
COPY --from=dashboard-builder /build/dashboard/dist /static
COPY docker/start.sh /usr/local/bin/pixiu_start
CMD ["pixiu_start"]
================================================
FILE: docker/Dockerfile-toolbox
================================================
# 使用 Ubuntu 作为基础镜像
FROM ubuntu:22.04
ARG K8S_VERSION
ARG HELM_VERSION
# 安装vim
RUN apt-get update && apt-get install -y vim
# 安装必要的软件包
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y curl bash-completion
# 安装 kubectl
RUN ARCH=$(uname -m|sed 's|x86_64|amd64|'|sed 's|aarch64|arm64|') && \
curl -LO "https://dl.k8s.io/release/${K8S_VERSION}/bin/linux/${ARCH}/kubectl" && \
install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl && \
rm -rf kubectl
# 安装 helm
RUN ARCH=$(uname -m|sed 's|x86_64|amd64|'|sed 's|aarch64|arm64|') && \
curl -LO "https://get.helm.sh/helm-${HELM_VERSION}-linux-${ARCH}.tar.gz" && \
tar -zxvf helm-${HELM_VERSION}-linux-${ARCH}.tar.gz && \
rm -rf helm-${HELM_VERSION}-linux-${ARCH}.tar.gz && \
mv linux-${ARCH}/helm /usr/local/bin/helm && \
rm -rf linux-${ARCH}
# 配置 bash 补全和自定义的 PS1 提示符
RUN echo 'source /etc/profile.d/bash_completion.sh' >> /root/.bashrc \
&& echo 'source <(kubectl completion bash)' >> /root/.bashrc \
&& echo 'source <(helm completion bash)' >> /root/.bashrc \
&& 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
# 设置工作目录
WORKDIR /root
# 设置环境变量等(可选)
ENV PATH="/usr/local/bin:${PATH}"
# 启动 bash 终端
CMD ["sleep", "infinity"]
================================================
FILE: docker/start.sh
================================================
#!/bin/sh
set -o errexit
set -o xtrace
if [[ ! -d "/etc/pixiu" ]]; then
mkdir -p /etc/pixiu
fi
if [[ -e "/static/config.json" ]]; then
rm -rf /static/config.json
fi
URL=$(grep "url" /etc/pixiu/config.yaml | awk -F'url:' '{print $2}' | tr -d '[:space:]')
if [[ -z "$URL" ]]; then
URL="http://localhost:8080"
fi
cat >/static/config.json << EOF
{
"url": "${URL}"
}
EOF
/app
================================================
FILE: docs/OWNERS
================================================
reviewers:
- caoyingjun
================================================
FILE: docs/README.md
================================================
# Pixiu
### 初始账号
```shell
pixiu/Pixiu123456!
```
### 主进程启动
```shell
go run cmd/pixiuserver.go --configfile ./config.yaml
```
### 访问 APIs doc
```shell
curl http://127.0.0.1:8090/api-ref/index.html
```
================================================
FILE: docs/apis.md
================================================
# Pixiu API Specification
- Pixiu API 是对 Pixiu 资源的调用接口,通常接口有 list, show details, create, update 和 delete, 以及对 resource 的具体动作, 如: 启动 cicd 资源的 job.
- API 应当遵循(以 cicd 资源的 job 为例):
## List
- method: GET
- URL: /cicd/jobs
- 正常返回码: 200
- 异常返回码: badRequest(400), unauthorized(401), forbidden(403)
- 请求参数:
- 响应结果:
## Show Details
- method: GET
- URL: /cicd/jobs/{job_id}
- 正常返回码: 200
- 异常返回码: badRequest(400), unauthorized(401), forbidden(403), itemNotFound(404)
- 请求参数:
- 响应结果:
## Create
- method: POST
- URL: /cicd/jobs
- 正常返回码: 200
- 异常返回码: badRequest(400), unauthorized(401), forbidden(403)
- 请求参数:
- 响应结果:
## Update
- method: PUT
- URL: /cicd/jobs/{image_id}
- 正常返回码: 200
- 异常返回码: badRequest(400), unauthorized(401), forbidden(403), itemNotFound(404)
- 请求参数:
- 响应结果:
## Delete
- method: DELETE
- URL: /cicd/jobs/{job_id}
- 正常返回码: 200
- 异常返回码: badRequest(400), unauthorized(401), forbidden(403), itemNotFound(404)
- 请求参数:
- 响应结果:
## Run
- method: POST
- URL: /cicd/jobs/{job_id}/run
- 正常返回码: 200
- 异常返回码: badRequest(400), unauthorized(401), forbidden(403), itemNotFound(404), NotImplemented(501)
- 请求参数:
- 响应结果:
================================================
FILE: docs/sql.md
================================================
# 创建 `pixiu` 数据库
```sql
CREATE DATABASE pixiu;
```
## 创建 `clusters` 表
```sql
CREATE TABLE `clusters` (
id int primary key NOT NULL AUTO_INCREMENT COMMENT '主键' ,
gmt_create datetime COMMENT '创建时间',
gmt_modified datetime COMMENT '修改时间',
resource_version int COMMENT '版本号',
plan_id int COMMENT 'plan表的id号',
name varchar(128) COMMENT 'k8s 集群名称',
alias_name varchar(128) COMMENT 'k8s 集群中文名称',
cluster_type int COMMENT 'Kubernetes 集群的类型',
status tinyint(4) COMMENT '集群状态',
kubernetes_version varchar(64) COMMENT 'k8s 集群版本',
nodes text COMMENT '集群节点详情',
protected bool COMMENT '集群删除保护',
kube_config text COMMENT 'kubeConfig 文件内容',
description text COMMENT 'k8s 集群描述信息',
extension text COMMENT '扩展预留',
KEY `idx_name` (`name`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB CHARSET=utf8 AUTO_INCREMENT=20220801;
```
## 创建 `users` 表
```sql
CREATE TABLE `users` (
id int primary key NOT NULL AUTO_INCREMENT COMMENT '主键' ,
gmt_create datetime COMMENT '创建时间',
gmt_modified datetime COMMENT '修改时间',
resource_version int COMMENT '版本号',
name varchar(128) COMMENT '用户名',
password varchar(256) COMMENT '用户密码',
email varchar(128) COMMENT '邮箱',
status tinyint COMMENT '状态: 1启用,2未启用',
role varchar(128) COMMENT '角色',
description text COMMENT '描述',
extension text COMMENT '扩展字段',
KEY `idx_name` (`name`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB CHARSET=utf8 AUTO_INCREMENT=21220801;
```
### 创建 `pixiu` 用户
```sql
# 用户 pixiu 的初始密码为 Pixiu123456!
insert into users(name, password) values ('pixiu', '$2a$10$SamcBWw.aPMDv5QadDr7f.2rDBWiwfTwnbh5sEEhaTkWfVwO96PfW');
```
## 创建 `tenants` 表
```sql
CREATE TABLE `tenants` (
id int primary key NOT NULL AUTO_INCREMENT COMMENT '主键' ,
gmt_create datetime COMMENT '创建时间',
gmt_modified datetime COMMENT '修改时间',
resource_version int COMMENT '版本号',
name varchar(128) COMMENT '租户名',
description text COMMENT '描述',
extension text COMMENT '扩展字段',
KEY `idx_name` (`name`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB CHARSET=utf8 AUTO_INCREMENT=22220801;
```
## 创建 `roles` 表
```sql
CREATE TABLE `roles` (
id int primary key NOT NULL AUTO_INCREMENT COMMENT '主键' ,
gmt_create datetime COMMENT '创建时间',
gmt_modified datetime COMMENT '修改时间',
resource_version int COMMENT '版本号',
name varchar(128) COMMENT '用户名',
status tinyint(4) COMMENT '状态',
sequence bigint(20) NOT NULL,
parent_id bigint(20) NOT NULL,
memo varchar(128) DEFAULT NULL,
KEY `idx_name` (`name`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB CHARSET=utf8 AUTO_INCREMENT=23220801;
```
## 创建 `clouds` 表
```sql
CREATE TABLE `clouds` (
id int primary key NOT NULL AUTO_INCREMENT COMMENT '主键' ,
gmt_create datetime COMMENT '创建时间',
gmt_modified datetime COMMENT '修改时间',
resource_version int COMMENT '版本号',
name varchar(128) COMMENT '用户名',
alias_name varchar(128) COMMENT '别名',
status int COMMENT '集群状态',
cloud_type int COMMENT '集群类型',
kube_version varchar(128) COMMENT 'k8s 集群版本',
node_number int COMMENT '集群节点数量',
resources varchar(128) COMMENT '资源数量',
description text COMMENT '描述',
extension text COMMENT '扩展字段',
KEY `idx_name` (`name`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB CHARSET=utf8 AUTO_INCREMENT=22220801;
```
## 创建 `kube_configs` 表
```sql
CREATE TABLE `kube_configs` (
id int primary key NOT NULL AUTO_INCREMENT COMMENT '主键' ,
gmt_create datetime COMMENT '创建时间',
gmt_modified datetime COMMENT '修改时间',
resource_version int COMMENT '版本号',
service_account varchar(128) COMMENT 'k8s service account',
cloud_name varchar(128) COMMENT '集群名',
cloud_id int COMMENT '所属 cloud id',
cluster_role varchar(128) COMMENT 'k8s cluster role',
config text COMMENT 'k8s kube_config',
expiration_timestamp text COMMENT '过期时间',
KEY `idx_cloud_name` (`cloud_name`),
UNIQUE KEY `service_account` (`service_account`)
) ENGINE=InnoDB CHARSET=utf8 AUTO_INCREMENT=22220801;
```
## 创建 `nodes` 表
```sql
CREATE TABLE `nodes` (
id int primary key NOT NULL AUTO_INCREMENT COMMENT '主键' ,
gmt_create datetime COMMENT '创建时间',
gmt_modified datetime COMMENT '修改时间',
resource_version int COMMENT '版本号',
cloud_id int COMMENT 'cloud ID',
role varchar(128) COMMENT '节点类型',
host_name varchar(128) COMMENT '节点名称',
address varchar(128) COMMENT '节点 ip 地址',
user varchar(128) COMMENT '用户名',
password varchar(128) COMMENT '节点密码',
KEY `idx_cloud` (`cloud_id`)
) ENGINE=InnoDB CHARSET=utf8 AUTO_INCREMENT=24220801;
```
## 创建 `events` 表
```sql
CREATE TABLE `events` (
id int primary key NOT NULL AUTO_INCREMENT COMMENT '主键' ,
gmt_create datetime COMMENT '创建时间',
gmt_modified datetime COMMENT '修改时间',
resource_version int COMMENT '版本号',
user varchar(128) COMMENT '用户名称',
client_ip varchar(128) COMMENT '登陆地址',
operator varchar(128) COMMENT '操作类型',
object varchar(128) COMMENT '操作对象',
message varchar(128) COMMENT '消息'
) ENGINE=InnoDB CHARSET=utf8 AUTO_INCREMENT=26220801;
```
## 创建 `audit`
```sql
CREATE TABLE `audits` (
`id` int primary key NOT NULL AUTO_INCREMENT COMMENT '主键' ,
`gmt_create` datetime COMMENT '创建时间',
`gmt_modified` datetime COMMENT '修改时间',
`resource_version` int COMMENT '版本号',
`operator` varchar(255) COLLATE utf8mb4_bin NOT NULL COMMENT '操作人',
`action` varchar(255) COLLATE utf8mb4_bin NOT NULL COMMENT '动作',
`ip` varchar(128) COLLATE utf8mb4_bin NOT NULL COMMENT '来源ip',
`status` tinyint(4) COLLATE utf8mb4_bin NOT NULL COMMENT '执行是否成功:0-失败,1-成功',
`path` varchar(255) COLLATE utf8mb4_bin NOT NULL COMMENT '详细内容',
`resource_type` varchar(128) COLLATE utf8mb4_bin NOT NULL COMMENT '操作的资源类型'
) ENGINE=InnoDB AUTO_INCREMENT=3355 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
```
================================================
FILE: go.mod
================================================
module github.com/caoyingjunz/pixiu
go 1.17
require (
github.com/bmatcuk/doublestar/v4 v4.6.1
github.com/caoyingjunz/pixiulib v0.0.0-20220819163605-c3c10ec3ed3c
github.com/casbin/casbin/v2 v2.97.0
github.com/casbin/gorm-adapter/v3 v3.12.0
github.com/docker/docker v20.10.12+incompatible
github.com/gin-contrib/cors v1.4.0
github.com/gin-contrib/requestid v0.0.6
github.com/gin-gonic/gin v1.8.1
github.com/go-playground/locales v0.14.1
github.com/go-playground/universal-translator v0.18.1
github.com/go-playground/validator/v10 v10.19.0
github.com/go-sql-driver/mysql v1.6.0
github.com/golang-jwt/jwt/v4 v4.4.2
github.com/google/uuid v1.3.0
github.com/gorilla/websocket v1.4.2
github.com/juju/ratelimit v1.0.2
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pelletier/go-toml/v2 v2.2.0 // indirect
github.com/pkg/sftp v1.13.6
github.com/robfig/cron/v3 v3.0.0
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.5.0
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a
github.com/swaggo/gin-swagger v1.5.3
github.com/swaggo/swag v1.8.6
golang.org/x/crypto v0.21.0
golang.org/x/sync v0.1.0
golang.org/x/time v0.1.0
gorm.io/driver/mysql v1.4.1
gorm.io/gorm v1.24.0
helm.sh/helm/v3 v3.8.2
k8s.io/api v0.23.5
k8s.io/apimachinery v0.23.5
k8s.io/cli-runtime v0.23.5
k8s.io/client-go v0.23.5
k8s.io/klog/v2 v2.80.1
k8s.io/metrics v0.23.5
k8s.io/utils v0.0.0-20221012122500-cfd413dd9e85 // indirect
)
require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/BurntSushi/toml v1.2.0 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.1.1 // indirect
github.com/Masterminds/sprig/v3 v3.2.2 // indirect
github.com/Masterminds/squirrel v1.5.2 // indirect
github.com/Microsoft/go-winio v0.5.1 // indirect
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/casbin/govaluate v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect
github.com/containerd/containerd v1.6.1 // indirect
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/cli v20.10.11+incompatible // indirect
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.4 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/glebarez/go-sqlite v1.20.3 // indirect
github.com/glebarez/sqlite v1.5.0 // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-logr/logr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/spec v0.20.7 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/googleapis/gnostic v0.5.5 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gosuri/uitable v0.0.4 // indirect
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.13.0 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.3.1 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgtype v1.12.0 // indirect
github.com/jackc/pgx/v4 v4.17.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jmoiron/sqlx v1.3.4 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.13.6 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lib/pq v1.10.4 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/microsoft/go-mssqldb v0.17.0 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/spdystream v0.2.0 // indirect
github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.11.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.30.0 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc // indirect
github.com/russross/blackfriday v1.5.2 // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.9.0 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/net v0.22.0 // indirect
golang.org/x/oauth2 v0.1.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/term v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.6.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90 // indirect
google.golang.org/grpc v1.47.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/gorp.v1 v1.7.2 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/driver/postgres v1.4.4 // indirect
gorm.io/driver/sqlserver v1.4.1 // indirect
gorm.io/plugin/dbresolver v1.3.0 // indirect
k8s.io/apiextensions-apiserver v0.23.5 // indirect
k8s.io/apiserver v0.23.5 // indirect
k8s.io/component-base v0.23.5 // indirect
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect
k8s.io/kubectl v0.23.5 // indirect
modernc.org/libc v1.22.2 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/sqlite v1.20.3 // indirect
oras.land/oras-go v1.1.1 // indirect
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect
sigs.k8s.io/kustomize/api v0.10.1 // indirect
sigs.k8s.io/kustomize/kyaml v0.13.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)
replace (
// github.com/glebarez/sqlite => github.com/glebarez/sqlite v1.4.0
github.com/pelletier/go-toml/v2 => github.com/pelletier/go-toml/v2 v2.1.1
golang.org/x/net => golang.org/x/net v0.17.0
// gorm.io/driver/sqlserver => gorm.io/driver/sqlserver v1.4.1
)
================================================
FILE: go.sum
================================================
bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM=
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg=
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go v56.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.0/go.mod h1:+6sju8gk8FRmSajX3Oz4G5Gm7P+mbqE9FVaXXFYTkCM=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
github.com/Azure/go-autorest/autorest v0.11.20/go.mod h1:o3tqFY+QR40VOlk+pV4d77mORO64jOXSgEnPQgLK6JY=
github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg=
github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
github.com/Azure/go-autorest/autorest/adal v0.9.15/go.mod h1:tGMin8I49Yij6AQ+rvV+Xa/zwxYQB5hmsd6DkfAx2+A=
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0=
github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU=
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8=
github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk=
github.com/Masterminds/squirrel v1.5.2 h1:UiOEi2ZX4RCSkpiNDQN5kro/XIBpSRk9iTqdIRPzUXE=
github.com/Masterminds/squirrel v1.5.2/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
github.com/Masterminds/vcs v1.13.3/go.mod h1:TiE7xuEjl1N4j016moRd6vezp6e6Lz23gypeXfzXeW8=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY=
github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ=
github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8=
github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg=
github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00=
github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600=
github.com/Microsoft/hcsshim v0.8.20/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4=
github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4=
github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg=
github.com/Microsoft/hcsshim v0.9.2 h1:wB06W5aYFfUB3IvootYAY2WnOmIdgPGfqSI6tufQNnY=
github.com/Microsoft/hcsshim v0.9.2/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc=
github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU=
github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0=
github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY=
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70=
github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng=
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ=
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o=
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
github.com/caoyingjunz/pixiulib v0.0.0-20220819163605-c3c10ec3ed3c h1:SyWVmVBugbQp3dQ3xz8rlkxU43MxmG86fY8JoxqtSBs=
github.com/caoyingjunz/pixiulib v0.0.0-20220819163605-c3c10ec3ed3c/go.mod h1:rQFvtjWLu4D7VSo6H/bI8h0tyWneUtuJJM+AcZBalPk=
github.com/casbin/casbin/v2 v2.55.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
github.com/casbin/casbin/v2 v2.97.0 h1:FFHIzY+6fLIcoAB/DKcG5xvscUo9XqRpBniRYhlPWkg=
github.com/casbin/casbin/v2 v2.97.0/go.mod h1:jX8uoN4veP85O/n2674r2qtfSXI6myvxW85f6TH50fw=
github.com/casbin/gorm-adapter/v3 v3.12.0 h1:jUJCwaLnB+QfUadOETD6ymxeDmOR9jQXTmUTzLMpS3g=
github.com/casbin/gorm-adapter/v3 v3.12.0/go.mod h1:jqaf4bUITbCyMPUellaTd8IQJ77JfVAbe77gZZnx98w=
github.com/casbin/govaluate v1.1.0 h1:6xdCWIpE9CwHdZhlVQW+froUrCsjb6/ZYNcXODfLT+E=
github.com/casbin/govaluate v1.1.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 h1:7aWHqerlJ41y6FOsEUvknqgXnGmJyJSbjhAWq5pO4F8=
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw=
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=
github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc=
github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs=
github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo=
github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=
github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE=
github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU=
github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU=
github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU=
github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E=
github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss=
github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss=
github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI=
github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=
github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM=
github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo=
github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo=
github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE=
github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU=
github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4=
github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8=
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE=
github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw=
github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.4.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ=
github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU=
github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI=
github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s=
github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g=
github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c=
github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s=
github.com/containerd/containerd v1.6.1 h1:oa2uY0/0G+JX4X7hpGCYvkp9FjUancz56kSNnb1sG3o=
github.com/containerd/containerd v1.6.1/go.mod h1:1nJz5xCZPusx6jJU8Frfct988y0NpumIq9ODB0kLtoE=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo=
github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y=
github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ=
github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM=
github.com/containerd/continuity v0.2.2/go.mod h1:pWygW9u7LtS1o4N/Tn0FoCFDIXZ7rxcMX7HX1Dmibvk=
github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0=
github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0=
github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4=
github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4=
github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU=
github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk=
github.com/containerd/go-cni v1.1.0/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA=
github.com/containerd/go-cni v1.1.3/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA=
github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=
github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=
github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g=
github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok=
github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok=
github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0=
github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA=
github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow=
github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms=
github.com/containerd/imgcrypt v1.1.3/go.mod h1:/TPA1GIDXMzbj01yd8pIbQiLdQxed5ue1wb8bP7PQu4=
github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c=
github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY=
github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY=
github.com/containerd/stargz-snapshotter/estargz v0.4.1/go.mod h1:x7Q9dg9QYb4+ELgxmo4gBUeJB0tl5dqH1Sdz0nJU1QM=
github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8=
github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=
github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=
github.com/containerd/ttrpc v1.1.0/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Evzy5KFQpQ=
github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk=
github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg=
github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s=
github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw=
github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y=
github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY=
github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY=
github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY=
github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
github.com/containernetworking/cni v1.0.1/go.mod h1:AKuhXbN5EzmD4yTNtfSsX3tPcmtrBI6QcRV0NiNt15Y=
github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM=
github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8=
github.com/containernetworking/plugins v1.0.1/go.mod h1:QHCfGpaTwYTbbH+nZXKVTxNBDZcxSOplJT5ico8/FLE=
github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc=
github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4=
github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY=
github.com/containers/ocicrypt v1.1.2/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI=
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I=
github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE=
github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684 h1:DBZ2sN7CK6dgvHVpQsQj4sRMCbWTmd17l+5SUCjnQSY=
github.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684/go.mod h1:UfCu3YXJJCI+IdnqGgYP82dk2+Joxmv+mUTVBES6wac=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v20.10.11+incompatible h1:tXU1ezXcruZQRrMP8RN2z9N91h+6egZTS1gsPsKantc=
github.com/docker/cli v20.10.11+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v20.10.11+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v20.10.12+incompatible h1:CEeNmFM0QZIsJCZKMkZx0ZcahTiewkrgiwfYD+dfl1U=
github.com/docker/docker v20.10.12+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o=
github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4=
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ=
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws=
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM=
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=
github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg=
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g=
github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
github.com/gin-contrib/requestid v0.0.6 h1:mGcxTnHQ45F6QU5HQRgQUDsAfHprD3P7g2uZ4cSZo9o=
github.com/gin-contrib/requestid v0.0.6/go.mod h1:9i4vKATX/CdggbkY252dPVasgVucy/ggBeELXuQztm4=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
github.com/glebarez/go-sqlite v1.19.1/go.mod h1:9AykawGIyIcxoSfpYWiX1SgTNHTNsa/FVc75cDkbp4M=
github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4=
github.com/glebarez/go-sqlite v1.20.3/go.mod h1:u3N6D/wftiAzIOJtZl6BmedqxmmkDfH3q+ihjqxC9u0=
github.com/glebarez/sqlite v1.5.0 h1:+8LAEpmywqresSoGlqjjT+I9m4PseIM3NcerIJ/V7mk=
github.com/glebarez/sqlite v1.5.0/go.mod h1:0wzXzTvfVJIN2GqRhCdMbnYd+m+aH5/QV7B30rM6NgY=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro=
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
github.com/go-openapi/spec v0.20.7 h1:1Rlu/ZrOCCob0n+JKKJAWhNWMPW8bOZRg8FJaY+0SKI=
github.com/go-openapi/spec v0.20.7/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gobuffalo/logger v1.0.3 h1:YaXOTHNPCvkqqA7w05A4v0k2tCdpr+sgFlgINbQ6gqc=
github.com/gobuffalo/logger v1.0.3/go.mod h1:SoeejUwldiS7ZsyCBphOGURmWdwUFXs0J7TCjEhjKxM=
github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM=
github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI=
github.com/gobuffalo/packr/v2 v2.8.1 h1:tkQpju6i3EtMXJ9uoF5GT6kB+LMTimDWD8Xvbz6zDVA=
github.com/gobuffalo/packr/v2 v2.8.1/go.mod h1:c/PLlOuTU+p3SybaJATW3H6lX/iK7xEz5OeMf+NnJpg=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU=
github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk=
github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k=
github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/cel-go v0.9.0/go.mod h1:U7ayypeSkw23szu4GaQTPJGx66c20mx8JklMSxrmI1w=
github.com/google/cel-spec v0.6.0/go.mod h1:Nwjgxy5CbjlPrtCWjeDjUyKMl8w41YBYGjsyDdqk0xA=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=
github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY=
github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ=
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw=
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys=
github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y=
github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w=
github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E=
github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w=
github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/karrick/godirwalk v1.15.8 h1:7+rWAZPn9zuRxaIqqT8Ohs2Q2Ac0msBqwRdxNCr2VVs=
github.com/karrick/godirwalk v1.15.8/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo=
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI=
github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc=
github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY=
github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI=
github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI=
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY=
github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE=
github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
github.com/moby/sys/mountinfo v0.5.0 h1:2Ks8/r6lopsxWi9m58nlwjaeSzUX9iiL1vj5qB/9ObI=
github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
github.com/moby/sys/signal v0.6.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg=
github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ=
github.com/moby/sys/symlink v0.2.0/go.mod h1:7uZVF2dqJjG/NsClqul95CqKOBRQyYSNnJ6BMgR/gFs=
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 h1:yH0SvLzcbZxcJXho2yh7CqdENGMQe73Cw3woZBpPli0=
github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU=
github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0=
github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0=
github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc=
github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs=
github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE=
github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo=
github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8=
github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.28.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.30.0 h1:JEkYlQnpzrzQFxi6gnukFPdQ+ac82oRhzMcIduJu/Ug=
github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578 h1:VstopitMQi3hZP0fzvnsLmzXZdQGc4bEcgu24cp+d4M=
github.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E=
github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc h1:BD7uZqkN8CpjJtN/tScAKiccBikU4dlqe/gNrkRaPY4=
github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc/go.mod h1:HFLT6i9iR4QBOF5rdCyjddC9t59ArqWJV2xx+jwcCMo=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4=
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a h1:kAe4YSu0O0UFn1DowNo2MY5p6xzqtJ/wQ7LZynSvGaY=
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
github.com/swaggo/gin-swagger v1.5.3 h1:8mWmHLolIbrhJJTflsaFoZzRBYVmEE7JZGIq08EiC0Q=
github.com/swaggo/gin-swagger v1.5.3/go.mod h1:3XJKSfHjDMB5dBo/0rrTXidPmgLeqsX89Yp4uA50HpI=
github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ=
github.com/swaggo/swag v1.8.6 h1:2rgOaLbonWu1PLP6G+/rYjSvPg0jQE0HtrEKuE380eg=
github.com/swaggo/swag v1.8.6/go.mod h1:jMLeXOOmYyjk8PvHTsXBdrubsNd9gUJTTCzL5iBnseg=
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI=
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI=
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE=
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY=
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE=
go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc=
go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4=
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0/go.mod h1:vEhqr0m4eTc+DWxfsXoXue2GBgV2uUwVznkGIHW/e5w=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4=
go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo=
go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs=
go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM=
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0/go.mod h1:hO1KLR7jcKaDDKDkvI9dP/FIhpmna5lkqPUQdEjFAM8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0/go.mod h1:keUU7UfnwWTWpJ+FWnyqmogPa82nuU5VUANFq49hlMY=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.3.0/go.mod h1:QNX1aly8ehqqX1LEa6YniTU7VY9I6R3X/oPxhGdTceE=
go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU=
go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw=
go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc=
go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs=
go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE=
go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE=
go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw=
go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ=
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc=
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
golang.org/x/oauth2 v0.1.0 h1:isLCZuhj4v+tYv7eskaN4v/TM+A1begWWgyVJDdl1+Y=
golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA=
golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200308013534-11ec41452d41/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw=
google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201102152239-715cce707fb0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90 h1:4SPz2GL2CXJt28MTF8V6Ap/9ZiVbQlJeGSd9qtA7DLs=
google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8=
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw=
gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.3.2/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U=
gorm.io/driver/mysql v1.4.1 h1:4InA6SOaYtt4yYpV1NF9B2kvUKe9TbvUd1iWrvxnjic=
gorm.io/driver/mysql v1.4.1/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c=
gorm.io/driver/postgres v1.4.4 h1:zt1fxJ+C+ajparn0SteEnkoPg0BQ6wOWXEQ99bteAmw=
gorm.io/driver/postgres v1.4.4/go.mod h1:whNfh5WhhHs96honoLjBAMwJGYEuA3m1hvgUbNXhPCw=
gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0=
gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig=
gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gorm.io/gorm v1.23.7/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gorm.io/gorm v1.24.0 h1:j/CoiSm6xpRpmzbFJsQHYj+I8bGYWLXVHeYEyyKlF74=
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gorm.io/plugin/dbresolver v1.3.0 h1:uFDX3bIuH9Lhj5LY2oyqR/bU6pqWuDgas35NAPF4X3M=
gorm.io/plugin/dbresolver v1.3.0/go.mod h1:Pr7p5+JFlgDaiM6sOrli5olekJD16YRunMyA2S7ZfKk=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
helm.sh/helm/v3 v3.8.2 h1:HDhe2nKek976VLMPZlIgJbNqwcqvHYBp1qy+sXQ4jiY=
helm.sh/helm/v3 v3.8.2/go.mod h1:NxtE2KObf2PrzDl6SIamPFPKyAqWi10iWuvKlQn/Yao=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo=
k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ=
k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8=
k8s.io/api v0.21.2/go.mod h1:Lv6UGJZ1rlMI1qusN8ruAp9PUBFyBwpEHAdG24vIsiU=
k8s.io/api v0.22.5/go.mod h1:mEhXyLaSD1qTOf40rRiKXkc+2iCem09rWLlFwhCEiAs=
k8s.io/api v0.23.5 h1:zno3LUiMubxD/V1Zw3ijyKO3wxrhbUF1Ck+VjBvfaoA=
k8s.io/api v0.23.5/go.mod h1:Na4XuKng8PXJ2JsploYYrivXrINeTaycCGcYgF91Xm8=
k8s.io/apiextensions-apiserver v0.23.5 h1:5SKzdXyvIJKu+zbfPc3kCbWpbxi+O+zdmAJBm26UJqI=
k8s.io/apiextensions-apiserver v0.23.5/go.mod h1:ntcPWNXS8ZPKN+zTXuzYMeg731CP0heCTl6gYBxLcuQ=
k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc=
k8s.io/apimachinery v0.21.2/go.mod h1:CdTY8fU/BlvAbJ2z/8kBwimGki5Zp8/fbVuLY8gJumM=
k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0=
k8s.io/apimachinery v0.22.5/go.mod h1:xziclGKwuuJ2RM5/rSFQSYAj0zdbci3DH8kj+WvyN0U=
k8s.io/apimachinery v0.23.5 h1:Va7dwhp8wgkUPWsEXk6XglXWU4IKYLKNlv8VkX7SDM0=
k8s.io/apimachinery v0.23.5/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM=
k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU=
k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM=
k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q=
k8s.io/apiserver v0.22.5/go.mod h1:s2WbtgZAkTKt679sYtSudEQrTGWUSQAPe6MupLnlmaQ=
k8s.io/apiserver v0.23.5 h1:2Ly8oUjz5cnZRn1YwYr+aFgDZzUmEVL9RscXbnIeDSE=
k8s.io/apiserver v0.23.5/go.mod h1:7wvMtGJ42VRxzgVI7jkbKvMbuCbVbgsWFT7RyXiRNTw=
k8s.io/cli-runtime v0.23.5 h1:Z7XUpGoJZYZB2uNjQfJjMbyDKyVkoBGye62Ap0sWQHY=
k8s.io/cli-runtime v0.23.5/go.mod h1:oY6QDF2qo9xndSq32tqcmRp2UyXssdGrLfjAVymgbx4=
k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y=
k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k=
k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0=
k8s.io/client-go v0.22.5/go.mod h1:cs6yf/61q2T1SdQL5Rdcjg9J1ElXSwbjSrW2vFImM4Y=
k8s.io/client-go v0.23.5 h1:zUXHmEuqx0RY4+CsnkOn5l0GU+skkRXKGJrhmE2SLd8=
k8s.io/client-go v0.23.5/go.mod h1:flkeinTO1CirYgzMPRWxUCnV0G4Fbu2vLhYCObnt/r4=
k8s.io/code-generator v0.19.7/go.mod h1:lwEq3YnLYb/7uVXLorOJfxg+cUu2oihFhHZ0n9NIla0=
k8s.io/code-generator v0.23.5/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk=
k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk=
k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI=
k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM=
k8s.io/component-base v0.22.5/go.mod h1:VK3I+TjuF9eaa+Ln67dKxhGar5ynVbwnGrUiNF4MqCI=
k8s.io/component-base v0.23.5 h1:8qgP5R6jG1BBSXmRYW+dsmitIrpk8F/fPEvgDenMCCE=
k8s.io/component-base v0.23.5/go.mod h1:c5Nq44KZyt1aLl0IpHX82fhsn84Sb0jjzwjpcA42bY0=
k8s.io/component-helpers v0.23.5/go.mod h1:5riXJgjTIs+ZB8xnf5M2anZ8iQuq37a0B/0BgoPQuSM=
k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM=
k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI=
k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI=
k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc=
k8s.io/cri-api v0.23.1/go.mod h1:REJE3PSU0h/LOV1APBrupxrEJqnoxZC8KWzkBUHwrK4=
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4=
k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o=
k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM=
k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE=
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 h1:E3J9oCLlaobFUqsjG9DfKbP2BmgwBL2p7pn0A3dG9W4=
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk=
k8s.io/kubectl v0.23.5 h1:DmDULqCaF4qstj0Im143XmncvqWtJxHzK8IrW2BzlU0=
k8s.io/kubectl v0.23.5/go.mod h1:lLgw7cVY8xbd7o637vOXPca/w6HC205KsPCRDYRCxwE=
k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
k8s.io/metrics v0.23.5 h1:u+3oGwdUWr30LT2v6MHdSJQ4Pbht9s0cVIWyuY5nf9c=
k8s.io/metrics v0.23.5/go.mod h1:WNAtV2a5BYbmDS8+7jSqYYV6E3efuGTpIwJ8PTD1wgs=
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20221012122500-cfd413dd9e85 h1:cTdVh7LYu82xeClmfzGtgyspNh6UxpwLWGi8R4sspNo=
k8s.io/utils v0.0.0-20221012122500-cfd413dd9e85/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
modernc.org/cc/v3 v3.37.0/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20=
modernc.org/cc/v3 v3.38.1/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20=
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
modernc.org/ccgo/v3 v3.0.0-20220904174949-82d86e1b6d56/go.mod h1:YSXjPL62P2AMSxBphRHPn7IkzhVHqkvOnRKAKh+W6ZI=
modernc.org/ccgo/v3 v3.0.0-20220910160915-348f15de615a/go.mod h1:8p47QxPkdugex9J4n9P2tLZ9bK01yngIVp00g4nomW0=
modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo=
modernc.org/ccgo/v3 v3.16.13-0.20221017192402-261537637ce8/go.mod h1:fUB3Vn0nVPReA+7IG7yZDfjv1TMWjhQP8gCxrFAtL5g=
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0=
modernc.org/libc v1.17.4/go.mod h1:WNg2ZH56rDEwdropAJeZPQkXmDwh+JCA1s/htl6r2fA=
modernc.org/libc v1.18.0/go.mod h1:vj6zehR5bfc98ipowQOM2nIDUZnVew/wNC/2tOGS+q0=
modernc.org/libc v1.19.0/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0=
modernc.org/libc v1.20.3/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0=
modernc.org/libc v1.21.4/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI=
modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0=
modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug=
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
modernc.org/memory v1.3.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.19.1/go.mod h1:UfQ83woKMaPW/ZBruK0T7YaFCrI+IE0LeWVY6pmnVms=
modernc.org/sqlite v1.20.3 h1:SqGJMMxjj1PHusLxdYxeQSodg7Jxn9WWkaAQjKrntZs=
modernc.org/sqlite v1.20.3/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A=
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
modernc.org/tcl v1.14.0/go.mod h1:gQ7c1YPMvryCHCcmf8acB6VPabE59QBeuRQLL7cTUlM=
modernc.org/tcl v1.15.0/go.mod h1:xRoGotBZ6dU+Zo2tca+2EqVEeMmOUBzHnhIwq4YrVnE=
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.6.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ=
modernc.org/z v1.7.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ=
oras.land/oras-go v1.1.1 h1:gI00ftziRivKXaw1BdMeEoIA4uBgga33iVlOsEwefFs=
oras.land/oras-go v1.1.1/go.mod h1:n2TE1ummt9MUyprGhT+Q7kGZUF4kVUpYysPFxeV2IpQ=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30/go.mod h1:fEO7lRTdivWO2qYVCVG7dEADOMo/MLDCVr8So2g88Uw=
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s=
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs=
sigs.k8s.io/kustomize/api v0.10.1 h1:KgU7hfYoscuqag84kxtzKdEC3mKMb99DPI3a0eaV1d0=
sigs.k8s.io/kustomize/api v0.10.1/go.mod h1:2FigT1QN6xKdcnGS2Ppp1uIWrtWN28Ms8A3OZUZhwr8=
sigs.k8s.io/kustomize/cmd/config v0.10.2/go.mod h1:K2aW7nXJ0AaT+VA/eO0/dzFLxmpFcTzudmAgDwPY1HQ=
sigs.k8s.io/kustomize/kustomize/v4 v4.4.1/go.mod h1:qOKJMMz2mBP+vcS7vK+mNz4HBLjaQSWRY22EF6Tb7Io=
sigs.k8s.io/kustomize/kyaml v0.13.0 h1:9c+ETyNfSrVhxvphs+K2dzT3dh5oVPPEqPOE/cUpScY=
sigs.k8s.io/kustomize/kyaml v0.13.0/go.mod h1:FTJxEZ86ScK184NpGSAQcfEqee0nul8oLCK30D47m4E=
sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y=
sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
================================================
FILE: hack/tools/licfmt/licfmt.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"bufio"
"bytes"
"flag"
"fmt"
"io"
"log"
"os"
"path/filepath"
"runtime"
"time"
"github.com/bmatcuk/doublestar/v4"
"golang.org/x/sync/errgroup"
)
const tpml = `/*
Copyright %s The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
`
var (
ignorePatterns = stringSlice{"vendor/**", "docs/**"}
year = flag.String("y", fmt.Sprint(time.Now().Year()), "copyright year(s)")
verbose = flag.Bool("v", false, "verbose mode: print the name of the files that are modified or were skipped")
)
func init() {
flag.Var(&ignorePatterns, "i", "file patterns to ignore, for example: -i vendor/**")
}
type stringSlice []string
func (i *stringSlice) String() string {
return fmt.Sprint(*i)
}
func (i *stringSlice) Set(value string) error {
*i = append(*i, value)
return nil
}
type file struct {
path string
mode os.FileMode
}
func main() {
flag.Parse()
if flag.NArg() == 0 {
flag.Usage()
os.Exit(1)
}
for _, p := range ignorePatterns {
if !doublestar.ValidatePattern(p) {
log.Fatalf("ignore pattern %q is invalid", p)
}
}
headerText := fmt.Sprintf(tpml, *year)
// process at most CPU number files in parallel
ch := make(chan *file, runtime.NumCPU())
done := make(chan struct{})
go func() {
var wg errgroup.Group
for f := range ch {
f := f
wg.Go(func() error {
updated, err := addLicenseHeader(f.path, headerText, f.mode)
if err != nil {
log.Printf("%s: %v", f.path, err)
return err
}
if *verbose && updated {
log.Printf("%s updated", f.path)
}
return nil
})
}
err := wg.Wait()
close(done)
if err != nil {
os.Exit(1)
}
}()
for _, d := range flag.Args() {
if err := walk(ch, d); err != nil {
log.Fatal(err)
}
}
close(ch)
<-done
}
// walk walks the file tree from root, sends go file to task queue.
func walk(ch chan<- *file, root string) error {
return filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
if err != nil {
log.Printf("%s error: %v", path, err)
}
if fi.IsDir() {
return nil
}
if match(path, ignorePatterns) || filepath.Ext(path) != ".go" {
if *verbose {
log.Printf("skipping: %s", path)
}
return nil
}
ch <- &file{path, fi.Mode()}
return nil
})
}
// match returns if path matches one of the provided file patterns.
func match(path string, patterns []string) bool {
for _, p := range patterns {
if ok, _ := doublestar.Match(p, path); ok {
return true
}
}
return false
}
// hasLicense returns if there is a license in the file header already.
func hasLicense(header []byte) bool {
return bytes.Contains(header, []byte("Copyright")) &&
bytes.Contains(header, []byte("Apache License"))
}
func notAlowEditd(header []byte) bool {
return bytes.Contains(header, []byte("DO NOT EDIT")) ||
bytes.Contains(header, []byte("generated by"))
}
// addLicenseHeader adds a license header to the file if it does not exist.
func addLicenseHeader(path, license string, fmode os.FileMode) (bool, error) {
f, err := os.OpenFile(path, os.O_RDONLY, fmode)
if err != nil {
return false, err
}
defer f.Close()
buf, dropped := scanLines(f)
if hasLicense(buf.Bytes()) || notAlowEditd(buf.Bytes()) {
return false, nil
}
// add license header at the beginning
new := bytes.NewBuffer(buf.Bytes())
if _, err := new.WriteString(license + "\n"); err != nil {
return false, err
}
// Caculate position of the line contains `package `.
offset := int64(buf.Len())
if dropped > 1 {
// CR characters are dropped, they should be added back.
offset += dropped - 1
}
// Move the cursor of original go file to the position.
if _, err := f.Seek(offset, io.SeekStart); err != nil {
return false, err
}
if _, err := new.ReadFrom(f); err != nil {
return false, err
}
// create a temporary file
tmpFile, err := os.CreateTemp("", "licfmt-")
if err != nil {
return false, err
}
defer os.Remove(tmpFile.Name())
defer tmpFile.Close()
if err = os.Chmod(tmpFile.Name(), fmode); err != nil {
return false, err
}
// rewrite the whole file to the temporary file
if _, err := new.WriteTo(tmpFile); err != nil {
return false, err
}
if err = tmpFile.Sync(); err != nil {
return false, err
}
// rename the tmp file to the original file
err = os.Rename(tmpFile.Name(), path)
return err == nil, err
}
// scanLines handles the file line by line
// buf: the lines before the package declaration
// dropped: the number of CR (`\r`) characters dropped
func scanLines(file *os.File) (buf bytes.Buffer, dropped int64) {
scanner := bufio.NewScanner(file)
dropCR := func(data []byte) []byte {
if len(data) > 0 && data[len(data)-1] == '\r' {
dropped++
return data[0 : len(data)-1]
}
return data
}
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
if i := bytes.IndexByte(data, '\n'); i >= 0 {
// We have a full newline-terminated line.
return i + 1, dropCR(data[0:i]), nil
}
// If we're at EOF, we have a final, non-terminated line. Return it.
if atEOF {
return len(data), dropCR(data), nil
}
// Request more data.
return 0, nil, nil
})
for scanner.Scan() {
line := scanner.Bytes()
if bytes.HasPrefix(line, []byte("package ")) {
// reading until the line contains `package p`
break
}
buf.Write(line)
buf.WriteString("\n")
}
return
}
================================================
FILE: hack/update-gofmt.sh
================================================
#!/bin/bash
# Copyright 2021 The Pixiu Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -o nounset
set -o errexit
set -o pipefail
find . -name "*.go" | grep -v vendor | xargs gofmt -s -w
================================================
FILE: hack/update-image.sh
================================================
#!/bin/bash
function update() {
docker ccr.ccs.tencentyun.com/pixiucloud/pixiu-aio:latest
docker rm -f pixiu-aio
sleep 3
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
}
update
================================================
FILE: hack/verify-gofmt.sh
================================================
#!/bin/bash
# Copyright 2021 The Pixiu Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -o errexit
set -o nounset
set -o pipefail
HELM_ROOT=$(dirname "${BASH_SOURCE}")/..
cd "${HELM_ROOT}"
find_files() {
find . -not \( \
\( \
-wholename './output' \
-o -wholename '*/vendor/*' \
\) -prune \
\) -name '*.go'
}
GOFMT="gofmt -s"
bad_files=$(find_files | xargs $GOFMT -l)
if [[ -n "${bad_files}" ]]; then
echo "Please run hack/update-gofmt.sh to fix the following files:"
echo "${bad_files}"
exit 1
fi
================================================
FILE: install.md
================================================
# 前置准备
```bash
确保 docker 已经安装
```
# 数据库
```bash
# 选择1:直接提供可用数据库
# 选择2:快速启动数据库
docker run -d --net host --restart=always --privileged=true --name mariadb -e MYSQL_ROOT_PASSWORD="Pixiu868686" ccr.ccs.tencentyun.com/pixiucloud/mysql:5.7
# 创建 pixiu 数据库
CREATE DATABASE pixiu;
```
# 获取部署驱动镜像
```shell
docker pull ccr.ccs.tencentyun.com/pixiucloud/kubez-ansible:v2.0.1
docker pull ccr.ccs.tencentyun.com/pixiucloud/kubez-ansible:v3.0.1
```
# 启动 pixiu 服务端
```bash
# 创建配置文件夹
mkdir -p /etc/pixiu/
# 后端配置(host 根据实际情况调整)
vim /etc/pixiu/config.yaml 写入后端如下配置
default:
# 运行模式,可选 debug 和 release
mode: debug
# 服务监听端口
listen: 8090
# 自动创建指定模型的数据库表结构,不会更新已存在的数据库表
auto_migrate: true
# 前端配置(ip 根据实际情况调整,如果是虚拟机,则配置成虚拟机的公网IP,安全组放通80(http)或者443(https)端口)
# 前端的端口需要和后端监听端口保持一致
dashboard:
url: http://localhost:8090
# 数据库地址信息, 根据实际情况配置
mysql:
host: pixiu # 数据库的ip
user: root
password: Pixiu868686
port: 3306
name: pixiu
worker:
engines:
- image: ccr.ccs.tencentyun.com/pixiucloud/kubez-ansible:v2.0.1
os_supported:
- centos7
- debian10
- ubuntu18.04
- image: ccr.ccs.tencentyun.com/pixiucloud/kubez-ansible:v3.0.1
os_supported:
- debian11
- ubuntu20.04
- ubuntu22.04
- rocky8.5
- rocky9.2
- rocky9.3
- openEuler22.03
```
# 启动 pixiu
```bash
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 ccr.ccs.tencentyun.com/pixiucloud/pixiu
登录效果
浏览器登陆: http://192.168.16.156
```
================================================
FILE: pkg/client/cache.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package client
import (
"context"
"sync"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
appsv1 "k8s.io/client-go/listers/apps/v1"
batchv1 "k8s.io/client-go/listers/batch/v1"
v1 "k8s.io/client-go/listers/core/v1"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
resourceclient "k8s.io/metrics/pkg/client/clientset/versioned/typed/metrics/v1beta1"
)
var (
groupVersionResources = []schema.GroupVersionResource{
{Group: "", Version: "v1", Resource: "pods"},
{Group: "", Version: "v1", Resource: "nodes"},
{Group: "apps", Version: "v1", Resource: "deployments"},
{Group: "apps", Version: "v1", Resource: "statefulsets"},
{Group: "apps", Version: "v1", Resource: "daemonsets"},
{Group: "batch", Version: "v1", Resource: "cronjobs"},
{Group: "batch", Version: "v1", Resource: "jobs"},
}
)
type PixiuInformer struct {
Shared informers.SharedInformerFactory
Cancel context.CancelFunc
}
func (p PixiuInformer) NodesLister() v1.NodeLister {
return p.Shared.Core().V1().Nodes().Lister()
}
func (p PixiuInformer) PodsLister() v1.PodLister {
return p.Shared.Core().V1().Pods().Lister()
}
func (p PixiuInformer) NamespacesLister() v1.NamespaceLister {
return p.Shared.Core().V1().Namespaces().Lister()
}
func (p PixiuInformer) DeploymentsLister() appsv1.DeploymentLister {
return p.Shared.Apps().V1().Deployments().Lister()
}
func (p *PixiuInformer) StatefulSetsLister() appsv1.StatefulSetLister {
return p.Shared.Apps().V1().StatefulSets().Lister()
}
func (p *PixiuInformer) DaemonSetsLister() appsv1.DaemonSetLister {
return p.Shared.Apps().V1().DaemonSets().Lister()
}
func (p *PixiuInformer) CronJobsLister() batchv1.CronJobLister {
return p.Shared.Batch().V1().CronJobs().Lister()
}
func (p *PixiuInformer) JobsLister() batchv1.JobLister { return p.Shared.Batch().V1().Jobs().Lister() }
type ClusterSet struct {
Client *kubernetes.Clientset
Config *restclient.Config
Metric *resourceclient.MetricsV1beta1Client
Informer *PixiuInformer
}
func (cs *ClusterSet) Complete(cfg []byte) error {
var err error
if cs.Config, err = clientcmd.RESTConfigFromKubeConfig(cfg); err != nil {
return err
}
if cs.Client, err = kubernetes.NewForConfig(cs.Config); err != nil {
return err
}
if cs.Metric, err = resourceclient.NewForConfig(cs.Config); err != nil {
return err
}
sharedInformer, cancel, err := NewSharedInformers(cs.Config)
if err != nil {
return err
}
cs.Informer = &PixiuInformer{
Shared: sharedInformer,
Cancel: cancel,
}
return nil
}
func NewSharedInformers(c *restclient.Config) (informers.SharedInformerFactory, context.CancelFunc, error) {
// 重新构造客户端
clientSet, err := kubernetes.NewForConfig(c)
if err != nil {
return nil, nil, err
}
informerFactory := informers.NewSharedInformerFactory(clientSet, 0)
for _, gvr := range groupVersionResources {
if _, err = informerFactory.ForResource(gvr); err != nil {
return nil, nil, err
}
}
ctx, cancel := context.WithCancel(context.Background())
// Start all informers.
informerFactory.Start(ctx.Done())
// Wait for all caches to sync.
informerFactory.WaitForCacheSync(ctx.Done())
return informerFactory, cancel, nil
}
type store map[string]ClusterSet
type Cache struct {
sync.RWMutex
store
}
func NewClusterCache() *Cache {
return &Cache{
store: make(store),
}
}
func (s *Cache) Get(name string) (ClusterSet, bool) {
s.RLock()
defer s.RUnlock()
cluster, ok := s.store[name]
return cluster, ok
}
func (s *Cache) GetConfig(name string) (*restclient.Config, bool) {
s.RLock()
defer s.RUnlock()
clusterSet, ok := s.store[name]
if !ok {
return nil, false
}
return clusterSet.Config, true
}
func (s *Cache) GetClient(name string) (*kubernetes.Clientset, bool) {
s.RLock()
defer s.RUnlock()
clusterSet, ok := s.store[name]
if !ok {
return nil, false
}
return clusterSet.Client, true
}
func (s *Cache) Set(name string, cs ClusterSet) {
s.Lock()
defer s.Unlock()
if s.store == nil {
s.store = store{}
}
s.store[name] = cs
}
func (s *Cache) Delete(name string) {
s.Lock()
defer s.Unlock()
// Cancel informer
cluster, ok := s.store[name]
if !ok {
return
}
cluster.Informer.Cancel()
// 从缓存移除集群数据
delete(s.store, name)
}
func (s *Cache) List() store {
s.Lock()
defer s.Unlock()
return s.store
}
func (s *Cache) Clear() {
s.Lock()
defer s.Unlock()
s.store = store{}
}
================================================
FILE: pkg/client/client.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package client
import (
"encoding/base64"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)
func ParseKubeConfigBytes(cfg string) ([]byte, error) {
kubeConfigBytes, err := base64.StdEncoding.DecodeString(cfg)
if err != nil {
return nil, err
}
return kubeConfigBytes, err
}
func NewClientSetFromBytes(data []byte) (*kubernetes.Clientset, error) {
config, err := clientcmd.RESTConfigFromKubeConfig(data)
if err != nil {
return nil, err
}
return kubernetes.NewForConfig(config)
}
func NewClientSetFromString(cfg string) (*kubernetes.Clientset, error) {
kubeConfigBytes, err := ParseKubeConfigBytes(cfg)
if err != nil {
return nil, err
}
return NewClientSetFromBytes(kubeConfigBytes)
}
func NewClusterSet(cfg string) (*ClusterSet, error) {
kubeConfigBytes, err := ParseKubeConfigBytes(cfg)
if err != nil {
return nil, err
}
cs := &ClusterSet{}
if err = cs.Complete(kubeConfigBytes); err != nil {
return nil, err
}
return cs, nil
}
================================================
FILE: pkg/client/helm.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package client
import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/discovery"
memory "k8s.io/client-go/discovery/cached"
"k8s.io/client-go/rest"
"k8s.io/client-go/restmapper"
"k8s.io/client-go/tools/clientcmd"
)
type HelmRESTClientGetter struct {
kubeConfig *rest.Config
}
var _ genericclioptions.RESTClientGetter = &HelmRESTClientGetter{}
// ToDiscoveryClient implements action.RESTClientGetter.
func (h *HelmRESTClientGetter) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
h.kubeConfig.Burst = 100
discoveryClient, err := discovery.NewDiscoveryClientForConfig(h.kubeConfig)
if err != nil {
return nil, err
}
return memory.NewMemCacheClient(discoveryClient), nil
}
// ToRESTConfig implements action.RESTClientGetter.
func (h *HelmRESTClientGetter) ToRESTConfig() (*rest.Config, error) {
return h.kubeConfig, nil
}
// ToRESTMapper implements action.RESTClientGetter.
func (h *HelmRESTClientGetter) ToRESTMapper() (meta.RESTMapper, error) {
discoveryClient, err := h.ToDiscoveryClient()
if err != nil {
return nil, err
}
mapper := restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient)
expander := restmapper.NewShortcutExpander(mapper, discoveryClient)
return expander, nil
}
func (h *HelmRESTClientGetter) ToRawKubeConfigLoader() clientcmd.ClientConfig {
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig
overrides := &clientcmd.ConfigOverrides{ClusterDefaults: clientcmd.ClusterDefaults}
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides)
}
func NewHelmRESTClientGetter(kubeConfig *rest.Config) *HelmRESTClientGetter {
return &HelmRESTClientGetter{
kubeConfig: kubeConfig,
}
}
================================================
FILE: pkg/client/task.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package client
import (
"context"
"fmt"
"sync"
"time"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/klog/v2"
"github.com/caoyingjunz/pixiu/pkg/db"
"github.com/caoyingjunz/pixiu/pkg/db/model"
)
type WrapObject struct {
LatestTime time.Time // 最近一次获取时间
Object []model.Task // TODO,临时实现,使用task的结构,后续优化成任意类型
}
type Task struct {
sync.RWMutex
Lister func(ctx context.Context, planId int64, opts ...db.Options) ([]model.Task, error)
items map[int64]WrapObject
}
func NewTaskCache() *Task {
t := &Task{items: make(map[int64]WrapObject)}
t.Run()
return t
}
func (t *Task) SetLister(Lister func(ctx context.Context, planId int64, opts ...db.Options) ([]model.Task, error)) {
t.Lock()
defer t.Unlock()
t.Lister = Lister
}
func (t *Task) Get(planId int64) ([]model.Task, bool) {
t.Lock()
defer t.Unlock()
wrapObject, ok := t.items[planId]
if !ok {
return nil, false
}
wrapObject.LatestTime = time.Now()
t.items[planId] = wrapObject
return wrapObject.Object, ok
}
func (t *Task) Set(planId int64, tasks []model.Task) {
t.Lock()
defer t.Unlock()
if t.items == nil {
t.items = map[int64]WrapObject{}
}
now := time.Now()
klog.Infof("add plan(%d) tasks into cache at %v", planId, now)
t.items[planId] = WrapObject{
Object: tasks,
LatestTime: now,
}
}
func (t *Task) SetByTask(planId int64, task model.Task) {
t.Lock()
defer t.Unlock()
wrapObject, ok := t.items[planId]
if !ok {
return
}
var (
index int
found bool
)
for i, s := range wrapObject.Object {
if s.Id == task.Id {
index = i
found = true
break
}
}
if !found {
return
}
wrapObject.Object[index] = task
wrapObject.LatestTime = time.Now()
t.items[planId] = wrapObject
}
func (t *Task) Delete(planId int64) {
t.Lock()
defer t.Unlock()
delete(t.items, planId)
}
// WaitForCacheSync
// 判断缓存中是否已经存在,如果不存在则先写入
func (t *Task) WaitForCacheSync(planId int64) error {
_, ok := t.Get(planId)
if ok {
return nil
}
tasks, err := t.Lister(context.TODO(), planId)
if err != nil {
return fmt.Errorf("failed to get plan(%d) tasks from database: %v", planId, err)
}
t.Set(planId, tasks)
return nil
}
func (t *Task) syncTasks() {
if t.Lister == nil || len(t.items) == 0 {
klog.V(2).Infof("syncing and waiting for the next loop")
return
}
t.Lock()
defer t.Unlock()
for planId, wrapObject := range t.items {
now := time.Now()
if now.Sub(wrapObject.LatestTime) > 5*time.Second {
// 如果对象5分钟未被操作,则从缓存中清理
klog.Infof("remove plan(%d) tasks from cache due to it not be handled for 5s", planId)
delete(t.items, planId)
// 处理下一个对象
continue
}
newTasks, err := t.Lister(context.TODO(), planId)
if err != nil {
klog.Errorf("[syncTasks] failed to list plan(%d) tasks: %v", planId, err)
delete(t.items, planId)
continue
}
t.items[planId] = WrapObject{
Object: newTasks,
LatestTime: now,
}
}
}
// Run 启动 syncTasks
// TODO: 后续优化 stopCh
func (t *Task) Run() {
stopCh := make(<-chan struct{})
// 10s 主动加载一次 task
// 不发起长连接情况下,无任何开销
go wait.Until(t.syncTasks, 10*time.Second, stopCh)
}
================================================
FILE: pkg/client/token_cache.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package client
import "sync"
// TokenCache
// TODO: 临时实现,后续优化
type TokenCache struct {
sync.RWMutex
items map[int64]string
}
func NewTokenCache() *TokenCache {
return &TokenCache{
items: map[int64]string{},
}
}
func (s *TokenCache) Get(uid int64) (string, bool) {
s.RLock()
defer s.RUnlock()
t, ok := s.items[uid]
return t, ok
}
func (s *TokenCache) Set(uid int64, token string) {
s.RLock()
defer s.RUnlock()
if s.items == nil {
s.items = map[int64]string{}
}
s.items[uid] = token
}
func (s *TokenCache) Delete(uid int64) {
s.RLock()
defer s.RUnlock()
delete(s.items, uid)
}
func (s *TokenCache) Clear() {
s.RLock()
defer s.RUnlock()
s.items = map[int64]string{}
}
================================================
FILE: pkg/client/user_cache.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package client
import "sync"
type UserCache struct {
sync.RWMutex
items map[int64]int
}
func NewUserCache() *UserCache {
return &UserCache{
items: map[int64]int{},
}
}
func (s *UserCache) Get(uid int64) (int, bool) {
s.RLock()
defer s.RUnlock()
status, ok := s.items[uid]
return status, ok
}
func (s *UserCache) Set(uid int64, status int) {
s.RLock()
defer s.RUnlock()
if s.items == nil {
s.items = map[int64]int{}
}
s.items[uid] = status
}
func (s *UserCache) Delete(uid int64) {
s.RLock()
defer s.RUnlock()
delete(s.items, uid)
}
func (s *UserCache) Clear() {
s.RLock()
defer s.RUnlock()
s.items = map[int64]int{}
}
================================================
FILE: pkg/controller/audit/audit.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package audit
import (
"context"
"time"
"k8s.io/klog/v2"
"github.com/caoyingjunz/pixiu/api/server/errors"
"github.com/caoyingjunz/pixiu/cmd/app/config"
"github.com/caoyingjunz/pixiu/pkg/db"
"github.com/caoyingjunz/pixiu/pkg/db/model"
"github.com/caoyingjunz/pixiu/pkg/types"
)
type AuditGetter interface {
Audit() Interface
}
type Interface interface {
List(ctx context.Context, listOption types.AuditListOptions) (interface{}, error)
Get(ctx context.Context, aid int64) (*types.Audit, error)
}
type audit struct {
cc config.Config
factory db.ShareDaoFactory
}
func (a *audit) Get(ctx context.Context, aid int64) (*types.Audit, error) {
object, err := a.factory.Audit().Get(ctx, aid)
if err != nil {
klog.Errorf("failed to get audit %d: %v", aid, err)
return nil, errors.ErrServerInternal
}
if object == nil {
return nil, errors.ErrAuditNotFound
}
return a.model2Type(object), nil
}
func (a *audit) List(ctx context.Context, listOption types.AuditListOptions) (interface{}, error) {
// 构建公共过滤 opts
filterOpts := buildAuditFilterOpts(listOption)
// 使用相同过滤条件获取总数
total, err := a.factory.Audit().Count(ctx, filterOpts...)
if err != nil {
klog.Errorf("failed to get audits count: %v", err)
return nil, err
}
page := listOption.Page
if page <= 0 {
page = 1
}
limit := int(listOption.Limit)
if limit <= 0 {
limit = 20
}
paginationOpts := append(filterOpts,
db.WithOffset((page-1)*limit),
db.WithLimit(limit),
db.WithOrderByDesc(),
)
objects, err := a.factory.Audit().List(ctx, paginationOpts...)
if err != nil {
klog.Errorf("failed to get audit events: %v", err)
return nil, errors.ErrServerInternal
}
var ts []types.Audit
for _, object := range objects {
ts = append(ts, *a.model2Type(&object))
}
return types.PageResponse{
PageRequest: listOption.PageRequest,
Total: int(total),
Items: ts,
}, nil
}
func buildAuditFilterOpts(opt types.AuditListOptions) []db.Options {
var opts []db.Options
if opt.Operator != "" {
opts = append(opts, db.WithAuditOperatorLike(opt.Operator))
}
if opt.Action != "" {
opts = append(opts, db.WithAuditAction(opt.Action))
}
if opt.ObjectType != "" {
opts = append(opts, db.WithAuditObjectType(opt.ObjectType))
}
if opt.Cluster != "" {
opts = append(opts, db.WithAuditCluster(opt.Cluster))
}
if opt.Status != nil {
opts = append(opts, db.WithAuditStatus(*opt.Status))
}
if opt.StartTime != "" {
if t, err := time.Parse(time.RFC3339, opt.StartTime); err == nil {
opts = append(opts, db.WithAuditCreatedAfter(t))
}
}
if opt.EndTime != "" {
if t, err := time.Parse(time.RFC3339, opt.EndTime); err == nil {
opts = append(opts, db.WithCreatedBefore(t))
}
}
return opts
}
func (a *audit) model2Type(o *model.Audit) *types.Audit {
return &types.Audit{
PixiuMeta: types.PixiuMeta{
Id: o.Id,
ResourceVersion: o.ResourceVersion,
},
TimeMeta: types.TimeMeta{
GmtCreate: o.GmtCreate,
GmtModified: o.GmtModified,
},
Ip: o.Ip,
Action: o.Action,
Status: o.Status,
Operator: o.Operator,
Path: o.Path,
ObjectType: o.ObjectType,
Duration: o.Duration,
ResponseCode: o.ResponseCode,
Cluster: o.Cluster,
ResourceName: o.ResourceName,
ResourceNamespace: o.ResourceNamespace,
}
}
func NewAudit(cfg config.Config, f db.ShareDaoFactory) *audit {
return &audit{
cc: cfg,
factory: f,
}
}
================================================
FILE: pkg/controller/auth/auth.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package auth
import (
"context"
"fmt"
"net/http"
"github.com/casbin/casbin/v2"
"k8s.io/klog/v2"
"github.com/caoyingjunz/pixiu/api/server/errors"
ctrlutil "github.com/caoyingjunz/pixiu/pkg/controller/util"
"github.com/caoyingjunz/pixiu/pkg/db"
"github.com/caoyingjunz/pixiu/pkg/db/model"
"github.com/caoyingjunz/pixiu/pkg/types"
)
type AuthGetter interface {
Auth() Interface
}
type (
Interface interface {
CreateRBACPolicy(ctx context.Context, req *types.RBACPolicyRequest) error
DeleteRBACPolicy(ctx context.Context, req *types.RBACPolicyRequest) error
ListRBACPolicies(ctx context.Context, req *types.ListRBACPolicyRequest) ([]types.RBACPolicy, error)
CreateGroupBinding(ctx context.Context, req *types.GroupBindingRequest) error
DeleteGroupBinding(ctx context.Context, req *types.GroupBindingRequest) error
ListGroupBindings(ctx context.Context, req *types.ListGroupBindingRequest) ([]types.RBACPolicy, error)
}
)
type auth struct {
enforcer *casbin.SyncedEnforcer
factory db.ShareDaoFactory
}
func NewAuth(factory db.ShareDaoFactory, enforcer *casbin.SyncedEnforcer) Interface {
return &auth{
factory: factory,
enforcer: enforcer,
}
}
// getPolicy returns the RBAC policy represented by the request body
func (a *auth) getPolicy(ctx context.Context, req *types.RBACPolicyRequest) (model.Policy, error) {
if req.UserId != nil {
// user RBAC policy
user, err := a.factory.User().Get(ctx, *req.UserId)
if err != nil {
klog.Errorf("failed to get user(%d): %v", *req.UserId, err)
return nil, errors.ErrServerInternal
}
if user == nil {
return nil, errors.NewError(fmt.Errorf("user(%d) is not found", *req.UserId), http.StatusBadRequest)
}
return model.NewUserPolicy(user.Name, req.ObjectType, req.SID, req.Operation), nil
}
// group RBAC policy
return model.NewGroupPolicy(*req.GroupName, req.ObjectType, req.SID, req.Operation), nil
}
// getBinding returns the group binding policy represented by the request body
func (a *auth) getBinding(ctx context.Context, req *types.GroupBindingRequest) (model.Policy, error) {
user, err := a.factory.User().Get(ctx, req.UserId)
if err != nil {
klog.Errorf("failed to get user(%d): %v", req.UserId, err)
return nil, errors.ErrServerInternal
}
if user == nil {
return nil, errors.NewError(fmt.Errorf("user(%d) is not found", req.UserId), http.StatusBadRequest)
}
policy, err := ctrlutil.GetGroupPolicy(a.enforcer, req.GroupName)
if err != nil {
klog.Errorf("failed to get group(%d): %v", req.GroupName, err)
return nil, errors.ErrServerInternal
}
if policy == nil {
return nil, errors.NewError(fmt.Errorf("group(%s) is not found", req.GroupName), http.StatusBadRequest)
}
return model.NewGroupBinding(user.Name, req.GroupName), nil
}
func (a *auth) CreateRBACPolicy(ctx context.Context, req *types.RBACPolicyRequest) error {
policy, err := a.getPolicy(ctx, req)
if err != nil {
return err
}
ok, err := a.enforcer.AddPolicy(policy.Raw())
if err != nil {
klog.Errorf("failed to create policy %v: %v", policy.Raw(), err)
return errors.ErrServerInternal
}
if !ok {
return errors.ErrRBACPolicyExists
}
return nil
}
func (a *auth) DeleteRBACPolicy(ctx context.Context, req *types.RBACPolicyRequest) error {
policy, err := a.getPolicy(ctx, req)
if err != nil {
return err
}
switch p := policy.(type) {
case model.UserPolicy:
klog.Infof("delete user policy: %v", policy.Raw())
case model.GroupPolicy:
// check existing group bindings
klog.Infof("delete group policy: %v", policy.Raw())
bindings, err := ctrlutil.GetGroupBindings(a.enforcer, ctrlutil.QueryWithGroupName(p.GetGroupName()))
if err != nil {
klog.Errorf("failed to get bindings of group policy(%v): %v", policy.Raw(), err)
return errors.ErrServerInternal
}
if len(bindings) > 0 {
return errors.NewError(fmt.Errorf("用户组 %s 已绑定至某些用户", p.GetGroupName()), http.StatusForbidden)
}
}
ok, err := a.enforcer.RemovePolicy(policy.Raw())
if err != nil {
klog.Errorf("failed to delete policy %v: %v", policy.Raw(), err)
return errors.ErrServerInternal
}
if !ok {
return errors.ErrRBACPolicyNotFound
}
return nil
}
func (a *auth) ListRBACPolicies(ctx context.Context, req *types.ListRBACPolicyRequest) ([]types.RBACPolicy, error) {
user, err := a.factory.User().Get(ctx, req.UserId)
if err != nil {
klog.Errorf("failed to get user(%d): %v", req.UserId, err)
return nil, errors.ErrServerInternal
}
if user == nil {
return nil, errors.NewError(fmt.Errorf("user(%d) is not found", req.UserId), http.StatusBadRequest)
}
conds := make([]ctrlutil.PolicyCondition, 0)
if req.ObjectType != nil {
conds = append(conds, ctrlutil.WithObjectType(*req.ObjectType))
}
if req.SID != nil {
conds = append(conds, ctrlutil.WithStringID(*req.SID))
}
if req.Operation != nil {
conds = append(conds, ctrlutil.WithOperation(*req.Operation))
}
policies, err := ctrlutil.GetUserPolicies(a.enforcer, user, conds...)
if err != nil {
klog.Errorf("failed to list policies: %v", err)
return nil, errors.ErrServerInternal
}
rbacPolicies := make([]types.RBACPolicy, len(policies))
for i, policy := range policies {
rbacPolicies[i] = *model2Type(policy)
}
return rbacPolicies, nil
}
func (a *auth) CreateGroupBinding(ctx context.Context, req *types.GroupBindingRequest) error {
binding, err := a.getBinding(ctx, req)
if err != nil {
return err
}
ok, err := a.enforcer.AddGroupingPolicy(binding.Raw())
if err != nil {
klog.Errorf("failed to create group binding %v: %v", binding.Raw(), err)
return errors.ErrServerInternal
}
if !ok {
return errors.ErrGroupBindingExists
}
return nil
}
func (a *auth) DeleteGroupBinding(ctx context.Context, req *types.GroupBindingRequest) error {
binding, err := a.getBinding(ctx, req)
if err != nil {
return err
}
ok, err := a.enforcer.RemoveGroupingPolicy(binding.Raw())
if err != nil {
klog.Errorf("failed to delete group binding %v: %v", binding.Raw(), err)
return errors.ErrServerInternal
}
if !ok {
return errors.ErrGroupBindingNotFound
}
return nil
}
func (a *auth) ListGroupBindings(ctx context.Context, req *types.ListGroupBindingRequest) ([]types.RBACPolicy, error) {
conds := make([]ctrlutil.BindingQueryCondition, 0)
if req.UserId != nil {
user, err := a.factory.User().Get(ctx, *req.UserId)
if err != nil {
klog.Errorf("failed to get user(%d): %v", *req.UserId, err)
return nil, errors.ErrServerInternal
}
if user == nil {
return nil, errors.NewError(fmt.Errorf("user(%d) is not found", *req.UserId), http.StatusBadRequest)
}
conds = append(conds, ctrlutil.QueryWithUserName(user.Name))
}
if req.GroupName != nil {
policy, err := ctrlutil.GetGroupPolicy(a.enforcer, *req.GroupName)
if err != nil {
klog.Errorf("failed to get group(%s): %v", *req.GroupName, err)
return nil, errors.ErrServerInternal
}
if policy == nil {
return nil, errors.NewError(fmt.Errorf("group(%s) is not found", *req.GroupName), http.StatusBadRequest)
}
conds = append(conds, ctrlutil.QueryWithGroupName(*req.GroupName))
}
bindings, err := ctrlutil.GetGroupBindings(a.enforcer, conds...)
if err != nil {
klog.Errorf("failed to get group bindings: %v", *req.GroupName, err)
return nil, errors.ErrServerInternal
}
bindingPolicies := make([]types.RBACPolicy, len(bindings))
for i, binding := range bindings {
bindingPolicies[i] = *model2Type(binding)
}
return bindingPolicies, nil
}
func model2Type(policy model.Policy) *types.RBACPolicy {
switch p := policy.(type) {
case model.UserPolicy:
return &types.RBACPolicy{
UserName: p.GetUserName(),
ObjectType: p.GetObjectType(),
StringID: p.GetSID(),
Operation: p.GetOperation(),
}
case model.GroupPolicy:
return &types.RBACPolicy{
GroupName: p.GetGroupName(),
ObjectType: p.GetObjectType(),
StringID: p.GetSID(),
Operation: p.GetOperation(),
}
case model.GroupBinding:
return &types.RBACPolicy{
UserName: p.GetUserName(),
GroupName: p.GetGroupName(),
}
}
return nil
}
================================================
FILE: pkg/controller/cluster/cluster.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cluster
import (
"context"
"fmt"
"io"
"net/http"
"regexp"
"strconv"
"strings"
"sync"
"time"
"github.com/casbin/casbin/v2"
"github.com/gorilla/websocket"
batchv1 "k8s.io/api/batch/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
apitypes "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"k8s.io/klog/v2"
"k8s.io/metrics/pkg/apis/metrics/v1beta1"
"github.com/caoyingjunz/pixiu/api/server/errors"
"github.com/caoyingjunz/pixiu/api/server/httputils"
"github.com/caoyingjunz/pixiu/cmd/app/config"
"github.com/caoyingjunz/pixiu/pkg/client"
ctrlutil "github.com/caoyingjunz/pixiu/pkg/controller/util"
"github.com/caoyingjunz/pixiu/pkg/db"
"github.com/caoyingjunz/pixiu/pkg/db/model"
"github.com/caoyingjunz/pixiu/pkg/types"
"github.com/caoyingjunz/pixiu/pkg/util"
"github.com/caoyingjunz/pixiu/pkg/util/uuid"
)
type ClusterGetter interface {
Cluster() Interface
}
type Interface interface {
Create(ctx context.Context, req *types.CreateClusterRequest) error
Update(ctx context.Context, cid int64, req *types.UpdateClusterRequest) error
Delete(ctx context.Context, cid int64) error
Get(ctx context.Context, cid int64) (*types.Cluster, error)
List(ctx context.Context, req *types.ListClusterRequest) (*types.PageResponse, error)
// Ping 检查和 k8s 集群的连通性
Ping(ctx context.Context, kubeConfig string) error
// Protect 设置集群的保护策略
Protect(ctx context.Context, cid int64, req *types.ProtectClusterRequest) error
// GetEventList 获取指定对象的事件,支持做聚合
GetEventList(ctx context.Context, cluster string, options types.EventOptions) (*v1.EventList, error)
// AggregateEvents 聚合指定资源的 events
AggregateEvents(ctx context.Context, cluster string, namespace string, name string, kind string) (*v1.EventList, error)
// WsHandler pod 的 webShell
WsHandler(ctx context.Context, webShellOptions *types.WebShellOptions, w http.ResponseWriter, r *http.Request) error
// WsNodeHandler node 的 webShell
WsNodeHandler(ctx context.Context, sshConfig *types.WebSSHRequest, w http.ResponseWriter, r *http.Request) error
// WatchPodLog 实时获取 pod 的日志
WatchPodLog(ctx context.Context, cluster string, namespace string, podName string, containerName string, tailLine int64, w http.ResponseWriter, r *http.Request) error
// ReRunJob 重新执行指定任务
ReRunJob(ctx context.Context, cluster string, namespace string, jobName string, resourceVersion string) error
GetKubeConfigByName(ctx context.Context, name string) (*restclient.Config, error)
GetIndexerResource(ctx context.Context, cluster string, resource string, namespace string, name string) (interface{}, error)
ListIndexerResources(ctx context.Context, cluster string, resource string, namespace string, listOption types.ListOptions) (interface{}, error)
// Run 启动 cluster worker 处理协程
Run(ctx context.Context, workers int) error
}
var ClusterIndexer client.Cache
func init() {
ClusterIndexer = *client.NewClusterCache()
}
type (
listerFunc func(ctx context.Context, informer *client.PixiuInformer, namespace string, listOption types.ListOptions) (interface{}, error)
getterFunc func(ctx context.Context, informer *client.PixiuInformer, namespace, name string) (interface{}, error)
)
type InformerResource struct {
// k8s 资源类型,比如 deployment, sts, daemonset 等
ResourceType string
ListerFunc listerFunc
GetterFunc getterFunc
}
type cluster struct {
cc config.Config
factory db.ShareDaoFactory
enforcer *casbin.SyncedEnforcer
listerFuncs map[string]listerFunc
getterFuncs map[string]getterFunc
}
func (c *cluster) preCreate(ctx context.Context, req *types.CreateClusterRequest) error {
// 实际创建前,先创建集群的连通性
if err := c.Ping(ctx, req.KubeConfig); err != nil {
return fmt.Errorf("尝试连接 kubernetes API 失败: %v", err)
}
return nil
}
func (c *cluster) Create(ctx context.Context, req *types.CreateClusterRequest) error {
user, err := httputils.GetUserFromRequest(ctx)
if err != nil {
return errors.NewError(err, http.StatusInternalServerError)
}
if err := c.preCreate(ctx, req); err != nil {
return errors.NewError(err, http.StatusBadRequest)
}
// TODO: 集群名称必须是由英文,数字组成
if len(req.Name) == 0 {
req.Name = uuid.NewRandName(8)
}
var cs *client.ClusterSet
var txFunc = func(cluster *model.Cluster) (err error) {
if cs, err = client.NewClusterSet(req.KubeConfig); err != nil {
return
}
// insert a user RBAC policy
policy := model.NewPolicyFromModels(user, model.ObjectCluster, cluster.Model, model.OpAll)
_, err = c.enforcer.AddPolicy(policy.Raw())
return
}
kubeNode := types.KubeNode{}
nodes, _ := kubeNode.Marshal()
if _, err := c.factory.Cluster().Create(ctx, &model.Cluster{
Name: req.Name,
AliasName: req.AliasName,
ClusterType: req.Type,
Protected: req.Protected,
KubeConfig: req.KubeConfig,
Description: req.Description,
Nodes: nodes,
}, txFunc); err != nil {
klog.Errorf("failed to create cluster %s: %v", req.Name, err)
return errors.ErrServerInternal
}
// TODO: 暂时不做创建后动作
ClusterIndexer.Set(req.Name, *cs)
return nil
}
func (c *cluster) Update(ctx context.Context, cid int64, req *types.UpdateClusterRequest) error {
object, err := c.factory.Cluster().Get(ctx, cid)
if err != nil {
klog.Errorf("failed to get cluster(%d): %v", cid, err)
return errors.ErrServerInternal
}
if object == nil {
return errors.ErrClusterNotFound
}
updates := make(map[string]interface{})
if req.AliasName != nil {
updates["alias_name"] = *req.AliasName
}
if req.Description != nil {
updates["description"] = *req.Description
}
if len(updates) == 0 {
return errors.ErrInvalidRequest
}
if err = c.factory.Cluster().Update(ctx, cid, *req.ResourceVersion, updates); err != nil {
klog.Errorf("failed to update cluster(%d): %v", cid, err)
return errors.ErrServerInternal
}
return nil
}
// 删除前置检查
// 开启集群删除保护,则不允许删除
func (c *cluster) preDelete(ctx context.Context, cid int64) (cluster *model.Cluster, err error) {
if cluster, err = c.factory.Cluster().Get(ctx, cid); err != nil {
klog.Errorf("failed to get cluster(%d): %v", cid, err)
return
}
if cluster == nil {
return nil, errors.ErrClusterNotFound
}
// 开启集群删除保护,则不允许删除
if cluster.Protected {
return nil, errors.NewError(fmt.Errorf("已开启集群删除保护功能,不允许删除 %s", cluster.AliasName),
http.StatusForbidden)
}
// TODO: 其他删除策略检查
return
}
func (c *cluster) Delete(ctx context.Context, cid int64) error {
user, err := httputils.GetUserFromRequest(ctx)
if err != nil {
return errors.NewError(err, http.StatusInternalServerError)
}
cluster, err := c.preDelete(ctx, cid)
if err != nil {
return err
}
var txFunc = func(cluster *model.Cluster) (err error) {
_, err = c.enforcer.RemoveNamedPolicy("p", user.Name, model.ObjectCluster.String(), cluster.GetSID())
return
}
if err := c.factory.Cluster().Delete(ctx, cluster, txFunc); err != nil {
klog.Errorf("failed to delete cluster(%d): %v", cid, err)
return errors.ErrServerInternal
}
// 从缓存中移除 clusterSet
ClusterIndexer.Delete(cluster.Name)
return nil
}
func (c *cluster) Get(ctx context.Context, cid int64) (*types.Cluster, error) {
object, err := c.factory.Cluster().Get(ctx, cid)
if err != nil {
return nil, errors.ErrServerInternal
}
if object == nil {
return nil, errors.ErrClusterNotFound
}
return c.model2Type(object), nil
}
func (c *cluster) List(ctx context.Context, req *types.ListClusterRequest) (*types.PageResponse, error) {
opts := ctrlutil.MakeDbOptions(ctx)
if req != nil && req.NameSelector != "" {
opts = append(opts, db.WithAliasNameLike(req.NameSelector))
}
if req != nil && req.Status != nil {
opts = append(opts, db.WithClusterStatus(*req.Status))
}
total, err := c.factory.Cluster().Count(ctx, opts...)
if err != nil {
return nil, err
}
pageReq := types.PageRequest{}
if req != nil {
pageReq = req.PageRequest
if req.Page > 0 && req.Limit > 0 {
opts = append(opts, db.WithOffset((req.Page-1)*req.Limit), db.WithLimit(req.Limit))
}
}
objects, err := c.factory.Cluster().List(ctx, opts...)
if err != nil {
return nil, err
}
cs := make([]types.Cluster, len(objects))
for i, object := range objects {
cs[i] = *c.model2Type(&object)
}
return &types.PageResponse{
PageRequest: pageReq,
Total: int(total),
Items: cs,
}, nil
}
// Ping 检查和 k8s 集群的连通性
// 如果能获取到 k8s 接口的正常返回,则返回 nil,否则返回具体 error
// kubeConfig 为 k8s 证书的 base64 字符串
func (c *cluster) Ping(ctx context.Context, kubeConfig string) error {
clientSet, err := client.NewClientSetFromString(kubeConfig)
if err != nil {
return err
}
// 调用 ns 资源,确保连通
var timeout int64 = 1
if _, err = clientSet.CoreV1().Namespaces().List(ctx, metav1.ListOptions{
TimeoutSeconds: &timeout,
}); err != nil {
klog.Errorf("failed to ping kubernetes: %v", err)
// 处理原始报错信息,仅返回连接不通的信息
return fmt.Errorf("kubernetes 集群连接测试失败")
}
return nil
}
func (c *cluster) Protect(ctx context.Context, cid int64, req *types.ProtectClusterRequest) error {
if err := c.factory.Cluster().Update(ctx, cid, *req.ResourceVersion, map[string]interface{}{
"protected": req.Protected,
}); err != nil {
klog.Errorf("failed to protect cluster(%d): %v", cid, err)
return err
}
return nil
}
func (c *cluster) GetEventList(ctx context.Context, cluster string, options types.EventOptions) (*v1.EventList, error) {
if options.Limit == 0 {
options.Limit = 500
}
opt := metav1.ListOptions{Limit: options.Limit}
fs := c.makeFieldSelector(apitypes.UID(options.Uid), options.Name, options.Namespace, options.Kind)
if len(fs) != 0 {
opt.FieldSelector = fs
}
clusterSet, err := c.GetClusterSetByName(ctx, cluster)
if err != nil {
return nil, err
}
return clusterSet.Client.CoreV1().Events(options.Namespace).List(ctx, opt)
}
// WatchPodLog streams the logs of a pod in a cluster to a websocket connection.
//
// Parameters:
// - ctx: The context.Context object for the request.
// - cluster: The name of the cluster.
// - namespace: The namespace of the pod.
// - podName: The name of the pod.
// - containerName: The name of the container.
// - tailLine: The number of lines to show from the end of the logs.
// - w: The http.ResponseWriter object for the websocket connection.
// - r: The *http.Request object for the websocket connection.
//
// Returns:
// - error: An error if there was a problem streaming the logs.
func (c *cluster) WatchPodLog(ctx context.Context, cluster string, namespace string, podName string, containerName string, tailLine int64, w http.ResponseWriter, r *http.Request) error {
clusterSet, err := c.GetClusterSetByName(ctx, cluster)
if err != nil {
klog.Errorf("failed to get cluster(%s) clientSet: %v", cluster, err)
return err
}
req := clusterSet.Client.CoreV1().Pods(namespace).GetLogs(podName, &v1.PodLogOptions{
Container: containerName,
Follow: true,
TailLines: &tailLine,
Timestamps: false,
})
if req == nil {
klog.Errorf("failed to get stream")
return fmt.Errorf("failed to get stream")
}
withTimeout, cancelFunc := context.WithTimeout(ctx, time.Minute*10)
defer cancelFunc()
reader, err := req.Stream(withTimeout)
if err != nil {
klog.Errorf("failed to get stream: %v", err)
return err
}
defer reader.Close()
conn, err := util.BuildWebSocketConnection(w, r)
if err != nil {
klog.Errorf("failed to build websocket connection: %v", err)
return err
}
defer conn.Close()
for {
buf := make([]byte, 1024)
n, err := reader.Read(buf)
if err != nil && err != io.EOF {
break
}
err = conn.WriteMessage(websocket.TextMessage, buf[0:n])
if err != nil {
klog.Errorf("failed to write message: %v ,this websocket connection will be closed", err)
break
}
}
return nil
}
const Retries = 3
// ReRunJob 重新运行(创建)任务,通过先删除在创建的方式实现,极端情况下可能导致 job 丢失
func (c *cluster) ReRunJob(ctx context.Context, cluster string, namespace string, jobName string, resourceVersion string) error {
cs, err := c.GetClusterSetByName(ctx, cluster)
if err != nil {
return err
}
job, err := cs.Client.BatchV1().Jobs(namespace).Get(ctx, jobName, metav1.GetOptions{})
if err != nil {
return err
}
if job.ResourceVersion != resourceVersion {
return fmt.Errorf("please apply your changes to the latest and re-run")
}
newJob := *job
// 重置不必要字段
newJob.ResourceVersion = ""
newJob.ObjectMeta.UID = ""
newJob.Status = batchv1.JobStatus{}
// 重置 uid 和 label
delete(newJob.Spec.Selector.MatchLabels, "controller-uid")
delete(newJob.Spec.Selector.MatchLabels, "batch.kubernetes.io/controller-uid")
delete(newJob.Spec.Template.ObjectMeta.Labels, "controller-uid")
delete(newJob.Spec.Template.ObjectMeta.Labels, "batch.kubernetes.io/controller-uid")
delete(newJob.Spec.Template.ObjectMeta.Labels, "batch.kubernetes.io/job-name")
delete(newJob.Spec.Template.ObjectMeta.Labels, "job-name")
// TODO: 备份一次job,避免失败job丢失
// 2. 删除job
if err = cs.Client.BatchV1().Jobs(namespace).Delete(ctx, jobName, metav1.DeleteOptions{}); err != nil {
return fmt.Errorf("failed to rerun job(%s) %v", jobName, err)
}
var jobErr error
// 3. 新建job,最多重试 3 次
for i := 0; i < Retries; i++ {
_, jobErr = cs.Client.BatchV1().Jobs(namespace).Create(ctx, &newJob, metav1.CreateOptions{})
if jobErr != nil {
time.Sleep(time.Second)
continue
}
break
}
if jobErr != nil {
return fmt.Errorf("failed to rerun job(%s) %v", jobName, err)
}
return nil
}
// AggregateEvents 聚合 k8s 资源的所有 events,比如 kind 为 deployment 时,则聚合 deployment,所属 rs 以及 pod 的事件
func (c *cluster) AggregateEvents(ctx context.Context, cluster string, namespace string, name string, kind string) (*v1.EventList, error) {
clusterSet, err := c.GetClusterSetByName(ctx, cluster)
if err != nil {
return nil, err
}
var fieldSelectors []string
switch kind {
case "deployment":
// TODO: 临时聚合方式,后续继续优化(简化)
// 获取 deployment
deployment, err := clusterSet.Client.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
klog.Errorf("failed to get deployment (%s/%s), err: %v", namespace, name, err)
return nil, err
}
fieldSelectors = append(fieldSelectors, c.makeFieldSelector(deployment.UID, deployment.Name, deployment.Namespace, "Deployment"))
var labels []string
for k, v := range deployment.Spec.Selector.MatchLabels {
labels = append(labels, fmt.Sprintf("%s=%s", k, v))
}
labelSelector := strings.Join(labels, ",")
kubeObject, err := c.GetKubeObjectByLabel(clusterSet.Client, namespace, labelSelector, "ReplicaSet", "Pod")
if err != nil {
return nil, err
}
// 获取 rs
allReplicaSets := kubeObject.GetReplicaSets()
var replicaSetUIDs []apitypes.UID
for _, rs := range allReplicaSets {
for _, ownerReference := range rs.OwnerReferences {
if ownerReference.Kind == "Deployment" && ownerReference.UID == deployment.UID {
fieldSelectors = append(fieldSelectors, c.makeFieldSelector(rs.UID, rs.Name, rs.Namespace, "ReplicaSet"))
replicaSetUIDs = append(replicaSetUIDs, rs.UID)
}
}
}
// 获取 pods
allPods := kubeObject.GetPods()
for _, p := range allPods {
for _, ownerReference := range p.OwnerReferences {
for _, replicaSetUID := range replicaSetUIDs {
if ownerReference.UID == replicaSetUID && ownerReference.Kind == "ReplicaSet" {
fieldSelectors = append(fieldSelectors, c.makeFieldSelector(p.UID, p.Name, p.Namespace, "Pod"))
}
}
}
}
default:
return nil, fmt.Errorf("unsupported kubernetes object kind %s", kind)
}
diff := len(fieldSelectors)
errCh := make(chan error, diff)
eventCh := make(chan *v1.EventList, diff)
var wg sync.WaitGroup
wg.Add(diff)
for _, fieldSelector := range fieldSelectors {
go func(fs string) {
defer wg.Done()
events, err := clusterSet.Client.CoreV1().Events(namespace).List(context.TODO(), metav1.ListOptions{
FieldSelector: fs,
Limit: 500,
})
if err != nil {
klog.Errorf("failed to get object(%s) events: %v", namespace, err)
errCh <- err
}
eventCh <- events
}(fieldSelector)
}
wg.Wait()
select {
case err := <-errCh:
if err != nil {
return nil, err
}
default:
}
eventList := &v1.EventList{Items: []v1.Event{}}
for i := 0; i < diff; i++ {
es := <-eventCh
if es == nil {
continue
}
eventList.Items = append(eventList.Items, es.Items...)
}
return eventList, nil
}
// GetKubeObjectByLabel
// TODO: 并发优化
func (c *cluster) GetKubeObjectByLabel(Client *kubernetes.Clientset, namespace string, labelSelector string, kinds ...string) (*types.KubeObject, error) {
object := &types.KubeObject{}
kindSet := sets.NewString(kinds...)
errCh := make(chan error, kindSet.Len())
var wg sync.WaitGroup
wg.Add(kindSet.Len())
// 后续优化
if kindSet.Has("ReplicaSet") {
go func() {
defer wg.Done()
allReplicaSets, err := Client.AppsV1().ReplicaSets(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector, Limit: 500})
if err != nil {
errCh <- err
} else {
object.SetReplicaSets(allReplicaSets.Items)
}
}()
}
if kindSet.Has("Pod") {
go func() {
defer wg.Done()
allPods, err := Client.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector, Limit: 500})
if err != nil {
errCh <- err
} else {
object.SetPods(allPods.Items)
}
}()
}
wg.Wait()
select {
case err := <-errCh:
if err != nil {
return nil, err
}
default:
}
return object, nil
}
func (c *cluster) GetKubeConfigByName(ctx context.Context, name string) (*restclient.Config, error) {
cs, err := c.GetClusterSetByName(ctx, name)
if err != nil {
return nil, err
}
return cs.Config, nil
}
// GetClusterSetByName 获取 ClusterSet, 缓存中不存在时,构建缓存再返回
func (c *cluster) GetClusterSetByName(ctx context.Context, name string) (client.ClusterSet, error) {
cs, ok := ClusterIndexer.Get(name)
if ok {
klog.Infof("Get %s clusterSet from cache", name)
return cs, nil
}
klog.Infof("building clusterSet for %s", name)
// 缓存中不存在,则新建并重写回缓存
object, err := c.factory.Cluster().GetClusterByName(ctx, name)
if err != nil {
return client.ClusterSet{}, err
}
if object == nil {
return client.ClusterSet{}, errors.ErrClusterNotFound
}
newClusterSet, err := client.NewClusterSet(object.KubeConfig)
if err != nil {
return client.ClusterSet{}, err
}
klog.Infof("set %s clusterSet into cache", name)
ClusterIndexer.Set(name, *newClusterSet)
return *newClusterSet, nil
}
// GetKubernetesMeta
// TODO:临时构造 client,后续通过 informer 的方式维护缓存
func (c *cluster) GetKubernetesMeta(ctx context.Context, clusterName string) (*types.KubernetesMeta, error) {
clusterSet, err := c.GetClusterSetByName(ctx, clusterName)
if err != nil {
return nil, err
}
// 获取 k8s 的节点信息
nodeList, err := clusterSet.Client.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
if err != nil {
return nil, err
}
nodes := nodeList.Items
// 在集群启动,但是没有节点加入时,命中该场景
if len(nodes) == 0 {
return nil, fmt.Errorf("no nodes found")
}
// 构造 kubernetes 资源数据格式
km := types.KubernetesMeta{
Nodes: len(nodes),
KubernetesVersion: nodes[0].Status.NodeInfo.KubeletVersion,
}
// TODO: 并发优化
// 获取集群所有节点的资源数据,并做整合
//metricList, err := clusterSet.Metric.NodeMetricses().List(ctx, metav1.ListOptions{})
//if err != nil {
// return nil, err
//}
//km.Resources = c.parseKubernetesResource(metricList.Items)
return &km, nil
}
func (c *cluster) GetKubernetesMetaFromPlan(ctx context.Context, planId int64) (*types.KubernetesMeta, error) {
planConfig, err := c.factory.Plan().GetConfigByPlan(ctx, planId)
if err != nil {
return nil, err
}
ks := &types.KubernetesSpec{}
if err = ks.Unmarshal(planConfig.Kubernetes); err != nil {
return nil, err
}
nodes, err := c.factory.Plan().ListNodes(ctx, planId)
if err != nil {
return nil, err
}
return &types.KubernetesMeta{
KubernetesVersion: "v" + ks.KubernetesVersion,
Nodes: len(nodes),
}, nil
}
// 构造事件的 FieldSelector, 如果参数为空则忽略
func (c *cluster) makeFieldSelector(uid apitypes.UID, name string, namespace string, kind string) string {
eventFS := make([]string, 0)
// 追加对象的 uid
if util.IsEmptyS(string(uid)) {
eventFS = append(eventFS, "involvedObject.uid="+string(uid))
}
if util.IsEmptyS(name) {
eventFS = append(eventFS, "involvedObject.name="+name)
}
if util.IsEmptyS(namespace) {
eventFS = append(eventFS, "involvedObject.namespace="+namespace)
}
if util.IsEmptyS(kind) {
eventFS = append(eventFS, "involvedObject.kind="+kind)
}
// 构造 kubernetes 原生 FieldSelector 参数格式
return strings.Join(eventFS, ",")
}
func (c *cluster) parseKubernetesResource(nodeMetrics []v1beta1.NodeMetrics) types.Resources {
// 初始化集群资源
resourceList := v1.ResourceList{
v1.ResourceCPU: resource.Quantity{},
v1.ResourceMemory: resource.Quantity{},
}
// 遍历所有 metric 数据,算集群总和,仅计算 cpu 和 memory
for _, metric := range nodeMetrics {
// 1. Cpu
cpuMetric := metric.Usage.Cpu()
if cpuMetric != nil {
cpuSum := resourceList[v1.ResourceCPU]
cpuSum.Add(*cpuMetric)
resourceList[v1.ResourceCPU] = cpuSum
}
// 2. Memory
memoryMetric := metric.Usage.Memory()
if memoryMetric != nil {
memSum := resourceList[v1.ResourceMemory]
memSum.Add(*memoryMetric)
resourceList[v1.ResourceMemory] = memSum
}
}
cpuSum := resourceList[v1.ResourceCPU]
memSum := resourceList[v1.ResourceMemory]
return types.Resources{
Cpu: strconv.FormatFloat(parseFloat64FromString(cpuSum.String())/1000/1000/1000, 'f', 2, 64) + " Core",
Memory: strconv.FormatFloat(parseFloat64FromString(memSum.String())/1024/1024, 'f', 2, 64) + " Gi"}
}
// parseFloat64FromString 从字符串中解析出包含的数字,并以 float64 返回。
// 无法解析时,返回 0
// 仅解析最先遇到的数字,效果:
// "666ddd" -> 666
// "666ddd888" -> 666
// "" 或者 "ddd"- > 0
func parseFloat64FromString(s string) float64 {
matcher := regexp.MustCompile(`\d+`)
fs := matcher.FindString(s)
if len(fs) == 0 {
return 0
}
f, err := strconv.ParseFloat(fs, 64)
if err != nil {
return 0
}
return f
}
func (c *cluster) model2Type(o *model.Cluster) *types.Cluster {
nodes := types.KubeNode{}
if strings.TrimSpace(o.Nodes) != "" {
if err := nodes.Unmarshal(o.Nodes); err != nil {
// 非核心数据
klog.Warningf("failed to unmarshal cluster nodes: %v", err)
}
}
tc := &types.Cluster{
PixiuMeta: types.PixiuMeta{
Id: o.Id,
ResourceVersion: o.ResourceVersion,
},
TimeMeta: types.TimeMeta{
GmtCreate: o.GmtCreate,
GmtModified: o.GmtModified,
},
Name: o.Name,
AliasName: o.AliasName,
ClusterType: o.ClusterType,
KubernetesVersion: o.KubernetesVersion,
Nodes: nodes,
PlanId: o.PlanId,
Status: o.ClusterStatus, // 默认是运行中状态,自建集群会根据实际任务状态修改状态
Protected: o.Protected,
Description: o.Description,
}
//var (
// kubernetesMeta *types.KubernetesMeta
// err error
//)
//
//if o.ClusterType == model.ClusterTypeStandard {
// // 导入的集群通过API获取相关数据
// // 获取失败时,返回空的 kubernetes Meta, 不终止主流程
// // TODO: 后续改成并发处理
// kubernetesMeta, err = c.GetKubernetesMeta(context.TODO(), o.Name)
//} else {
// // 自建的集群通过plan配置获取版本信息
// kubernetesMeta, err = c.GetKubernetesMetaFromPlan(context.TODO(), o.PlanId)
//
// // 自建的集群需要从 plan task 获取状态
// tc.Status, _ = c.GetClusterStatusFromPlanTask(o.PlanId)
//}
//if err != nil {
// klog.Warning("failed to get kubernetes Meta: %v", err)
//} else {
// tc.KubernetesMeta = *kubernetesMeta
//}
return tc
}
func (c *cluster) GetClusterStatusFromPlanTask(planId int64) (model.ClusterStatus, error) {
status := model.ClusterStatusRunning
// 尝试获取最新的任务状态
// 获取失败也不中断返回
if tasks, err := c.factory.Plan().ListTasks(context.TODO(), planId); err == nil {
if len(tasks) == 0 {
status = model.ClusterStatusUnStart
} else {
for _, task := range tasks {
if task.Status != model.SuccessPlanStatus {
if task.Status == model.FailedPlanStatus {
status = model.ClusterStatusFailed
} else {
status = model.ClusterStatusDeploy
}
break
}
}
}
}
return status, nil
}
func (c *cluster) registerIndexers(informerResources ...InformerResource) {
for _, informerResource := range informerResources {
c.listerFuncs[informerResource.ResourceType] = informerResource.ListerFunc
c.getterFuncs[informerResource.ResourceType] = informerResource.GetterFunc
}
}
func (c *cluster) Run(ctx context.Context, workers int) error {
klog.Infof("starting cluster manager")
// 同步集群状态,节点数,版本
go wait.UntilWithContext(ctx, c.Sync, 5*time.Second)
return nil
}
func (c *cluster) Sync(ctx context.Context) {
// TODO: 后续添加同步任务
}
func NewCluster(cfg config.Config, f db.ShareDaoFactory, e *casbin.SyncedEnforcer) *cluster {
c := &cluster{
cc: cfg,
factory: f,
enforcer: e,
listerFuncs: make(map[string]listerFunc),
getterFuncs: make(map[string]getterFunc),
}
// TODO: code generation?
//c.registerIndexers([]InformerResource{
// {
// ResourceType: ResourcePod,
// ListerFunc: func(ctx context.Context, informer *client.PixiuInformer, namespace string, listOption types.ListOptions) (interface{}, error) {
// return c.ListPods(ctx, informer.PodsLister(), namespace, listOption)
// },
// GetterFunc: func(ctx context.Context, informer *client.PixiuInformer, namespace, name string) (interface{}, error) {
// return c.GetPod(ctx, informer.PodsLister(), namespace, name)
// },
// },
// {
// ResourceType: ResourceDeployment,
// ListerFunc: func(ctx context.Context, informer *client.PixiuInformer, namespace string, listOption types.ListOptions) (interface{}, error) {
// return c.ListDeployments(ctx, informer.DeploymentsLister(), namespace, listOption)
// },
// GetterFunc: func(ctx context.Context, informer *client.PixiuInformer, namespace, name string) (interface{}, error) {
// return c.GetDeployment(ctx, informer.DeploymentsLister(), namespace, name)
// },
// },
// {
// ResourceType: ResourceStatefulSet,
// ListerFunc: func(ctx context.Context, informer *client.PixiuInformer, namespace string, listOption types.ListOptions) (interface{}, error) {
// return c.ListStatefulSets(ctx, informer.StatefulSetsLister(), namespace, listOption)
// },
// GetterFunc: func(ctx context.Context, informer *client.PixiuInformer, namespace, name string) (interface{}, error) {
// return c.GetStatefulSet(ctx, informer.StatefulSetsLister(), namespace, name)
// },
// },
// {
// ResourceType: ResourceDaemonSet,
// ListerFunc: func(ctx context.Context, informer *client.PixiuInformer, namespace string, listOption types.ListOptions) (interface{}, error) {
// return c.ListDaemonSets(ctx, informer.DaemonSetsLister(), namespace, listOption)
// },
// GetterFunc: func(ctx context.Context, informer *client.PixiuInformer, namespace, name string) (interface{}, error) {
// return c.GetDaemonSet(ctx, informer.DaemonSetsLister(), namespace, name)
// },
// },
// {
// ResourceType: ResourceCronJob,
// ListerFunc: func(ctx context.Context, informer *client.PixiuInformer, namespace string, listOption types.ListOptions) (interface{}, error) {
// return c.ListCronJobs(ctx, informer.CronJobsLister(), namespace, listOption)
// },
// GetterFunc: func(ctx context.Context, informer *client.PixiuInformer, namespace, name string) (interface{}, error) {
// return c.GetCronJob(ctx, informer.CronJobsLister(), namespace, name)
// },
// },
// {
// ResourceType: ResourceJob,
// ListerFunc: func(ctx context.Context, informer *client.PixiuInformer, namespace string, listOption types.ListOptions) (interface{}, error) {
// return c.ListJobs(ctx, informer.JobsLister(), namespace, listOption)
// },
// GetterFunc: func(ctx context.Context, informer *client.PixiuInformer, namespace, name string) (interface{}, error) {
// return c.GetJob(ctx, informer.JobsLister(), namespace, name)
// },
// },
// {
// ResourceType: ResourceNode,
// ListerFunc: func(ctx context.Context, informer *client.PixiuInformer, namespace string, listOption types.ListOptions) (interface{}, error) {
// return c.ListNodes(ctx, informer.NodesLister(), namespace, listOption)
// },
// GetterFunc: func(ctx context.Context, informer *client.PixiuInformer, namespace, name string) (interface{}, error) {
// return c.GetNode(ctx, informer.NodesLister(), namespace, name)
// },
// },
// // TODO: 补充更多资源实现
//}...)
return c
}
================================================
FILE: pkg/controller/cluster/informer.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cluster
import (
"context"
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
listersv1 "k8s.io/client-go/listers/apps/v1"
listersbatchv1 "k8s.io/client-go/listers/batch/v1"
v1 "k8s.io/client-go/listers/core/v1"
"k8s.io/klog/v2"
"github.com/caoyingjunz/pixiu/pkg/types"
)
const (
ResourceNode = "node"
ResourcePod = "pod"
ResourceDeployment = "deployment"
ResourceStatefulSet = "statefulset"
ResourceDaemonSet = "daemonset"
ResourceCronJob = "cronjob"
ResourceJob = "job"
)
func (c *cluster) GetIndexerResource(ctx context.Context, cluster string, resource string, namespace string, name string) (interface{}, error) {
if len(namespace) == 0 || len(name) == 0 {
return nil, fmt.Errorf("namespace or name is empty")
}
cs, err := c.GetClusterSetByName(ctx, cluster)
if err != nil {
return nil, err
}
// getter functions should be registered in NewCluster function
fn, ok := c.getterFuncs[resource]
if !ok {
return nil, fmt.Errorf("unsupported resource type %s", resource)
}
return fn(ctx, cs.Informer, namespace, name)
}
func (c *cluster) GetPod(ctx context.Context, podsLister v1.PodLister, namespace string, name string) (interface{}, error) {
pod, err := podsLister.Pods(namespace).Get(name)
if err != nil {
klog.Error("failed to get pod (%s/%s) from indexer: %v", namespace, name, err)
return nil, err
}
return pod, nil
}
func (c *cluster) GetDeployment(ctx context.Context, deploymentsLister listersv1.DeploymentLister, namespace string, name string) (interface{}, error) {
deploy, err := deploymentsLister.Deployments(namespace).Get(name)
if err != nil {
klog.Error("failed to get deployment (%s/%s) from indexer: %v", namespace, name, err)
return nil, err
}
return deploy, nil
}
func (c *cluster) GetStatefulSet(ctx context.Context, statefulSetsLister listersv1.StatefulSetLister, namespace string, name string) (interface{}, error) {
statefulSet, err := statefulSetsLister.StatefulSets(namespace).Get(name)
if err != nil {
klog.Error("failed to get statefulSet (%s/%s) from indexer: %v", namespace, name, err)
return nil, err
}
return statefulSet, nil
}
func (c *cluster) GetDaemonSet(ctx context.Context, daemonSetsLister listersv1.DaemonSetLister, namespace string, name string) (interface{}, error) {
daemonSet, err := daemonSetsLister.DaemonSets(namespace).Get(name)
if err != nil {
klog.Error("failed to get daemonset (%s/%s) from indexer: %v", namespace, name, err)
return nil, err
}
return daemonSet, nil
}
func (c *cluster) GetCronJob(ctx context.Context, cronJobsLister listersbatchv1.CronJobLister, namespace string, name string) (interface{}, error) {
cronJob, err := cronJobsLister.CronJobs(namespace).Get(name)
if err != nil {
klog.Error("failed to get cronjob (%s/%s) from indexer: %v", namespace, name, err)
return nil, err
}
return cronJob, nil
}
func (c *cluster) GetJob(ctx context.Context, cronJobsLister listersbatchv1.JobLister, namespace string, name string) (interface{}, error) {
job, err := cronJobsLister.Jobs(namespace).Get(name)
if err != nil {
klog.Error("failed to get job (%s/%s) from indexer: %v", namespace, name, err)
return nil, err
}
return job, nil
}
func (c *cluster) GetNode(ctx context.Context, nodesLister v1.NodeLister, namespace string, name string) (interface{}, error) {
node, err := nodesLister.Get(name)
if err != nil {
klog.Error("failed to get node (%s/%s) from indexer: %v", namespace, name, err)
return nil, err
}
return node, nil
}
func (c *cluster) ListIndexerResources(ctx context.Context, cluster string, resource string, namespace string, listOption types.ListOptions) (interface{}, error) {
// 获取客户端缓存
cs, err := c.GetClusterSetByName(ctx, cluster)
if err != nil {
return nil, err
}
// lister functions should be registered in NewCluster function
fn, ok := c.listerFuncs[resource]
if !ok {
return nil, fmt.Errorf("unsupported resource type %s", resource)
}
if namespace == "all_namespaces" {
namespace = ""
}
return fn(ctx, cs.Informer, namespace, listOption)
}
func (c *cluster) ListPods(ctx context.Context, podsLister v1.PodLister, namespace string, listOption types.ListOptions) (interface{}, error) {
pods, err := podsLister.Pods(namespace).List(labels.Everything())
if err != nil {
return nil, err
}
// 构造通用的 objects
objects := make([]metav1.Object, 0)
for _, pod := range pods {
objects = append(objects, pod)
}
return c.listObjects(objects, namespace, listOption)
}
// ListDeployments 从缓存中获取 deployment 列表
func (c *cluster) ListDeployments(ctx context.Context, deploymentsLister listersv1.DeploymentLister, namespace string, listOption types.ListOptions) (interface{}, error) {
deployments, err := deploymentsLister.Deployments(namespace).List(labels.Everything())
if err != nil {
return nil, err
}
objects := make([]metav1.Object, 0)
for _, deployment := range deployments {
objects = append(objects, deployment)
}
return c.listObjects(objects, namespace, listOption)
}
func (c *cluster) ListStatefulSets(ctx context.Context, statefulSetsLister listersv1.StatefulSetLister, namespace string, listOption types.ListOptions) (interface{}, error) {
statefulSets, err := statefulSetsLister.StatefulSets(namespace).List(labels.Everything())
if err != nil {
return nil, err
}
objects := make([]metav1.Object, 0)
for _, statefulSet := range statefulSets {
objects = append(objects, statefulSet)
}
return c.listObjects(objects, namespace, listOption)
}
func (c *cluster) ListDaemonSets(ctx context.Context, daemonSetsLister listersv1.DaemonSetLister, namespace string, listOption types.ListOptions) (interface{}, error) {
daemonSets, err := daemonSetsLister.DaemonSets(namespace).List(labels.Everything())
if err != nil {
return nil, err
}
objects := make([]metav1.Object, 0)
for _, daemonSet := range daemonSets {
objects = append(objects, daemonSet)
}
return c.listObjects(objects, namespace, listOption)
}
func (c *cluster) ListCronJobs(ctx context.Context, cronJobsLister listersbatchv1.CronJobLister, namespace string, listOption types.ListOptions) (interface{}, error) {
cronJobs, err := cronJobsLister.CronJobs(namespace).List(labels.Everything())
if err != nil {
return nil, err
}
objects := make([]metav1.Object, 0)
for _, cronJob := range cronJobs {
objects = append(objects, cronJob)
}
return c.listObjects(objects, namespace, listOption)
}
func (c *cluster) ListJobs(ctx context.Context, jobsLister listersbatchv1.JobLister, namespace string, listOption types.ListOptions) (interface{}, error) {
jobs, err := jobsLister.Jobs(namespace).List(labels.Everything())
if err != nil {
return nil, err
}
objects := make([]metav1.Object, 0)
for _, job := range jobs {
objects = append(objects, job)
}
return c.listObjects(objects, namespace, listOption)
}
func (c *cluster) ListNodes(ctx context.Context, nodesLister v1.NodeLister, namespace string, listOption types.ListOptions) (interface{}, error) {
nodes, err := nodesLister.List(labels.Everything())
if err != nil {
return nil, err
}
objects := make([]metav1.Object, 0)
for _, node := range nodes {
objects = append(objects, node)
}
return c.listObjects(objects, namespace, listOption)
}
================================================
FILE: pkg/controller/cluster/util.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cluster
import (
"sort"
"strings"
"github.com/caoyingjunz/pixiu/pkg/types"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
)
func (c *cluster) forQuery(objects []metav1.Object, queryOption types.QueryOption) []metav1.Object {
if len(queryOption.LabelSelector) == 0 && len(queryOption.NameSelector) == 0 {
return objects
}
queryObjects := make([]metav1.Object, 0)
for _, object := range objects {
// 标签搜索
// TODO: 多个标签存在时,存在乱序时无法生效
// 名称搜索
if (len(queryOption.LabelSelector) != 0 && strings.Contains(labels.FormatLabels(object.GetLabels()), queryOption.LabelSelector)) || (len(queryOption.NameSelector) != 0 && strings.Contains(object.GetName(), queryOption.NameSelector)) {
queryObjects = append(queryObjects, object)
}
}
return queryObjects
}
func (c *cluster) forPage(objects []metav1.Object, pageOption types.PageRequest) []metav1.Object {
if !pageOption.IsPaged() {
return objects
}
offset, end, err := pageOption.Offset(len(objects))
if err != nil {
return nil
}
return objects[offset:end]
}
func (c *cluster) forSorted(objects []metav1.Object, namespace string) []metav1.Object {
sort.SliceStable(objects, func(i, j int) bool {
return objects[i].GetName() < objects[j].GetName()
})
// 全量获取 pod 时,以命名空间排序
if len(namespace) == 0 {
sort.SliceStable(objects, func(i, j int) bool {
return objects[i].GetNamespace() < objects[j].GetNamespace()
})
}
return objects
}
func (c *cluster) listObjects(objects []metav1.Object, namespace string, listOption types.ListOptions) (types.PageResponse, error) {
objects = c.forQuery(objects, listOption.QueryOption)
objects = c.forSorted(objects, namespace)
return types.PageResponse{
PageRequest: listOption.PageRequest,
Total: len(objects),
Items: c.forPage(objects, listOption.PageRequest),
}, nil
}
================================================
FILE: pkg/controller/cluster/ws.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cluster
import (
"bytes"
"context"
"net/http"
"sync"
"time"
"github.com/gorilla/websocket"
v1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/tools/remotecommand"
"k8s.io/klog/v2"
"github.com/caoyingjunz/pixiu/pkg/types"
sshutil "github.com/caoyingjunz/pixiu/pkg/util/ssh"
)
func (c *cluster) WsHandler(ctx context.Context, opt *types.WebShellOptions, w http.ResponseWriter, r *http.Request) error {
cs, err := c.GetClusterSetByName(ctx, opt.Cluster)
if err != nil {
klog.Errorf("failed to get cluster(%s) client set: %v", opt.Cluster, err)
return err
}
session, err := types.NewTerminalSession(w, r)
if err != nil {
return err
}
// 处理关闭
defer func() {
_ = session.Close()
}()
klog.Infof("connecting to %s/%s,", opt.Namespace, opt.Pod)
cmd := opt.Command
if len(cmd) == 0 {
cmd = "/bin/bash"
}
// 组装 POST 请求
req := cs.Client.CoreV1().RESTClient().Post().
Resource("pods").
Name(opt.Pod).
Namespace(opt.Namespace).
SubResource("exec").
VersionedParams(&v1.PodExecOptions{
Container: opt.Container,
Command: []string{cmd},
Stderr: true,
Stdin: true,
Stdout: true,
TTY: true,
}, scheme.ParameterCodec)
// remotecommand 主要实现了http 转 SPDY 添加X-Stream-Protocol-Version相关header 并发送请求
executor, err := remotecommand.NewSPDYExecutor(cs.Config, "POST", req.URL())
if err != nil {
return err
}
// 与 kubelet 建立 stream 连接
if err = executor.Stream(remotecommand.StreamOptions{
Stdout: session,
Stdin: session,
Stderr: session,
TerminalSizeQueue: session,
Tty: true,
}); err != nil {
_, _ = session.Write([]byte("exec pod command failed," + err.Error()))
// 标记关闭terminal
session.Done()
}
return nil
}
var BufPool = sync.Pool{New: func() interface{} { return new(bytes.Buffer) }}
func (c *cluster) WsNodeHandler(ctx context.Context, sshConfig *types.WebSSHRequest, w http.ResponseWriter, r *http.Request) error {
upgrader := &websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024 * 10,
HandshakeTimeout: time.Second * 2,
CheckOrigin: func(r *http.Request) bool {
return true
},
Subprotocols: []string{r.Header.Get("Sec-WebSocket-Protocol")},
}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return err
}
defer conn.Close()
sshClient, err := sshutil.NewSSHClient(sshConfig)
if err != nil {
return err
}
defer sshClient.Close()
turn, err := types.NewTurn(conn, sshClient)
if err != nil {
return err
}
defer turn.Close()
// 处理连接
handler(turn)
return nil
}
func handler(turn *types.Turn) {
logBuff := BufPool.Get().(*bytes.Buffer)
logBuff.Reset()
defer BufPool.Put(logBuff)
wg := &sync.WaitGroup{}
wg.Add(2)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go turn.StartLoopRead(ctx, wg, logBuff)
go turn.StartSessionWait(wg)
wg.Wait()
}
================================================
FILE: pkg/controller/controller.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
import (
"github.com/casbin/casbin/v2"
"github.com/caoyingjunz/pixiu/cmd/app/config"
"github.com/caoyingjunz/pixiu/pkg/controller/audit"
"github.com/caoyingjunz/pixiu/pkg/controller/auth"
"github.com/caoyingjunz/pixiu/pkg/controller/cluster"
"github.com/caoyingjunz/pixiu/pkg/controller/helm"
"github.com/caoyingjunz/pixiu/pkg/controller/plan"
"github.com/caoyingjunz/pixiu/pkg/controller/tenant"
"github.com/caoyingjunz/pixiu/pkg/controller/user"
"github.com/caoyingjunz/pixiu/pkg/db"
)
type PixiuInterface interface {
cluster.ClusterGetter
tenant.TenantGetter
user.UserGetter
plan.PlanGetter
audit.AuditGetter
auth.AuthGetter
helm.HelmGetter
}
type pixiu struct {
cc config.Config
factory db.ShareDaoFactory
enforcer *casbin.SyncedEnforcer
}
func (p *pixiu) Cluster() cluster.Interface { return cluster.NewCluster(p.cc, p.factory, p.enforcer) }
func (p *pixiu) Tenant() tenant.Interface { return tenant.NewTenant(p.cc, p.factory) }
func (p *pixiu) User() user.Interface { return user.NewUser(p.cc, p.factory, p.enforcer) }
func (p *pixiu) Plan() plan.Interface { return plan.NewPlan(p.cc, p.factory) }
func (p *pixiu) Audit() audit.Interface { return audit.NewAudit(p.cc, p.factory) }
func (p *pixiu) Auth() auth.Interface { return auth.NewAuth(p.factory, p.enforcer) }
func (p *pixiu) Helm() helm.Interface { return helm.NewHelm(p.factory) }
func New(cfg config.Config, f db.ShareDaoFactory, enforcer *casbin.SyncedEnforcer) PixiuInterface {
return &pixiu{
cc: cfg,
factory: f,
enforcer: enforcer,
}
}
================================================
FILE: pkg/controller/helm/helm.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package helm
import (
"context"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/cli"
"k8s.io/klog/v2"
"github.com/caoyingjunz/pixiu/pkg/client"
"github.com/caoyingjunz/pixiu/pkg/controller/cluster"
"github.com/caoyingjunz/pixiu/pkg/db"
)
type HelmGetter interface {
Helm() Interface
}
type Interface interface {
Release(cluster, namespace string) ReleaseInterface
Repository() RepositoryInterface
}
type Helm struct {
factory db.ShareDaoFactory
}
func (h *Helm) Release(cluster, namespace string) ReleaseInterface {
cs := h.MustGetClusterSetByName(context.Background(), cluster)
settings := cli.New()
settings.SetNamespace(namespace)
actionConfig := new(action.Configuration)
resetClientGetter := client.NewHelmRESTClientGetter(cs.Config)
actionConfig.Init(
resetClientGetter,
settings.Namespace(),
"secrets",
klog.Infof,
)
return NewReleases(actionConfig, settings)
}
func (h *Helm) Repository() RepositoryInterface {
return NewRepository(h.factory)
}
func NewHelm(factory db.ShareDaoFactory) Interface {
return &Helm{
factory: factory,
}
}
func (h *Helm) MustGetClusterSetByName(ctx context.Context, name string) client.ClusterSet {
cs, ok := cluster.ClusterIndexer.Get(name)
if ok {
klog.Infof("Get %s clusterSet from indexer", name)
return cs
}
klog.Infof("building clusterSet for %s", name)
// 缓存中不存在,则新建并重写回缓存
object, err := h.factory.Cluster().GetClusterByName(ctx, name)
if err != nil {
return client.ClusterSet{}
}
if object == nil {
return client.ClusterSet{}
}
newClusterSet, err := client.NewClusterSet(object.KubeConfig)
if err != nil {
return client.ClusterSet{}
}
klog.Infof("set %s clusterSet into indexer", name)
cluster.ClusterIndexer.Set(name, *newClusterSet)
return *newClusterSet
}
================================================
FILE: pkg/controller/helm/releases.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package helm
import (
"context"
"fmt"
"io"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/cli"
"helm.sh/helm/v3/pkg/downloader"
"helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/registry"
"helm.sh/helm/v3/pkg/release"
"k8s.io/klog/v2"
"github.com/caoyingjunz/pixiu/pkg/types"
)
type ReleaseInterface interface {
Install(ctx context.Context, form *types.Release) (*release.Release, error)
Get(ctx context.Context, name string) (*release.Release, error)
List(ctx context.Context) ([]*release.Release, error)
Uninstall(ctx context.Context, name string) (*release.UninstallReleaseResponse, error)
Upgrade(ctx context.Context, form *types.Release) (*release.Release, error)
History(ctx context.Context, name string) ([]*release.Release, error)
Rollback(ctx context.Context, name string, toVersion int) error
}
type Releases struct {
settings *cli.EnvSettings
actionConfig *action.Configuration
}
func NewReleases(actionConfig *action.Configuration, settings *cli.EnvSettings) *Releases {
return &Releases{
actionConfig: actionConfig,
settings: settings,
}
}
var _ ReleaseInterface = &Releases{}
func (r *Releases) Get(ctx context.Context, name string) (*release.Release, error) {
client := action.NewGet(r.actionConfig)
return client.Run(name)
}
func (r *Releases) List(ctx context.Context) ([]*release.Release, error) {
client := action.NewList(r.actionConfig)
return client.Run()
}
// InstallRelease install release
func (r *Releases) Install(ctx context.Context, form *types.Release) (*release.Release, error) {
client := action.NewInstall(r.actionConfig)
client.ReleaseName = form.Name
client.Namespace = r.settings.Namespace()
client.Version = form.Version
client.DryRun = form.Preview
if client.DryRun {
client.Description = "server"
}
chart, err := r.locateChart(client.ChartPathOptions, form.Chart, r.settings)
if err != nil {
return nil, err
}
out, err := client.Run(chart, form.Values)
if err != nil {
return nil, err
}
return out, nil
}
func (r *Releases) Uninstall(ctx context.Context, name string) (*release.UninstallReleaseResponse, error) {
client := action.NewUninstall(r.actionConfig)
return client.Run(name)
}
// UpgradeRelease upgrade release
func (r *Releases) Upgrade(ctx context.Context, form *types.Release) (*release.Release, error) {
client := action.NewUpgrade(r.actionConfig)
client.Namespace = r.settings.Namespace()
client.DryRun = form.Preview
if client.DryRun {
client.Description = "server"
}
chart, err := r.locateChart(client.ChartPathOptions, form.Chart, r.settings)
if err != nil {
return nil, err
}
out, err := client.Run(form.Name, chart, form.Values)
if err != nil {
return nil, err
}
return out, nil
}
func (r *Releases) History(ctx context.Context, name string) ([]*release.Release, error) {
client := action.NewHistory(r.actionConfig)
return client.Run(name)
}
func (r *Releases) Rollback(ctx context.Context, name string, toVersion int) error {
klog.Error("version: ", toVersion)
_, err := r.Get(ctx, name)
if err != nil {
return err
}
client := action.NewRollback(r.actionConfig)
client.Version = toVersion
return client.Run(name)
}
func (r *Releases) locateChart(pathOpts action.ChartPathOptions, chart string, settings *cli.EnvSettings) (*chart.Chart, error) {
// from cmd/helm/install.go and cmd/helm/upgrade.go
cp, err := pathOpts.LocateChart(chart, settings)
if err != nil {
return nil, err
}
p := getter.All(settings)
// Check chart dependencies to make sure all are present in /charts
chartRequested, err := loader.Load(cp)
if err != nil {
return nil, err
}
if err := checkIfInstallable(chartRequested); err != nil {
return nil, err
}
registryClient, err := registry.NewClient(
registry.ClientOptDebug(false),
//registry.ClientOptWriter(out),
registry.ClientOptCredentialsFile(settings.RegistryConfig),
)
if err != nil {
return nil, fmt.Errorf("failed to crete helm config object %v", err)
}
if req := chartRequested.Metadata.Dependencies; req != nil {
// If CheckDependencies returns an error, we have unfulfilled dependencies.
// As of Helm 2.4.0, this is treated as a stopping condition:
// https://github.com/helm/helm/issues/2209
if err := action.CheckDependencies(chartRequested, req); err != nil {
err = fmt.Errorf("an error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies: %v", err)
if true { // client.DependencyUpdate
man := &downloader.Manager{
Out: io.Discard,
ChartPath: cp,
Keyring: pathOpts.Keyring,
SkipUpdate: false,
Getters: p,
RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache,
Debug: settings.Debug,
RegistryClient: registryClient, // added on top of Helm code
}
if err := man.Update(); err != nil {
return nil, err
}
// Reload the chart with the updated Chart.lock file.
if chartRequested, err = loader.Load(cp); err != nil {
return nil, fmt.Errorf("failed reloading chart after repo update : %v", err)
}
} else {
return nil, err
}
}
}
return chartRequested, nil
}
func checkIfInstallable(ch *chart.Chart) error {
switch ch.Metadata.Type {
case "", "application":
return nil
}
return fmt.Errorf("%s charts are not installable", ch.Metadata.Type)
}
================================================
FILE: pkg/controller/helm/repository.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package helm
import (
"context"
"fmt"
"net/url"
"strings"
"k8s.io/klog/v2"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/cli"
"helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/repo"
"k8s.io/apimachinery/pkg/util/yaml"
"github.com/caoyingjunz/pixiu/pkg/db"
"github.com/caoyingjunz/pixiu/pkg/db/model"
"github.com/caoyingjunz/pixiu/pkg/types"
)
type RepositoryGetter interface {
Repository() RepositoryInterface
}
type RepositoryInterface interface {
Create(ctx context.Context, repo *types.CreateRepository) error
Delete(ctx context.Context, id int64) error
Get(ctx context.Context, id int64) (*model.Repository, error)
List(ctx context.Context) ([]*model.Repository, error)
Update(ctx context.Context, id int64, update *types.UpdateRepository) error
GetChartsById(ctx context.Context, id int64) (*model.ChartIndex, error)
GetChartsByURL(ctx context.Context, repoURL string) (*model.ChartIndex, error)
GetChartValues(ctx context.Context, chart, version string) (string, error)
}
type Repository struct {
settings *cli.EnvSettings
actionConfig *action.Configuration
factory db.ShareDaoFactory
}
func NewRepository(f db.ShareDaoFactory) *Repository {
settings := cli.New()
actionConfig := new(action.Configuration)
actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), "secrets", klog.Infof)
return &Repository{factory: f, settings: settings, actionConfig: actionConfig}
}
var _ RepositoryInterface = &Repository{}
func (r *Repository) Create(ctx context.Context, repo *types.CreateRepository) error {
repoModel := &model.Repository{
Name: repo.Name,
URL: repo.URL,
}
if res, _ := r.GetByName(ctx, repoModel.Name); res != nil {
return fmt.Errorf("repository %s already exists", repoModel.Name)
}
_, err := r.factory.Repository().Create(ctx, repoModel)
return err
}
func (r *Repository) Delete(ctx context.Context, id int64) error {
return r.factory.Repository().Delete(ctx, id)
}
func (r *Repository) Get(ctx context.Context, id int64) (*model.Repository, error) {
return r.factory.Repository().Get(ctx, id)
}
func (r *Repository) GetByName(ctx context.Context, name string) (*model.Repository, error) {
return r.factory.Repository().GetByName(ctx, name)
}
func (r *Repository) List(ctx context.Context) ([]*model.Repository, error) {
return r.factory.Repository().List(ctx)
}
func (r *Repository) Update(ctx context.Context, id int64, update *types.UpdateRepository) error {
updates := map[string]interface{}{
"name": update.Name,
"url": update.URL,
"username": update.Username,
"password": update.Password,
}
return r.factory.Repository().Update(ctx, id, *update.ResourceVersion, updates)
}
func (r *Repository) GetChartsById(ctx context.Context, id int64) (*model.ChartIndex, error) {
repository, err := r.Get(ctx, id)
if err != nil {
return nil, err
}
entry := &repo.Entry{
Name: repository.Name,
URL: repository.URL,
Username: repository.Username,
Password: repository.Password,
}
return r.fetch(ctx, entry)
}
func (r *Repository) GetChartsByURL(ctx context.Context, repoURL string) (*model.ChartIndex, error) {
entry := &repo.Entry{
URL: repoURL,
}
return r.fetch(ctx, entry)
}
func (r *Repository) GetChartValues(_ context.Context, chart, version string) (string, error) {
client := action.NewShowWithConfig(action.ShowValues, r.actionConfig)
client.Version = version
cp, err := client.ChartPathOptions.LocateChart(chart, r.settings)
if err != nil {
return "", err
}
out, err := client.Run(cp)
if err != nil {
return "", err
}
return out, nil
}
func (r *Repository) resolveReferenceURL(baseURL, refURL string) (string, error) {
parsedRefURL, err := url.Parse(refURL)
if err != nil {
return "", fmt.Errorf("failed to parse %s as URL, %v", refURL, err)
}
if parsedRefURL.IsAbs() {
return refURL, nil
}
parsedBaseURL, err := url.Parse(baseURL)
if err != nil {
return "", fmt.Errorf("failed to parse %s as URL, %v", baseURL, err)
}
parsedBaseURL.RawPath = strings.TrimSuffix(parsedBaseURL.RawPath, "/") + "/"
parsedBaseURL.Path = strings.TrimSuffix(parsedBaseURL.Path, "/") + "/"
resolvedURL := parsedBaseURL.ResolveReference(parsedRefURL)
resolvedURL.RawQuery = parsedBaseURL.RawQuery
return resolvedURL.String(), nil
}
func (r *Repository) fetch(_ context.Context, entry *repo.Entry) (*model.ChartIndex, error) {
var charts model.ChartIndex
rep, err := repo.NewChartRepository(entry, getter.All(r.settings))
if err != nil {
return nil, err
}
// download index
if _, err = rep.DownloadIndexFile(); err != nil {
return nil, err
}
indexURL, err := r.resolveReferenceURL(rep.Config.URL, "index.yaml")
if err != nil {
return nil, err
}
resp, err := rep.Client.Get(indexURL,
getter.WithURL(rep.Config.URL),
getter.WithInsecureSkipVerifyTLS(rep.Config.InsecureSkipTLSverify),
getter.WithTLSClientConfig(rep.Config.CertFile, rep.Config.KeyFile, rep.Config.CAFile),
getter.WithBasicAuth(rep.Config.Username, rep.Config.Password),
getter.WithPassCredentialsAll(rep.Config.PassCredentialsAll),
)
if err != nil {
return nil, err
}
err = yaml.Unmarshal(resp.Bytes(), &charts)
if err != nil {
return nil, err
}
return &charts, nil
}
================================================
FILE: pkg/controller/plan/bootstrap_servers.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (phe "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plan
import (
"context"
"time"
"github.com/caoyingjunz/pixiu/pkg/util/container"
)
type BootStrap struct {
handlerTask
dir string
runner string
}
func (b BootStrap) Name() string { return "初始化部署环境" }
// Run 以容器的形式执行 BootStrap 任务,如果存在旧的容器,则先删除在执行
func (b BootStrap) Run() error {
cli, err := container.NewContainer("bootstrap-servers", b.GetPlanId(), b.dir)
if err != nil {
return err
}
defer cli.Close()
ctx, cancel := context.WithTimeout(context.Background(), 600*time.Second)
defer cancel()
// 启动执行容器
if err = cli.StartAndWaitForContainer(ctx, b.runner); err != nil {
return err
}
return nil
}
================================================
FILE: pkg/controller/plan/checker.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (phe "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plan
type Check struct {
handlerTask
}
func (c Check) Name() string { return "部署预检查" }
func (c Check) Run() error {
if err := c.data.validate(); err != nil {
return err
}
return nil
}
================================================
FILE: pkg/controller/plan/deploy.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (phe "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plan
import (
"context"
"time"
"github.com/caoyingjunz/pixiu/pkg/db/model"
"github.com/caoyingjunz/pixiu/pkg/util/container"
)
type Deploy struct {
handlerTask
dir string
runner string
}
func (b Deploy) Name() string { return "部署Master" }
// Run 以容器的形式执行 BootStrap 任务,如果存在旧的容器,则先删除在执行
func (b Deploy) Run() error {
cli, err := container.NewContainer("deploy", b.GetPlanId(), b.dir)
if err != nil {
return err
}
defer cli.Close()
ctx, cancel := context.WithTimeout(context.Background(), 600*time.Second)
defer cancel()
// 启动执行容器
if err = cli.StartAndWaitForContainer(ctx, b.runner); err != nil {
return err
}
return nil
}
type DeployNode struct {
handlerTask
}
func (b DeployNode) Name() string { return "部署Node" }
// Run 以容器的形式执行 BootStrap 任务,如果存在旧的容器,则先删除在执行
func (b DeployNode) Run() error {
return nil
}
type DeployChart struct {
handlerTask
}
func (b DeployChart) Name() string { return "部署基础组件" }
func (b DeployChart) Step() model.PlanStep { return model.CompletedPlanStep }
// Run 以容器的形式执行 BootStrap 任务,如果存在旧的容器,则先删除在执行
func (b DeployChart) Run() error {
return nil
}
================================================
FILE: pkg/controller/plan/plan.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (phe "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plan
import (
"context"
"encoding/json"
"fmt"
"net/http"
"sync"
"gorm.io/gorm"
"k8s.io/client-go/util/workqueue"
"k8s.io/klog/v2"
"github.com/caoyingjunz/pixiu/api/server/errors"
"github.com/caoyingjunz/pixiu/cmd/app/config"
"github.com/caoyingjunz/pixiu/pkg/client"
"github.com/caoyingjunz/pixiu/pkg/db"
"github.com/caoyingjunz/pixiu/pkg/db/model"
"github.com/caoyingjunz/pixiu/pkg/types"
"github.com/caoyingjunz/pixiu/pkg/util/uuid"
)
type PlanGetter interface {
Plan() Interface
}
type Interface interface {
Create(ctx context.Context, req *types.CreatePlanRequest) error
Update(ctx context.Context, planID int64, req *types.UpdatePlanRequest) error
Delete(ctx context.Context, pid int64) error
Get(ctx context.Context, pid int64) (*types.Plan, error)
List(ctx context.Context, req *types.ListPlanRequest) (*types.PageResponse, error)
GetWithSubResources(ctx context.Context, planId int64) (*types.Plan, error)
// Start 启动部署任务
Start(ctx context.Context, pid int64) error
// Stop 终止部署任务
Stop(ctx context.Context, pid int64) error
CreateNode(ctx context.Context, pid int64, req *types.CreatePlanNodeRequest) error
UpdateNode(ctx context.Context, pid int64, nodeId int64, req *types.UpdatePlanNodeRequest) error
DeleteNode(ctx context.Context, pid int64, nodeId int64) error
GetNode(ctx context.Context, pid int64, nodeId int64) (*types.PlanNode, error)
ListNodes(ctx context.Context, pid int64) ([]types.PlanNode, error)
CreateConfig(ctx context.Context, planId int64, req *types.CreatePlanConfigRequest) error
UpdateConfig(ctx context.Context, pid int64, cfgId int64, req *types.UpdatePlanConfigRequest) error
DeleteConfig(ctx context.Context, pid int64, cfgId int64) error
GetConfig(ctx context.Context, planId int64) (*types.PlanConfig, error)
// Run 启动 plan worker 处理协程
Run(ctx context.Context, workers int) error
RunTask(ctx context.Context, planId int64, taskId int64) error
ListTasks(ctx context.Context, planId int64) ([]types.PlanTask, error)
WatchTasks(ctx context.Context, planId int64, w http.ResponseWriter, r *http.Request)
WatchTaskLog(ctx context.Context, planId int64, taskId int64, w http.ResponseWriter, r *http.Request) error
}
var taskQueue workqueue.RateLimitingInterface
var taskC *client.Task
func init() {
taskQueue = workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "tasks")
taskC = client.NewTaskCache()
}
type plan struct {
cc config.Config
factory db.ShareDaoFactory
}
// Create
// 1. 创建部署计划
// 2. 创建部署配置
// 3. 创建节点列表
// 4. 创建扩展组件
// 5. 创建容器服务
func (p *plan) Create(ctx context.Context, req *types.CreatePlanRequest) error {
planModel := &model.Plan{
Name: req.Name,
Description: req.Description,
}
createdPlan, err := p.factory.Plan().Create(ctx, planModel, p.createPlanSubResources(ctx, req))
if err != nil {
klog.Errorf("failed to create plan %s: %v", req.Name, err)
return errors.ErrServerInternal
}
planId := createdPlan.Id
// 如果启用pixiu注册功能,则创建容器服务
kubeNode := types.KubeNode{Ready: []string{}, NotReady: []string{}}
nodes, _ := kubeNode.Marshal()
_, err = p.factory.Cluster().Create(ctx, &model.Cluster{
Name: uuid.NewRandName(8),
AliasName: req.Name,
Description: req.Description,
ClusterType: model.ClusterTypeCustom,
PlanId: planId,
Protected: true,
Nodes: nodes,
})
if err != nil {
klog.Errorf("failed to register cluster for plan: %v", err)
_ = p.Delete(ctx, planId)
return errors.ErrServerInternal
}
return nil
}
func (p *plan) createPlanSubResources(ctx context.Context, req *types.CreatePlanRequest) db.CreatePlanOption {
return func(planModel *model.Plan, tx *gorm.DB) (*gorm.DB, error) {
if err := p.preCreateConfig(ctx, planModel.Id, &req.Config); err != nil {
return nil, err
}
planConfig, err := p.buildPlanConfig(ctx, &req.Config)
if err != nil {
return nil, err
}
planConfig.PlanId = planModel.Id
if err := p.factory.Plan().TxCreateConfig(ctx, tx, planConfig); err != nil {
klog.Errorf("failed to create plan(%d) config: %v", planModel.Id, err)
return nil, err
}
for i := range req.Nodes {
nodeReq := &req.Nodes[i]
node, err := p.buildNodeFromRequest(planModel.Id, nodeReq)
if err != nil {
klog.Errorf("failed to build plan(%d) node from request: %v", planModel.Id, err)
return nil, err
}
if err := p.factory.Plan().TxCreateNode(ctx, tx, node); err != nil {
klog.Errorf("failed to create node(%s): %v", nodeReq.Name, err)
return nil, err
}
}
return tx, nil
}
}
// Update
// 更新部署计划
func (p *plan) Update(ctx context.Context, planId int64, req *types.UpdatePlanRequest) error {
oldPlan, err := p.factory.Plan().Get(ctx, planId)
if err != nil {
klog.Errorf("failed to get plan(%d) %v", planId, err)
return errors.ErrServerInternal
}
// 必要时更新 plan
if oldPlan.Description != req.Description {
if err := p.factory.Plan().Update(ctx, planId, *req.ResourceVersion, map[string]interface{}{"description": req.Description}); err != nil {
klog.Errorf("failed to update plan %d: %v", planId, err)
return errors.ErrServerInternal
}
}
// 必要时更新部署计划配置
if err = p.UpdateConfigIfNeeded(ctx, planId, req); err != nil {
klog.Errorf("failed to update plan(%d) config: %v", planId, err)
return errors.ErrServerInternal
}
// 必要时更新部署计划 nodes
if err = p.updateNodesIfNeeded(ctx, planId, req); err != nil {
klog.Errorf("failed to update plan(%d) nodes: %v", planId, err)
return errors.ErrServerInternal
}
return nil
}
// 删除前检查
// 有正在运行中的任务则不允许删除
func (p *plan) preDelete(ctx context.Context, planId int64) error {
isRunning, err := p.TaskIsRunning(ctx, planId)
if err != nil {
return errors.ErrServerInternal
}
if isRunning {
return errors.ErrNotAcceptable
}
return nil
}
// Delete
// 1. 删除部署计划
// 2. 删除关联任务
// 3. 删除关联配置
// 4. 删除关联节点
func (p *plan) Delete(ctx context.Context, planId int64) error {
// 删除前校验
if err := p.preDelete(ctx, planId); err != nil {
return err
}
// 执行实际的删除逻辑
_, err := p.factory.Plan().Delete(ctx, planId)
if err != nil {
klog.Errorf("failed to delete plan %d: %v", planId, err)
return errors.ErrServerInternal
}
// 删除 plan 关联资源
// 2. 删除部署计划后,同步删除任务,删除任务失败时,可直接忽略
if err = p.factory.Plan().DeleteTask(ctx, planId); err != nil {
klog.Errorf("failed to delete plan(%d) task: %v", planId, err)
return err
}
// 3. 删除关联配置
if err = p.factory.Plan().DeleteConfigByPlan(ctx, planId); err != nil {
klog.Errorf("failed to delete plan(%d) config: %v", planId, err)
return err
}
// 4. 删除关联nodes
if err = p.factory.Plan().DeleteNodesByPlan(ctx, planId); err != nil {
klog.Errorf("failed to delete plan(%d) nodes: %v", planId, err)
return err
}
return nil
}
func (p *plan) Get(ctx context.Context, pid int64) (*types.Plan, error) {
object, err := p.factory.Plan().Get(ctx, pid)
if err != nil {
klog.Errorf("failed to get plan %d: %v", pid, err)
return nil, errors.ErrServerInternal
}
return p.model2Type(object)
}
// GetWithSubResources
// 获取 plan
// 获取 configs
// 获取 nodes
func (p *plan) GetWithSubResources(ctx context.Context, planId int64) (*types.Plan, error) {
result, err := p.Get(ctx, planId)
if err != nil {
return nil, err
}
// 追加配置
cfg, err := p.GetConfig(ctx, planId)
if err != nil {
return nil, err
}
result.Config = *cfg
// 追加节点
result.Nodes, err = p.ListNodes(ctx, planId)
if err != nil {
return nil, err
}
return result, nil
}
func (p *plan) List(ctx context.Context, req *types.ListPlanRequest) (*types.PageResponse, error) {
var opts []db.Options
if req != nil && req.NameSelector != "" {
opts = append(opts, db.WithPlanNameLike(req.NameSelector))
}
total, err := p.factory.Plan().Count(ctx, opts...)
if err != nil {
klog.Errorf("failed to count plans: %v", err)
return nil, errors.ErrServerInternal
}
pageReq := types.PageRequest{}
if req != nil {
pageReq = req.PageRequest
if req.Page > 0 && req.Limit > 0 {
opts = append(opts, db.WithOffset((req.Page-1)*req.Limit), db.WithLimit(req.Limit))
}
}
objects, err := p.factory.Plan().List(ctx, opts...)
if err != nil {
klog.Errorf("failed to get plans: %v", err)
return nil, errors.ErrServerInternal
}
ps := make([]types.Plan, 0, len(objects))
for _, object := range objects {
no, err := p.model2Type(&object)
if err != nil {
return nil, err
}
// 状态过滤(在内存中过滤,因为状态来自关联表)
if req != nil && req.Step != "" && string(no.Step) != req.Step {
continue
}
ps = append(ps, *no)
}
return &types.PageResponse{
PageRequest: pageReq,
Total: int(total),
Items: ps,
}, nil
}
// listAll 内部使用,返回所有计划(不带分页和过滤)
func (p *plan) listAll(ctx context.Context) ([]types.Plan, error) {
objects, err := p.factory.Plan().List(ctx)
if err != nil {
klog.Errorf("failed to get plans: %v", err)
return nil, errors.ErrServerInternal
}
var ps []types.Plan
for _, object := range objects {
no, err := p.model2Type(&object)
if err != nil {
return nil, err
}
ps = append(ps, *no)
}
return ps, nil
}
func (p *plan) SyncTaskStatus(ctx context.Context) error {
plans, err := p.listAll(ctx)
if err != nil {
return err
}
var wg sync.WaitGroup
errChan := make(chan error, len(plans))
for _, planP := range plans {
wg.Add(1)
go func(planId int64) {
defer wg.Done()
if err = p.syncStatus(ctx, planId); err != nil {
errChan <- err
}
}(planP.Id)
}
wg.Wait()
select {
case err := <-errChan:
return err
default:
}
return nil
}
// 启动前校验
// 1. 配置
// 2. 节点
// 3. 校验runner
// 3. 运行任务
func (p *plan) preStart(ctx context.Context, pid int64) error {
// 1. 校验配置
cfg, err := p.GetConfig(ctx, pid)
if err != nil {
return fmt.Errorf("failed to get plan(%d) config %v", pid, err)
}
// TODO: 根据具体情况对参数
// 2. 校验节点
nodes, err := p.ListNodes(ctx, pid)
if err != nil {
return fmt.Errorf("failed to get plan(%d) nodes %v", pid, err)
}
if len(nodes) == 0 {
return fmt.Errorf("部署计划暂无关联节点")
}
// 3. 校验runner
runner, err := p.GetRunner(cfg.OSImage)
if err != nil {
return err
}
klog.Infof("plan(%d) runner is %s", pid, runner)
// 4. 校验运行任务
isRunning, err := p.TaskIsRunning(ctx, pid)
if err != nil {
return errors.ErrServerInternal
}
if isRunning {
return errors.ErrNotAcceptable
}
return nil
}
// TaskIsRunning
// 校验是否有任务正在运行
func (p *plan) TaskIsRunning(ctx context.Context, planId int64) (bool, error) {
tasks, err := p.factory.Plan().ListTasks(ctx, planId)
if err != nil {
klog.Errorf("failed to get tasks of plan %d: %v", planId, err)
return false, errors.ErrServerInternal
}
for _, task := range tasks {
if task.Status == model.RunningPlanStatus {
klog.Warningf("task %d of plan %d is running", task.Id, planId)
return true, nil
}
}
return false, nil
}
func (p *plan) Start(ctx context.Context, pid int64) error {
// 启动前校验
if err := p.preStart(ctx, pid); err != nil {
return err
}
taskQueue.Add(pid)
return nil
}
func (p *plan) Stop(ctx context.Context, pid int64) error {
return nil
}
func (p *plan) model2Type(o *model.Plan) (*types.Plan, error) {
status := model.SuccessPlanStatus
// 尝试获取最新的任务状态
// 获取失败也不中断返回
if tasks, err := p.factory.Plan().ListTasks(context.TODO(), o.Id); err == nil {
if len(tasks) == 0 {
status = model.UnStartPlanStatus
} else {
for _, task := range tasks {
if task.Status != model.SuccessPlanStatus {
status = task.Status
break
}
}
}
}
// 获取 kubernetes 版本,获取失败不中断
kubernetesVersion := ""
if cfg, err := p.factory.Plan().GetConfigByPlan(context.TODO(), o.Id); err == nil && cfg != nil {
var kubeSpec struct {
KubernetesVersion string `json:"kubernetes_version"`
}
if err := json.Unmarshal([]byte(cfg.Kubernetes), &kubeSpec); err == nil {
kubernetesVersion = kubeSpec.KubernetesVersion
}
}
// 获取节点总数,获取失败不中断
nodeCount := 0
if nodes, err := p.factory.Plan().ListNodes(context.TODO(), o.Id); err == nil {
nodeCount = len(nodes)
}
return &types.Plan{
PixiuMeta: types.PixiuMeta{
Id: o.Id,
ResourceVersion: o.ResourceVersion,
},
TimeMeta: types.TimeMeta{
GmtCreate: o.GmtCreate,
GmtModified: o.GmtModified,
},
Name: o.Name,
Description: o.Description,
Step: status,
KubernetesVersion: kubernetesVersion,
NodeCount: nodeCount,
}, nil
}
func NewPlan(cfg config.Config, f db.ShareDaoFactory) *plan {
return &plan{
cc: cfg,
factory: f,
}
}
================================================
FILE: pkg/controller/plan/plan_config.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (phe "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plan
import (
"context"
"fmt"
"k8s.io/klog/v2"
"github.com/caoyingjunz/pixiu/api/server/errors"
"github.com/caoyingjunz/pixiu/pkg/db/model"
"github.com/caoyingjunz/pixiu/pkg/types"
)
func (p *plan) preCreateConfig(ctx context.Context, planId int64, req *types.CreatePlanConfigRequest) error {
_, err := p.factory.Plan().GetConfigByPlan(ctx, planId)
if err == nil {
return fmt.Errorf("plan(%d) 配置已存在", planId)
}
return nil
}
func (p *plan) CreateConfig(ctx context.Context, pid int64, req *types.CreatePlanConfigRequest) error {
// 创建前检查
if err := p.preCreateConfig(ctx, pid, req); err != nil {
return err
}
planConfig, err := p.buildPlanConfig(ctx, req)
if err != nil {
return err
}
planConfig.PlanId = pid
// 创建配置
if _, err = p.factory.Plan().CreateConfig(ctx, planConfig); err != nil {
klog.Errorf("failed to create plan(%s) config: %v", pid, err)
return err
}
return nil
}
// UpdateConfig
// TODO
func (p *plan) UpdateConfig(ctx context.Context, pid int64, cfgId int64, req *types.UpdatePlanConfigRequest) error {
return nil
}
// UpdateConfigIfNeeded
// 更新部署计划配置
func (p *plan) UpdateConfigIfNeeded(ctx context.Context, planId int64, req *types.UpdatePlanRequest) error {
oldConfig, err := p.factory.Plan().GetConfigByPlan(ctx, planId)
if err != nil {
return errors.ErrServerInternal
}
newConfig := req.Config
updates := make(map[string]interface{})
if oldConfig.Region != newConfig.Region {
updates["region"] = newConfig.Region
}
if oldConfig.OSImage != newConfig.OSImage {
updates["os_image"] = newConfig.OSImage
}
newKubernetes, err := p.buildAndCleanKubernetesConfig(newConfig.Kubernetes)
if err != nil {
return err
}
if oldConfig.Kubernetes != newKubernetes {
updates["kubernetes"] = newKubernetes
}
newNetwork, err := newConfig.Network.Marshal()
if err != nil {
return err
}
if oldConfig.Network != newNetwork {
updates["network"] = newNetwork
}
newRuntime, err := newConfig.Runtime.Marshal()
if err != nil {
return err
}
if oldConfig.Runtime != newRuntime {
updates["runtime"] = newRuntime
}
newComponent, err := newConfig.Component.Marshal()
if err != nil {
return err
}
if oldConfig.Component != newComponent {
updates["component"] = newComponent
}
// 没有更新,则直接返回
if len(updates) == 0 {
return nil
}
if err = p.factory.Plan().UpdateConfig(ctx, oldConfig.Id, oldConfig.ResourceVersion, updates); err != nil {
klog.Errorf("failed to update plan(%d) config: %v", planId, err)
return errors.ErrServerInternal
}
return nil
}
func (p *plan) DeleteConfig(ctx context.Context, pid int64, cfgId int64) error {
if _, err := p.factory.Plan().DeleteConfig(ctx, cfgId); err != nil {
klog.Errorf("failed to delete plan(%d) config(%d): %v", pid, cfgId, err)
return errors.ErrServerInternal
}
return nil
}
func (p *plan) GetConfig(ctx context.Context, pid int64) (*types.PlanConfig, error) {
object, err := p.factory.Plan().GetConfigByPlan(ctx, pid)
if err != nil {
klog.Errorf("failed to get plan(%d) config: %v", pid, err)
return nil, errors.ErrServerInternal
}
return p.modelConfig2Type(object)
}
func (p *plan) buildAndCleanKubernetesConfig(ks types.KubernetesSpec) (string, error) {
if ks.EnablePublicIp {
if len(ks.ApiServer) == 0 {
return "", fmt.Errorf("启用 ApiServer 地址,但是未配置关联 IP")
}
} else {
if len(ks.ApiServer) != 0 {
ks.ApiServer = ""
}
}
return ks.Marshal()
}
func (p *plan) buildPlanConfig(ctx context.Context, req *types.CreatePlanConfigRequest) (*model.Config, error) {
kubeConfig, err := p.buildAndCleanKubernetesConfig(req.Kubernetes)
if err != nil {
return nil, err
}
networkConfig, err := req.Network.Marshal()
if err != nil {
return nil, err
}
runtimeConfig, err := req.Runtime.Marshal()
if err != nil {
return nil, err
}
componentConfig, err := req.Component.Marshal()
if err != nil {
return nil, err
}
return &model.Config{
Region: req.Region,
OSImage: req.OSImage,
Kubernetes: kubeConfig,
Network: networkConfig,
Runtime: runtimeConfig,
Component: componentConfig,
}, nil
}
func (p *plan) modelConfig2Type(o *model.Config) (*types.PlanConfig, error) {
ks := &types.KubernetesSpec{}
if err := ks.Unmarshal(o.Kubernetes); err != nil {
return nil, err
}
ns := &types.NetworkSpec{}
if err := ns.Unmarshal(o.Network); err != nil {
return nil, err
}
rs := &types.RuntimeSpec{}
if err := rs.Unmarshal(o.Runtime); err != nil {
return nil, err
}
cs := &types.ComponentSpec{}
if err := cs.Unmarshal(o.Component); err != nil {
return nil, err
}
return &types.PlanConfig{
PixiuMeta: types.PixiuMeta{
Id: o.Id,
ResourceVersion: o.ResourceVersion,
},
TimeMeta: types.TimeMeta{
GmtCreate: o.GmtCreate,
GmtModified: o.GmtModified,
},
PlanId: o.PlanId,
Region: o.Region,
OSImage: o.OSImage,
Kubernetes: *ks,
Network: *ns,
Runtime: *rs,
Component: *cs,
}, nil
}
================================================
FILE: pkg/controller/plan/plan_node.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (phe "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plan
import (
"context"
"strings"
"k8s.io/klog/v2"
"github.com/caoyingjunz/pixiu/api/server/errors"
"github.com/caoyingjunz/pixiu/pkg/db/model"
"github.com/caoyingjunz/pixiu/pkg/types"
utilerrors "github.com/caoyingjunz/pixiu/pkg/util/errors"
)
// 创建前预检查
// 1. 创建 node 时 plan 必须存在
func (p *plan) preCreateNode(ctx context.Context, pid int64, req *types.CreatePlanNodeRequest) error {
_, err := p.Get(ctx, pid)
if err != nil {
return err
}
return nil
}
func (p *plan) CreateNode(ctx context.Context, pid int64, req *types.CreatePlanNodeRequest) error {
if err := p.preCreateNode(ctx, pid, req); err != nil {
return err
}
if err := p.createNode(ctx, pid, req); err != nil {
return err
}
return nil
}
func (p *plan) CreateNodes(ctx context.Context, planId int64, nodes []types.CreatePlanNodeRequest) error {
_, err := p.Get(ctx, planId)
if err != nil {
return err
}
for _, node := range nodes {
if err = p.createNode(ctx, planId, &node); err != nil {
return err
}
}
return nil
}
func (p *plan) UpdateNode(ctx context.Context, pid int64, nodeId int64, req *types.UpdatePlanNodeRequest) error {
return nil
}
// 删除多余的节点
// 新增没有的节点
// 更新已存在的节点
func (p *plan) updateNodesIfNeeded(ctx context.Context, planId int64, req *types.UpdatePlanRequest) error {
oldNodes, err := p.factory.Plan().ListNodes(ctx, planId)
if err != nil {
return err
}
newNodes := req.Nodes
newMap := make(map[string]types.CreatePlanNodeRequest)
for _, newNode := range newNodes {
newMap[newNode.Name] = newNode
}
// 遍历寻找待删除节点然后执行删除
var delNodes []string
for _, oldNode := range oldNodes {
name := oldNode.Name
_, found := newMap[name]
if !found {
delNodes = append(delNodes, name)
}
}
if len(delNodes) != 0 {
if err = p.factory.Plan().DeleteNodesByNames(ctx, planId, delNodes); err != nil {
klog.Errorf("failed deleting nodes %v %v", delNodes, err)
return err
}
}
for _, newNode := range newNodes {
node, err := p.buildNodeFromRequest(planId, &newNode)
if err != nil {
return err
}
if err = p.CreateOrUpdateNode(ctx, node); err != nil {
return err
}
}
return nil
}
func (p *plan) buildNodeFromRequest(planId int64, req *types.CreatePlanNodeRequest) (*model.Node, error) {
auth, err := req.Auth.Marshal()
if err != nil {
return nil, err
}
return &model.Node{
Name: req.Name,
PlanId: planId,
Role: strings.Join(req.Role, ","),
CRI: req.CRI,
Ip: req.Ip,
Auth: auth,
}, nil
}
func (p *plan) createNode(ctx context.Context, planId int64, req *types.CreatePlanNodeRequest) error {
node, err := p.buildNodeFromRequest(planId, req)
if err != nil {
klog.Errorf("failed to build plan(%d) node from request: %v", planId, err)
return err
}
if _, err = p.factory.Plan().CreateNode(ctx, node); err != nil {
klog.Errorf("failed to create node(%s): %v", req.Name, err)
return err
}
return nil
}
func (p *plan) DeleteNode(ctx context.Context, pid int64, nodeId int64) error {
if _, err := p.factory.Plan().DeleteNode(ctx, nodeId); err != nil {
klog.Errorf("failed to delete plan(%d) node(%d): %v", pid, nodeId, err)
return errors.ErrServerInternal
}
return nil
}
func (p *plan) GetNode(ctx context.Context, pid int64, nodeId int64) (*types.PlanNode, error) {
object, err := p.factory.Plan().GetNode(ctx, nodeId)
if err != nil {
klog.Errorf("failed to get plan(%d) node(%d): %v", pid, nodeId, err)
return nil, errors.ErrServerInternal
}
return p.modelNode2Type(object)
}
func (p *plan) ListNodes(ctx context.Context, pid int64) ([]types.PlanNode, error) {
objects, err := p.factory.Plan().ListNodes(ctx, pid)
if err != nil {
klog.Errorf("failed to get plan(%d) nodes: %v", pid, err)
return nil, errors.ErrServerInternal
}
var nodes []types.PlanNode
for _, object := range objects {
n, err := p.modelNode2Type(&object)
if err != nil {
return nil, err
}
nodes = append(nodes, *n)
}
return nodes, nil
}
// CreateOrUpdateNode
// TODO: 优化
func (p *plan) CreateOrUpdateNode(ctx context.Context, object *model.Node) error {
old, err := p.factory.Plan().GetNodeByName(ctx, object.PlanId, object.Name)
if err != nil {
if !utilerrors.IsRecordNotFound(err) {
return err
}
// 不存在则创建
klog.Infof("plan(%d) node(%s) not exist, try to create it.", object.PlanId, object.Name)
_, err = p.factory.Plan().CreateNode(ctx, object)
if err != nil {
return err
}
return nil
}
klog.Infof("plan(%d) node(%s) already exist", object.PlanId, object.Name)
// 已存在尝试更新
updates := p.buildNodeUpdates(old, object)
if len(updates) == 0 {
return nil
}
klog.Infof("plan(%d) node(%s) already exist and need to update %v", object.PlanId, object.Name, updates)
return p.factory.Plan().UpdateNode(ctx, old.Id, old.ResourceVersion, updates)
}
func (p *plan) modelNode2Type(o *model.Node) (*types.PlanNode, error) {
auth := types.PlanNodeAuth{}
if err := auth.Unmarshal(o.Auth); err != nil {
return nil, err
}
return &types.PlanNode{
PixiuMeta: types.PixiuMeta{
Id: o.Id,
ResourceVersion: o.ResourceVersion,
},
TimeMeta: types.TimeMeta{
GmtCreate: o.GmtCreate,
GmtModified: o.GmtModified,
},
PlanId: o.PlanId,
Name: o.Name,
Role: strings.Split(o.Role, ","),
Ip: o.Ip,
Auth: auth,
}, nil
}
func (p *plan) buildNodeUpdates(old, object *model.Node) map[string]interface{} {
updates := make(map[string]interface{})
if old.Ip != object.Ip {
updates["ip"] = object.Ip
}
if old.Role != object.Role {
updates["role"] = object.Role
}
if old.Auth != object.Auth {
updates["auth"] = object.Auth
}
return updates
}
================================================
FILE: pkg/controller/plan/plan_task.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (phe "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plan
import (
"bufio"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"k8s.io/klog/v2"
"github.com/caoyingjunz/pixiu/pkg/db/model"
"github.com/caoyingjunz/pixiu/pkg/types"
"github.com/caoyingjunz/pixiu/pkg/util/container"
)
// RunTask
// 只运行指定的计划任务
func (p *plan) RunTask(ctx context.Context, planId int64, taskId int64) error {
return nil
}
func (p *plan) ListTasks(ctx context.Context, planId int64) ([]types.PlanTask, error) {
objects, err := p.factory.Plan().ListTasks(ctx, planId)
if err != nil {
klog.Errorf("failed to get plan(%d) tasks: %v", planId, err)
return nil, err
}
var tasks []types.PlanTask
for _, object := range objects {
tasks = append(tasks, *p.modelTask2Type(&object))
}
return tasks, nil
}
func (p *plan) WatchTasks(ctx context.Context, planId int64, w http.ResponseWriter, r *http.Request) {
flush, _ := w.(http.Flusher)
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
// 初始化 Lister
if taskC.Lister == nil {
taskC.SetLister(p.factory.Plan().ListTasks)
}
// 等待缓存同步
if err := taskC.WaitForCacheSync(planId); err != nil {
return
}
for {
select {
case <-r.Context().Done():
klog.Infof("plan(%d) watch API has been closed by client and cache will be removed after 5s", planId)
return
default:
tasks, ok := taskC.Get(planId)
if ok {
var ts []types.PlanTask
for _, object := range tasks {
ts = append(ts, *p.modelTask2Type(&object))
}
if err := json.NewEncoder(w).Encode(ts); err != nil {
klog.Errorf("failed to encode tasks: %v", err)
break
}
flush.Flush()
}
// 同步事件间隔为 3s
time.Sleep(3 * time.Second)
}
}
}
func (p *plan) WatchTaskLog(ctx context.Context, planId int64, taskId int64, w http.ResponseWriter, r *http.Request) error {
task, err := p.factory.Plan().GetTaskById(ctx, taskId)
if err != nil {
klog.Errorf("failed to get tasks of plan %d: %v", planId, err)
return err
}
if task.Status == model.UnStartPlanStatus {
return fmt.Errorf("任务尚未开始")
}
c, err := container.NewContainer("WatchTaskLog", planId, "")
if err != nil {
return err
}
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
// TODO 临时指定,后期根据步骤id去做查询判断
var step string
switch task.Name {
case "初始化部署环境":
step = "bootstrap-servers"
case "部署Master":
step = "deploy"
case "部署Node":
step = "deploy"
case "部署基础组件":
step = "deploy"
default:
step = "bootstrap-servers"
}
containerId := fmt.Sprintf("%s-%d", step, planId)
readCloser, err := c.WatchContainerLog(ctx, containerId, "")
if err != nil {
return err
}
defer readCloser.Close()
// 读取日志
scanner := bufio.NewScanner(readCloser)
flush, _ := w.(http.Flusher)
for scanner.Scan() {
line := append(scanner.Bytes(), byte('\n'))
// 去掉前8不可见字符
_, err = w.Write(line[8:])
if err == io.EOF {
break
}
if err != nil {
return err
}
flush.Flush()
}
return nil
}
func (p *plan) modelTask2Type(o *model.Task) *types.PlanTask {
return &types.PlanTask{
PixiuMeta: types.PixiuMeta{
Id: o.Id,
ResourceVersion: o.ResourceVersion,
},
TimeMeta: types.TimeMeta{
GmtCreate: o.GmtCreate,
GmtModified: o.GmtModified,
},
Name: o.Name,
PlanId: o.PlanId,
Status: o.Status,
Message: o.Message,
}
}
func (p *plan) modelTask2TypeList(o []*model.Task) []types.PlanTask {
var tasks []types.PlanTask
for _, object := range o {
tasks = append(tasks, *p.modelTask2Type(object))
}
return tasks
}
================================================
FILE: pkg/controller/plan/register.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (phe "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plan
import (
"context"
"encoding/base64"
"fmt"
"io"
"net"
"strings"
"time"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
"k8s.io/klog/v2"
"github.com/caoyingjunz/pixiu/pkg/db"
"github.com/caoyingjunz/pixiu/pkg/db/model"
"github.com/caoyingjunz/pixiu/pkg/types"
)
const (
KubeConfigFile = "/etc/kubernetes/admin.conf"
)
type Register struct {
handlerTask
factory db.ShareDaoFactory
}
func (c Register) Name() string { return "集群注册" }
func (c Register) Run() error {
ks := &types.KubernetesSpec{}
if err := ks.Unmarshal(c.data.Config.Kubernetes); err != nil {
return err
}
// 如果未启用自注册功能,则直接跳过
if !ks.Register {
klog.Infof("部署计划未启用自注册功能,skipping")
}
// 从 master 节点获取 kubeConfig 内容,注入集群服务
var masterNodes []model.Node
for _, node := range c.data.Nodes {
if strings.Contains(node.Role, model.MasterRole) {
masterNodes = append(masterNodes, node)
}
}
var (
kubeConfig []byte
err error
)
for _, masterNode := range masterNodes {
kubeConfig, err = getKubeConfigFromMasterNode(masterNode)
if err == nil {
break
} else {
klog.Warningf("failed to get kubeConfig from master(%s): %v, trying the other masters", masterNode.Name, err)
}
}
if len(kubeConfig) == 0 {
return fmt.Errorf("get the empty kubeconfig from master nodes")
}
config64 := base64.StdEncoding.EncodeToString(kubeConfig)
if err = c.factory.Cluster().UpdateByPlan(context.TODO(),
c.data.PlanId, map[string]interface{}{"kube_config": config64}); err != nil {
return err
}
return nil
}
func getKubeConfigFromMasterNode(maserNode model.Node) ([]byte, error) {
sftpClient, err := newSftpClient(maserNode)
if err != nil {
return nil, err
}
defer sftpClient.Close()
srcFile, err := sftpClient.Open(KubeConfigFile)
if err != nil {
return nil, err
}
defer srcFile.Close()
buf, err := io.ReadAll(srcFile)
if err != nil {
return nil, err
}
return buf, nil
}
func newSftpClient(node model.Node) (*sftp.Client, error) {
nodeAuth := types.PlanNodeAuth{}
if err := nodeAuth.Unmarshal(node.Auth); err != nil {
return nil, err
}
var clientConfig *ssh.ClientConfig
switch nodeAuth.Type {
case types.PasswordAuth:
// 1. 使用密码
clientConfig = &ssh.ClientConfig{
User: nodeAuth.Password.User,
Auth: []ssh.AuthMethod{
ssh.Password(nodeAuth.Password.Password),
},
Timeout: 30 * time.Second,
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
}
case types.KeyAuth:
//2. 使用秘钥
key := []byte(nodeAuth.Key.Data)
signer, err := ssh.ParsePrivateKey(key)
if err != nil {
return nil, err
}
clientConfig = &ssh.ClientConfig{
User: "root", // 秘钥登陆时,默认 root
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
Timeout: 30 * time.Second,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
default:
return nil, fmt.Errorf("unsupported ssh auth type: %s", nodeAuth.Type)
}
addr := fmt.Sprintf("%s:%d", node.Ip, 22)
sshClient, err := ssh.Dial("tcp", addr, clientConfig)
if err != nil {
return nil, err
}
return sftp.NewClient(sshClient)
}
================================================
FILE: pkg/controller/plan/render.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (phe "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plan
import (
"bytes"
"fmt"
"path/filepath"
"strings"
"text/template"
"github.com/caoyingjunz/pixiu/pkg/db/model"
"github.com/caoyingjunz/pixiu/pkg/types"
"github.com/caoyingjunz/pixiu/pkg/util"
pixiutpl "github.com/caoyingjunz/pixiu/template"
)
// Render 渲染 pixiu 部署配置
// 1. 渲染 hosts
// 2. 渲染 globals.yaml
// 3. 渲染 multinode
// 具体参考 https://github.com/pixiu-io/kubez-ansible
type Render struct {
handlerTask
dir string
}
func (r Render) Name() string { return "配置渲染" }
func (r Render) Run() error {
// 渲染 hosts
if err := r.doRender("hosts", pixiutpl.HostTemplate, r.data); err != nil {
return err
}
// 渲染 multiNode
nodes, err := ParseMultinode(r.data, r.dir)
if err != nil {
return err
}
if err := r.doRender("multinode", pixiutpl.MultiModeTemplate, nodes); err != nil {
return err
}
// 渲染 globals
cfg, err := ParseConfig(r.data)
if err != nil {
return err
}
if err := r.doRender("globals.yml", pixiutpl.GlobalsTemplate, cfg); err != nil {
return err
}
return nil
}
func (r Render) doRender(name string, text string, data interface{}) error {
tpl := template.New(name)
tpl = template.Must(tpl.Parse(text))
var buf bytes.Buffer
if err := tpl.Execute(&buf, data); err != nil {
return err
}
filename, err := GetRenderFile(r.GetPlanId(), r.dir, name)
if err != nil {
return err
}
if err = util.WriteToFile(filename, buf.Bytes()); err != nil {
return err
}
return nil
}
type Multinode struct {
DockerMaster []types.PlanNode
DockerNode []types.PlanNode
ContainerdMaster []types.PlanNode
ContainerdNode []types.PlanNode
}
func ParseMultinode(data TaskData, workDir string) (Multinode, error) {
multinode := Multinode{
DockerMaster: make([]types.PlanNode, 0),
DockerNode: make([]types.PlanNode, 0),
ContainerdMaster: make([]types.PlanNode, 0),
ContainerdNode: make([]types.PlanNode, 0),
}
runtime := types.RuntimeSpec{}
if err := runtime.Unmarshal(data.Config.Runtime); err != nil {
return multinode, err
}
for _, node := range data.Nodes {
nodeAuth := types.PlanNodeAuth{}
err := nodeAuth.Unmarshal(node.Auth)
if err != nil {
return multinode, err
}
// 生成rsa的渲染文件
_, err = RenderRSA(data.PlanId, node.Name, workDir, nodeAuth)
if err != nil {
return multinode, err
}
if nodeAuth.Type == types.KeyAuth {
if nodeAuth.Key == nil {
return multinode, fmt.Errorf("node(%s) key auth config is empty", node.Name)
}
nodeAuth.Key.File = fmt.Sprintf("/configs/ssh/%s/id_rsa", node.Name)
}
if nodeAuth.Type == types.PasswordAuth && nodeAuth.Password == nil {
return multinode, fmt.Errorf("node(%s) password auth config is empty", node.Name)
}
planNode := types.PlanNode{Name: node.Name, Auth: nodeAuth}
roles := strings.Split(node.Role, ",")
if runtime.IsDocker() {
for _, role := range roles {
if role == model.MasterRole {
multinode.DockerMaster = append(multinode.DockerMaster, planNode)
}
if role == model.NodeRole {
multinode.DockerNode = append(multinode.DockerNode, planNode)
}
}
}
if runtime.IsContainerd() {
for _, role := range roles {
if role == model.MasterRole {
multinode.ContainerdMaster = append(multinode.ContainerdMaster, planNode)
}
if role == model.NodeRole {
multinode.ContainerdNode = append(multinode.ContainerdNode, planNode)
}
}
}
}
return multinode, nil
}
// GetRenderFile
// TODO: 后续优化
func GetRenderFile(planId int64, workDir string, f string) (string, error) {
planDir := filepath.Join(workDir, fmt.Sprintf("%d", planId))
if err := util.EnsureDirectoryExists(planDir); err != nil {
return "", err
}
return filepath.Join(planDir, f), nil
}
func RenderRSA(planId int64, name string, workDir string, auth types.PlanNodeAuth) (string, error) {
if auth.Type == types.KeyAuth {
if auth.Key == nil {
return "", fmt.Errorf("node(%s) key auth config is empty", name)
}
f, err := GetRSAFile(planId, workDir, name)
if err != nil {
return "", err
}
if err = util.WriteToFile(f, []byte(auth.Key.Data)); err != nil {
return "", err
}
return f, nil
}
return "", nil
}
func GetRSAFile(planId int64, workDir string, name string) (string, error) {
rsaDir := filepath.Join(workDir, fmt.Sprintf("%d", planId), "ssh", name)
if err := util.EnsureDirectoryExists(rsaDir); err != nil {
return "", err
}
return filepath.Join(rsaDir, "id_rsa"), nil
}
func ParseConfig(data TaskData) (*types.PlanConfig, error) {
config := data.Config
network := types.NetworkSpec{}
if err := network.Unmarshal(config.Network); err != nil {
return nil, err
}
kubernetes := types.KubernetesSpec{}
if err := kubernetes.Unmarshal(config.Kubernetes); err != nil {
return nil, err
}
component := types.ComponentSpec{}
if err := component.Unmarshal(config.Component); err != nil {
return nil, err
}
return &types.PlanConfig{
Kubernetes: kubernetes,
Network: network,
Component: component,
}, nil
}
================================================
FILE: pkg/controller/plan/worker.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (phe "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plan
import (
"context"
"fmt"
"time"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/klog/v2"
"github.com/caoyingjunz/pixiu/pkg/db/model"
"github.com/caoyingjunz/pixiu/pkg/util/errors"
)
type Handler interface {
GetPlanId() int64
Name() string // 检查项名称
Step() model.PlanStep // 未开始,运行中,异常和完成
Run() error // 执行
}
type handlerTask struct {
data TaskData
}
func (t handlerTask) GetPlanId() int64 { return t.data.PlanId }
func (t handlerTask) Step() model.PlanStep { return model.RunningPlanStep }
func newHandlerTask(data TaskData) handlerTask {
return handlerTask{data: data}
}
func (p *plan) Run(ctx context.Context, workers int) error {
// 进程启动时,尝试同步任务状态
klog.Infof("starting to sync task manager")
if err := p.SyncTaskStatus(ctx); err != nil {
return err
}
// 启动部署计划控制器
klog.Infof("starting deployment manager")
for i := 0; i < workers; i++ {
go wait.UntilWithContext(ctx, p.worker, time.Second)
}
return nil
}
func (p *plan) worker(ctx context.Context) {
for p.process(ctx) {
}
}
func (p *plan) process(ctx context.Context) bool {
key, quit := taskQueue.Get()
if quit {
return false
}
defer taskQueue.Done(key)
p.syncHandler(ctx, key.(int64))
return true
}
type TaskData struct {
PlanId int64
Config *model.Config
Nodes []model.Node
}
func (t TaskData) validate() error {
return nil
}
func (p *plan) getTaskData(ctx context.Context, planId int64) (TaskData, error) {
nodes, err := p.factory.Plan().ListNodes(ctx, planId)
if err != nil {
return TaskData{}, err
}
cfg, err := p.factory.Plan().GetConfigByPlan(ctx, planId)
if err != nil {
return TaskData{}, err
}
return TaskData{
PlanId: planId,
Config: cfg,
Nodes: nodes,
}, nil
}
// 实际处理函数
// 处理步骤:
// 1. 检查部署参数是否符合要求
// 2. 渲染环境
// 3. 执行部署
// 4. 部署后环境清理
func (p *plan) syncHandler(ctx context.Context, planId int64) {
klog.Infof("starting plan(%d) task", planId)
defer klog.Infof("completed plan(%d) task", planId)
taskData, err := p.getTaskData(ctx, planId)
if err != nil {
klog.Errorf("failed to get task data: %v", err)
return
}
runner, err := p.GetRunner(taskData.Config.OSImage)
if err != nil {
klog.Errorf("failed to get image(%s) for worker: %v", taskData.Config.OSImage, err)
return
}
// Runner的工作目录
dir := p.WorkDir()
task := newHandlerTask(taskData)
handlers := []Handler{
Check{handlerTask: task},
Render{handlerTask: task, dir: dir},
BootStrap{handlerTask: task, dir: dir, runner: runner},
Deploy{handlerTask: task, dir: dir, runner: runner},
DeployNode{handlerTask: task},
Register{handlerTask: task, factory: p.factory},
DeployChart{handlerTask: task},
}
if err = p.syncTasks(handlers...); err != nil {
klog.Errorf("failed to sync task: %v", err)
}
}
func (p *plan) createPlanTasksIfNotExist(tasks ...Handler) error {
for _, task := range tasks {
planId := task.GetPlanId()
name := task.Name()
step := task.Step()
_, err := p.factory.Plan().GetTaskByName(context.TODO(), planId, name)
// 存在则直接返回
if err == nil {
return nil
}
// 非不存在报错则报异常
if !errors.IsRecordNotFound(err) {
klog.Infof("failed to get plan(%d) tasks(%s) for first created: %v", planId, name, err)
return err
}
// 不存在记录则新建
if _, err = p.factory.Plan().CreateTask(context.TODO(), &model.Task{
Name: name,
PlanId: planId,
Step: step,
Status: model.UnStartPlanStatus,
}); err != nil {
klog.Errorf("failed to init plan(%d) task(%s): %v", planId, name, err)
return err
}
}
return nil
}
func (p *plan) WorkDir() string {
return p.cc.Worker.WorkDir
}
func (p *plan) GetRunner(osImage string) (string, error) {
engines := p.cc.Worker.Engines
for _, engine := range engines {
for _, os := range engine.OSSupported {
if os == osImage {
return engine.Image, nil
}
}
}
return "", fmt.Errorf("osImage(%s) runner not found", osImage)
}
// 同步任务状态
// 任务启动时设置为运行中,结束时同步为结束状态(成功或者失败)
// TODO: 后续优化,判断对应部署容器是否在运行,根据容器的运行结果同步状态
func (p *plan) syncStatus(ctx context.Context, planId int64) error {
tasks, err := p.factory.Plan().ListTasks(ctx, planId)
if err != nil {
return err
}
for _, task := range tasks {
if task.Status != model.RunningPlanStatus {
continue
}
if _, err = p.factory.Plan().UpdateTask(ctx, planId, task.Name, map[string]interface{}{
"status": model.FailedPlanStatus, "step": model.FailedPlanStep, "message": "服务异常修正,请重新启动部署计划", "gmt_modified": time.Now(),
}); err != nil {
klog.Errorf("failed to update plan(%d) status: %v", planId, err)
return err
}
}
return nil
}
func (p *plan) syncTasks(tasks ...Handler) error {
// 初始化记录
if err := p.createPlanTasksIfNotExist(tasks...); err != nil {
return err
}
// 执行任务并更新状态
for _, task := range tasks {
planId := task.GetPlanId()
name := task.Name()
klog.Infof("starting plan(%d) task(%s)", planId, name)
// TODO: 通过闭包方式优化
start, err := p.factory.Plan().UpdateTask(context.TODO(), planId, name, map[string]interface{}{
"status": model.RunningPlanStatus, "message": "", "gmt_create": time.Now(),
})
if err != nil {
klog.Errorf("failed to update plan(%d) status before run task(%s): %v", planId, name, err)
return err
}
taskC.SetByTask(planId, *start)
status := model.SuccessPlanStatus
step := task.Step()
message := ""
// 执行检查
runErr := task.Run()
if runErr != nil {
status = model.FailedPlanStatus
step = model.FailedPlanStep
message = runErr.Error()
}
// 执行完成之后更新状态
end, err := p.factory.Plan().UpdateTask(context.TODO(), planId, name, map[string]interface{}{
"status": status, "message": message, "step": step, "gmt_modified": time.Now(),
})
if err != nil {
klog.Errorf("failed to update plan(%d) status after run task(%s): %v", planId, name, err)
return err
}
taskC.SetByTask(planId, *end)
klog.Infof("completed plan(%d) task(%s)", planId, name)
if runErr != nil {
return runErr
}
}
return nil
}
================================================
FILE: pkg/controller/tenant/tenant.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tenant
import (
"context"
"k8s.io/klog/v2"
"github.com/caoyingjunz/pixiu/api/server/errors"
"github.com/caoyingjunz/pixiu/cmd/app/config"
"github.com/caoyingjunz/pixiu/pkg/db"
"github.com/caoyingjunz/pixiu/pkg/db/model"
"github.com/caoyingjunz/pixiu/pkg/types"
)
type TenantGetter interface {
Tenant() Interface
}
type Interface interface {
Create(ctx context.Context, req *types.CreateTenantRequest) error
Update(ctx context.Context, tid int64, req *types.UpdateTenantRequest) error
Delete(ctx context.Context, tid int64) error
Get(ctx context.Context, tid int64) (*types.Tenant, error)
List(ctx context.Context) ([]types.Tenant, error)
}
type tenant struct {
cc config.Config
factory db.ShareDaoFactory
}
func (t *tenant) Create(ctx context.Context, req *types.CreateTenantRequest) error {
object, err := t.factory.Tenant().GetTenantByName(ctx, req.Name)
if err != nil {
klog.Errorf("failed to get tenant %s: %v", req.Name, err)
return errors.ErrServerInternal
}
if object != nil {
return errors.ErrTenantExists
}
tenant := &model.Tenant{
Name: req.Name,
}
if req.Description != nil {
tenant.Description = *req.Description
}
if _, err = t.factory.Tenant().Create(ctx, tenant); err != nil {
klog.Errorf("failed to create tenant %s: %v", req.Name, err)
return errors.ErrServerInternal
}
return nil
}
func (t *tenant) Update(ctx context.Context, tid int64, req *types.UpdateTenantRequest) error {
object, err := t.factory.Tenant().Get(ctx, tid)
if err != nil {
klog.Errorf("failed to get tenant %d: %v", tid, err)
return errors.ErrServerInternal
}
if object == nil {
return errors.ErrTenantNotFound
}
updates := make(map[string]interface{})
if req.Name != nil {
updates["name"] = *req.Name
}
if req.Description != nil {
updates["description"] = *req.Description
}
if len(updates) == 0 {
return errors.ErrInvalidRequest
}
if err := t.factory.Tenant().Update(ctx, tid, *req.ResourceVersion, updates); err != nil {
klog.Errorf("failed to update tenant %d: %v", tid, err)
return errors.ErrServerInternal
}
return nil
}
func (t *tenant) Delete(ctx context.Context, tid int64) error {
_, err := t.factory.Tenant().Delete(ctx, tid)
if err != nil {
klog.Errorf("failed to delete tenant %d: %v", tid, err)
return errors.ErrServerInternal
}
return nil
}
func (t *tenant) Get(ctx context.Context, tid int64) (*types.Tenant, error) {
object, err := t.factory.Tenant().Get(ctx, tid)
if err != nil {
klog.Errorf("failed to get tenant %d: %v", tid, err)
return nil, errors.ErrServerInternal
}
if object == nil {
return nil, errors.ErrTenantNotFound
}
return t.model2Type(object), nil
}
func (t *tenant) List(ctx context.Context) ([]types.Tenant, error) {
objects, err := t.factory.Tenant().List(ctx)
if err != nil {
klog.Errorf("failed to get tenants: %v", err)
return nil, errors.ErrServerInternal
}
var ts []types.Tenant
for _, object := range objects {
ts = append(ts, *t.model2Type(&object))
}
return ts, nil
}
func (t *tenant) model2Type(o *model.Tenant) *types.Tenant {
return &types.Tenant{
PixiuMeta: types.PixiuMeta{
Id: o.Id,
ResourceVersion: o.ResourceVersion,
},
TimeMeta: types.TimeMeta{
GmtCreate: o.GmtCreate,
GmtModified: o.GmtModified,
},
Name: o.Name,
Description: o.Description,
}
}
func NewTenant(cfg config.Config, f db.ShareDaoFactory) *tenant {
return &tenant{
cc: cfg,
factory: f,
}
}
================================================
FILE: pkg/controller/user/user.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package user
import (
"context"
"fmt"
"github.com/casbin/casbin/v2"
"k8s.io/klog/v2"
"github.com/caoyingjunz/pixiu/api/server/errors"
"github.com/caoyingjunz/pixiu/api/server/httputils"
"github.com/caoyingjunz/pixiu/cmd/app/config"
"github.com/caoyingjunz/pixiu/pkg/client"
"github.com/caoyingjunz/pixiu/pkg/db"
"github.com/caoyingjunz/pixiu/pkg/db/model"
"github.com/caoyingjunz/pixiu/pkg/types"
"github.com/caoyingjunz/pixiu/pkg/util"
tokenutil "github.com/caoyingjunz/pixiu/pkg/util/token"
)
var (
userIndexer client.UserCache
tokenIndexer client.TokenCache
)
func init() {
userIndexer = *client.NewUserCache()
tokenIndexer = *client.NewTokenCache()
}
type UserGetter interface {
User() Interface
}
type Interface interface {
Create(ctx context.Context, req *types.CreateUserRequest) error
Update(ctx context.Context, userId int64, req *types.UpdateUserRequest) error
Delete(ctx context.Context, userId int64) error
Get(ctx context.Context, userId int64) (*types.User, error)
List(ctx context.Context, req *types.ListUserRequest) (*types.PageResponse, error)
// UpdatePassword 用户修改密码或者管理员重置密码
UpdatePassword(ctx context.Context, userId int64, req *types.UpdateUserPasswordRequest) error
// GetCount 仅获取用户数量
GetCount(ctx context.Context, opts types.ListOptions) (int64, error)
// GetStatus 获取用户状态,优先从缓存获取,如果没有则从库里获取,然后同步到缓存
GetStatus(ctx context.Context, uid int64) (int, error)
Login(ctx context.Context, req *types.LoginRequest) (*types.LoginResponse, error)
Logout(ctx context.Context, userId int64) error
GetLoginToken(ctx context.Context, userId int64) (string, error)
}
type user struct {
cc config.Config
factory db.ShareDaoFactory
enforcer *casbin.SyncedEnforcer
}
func (u *user) Create(ctx context.Context, req *types.CreateUserRequest) error {
encrypt, err := util.EncryptUserPassword(req.Password)
if err != nil {
klog.Errorf("failed to encrypt user password: %v", err)
return errors.ErrServerInternal
}
object, err := u.factory.User().GetUserByName(ctx, req.Name)
if err != nil {
klog.Errorf("failed to get user %s: %v", req.Name, err)
return errors.ErrServerInternal
}
if object != nil {
err = errors.ErrUserExists // 记录错误
return err
}
// 超级管理员全局只允许存在一个
if req.Role == model.RoleRoot {
root, err := u.factory.User().GetRoot(ctx)
if err != nil {
klog.Errorf("failed to check root user: %v", err)
return errors.ErrServerInternal
}
if root != nil {
return errors.ErrRootAlreadyExists
}
}
txFunc := func() (err error) {
if req.Role == model.RoleRoot {
bindings := model.NewGroupBinding(req.Name, model.AdminGroup)
_, err = u.enforcer.AddGroupingPolicy(bindings.Raw())
}
return
}
if _, err = u.factory.User().Create(ctx, &model.User{
Name: req.Name,
Password: encrypt,
Status: req.Status,
Role: req.Role,
Email: req.Email,
Phone: req.Phone,
Description: req.Description,
}, txFunc); err != nil {
klog.Errorf("failed to create user %s: %v", req.Name, err)
return errors.ErrServerInternal
}
return nil
}
func (u *user) Update(ctx context.Context, uid int64, req *types.UpdateUserRequest) error {
updates := map[string]interface{}{
"status": req.Status,
"email": req.Email,
"phone": req.Phone,
"description": req.Description,
}
if err := u.factory.User().Update(ctx, uid, *req.ResourceVersion, updates); err != nil {
klog.Errorf("failed to update user(%d): %v", uid, err)
return errors.ErrServerInternal
}
userIndexer.Set(uid, int(req.Status))
return nil
}
func (u *user) preResetPassword(ctx context.Context, userId int64, operatorId int64, req *types.UpdateUserPasswordRequest) error {
// 操作人必须具备管理员权限
operator, err := u.Get(ctx, operatorId)
if err != nil {
return err
}
if operator.Role != model.RoleRoot {
return fmt.Errorf("非超级管理员,不允许重置用户密码")
}
return nil
}
func (u *user) preChangePassword(ctx context.Context, userId int64, operatorId int64, req *types.UpdateUserPasswordRequest) error {
if operatorId != userId {
return fmt.Errorf("用户只能修改自己的密码")
}
object, err := u.Get(ctx, userId)
if err != nil {
return err
}
// 校验旧密码是否正确
if err = util.ValidateUserPassword(object.Password, req.Old); err != nil {
klog.Errorf("检验用户密码失败: %v", err)
return errors.ErrInvalidPassword
}
return nil
}
// UpdatePassword 支持用户修改密码和管理员重置密码
// 修改密码: 用户只能修改自己密码
// 重启密码: 管理员可以重置他人密码
func (u *user) UpdatePassword(ctx context.Context, userId int64, req *types.UpdateUserPasswordRequest) error {
// 新老密码不允许相同
if req.New == req.Old {
return errors.ErrDuplicatedPassword
}
operatorId, err := httputils.GetUserIdFromContext(ctx)
if err != nil {
return err
}
if req.Reset {
// 管理员重置密码前置检查
if err = u.preResetPassword(ctx, userId, operatorId, req); err != nil {
return err
}
} else {
// 用户修改密码前置检查
if err = u.preChangePassword(ctx, userId, operatorId, req); err != nil {
return err
}
}
newPass, err := util.EncryptUserPassword(req.New)
if err != nil {
klog.Errorf("failed to encrypt user password: %v", err)
return errors.ErrServerInternal
}
if err = u.factory.User().Update(ctx, userId, *req.ResourceVersion, map[string]interface{}{
"password": newPass,
}); err != nil {
klog.Errorf("failed to update user(%d) password: %v", userId, err)
return errors.ErrServerInternal
}
tokenIndexer.Delete(userId)
return nil
}
func (u *user) Delete(ctx context.Context, userId int64) error {
if err := u.factory.User().Delete(ctx, userId); err != nil {
klog.Errorf("failed to delete user(%d): %v", userId, err)
return errors.ErrServerInternal
}
userIndexer.Delete(userId)
tokenIndexer.Delete(userId)
return nil
}
func (u *user) Get(ctx context.Context, userId int64) (*types.User, error) {
object, err := u.factory.User().Get(ctx, userId)
if err != nil {
klog.Errorf("failed to get user(%d): %v", userId, err)
return nil, errors.ErrServerInternal
}
if object == nil {
return nil, errors.ErrUserNotFound
}
return model2Type(object), nil
}
func (u *user) List(ctx context.Context, req *types.ListUserRequest) (*types.PageResponse, error) {
opts := []db.Options{db.WithOrderByDesc()}
if req != nil {
opts = append(opts,
db.WithUserNameLike(req.UserName),
db.WithUserPhoneLike(req.UserPhone),
db.WithUserEmailLike(req.UserEmail),
)
if req.Status != nil {
opts = append(opts, db.WithUserStatus(*req.Status))
}
}
total, err := u.factory.User().Count(ctx, opts...)
if err != nil {
klog.Errorf("failed to get user counts: %v", err)
return nil, errors.ErrServerInternal
}
pageReq := types.PageRequest{}
if req != nil {
pageReq = req.PageRequest
if req.Page > 0 && req.Limit > 0 {
opts = append(opts, db.WithOffset((req.Page-1)*req.Limit), db.WithLimit(req.Limit))
}
}
objects, err := u.factory.User().List(ctx, opts...)
if err != nil {
klog.Errorf("failed to get user list: %v", err)
return nil, errors.ErrServerInternal
}
var users []types.User
for _, object := range objects {
users = append(users, *model2Type(&object))
}
return &types.PageResponse{
PageRequest: pageReq,
Total: int(total),
Items: users,
}, nil
}
func (u *user) GetCount(ctx context.Context, opts types.ListOptions) (int64, error) {
userCount, err := u.factory.User().Count(ctx)
if err != nil {
klog.Errorf("failed to get user counts: %v", err)
return 0, errors.ErrServerInternal
}
return userCount, nil
}
// GetStatus 获取用户状态,优先从缓存获取,如果没有则从库里获取,然后同步到缓存
func (u *user) GetStatus(ctx context.Context, uid int64) (int, error) {
status, ok := userIndexer.Get(uid)
if ok {
return status, nil
}
object, err := u.factory.User().Get(ctx, uid)
if err != nil {
klog.Errorf("failed to get user(%d): %v", uid, err)
return 0, errors.ErrServerInternal
}
if object == nil {
return 0, errors.ErrUserNotFound
}
userIndexer.Set(uid, int(object.Status))
return int(object.Status), nil
}
func (u *user) Login(ctx context.Context, req *types.LoginRequest) (*types.LoginResponse, error) {
object, err := u.factory.User().GetUserByName(ctx, req.Name)
if err != nil {
return nil, errors.ErrServerInternal
}
if object == nil {
return nil, errors.ErrUserNotFound
}
// 如果用户已被禁用,则不允许登陆
if object.Status == 2 {
return nil, fmt.Errorf("用户已被禁用")
}
if err = util.ValidateUserPassword(object.Password, req.Password); err != nil {
klog.Errorf("检验用户密码失败: %v", err)
return nil, errors.ErrInvalidPassword
}
// 生成登陆的 token 信息
key := u.GetTokenKey()
token, err := tokenutil.GenerateToken(object.Id, object.Name, key)
if err != nil {
return nil, fmt.Errorf("生成用户 token 失败: %v", err)
}
tokenIndexer.Set(object.Id, token)
return &types.LoginResponse{
UserId: object.Id,
UserName: object.Name,
Token: token,
Role: object.Role,
User: object,
}, nil
}
// Logout
// 允许用户登出登陆状态
// TODO: 临时实现,后续优化
func (u *user) Logout(ctx context.Context, userId int64) error {
tokenIndexer.Delete(userId)
return nil
}
func (u *user) GetLoginToken(ctx context.Context, userId int64) (string, error) {
t, exists := tokenIndexer.Get(userId)
if !exists {
return "", fmt.Errorf("invalid empty token")
}
return t, nil
}
func (u *user) GetTokenKey() []byte {
k := u.cc.Default.JWTKey
return []byte(k)
}
// 将 model user 转换成 types
func model2Type(o *model.User) *types.User {
return &types.User{
PixiuMeta: types.PixiuMeta{
Id: o.Id,
ResourceVersion: o.ResourceVersion,
},
Name: o.Name,
Description: o.Description,
Status: o.Status,
Role: o.Role,
Email: o.Email,
Phone: o.Phone,
TimeMeta: types.TimeMeta{
GmtCreate: o.GmtCreate,
GmtModified: o.GmtModified,
},
}
}
func NewUser(cfg config.Config, f db.ShareDaoFactory, e *casbin.SyncedEnforcer) *user {
return &user{
cc: cfg,
factory: f,
enforcer: e,
}
}
================================================
FILE: pkg/controller/user/user_test.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package user
================================================
FILE: pkg/controller/util/util.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"context"
"github.com/casbin/casbin/v2"
"github.com/gin-gonic/gin"
"github.com/caoyingjunz/pixiu/api/server/httputils"
"github.com/caoyingjunz/pixiu/pkg/db"
"github.com/caoyingjunz/pixiu/pkg/db/model"
"github.com/caoyingjunz/pixiu/pkg/util"
)
func MakeDbOptions(ctx context.Context) (opts []db.Options) {
exists, ids := httputils.GetIdRangeFromListReq(ctx)
if exists {
opts = append(opts, db.WithIDIn(ids...))
}
return
}
func SetIdRangeContext(c *gin.Context, enforcer *casbin.SyncedEnforcer, user *model.User, obj string) error {
bindings, err := GetGroupBindings(enforcer, QueryWithUserName(user.Name))
if err != nil {
return err
}
if model.BindingToAdmin(bindings) {
// This user is an admin/root, it's unnecessary to set object IDs list to context.
return nil
}
ups, err := GetUserPolicies(enforcer, user, WithObjectType(model.ObjectType(obj)))
if err != nil {
return err
}
policies := make([]model.Policy, len(ups))
for i, up := range ups {
policies[i] = up
}
if all, ids := model.GetIdRangeFromPolicy(policies); !all {
// Set a list of object IDs to context.
httputils.SetIdRangeContext(c, ids)
}
// If policy with all operation(*) exists, it's unnecessary to set object IDs list to context.
return nil
}
type BindingQueryCondition func(c *policyConditions) (index int)
func QueryWithGroupName(name string) BindingQueryCondition {
return func(c *policyConditions) int {
c.conds[1] = &name
return 1
}
}
func QueryWithUserName(name string) BindingQueryCondition {
return func(c *policyConditions) int {
c.conds[0] = &name
return 0
}
}
func GetGroupBindings(enforcer *casbin.SyncedEnforcer, conds ...BindingQueryCondition) ([]model.GroupBinding, error) {
var index int
pc := &policyConditions{
conds: [4]*string{},
}
for _, cond := range conds {
i := cond(pc)
index = util.Less(i, index)
}
rp, err := enforcer.GetFilteredNamedGroupingPolicy("g", index, pc.get()...)
if err != nil {
return nil, err
}
bindings := make([]model.GroupBinding, len(rp))
for i, p := range rp {
copy(bindings[i][:], p)
}
return bindings, nil
}
type policyConditions struct {
conds [4]*string
}
func newPolicyConditions(name string) *policyConditions {
return &policyConditions{
conds: [4]*string{&name},
}
}
func (c *policyConditions) get() (conds []string) {
for _, cond := range c.conds {
if cond != nil {
conds = append(conds, *cond)
} else {
break
}
}
return
}
type PolicyCondition func(c *policyConditions)
func WithObjectType(t model.ObjectType) PolicyCondition {
return func(c *policyConditions) {
s := t.String()
c.conds[1] = &s
}
}
func WithStringID(sid string) PolicyCondition {
return func(c *policyConditions) {
c.conds[2] = &sid
}
}
func WithOperation(op model.Operation) PolicyCondition {
return func(c *policyConditions) {
s := op.String()
c.conds[3] = &s
}
}
func GetUserPolicies(enforcer *casbin.SyncedEnforcer, user *model.User, conds ...PolicyCondition) ([]model.UserPolicy, error) {
pc := newPolicyConditions(user.Name)
for _, cond := range conds {
cond(pc)
}
rp, err := enforcer.GetFilteredNamedPolicy("p", 0, pc.get()...)
if err != nil {
return nil, err
}
policies := make([]model.UserPolicy, len(rp))
for i, p := range rp {
_ = copy(policies[i][:], p)
}
return policies, nil
}
func GetGroupPolicy(enforcer *casbin.SyncedEnforcer, name string) (*model.GroupPolicy, error) {
rp, err := enforcer.GetFilteredNamedPolicy("p", 0, name)
if err != nil {
return nil, err
}
if len(rp) == 0 {
return nil, nil
}
policy := model.GroupPolicy{}
_ = copy(policy[:], rp[0])
return &policy, nil
}
================================================
FILE: pkg/db/audit.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package db
import (
"context"
"time"
"gorm.io/gorm"
"github.com/caoyingjunz/pixiu/pkg/db/model"
"github.com/caoyingjunz/pixiu/pkg/util/errors"
)
type AuditInterface interface {
List(ctx context.Context, opts ...Options) ([]model.Audit, error)
Get(ctx context.Context, id int64) (*model.Audit, error)
Create(ctx context.Context, object *model.Audit) (*model.Audit, error)
BatchDelete(ctx context.Context, opts ...Options) (int64, error)
Count(ctx context.Context, opts ...Options) (int64, error)
}
type audit struct {
db *gorm.DB
}
func newAudit(db *gorm.DB) AuditInterface {
return &audit{db: db}
}
func (a *audit) Create(ctx context.Context, object *model.Audit) (*model.Audit, error) {
now := time.Now()
object.GmtCreate = now
object.GmtModified = now
if err := a.db.WithContext(ctx).Create(object).Error; err != nil {
return nil, err
}
return object, nil
}
func (a *audit) Get(ctx context.Context, aid int64) (*model.Audit, error) {
var object model.Audit
if err := a.db.WithContext(ctx).Where("id = ?", aid).First(&object).Error; err != nil {
if errors.IsRecordNotFound(err) {
return nil, nil
}
return nil, err
}
return &object, nil
}
func (a *audit) List(ctx context.Context, opts ...Options) ([]model.Audit, error) {
var audits []model.Audit
tx := a.db.WithContext(ctx)
for _, opt := range opts {
tx = opt(tx)
}
if err := tx.Find(&audits).Error; err != nil {
return nil, err
}
return audits, nil
}
func (a *audit) BatchDelete(ctx context.Context, opts ...Options) (int64, error) {
tx := a.db.WithContext(ctx)
for _, opt := range opts {
tx = opt(tx)
}
err := tx.Delete(&model.Audit{}).Error
return tx.RowsAffected, err
}
func (a *audit) Count(ctx context.Context, opts ...Options) (int64, error) {
tx := a.db.WithContext(ctx)
for _, opt := range opts {
tx = opt(tx)
}
var total int64
err := tx.Model(&model.Audit{}).Count(&total).Error
return total, err
}
================================================
FILE: pkg/db/cluster.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package db
import (
"context"
"time"
"gorm.io/gorm"
"github.com/caoyingjunz/pixiu/pkg/db/model"
"github.com/caoyingjunz/pixiu/pkg/util/errors"
)
type ClusterInterface interface {
Create(ctx context.Context, object *model.Cluster, fns ...func(*model.Cluster) error) (*model.Cluster, error)
Update(ctx context.Context, cid int64, resourceVersion int64, updates map[string]interface{}) error
Delete(ctx context.Context, cluster *model.Cluster, fns ...func(*model.Cluster) error) error
Get(ctx context.Context, cid int64, opts ...Options) (*model.Cluster, error)
List(ctx context.Context, opts ...Options) ([]model.Cluster, error)
Count(ctx context.Context, opts ...Options) (int64, error)
// InternalUpdate 内部更新,不更新版本号
InternalUpdate(ctx context.Context, cid int64, updates map[string]interface{}) error
GetClusterByName(ctx context.Context, name string) (*model.Cluster, error)
UpdateByPlan(ctx context.Context, planId int64, updates map[string]interface{}) error
}
type cluster struct {
db *gorm.DB
}
func (c *cluster) Create(ctx context.Context, object *model.Cluster, fns ...func(*model.Cluster) error) (*model.Cluster, error) {
now := time.Now()
object.GmtCreate = now
object.GmtModified = now
if err := c.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := tx.Create(object).Error; err != nil {
return err
}
for _, fn := range fns {
if err := fn(object); err != nil {
return err
}
}
return nil
}); err != nil {
return nil, err
}
return object, nil
}
func (c *cluster) Update(ctx context.Context, cid int64, resourceVersion int64, updates map[string]interface{}) error {
// 系统维护字段
updates["gmt_modified"] = time.Now()
updates["resource_version"] = resourceVersion + 1
f := c.db.WithContext(ctx).Model(&model.Cluster{}).Where("id = ? and resource_version = ?", cid, resourceVersion).Updates(updates)
if f.Error != nil {
return f.Error
}
if f.RowsAffected == 0 {
return errors.ErrRecordNotUpdate
}
return nil
}
// InternalUpdate 程序内部更新
func (c *cluster) InternalUpdate(ctx context.Context, cid int64, updates map[string]interface{}) error {
// 系统维护字段
updates["gmt_modified"] = time.Now()
f := c.db.WithContext(ctx).Model(&model.Cluster{}).Where("id = ?", cid).Updates(updates)
if f.Error != nil {
return f.Error
}
if f.RowsAffected == 0 {
return errors.ErrRecordNotUpdate
}
return nil
}
func (c *cluster) Delete(ctx context.Context, cluster *model.Cluster, fns ...func(*model.Cluster) error) error {
// 仅当数据库支持回写功能时才能正常
//if err := c.db.Clauses(clause.Returning{}).Where("id = ?", cid).Delete(&object).Error; err != nil {
// return nil, err
//}
return c.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := tx.Delete(cluster).Error; err != nil {
return err
}
for _, fn := range fns {
if err := fn(cluster); err != nil {
return err
}
}
return nil
})
}
func (c *cluster) Get(ctx context.Context, cid int64, opts ...Options) (*model.Cluster, error) {
var object model.Cluster
tx := c.db.WithContext(ctx)
for _, opt := range opts {
tx = opt(tx)
}
if err := tx.First(&object, cid).Error; err != nil {
if errors.IsRecordNotFound(err) {
return nil, nil
}
return nil, err
}
return &object, nil
}
func (c *cluster) List(ctx context.Context, opts ...Options) ([]model.Cluster, error) {
var cs []model.Cluster
tx := c.db.WithContext(ctx)
for _, opt := range opts {
tx = opt(tx)
}
if err := tx.Find(&cs).Error; err != nil {
return nil, err
}
return cs, nil
}
func (c *cluster) Count(ctx context.Context, opts ...Options) (int64, error) {
var total int64
tx := c.db.WithContext(ctx).Model(&model.Cluster{})
for _, opt := range opts {
tx = opt(tx)
}
if err := tx.Count(&total).Error; err != nil {
return 0, err
}
return total, nil
}
func (c *cluster) GetClusterByName(ctx context.Context, name string) (*model.Cluster, error) {
var object model.Cluster
if err := c.db.WithContext(ctx).Where("name = ?", name).First(&object).Error; err != nil {
if errors.IsRecordNotFound(err) {
return nil, nil
}
return nil, err
}
return &object, nil
}
func (c *cluster) UpdateByPlan(ctx context.Context, planId int64, updates map[string]interface{}) error {
updates["gmt_modified"] = time.Now()
f := c.db.WithContext(ctx).Model(&model.Cluster{}).Where("plan_id = ?", planId).Updates(updates)
if f.Error != nil {
return f.Error
}
if f.RowsAffected == 0 {
return errors.ErrRecordNotUpdate
}
return nil
}
func newCluster(db *gorm.DB) ClusterInterface {
return &cluster{db}
}
================================================
FILE: pkg/db/factory.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package db
import (
"gorm.io/gorm"
)
type ShareDaoFactory interface {
Cluster() ClusterInterface
Tenant() TenantInterface
User() UserInterface
Plan() PlanInterface
Audit() AuditInterface
Repository() RepositoryInterface
}
type shareDaoFactory struct {
db *gorm.DB
}
func (f *shareDaoFactory) Cluster() ClusterInterface { return newCluster(f.db) }
func (f *shareDaoFactory) Tenant() TenantInterface { return newTenant(f.db) }
func (f *shareDaoFactory) User() UserInterface { return newUser(f.db) }
func (f *shareDaoFactory) Plan() PlanInterface { return newPlan(f.db) }
func (f *shareDaoFactory) Audit() AuditInterface { return newAudit(f.db) }
func (f *shareDaoFactory) Repository() RepositoryInterface { return newRepository(f.db) }
func NewDaoFactory(db *gorm.DB, migrate bool) (ShareDaoFactory, error) {
if migrate {
// 自动创建指定模型的数据库表结构
if err := newMigrator(db).AutoMigrate(); err != nil {
return nil, err
}
}
return &shareDaoFactory{
db: db,
}, nil
}
================================================
FILE: pkg/db/logger.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package db
import (
"context"
"time"
"gorm.io/gorm/logger"
)
type (
SQLs []string
DBLogger struct {
logger.LogLevel
SlowThreshold time.Duration // slow SQL queries
}
)
const SQLContextKey = "sqls"
func NewLogger(level logger.LogLevel, slowThreshold time.Duration) *DBLogger {
return &DBLogger{
LogLevel: level,
SlowThreshold: slowThreshold,
}
}
func (l *DBLogger) LogMode(level logger.LogLevel) logger.Interface {
l.LogLevel = level
return l
}
func (l *DBLogger) Info(ctx context.Context, msg string, data ...interface{}) {}
func (l *DBLogger) Warn(ctx context.Context, msg string, data ...interface{}) {}
func (l *DBLogger) Error(ctx context.Context, msg string, data ...interface{}) {}
func (l *DBLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
if l.LogLevel <= logger.Silent {
return
}
sql, _ := fc()
if v := ctx.Value(SQLContextKey); v != nil {
sqls := v.(*SQLs)
*sqls = append(*sqls, sql)
}
}
func WithDBContext(ctx context.Context) context.Context {
return context.WithValue(ctx, SQLContextKey, new(SQLs))
}
// GetSQLs returns all the SQL statements executed in the current context.
func GetSQLs(ctx context.Context) SQLs {
if v := ctx.Value(SQLContextKey); v != nil {
return *v.(*SQLs)
}
return SQLs{}
}
================================================
FILE: pkg/db/migrator.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package db
import (
"github.com/caoyingjunz/pixiu/pkg/db/model"
"gorm.io/gorm"
)
type migrator struct {
db *gorm.DB
}
// AutoMigrate 自动创建指定模型的数据库表结构
func (m *migrator) AutoMigrate() error {
return m.CreateTables(model.GetMigrationModels()...)
}
func (m *migrator) CreateTables(dst ...interface{}) error {
db := m.db.Set("gorm:table_options", "AUTO_INCREMENT=20220801 DEFAULT CHARSET=utf8")
for _, d := range dst {
if db.Migrator().HasTable(d) {
continue
}
if err := db.Migrator().CreateTable(d); err != nil {
return err
}
}
return nil
}
func newMigrator(db *gorm.DB) *migrator {
return &migrator{db}
}
================================================
FILE: pkg/db/model/audit.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package model
import (
"fmt"
"github.com/caoyingjunz/pixiu/pkg/db/model/pixiu"
)
func init() {
register(&Audit{})
}
type AuditOperationStatus uint8
const (
AuditOpFail AuditOperationStatus = iota // 执行失败
AuditOpSuccess // 执行成功
AuditOpUnknown // 获取执行状态失败
)
func (s AuditOperationStatus) String() string {
switch s {
case AuditOpFail:
return "failed"
case AuditOpSuccess:
return "succeed"
default:
return "unknown"
}
}
type Audit struct {
pixiu.Model
RequestId string `gorm:"column:request_id;type:varchar(32);index" json:"request_id"` // 请求 ID
Ip string `gorm:"type:varchar(128)" json:"ip"` // 客户端 IP
Action string `gorm:"type:varchar(255)" json:"action"` // HTTP 方法 [POST/DELETE/PUT/GET]
Operator string `gorm:"type:varchar(255)" json:"operator"` // 操作人 ID
Path string `gorm:"type:varchar(255)" json:"path"` // HTTP 路径
ObjectType ObjectType `gorm:"column:resource_type;type:varchar(128)" json:"resource_type"` // 操作资源类型 [cluster/plan...]
Status AuditOperationStatus `gorm:"type:tinyint" json:"status"` // 记录操作运行结果[OperationStatus]
Duration int64 `gorm:"column:duration;type:bigint;default:0" json:"duration"` // 请求耗时 ms
ResponseCode int `gorm:"column:response_code;type:int;default:0" json:"response_code"` // HTTP 响应码
Cluster string `gorm:"column:cluster;type:varchar(255)" json:"cluster"` // K8s 集群名
ResourceName string `gorm:"column:resource_name;type:varchar(255)" json:"resource_name"` // 资源名称
ResourceNamespace string `gorm:"column:resource_namespace;type:varchar(255)" json:"resource_namespace"` // 资源命名空间
}
func (a *Audit) String() string {
return fmt.Sprintf("user %s(ip addr: %s) access %s with %s then %s (duration: %dms)", a.Operator, a.Ip,
a.Path, a.Action, a.Status.String(), a.Duration)
}
func (a *Audit) TableName() string {
return "audits"
}
================================================
FILE: pkg/db/model/cluster.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package model
import "github.com/caoyingjunz/pixiu/pkg/db/model/pixiu"
func init() {
register(&Cluster{})
}
// ClusterType Kubernetes 集群的类型
type ClusterType uint8
const (
ClusterTypeStandard ClusterType = iota // 标准集群
ClusterTypeCustom // 自建集群
)
type ClusterStatus uint8
const (
ClusterStatusRunning ClusterStatus = iota // 运行中
ClusterStatusDeploy // 部署中
ClusterStatusUnStart // 等待部署
ClusterStatusFailed // 部署失败
ClusterStatusError // 集群失联,API不可用
)
// Cluster kubernetes 集群信息
type Cluster struct {
pixiu.Model
// 集群名称,全局唯一
Name string `gorm:"index:idx_name,unique" json:"name"`
// 集群别名,可以重复,允许为中文
AliasName string `json:"alias_name"`
// 0:标准集群 1: 自建集群
ClusterType `gorm:"type:tinyint" json:"cluster_type"`
// 自建集群关联的 PlanId
PlanId int64
// 集群运行状态 0: 运行中 1: 部署中 2: 等待部署 3: 部署失败 4: 运行中断 5: 所有的 node 不健康
ClusterStatus `gorm:"column:status;type:tinyint" json:"status"`
// 集群的版本
KubernetesVersion string `gorm:"type:varchar(255)" json:"kubernetes_version,omitempty"`
// 集群节点健康数,json 字符串
Nodes string `gorm:"type:text" json:"nodes"`
// 集群删除保护,开启集群删除保护时不允许删除集群
// 0: 关闭集群删除保护 1: 开启集群删除保护
Protected bool `json:"protected"`
// k8s kubeConfig base64 字段
KubeConfig string `json:"kube_config"`
// 集群用途描述,可以为空
Description string `gorm:"type:text" json:"description"`
}
func (*Cluster) TableName() string {
return "clusters"
}
================================================
FILE: pkg/db/model/model.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package model
var models = make([]interface{}, 0)
func register(model ...interface{}) {
models = append(models, model...)
}
// GetMigrationModels is a helper function returns all models for table initalization.
func GetMigrationModels() []interface{} {
return models
}
================================================
FILE: pkg/db/model/pixiu/model.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package pixiu
import (
"strconv"
"time"
)
type Model struct {
Id int64 `gorm:"column:id;primaryKey;autoIncrement;not null" json:"id"`
GmtCreate time.Time `gorm:"column:gmt_create;type:datetime;default:current_timestamp;not null" json:"gmt_create"`
GmtModified time.Time `gorm:"column:gmt_modified;type:datetime;default:current_timestamp;not null" json:"gmt_modified"`
ResourceVersion int64 `gorm:"column:resource_version;default:0;not null" json:"resource_version"`
}
func (m Model) GetSID() string {
return strconv.FormatInt(m.Id, 10)
}
================================================
FILE: pkg/db/model/plan.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package model
import (
"github.com/caoyingjunz/pixiu/pkg/db/model/pixiu"
)
func init() {
register(&Plan{}, &Node{}, &Config{}, &Task{})
}
type Plan struct {
pixiu.Model
Name string `gorm:"index:idx_name,unique" json:"name"`
Description string `gorm:"type:text" json:"description"`
}
func (plan *Plan) TableName() string {
return "plans"
}
type KubeRole string
const (
MasterRole string = "master" // kubernetes master role
NodeRole string = "node" // kubernetes node role
)
type CRI string
const (
DockerCRI CRI = "docker"
ContainerdCRI CRI = "containerd"
)
type Node struct {
pixiu.Model
Name string `json:"name"` // 主机名,相同plan内不允许重复
PlanId int64 `json:"plan_id"`
Role string `json:"role"` // k8s 节点的角色,master 和 node
CRI CRI `json:"cri"`
Ip string `json:"ip"`
Auth string `json:"auth"`
}
func (node *Node) TableName() string {
return "nodes"
}
type Config struct {
pixiu.Model
PlanId int64 `json:"plan_id"`
Region string `json:"region"`
OSImage string `json:"os_image"`
Kubernetes string `json:"kubernetes"`
Network string `json:"network"`
Runtime string `json:"runtime"`
Component string `json:"component"`
}
func (config *Config) TableName() string {
return "configs"
}
type PlanStep int
const (
UnStartedPlanStep PlanStep = iota
RunningPlanStep
FailedPlanStep
CompletedPlanStep
)
type TaskStatus string
const (
FailedPlanStatus TaskStatus = "已失败"
SuccessPlanStatus TaskStatus = "已成功"
UnStartPlanStatus TaskStatus = "未开始"
RunningPlanStatus TaskStatus = "运行中"
)
type Task struct {
pixiu.Model
Name string `json:"name"`
PlanId int64 `json:"plan_id"`
Step PlanStep `json:"step"`
Status TaskStatus `json:"status"`
Message string `json:"message"`
}
func (task *Task) TableName() string {
return "tasks"
}
================================================
FILE: pkg/db/model/rbac.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package model
import (
"reflect"
"strconv"
"github.com/caoyingjunz/pixiu/pkg/db/model/pixiu"
"github.com/caoyingjunz/pixiu/pkg/util"
)
const (
AdminGroup = "root"
SidAll = "*"
)
type Operation string
const (
OpRead Operation = "read"
OpCreate Operation = "create"
OpUpdate Operation = "update"
OpDelete Operation = "delete"
OpAll Operation = "*"
)
func (o Operation) String() string {
return string(o)
}
var OperationMap = map[Operation]struct{}{
OpRead: {},
OpCreate: {},
OpUpdate: {},
OpDelete: {},
OpAll: {},
}
type ObjectType string
const (
ObjectUser ObjectType = "users"
ObjectCluster ObjectType = "clusters"
ObjectTenant ObjectType = "tenants"
ObjectPlan ObjectType = "plans"
ObjectAuth ObjectType = "auth"
ObjectAll ObjectType = "*"
)
func (o ObjectType) String() string {
return string(o)
}
var ObjectTypeMap = map[ObjectType]struct{}{
ObjectUser: {},
ObjectCluster: {},
ObjectTenant: {},
ObjectPlan: {},
ObjectAuth: {},
ObjectAll: {},
}
// TODO:
type RBACInterface interface{}
// Casbin RBAC model
// ref: https://github.com/casbin/casbin/blob/master/examples/rbac_model.conf
const RBACModel = `
[request_definition]
r = sub, obj, id, op
[policy_definition]
p = sub, obj, id, op
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && keyMatch(r.id, p.id) && keyMatch(r.op, p.op)`
// TODO:
type CasbinRBACImpl struct{}
type Policy interface {
Raw() []string
}
// UserPolicy is a RBAC policy for user.
// e.g. ["foo", "clusters", "*", "read"]
type UserPolicy [4]string
// NewUserPolicy returns a policy slice for user.
// e.g. ["foo", "clusters", "*", "read"]: foo is a user name
func NewUserPolicy(userName string, obj ObjectType, sid string, op Operation) UserPolicy {
return UserPolicy{userName, obj.String(), sid, op.String()}
}
func (p UserPolicy) Raw() []string {
return p[:]
}
func (p UserPolicy) GetUserName() string {
return p[0]
}
func (p UserPolicy) GetObjectType() ObjectType {
return ObjectType(p[1])
}
func (p UserPolicy) GetSID() string {
return p[2]
}
func (p UserPolicy) GetOperation() Operation {
return Operation(p[3])
}
// GroupPolicy is a RBAC policy for group.
// e.g. ["master", "clusters", "*", "*"]
type GroupPolicy [4]string
// NewGroupPolicy returns a policy slice for group.
// e.g. ["master", "clusters", "*", "*"]: master is a group name
func NewGroupPolicy(groupName string, obj ObjectType, sid string, op Operation) GroupPolicy {
return GroupPolicy{groupName, obj.String(), sid, op.String()}
}
func (p GroupPolicy) Raw() []string {
return p[:]
}
func (p GroupPolicy) GetGroupName() string {
return p[0]
}
func (p GroupPolicy) GetObjectType() ObjectType {
return ObjectType(p[1])
}
func (p GroupPolicy) GetSID() string {
return p[2]
}
func (p GroupPolicy) GetOperation() Operation {
return Operation(p[3])
}
// GroupBinding binds a user to a group.
// e.g. ["foo", "master"]: user foo belongs to group master
type GroupBinding [2]string
// NewGroupBinding returns a binding slice for relationship between user and group.
func NewGroupBinding(userName, groupName string) GroupBinding {
return GroupBinding{userName, groupName}
}
func (p GroupBinding) Raw() []string {
return p[:]
}
func (p GroupBinding) GetUserName() string {
return p[0]
}
func (p GroupBinding) GetGroupName() string {
return p[1]
}
// AdminPolicy is the specific policy for admin/root user.
var AdminPolicy = NewGroupPolicy(AdminGroup, ObjectAll, SidAll, OpAll)
// IsAdminPolicy returns true if the policy is the admin policy.
func IsAdminPolicy(policy Policy) bool {
switch p := policy.(type) {
case GroupPolicy:
return reflect.DeepEqual(p.Raw(), AdminPolicy.Raw())
default:
return false
}
}
// BindingToAdmin returns true if policy binding to admin group exists.
func BindingToAdmin(policies []GroupBinding) bool {
for _, policy := range policies {
if policy.GetGroupName() == AdminGroup {
return true
}
}
return false
}
// NewPolicyFromModels returns a policy slice.
// e.g. ["foo", "clusters", "*", "*"]
func NewPolicyFromModels(user *User, obj ObjectType, model pixiu.Model, op Operation) Policy {
return NewUserPolicy(user.Name, obj, model.GetSID(), op)
}
// NOTE: GetIdRangeFromPolicy is only used for listing API request.
// GetIdRangeFromPolicy returns true and an empty list when policy with all operation(*) are allowed exists,
// otherwise it returns false and a list of object IDs.
func GetIdRangeFromPolicy(policies []Policy) (all bool, ids []int64) {
ids = make([]int64, 0)
for _, policy := range policies {
if _, ok := policy.(GroupBinding); ok {
continue
}
raw := policy.Raw() // e.g. ["foo", "clusters", "*", "read"]
sid := raw[2]
switch sid {
case "":
continue
case SidAll:
// permit to read all
return true, []int64{}
}
// operation
if !(raw[3] == OpRead.String() || raw[3] == OpAll.String()) {
continue
}
id, err := strconv.ParseInt(sid, 10, 64)
if err != nil {
// invalid sid
continue
}
ids = append(ids, id)
}
return false, util.DeduplicateIntSlice(ids)
}
================================================
FILE: pkg/db/model/rbac_test.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package model
import (
"reflect"
"strconv"
"testing"
)
func TestGetIdRangeFromPolicies(t *testing.T) {
type args struct {
policies []Policy
}
tests := []struct {
name string
args args
wantAll bool
wantIds []int64
}{
{
name: "case 1",
args: args{
policies: []Policy{},
},
wantAll: false,
wantIds: []int64{},
},
{
name: "case 2",
args: args{
policies: []Policy{
NewUserPolicy("foo", ObjectCluster, strconv.Itoa(1), OpRead),
},
},
wantAll: false,
wantIds: []int64{1},
},
{
name: "case 3",
args: args{
policies: []Policy{
NewUserPolicy("foo", ObjectCluster, strconv.Itoa(1), OpRead),
NewUserPolicy("foo", ObjectCluster, strconv.Itoa(2), OpRead),
},
},
wantAll: false,
wantIds: []int64{1, 2},
},
{
name: "case 4",
args: args{
policies: []Policy{
NewUserPolicy("foo", ObjectCluster, "", OpRead),
},
},
wantAll: false,
wantIds: []int64{},
},
{
name: "case 5",
args: args{
policies: []Policy{
NewUserPolicy("foo", ObjectCluster, "*", OpRead),
},
},
wantAll: true,
wantIds: []int64{},
},
{
name: "case 6",
args: args{
policies: []Policy{
NewUserPolicy("foo", ObjectCluster, strconv.Itoa(1), OpRead),
NewUserPolicy("foo", ObjectCluster, "*", OpRead),
},
},
wantAll: true,
wantIds: []int64{},
},
{
name: "case 7",
args: args{
policies: []Policy{
NewUserPolicy("foo", ObjectCluster, strconv.Itoa(1), OpRead),
NewUserPolicy("foo", ObjectCluster, strconv.Itoa(1), OpRead),
},
},
wantAll: false,
wantIds: []int64{1},
},
{
name: "case 8",
args: args{
policies: []Policy{
NewUserPolicy("foo", ObjectCluster, strconv.Itoa(1), OpRead),
NewUserPolicy("foo", ObjectCluster, strconv.Itoa(1), OpRead),
NewUserPolicy("foo", ObjectCluster, "*", OpRead),
},
},
wantAll: true,
wantIds: []int64{},
},
{
name: "case 9",
args: args{
policies: []Policy{
NewUserPolicy("foo", ObjectCluster, strconv.Itoa(1), OpDelete),
NewUserPolicy("foo", ObjectCluster, strconv.Itoa(2), OpUpdate),
NewUserPolicy("foo", ObjectCluster, strconv.Itoa(3), OpRead),
},
},
wantAll: false,
wantIds: []int64{3},
},
{
name: "case 10",
args: args{
policies: []Policy{
NewUserPolicy("foo", ObjectCluster, strconv.Itoa(1), OpDelete),
NewUserPolicy("foo", ObjectCluster, strconv.Itoa(2), OpUpdate),
NewUserPolicy("foo", ObjectCluster, strconv.Itoa(3), OpRead),
NewUserPolicy("foo", ObjectCluster, "*", OpRead),
},
},
wantAll: true,
wantIds: []int64{},
},
{
name: "case 11",
args: args{
policies: []Policy{
NewUserPolicy("foo", ObjectCluster, strconv.Itoa(1), OpDelete),
NewUserPolicy("foo", ObjectCluster, strconv.Itoa(2), OpUpdate),
NewUserPolicy("foo", ObjectCluster, strconv.Itoa(3), OpRead),
},
},
wantAll: false,
wantIds: []int64{3},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotAll, gotIds := GetIdRangeFromPolicy(tt.args.policies)
if gotAll != tt.wantAll {
t.Errorf("GetIdRangeFromPolicies() gotAll = %v, want %v", gotAll, tt.wantAll)
}
if !reflect.DeepEqual(gotIds, tt.wantIds) {
t.Errorf("GetIdRangeFromPolicies() gotIds = %v, want %v", gotIds, tt.wantIds)
}
})
}
}
func TestIsAdminPolicy(t *testing.T) {
type args struct {
policy Policy
}
tests := []struct {
name string
args args
want bool
}{
{
name: "case 1",
args: args{
policy: NewUserPolicy("foo", ObjectCluster, "*", OpRead),
},
want: false,
},
{
name: "case 1",
args: args{
policy: AdminPolicy,
},
want: true,
},
{
name: "case 2",
args: args{
policy: GroupPolicy{},
},
want: false,
},
{
name: "case 3",
args: args{
policy: NewGroupPolicy("*", "*", "*", "*"),
},
want: false,
},
{
name: "case 4",
args: args{
policy: NewGroupPolicy("*", "root", "*", "*"),
},
want: false,
},
{
name: "case 5",
args: args{
policy: NewGroupPolicy("*", "*", "root", "*"),
},
want: false,
},
{
name: "case 6",
args: args{
policy: NewGroupPolicy("*", "*", "*", "root"),
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsAdminPolicy(tt.args.policy); got != tt.want {
t.Errorf("IsAdminPolicy() = %v, want %v", got, tt.want)
}
})
}
}
func TestBindingToAdmin(t *testing.T) {
type args struct {
policies []GroupBinding
}
tests := []struct {
name string
args args
want bool
}{
{
name: "case 1",
args: args{},
want: false,
},
{
name: "case 2",
args: args{
policies: []GroupBinding{
NewGroupBinding("foo", "bar"),
},
},
want: false,
},
{
name: "case 3",
args: args{
policies: []GroupBinding{
NewGroupBinding("foo", AdminGroup),
},
},
want: true,
},
{
name: "case 4",
args: args{
policies: []GroupBinding{
NewGroupBinding("foo", "bar"),
NewGroupBinding("foo", AdminGroup),
},
},
want: true,
},
{
name: "case 5",
args: args{
policies: []GroupBinding{
NewGroupBinding("foo", "bar"),
NewGroupBinding("foo", "baz"),
},
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := BindingToAdmin(tt.args.policies); got != tt.want {
t.Errorf("HasAdminGroupPolicy() = %v, want %v", got, tt.want)
}
})
}
}
================================================
FILE: pkg/db/model/repository.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package model
import (
"time"
"github.com/caoyingjunz/pixiu/pkg/db/model/pixiu"
)
func init() {
register(&Repository{})
}
type Repository struct {
pixiu.Model
Name string `gorm:"column:name; index:idx_name,unique; not null" json:"name"`
URL string `gorm:"column:url;not null" json:"url"`
Username string `gorm:"column:username" json:"username"`
Password string `gorm:"column:password" json:"password"`
}
func (*Repository) TableName() string {
return "repositories"
}
type ChartIndex struct {
APIVersion string `json:"apiVersion"`
Entries Entries `json:"entries"`
}
type Entries map[string][]ChartVersion
type ChartVersion struct {
Annotations map[string]string `json:"annotations"`
APIVersion string `json:"apiVersion"`
AppVersion string `json:"appVersion"`
Created time.Time `json:"created"`
Dependencies []Dependency `json:"dependencies"`
Description string `json:"description"`
Digest string `json:"digest"`
Icon string `json:"icon"`
Maintainers []Maintainer `json:"maintainers"`
Name string `json:"name"`
Sources []string `json:"sources"`
Type string `json:"type"`
URLs []string `json:"urls"`
Version string `json:"version"`
}
type Dependency struct {
Condition string `json:"condition"`
Name string `json:"name"`
Repository string `json:"repository"`
Version string `json:"version"`
Alias string `json:"alias,omitempty"`
}
type Maintainer struct {
Name string `json:"name"`
}
================================================
FILE: pkg/db/model/tenant.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package model
import "github.com/caoyingjunz/pixiu/pkg/db/model/pixiu"
func init() {
register(&Tenant{})
}
type Tenant struct {
pixiu.Model
Name string `gorm:"index:idx_name,unique" json:"name"`
Description string `gorm:"type:text" json:"description"`
Extension string `gorm:"type:text" json:"extension,omitempty"`
}
func (tenant *Tenant) TableName() string {
return "tenants"
}
================================================
FILE: pkg/db/model/user.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package model
import "github.com/caoyingjunz/pixiu/pkg/db/model/pixiu"
func init() {
register(&User{})
}
type UserRole uint8
const (
RoleUser UserRole = iota // 普通用户
RoleAdmin // 管理员
RoleRoot // 超级管理员
)
type UserStatus uint8 // TODO
type User struct {
pixiu.Model
Name string `gorm:"index:idx_name,unique" json:"name"`
Password string `gorm:"type:varchar(256)" json:"-"`
Status UserStatus `gorm:"type:tinyint" json:"status"`
Role UserRole `gorm:"type:tinyint" json:"role"`
Email string `gorm:"type:varchar(128)" json:"email"`
Phone string `gorm:"column:phone;type:varchar(32)" json:"phone"`
Description string `gorm:"type:text" json:"description"`
Extension string `gorm:"type:text" json:"extension,omitempty"`
}
func (user *User) TableName() string {
return "users"
}
================================================
FILE: pkg/db/options.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package db
import (
"time"
"gorm.io/gorm"
)
type Options func(*gorm.DB) *gorm.DB
func WithOrderByASC() Options {
return func(tx *gorm.DB) *gorm.DB {
return tx.Order("id ASC")
}
}
func WithOrderByDesc() Options {
return func(tx *gorm.DB) *gorm.DB {
return tx.Order("id DESC")
}
}
func WithOffset(offset int) Options {
return func(tx *gorm.DB) *gorm.DB {
return tx.Offset(offset)
}
}
func WithCreatedBefore(t time.Time) Options {
return func(tx *gorm.DB) *gorm.DB {
return tx.Where("gmt_create < ?", t)
}
}
func WithLimit(limit int) Options {
return func(tx *gorm.DB) *gorm.DB {
if limit == 0 {
// `LIMIT 0` statement will return 0 rows, it's meaningless.
return tx
}
return tx.Limit(limit)
}
}
func WithIDIn(ids ...int64) Options {
return func(tx *gorm.DB) *gorm.DB {
// e.g. `WHERE id IN (1, 2, 3)`
return tx.Where("id IN ?", ids)
}
}
func WithAliasNameLike(name string) Options {
return func(tx *gorm.DB) *gorm.DB {
if name == "" {
return tx
}
return tx.Where("alias_name like ?", "%"+name+"%")
}
}
func WithClusterStatus(status int) Options {
return func(tx *gorm.DB) *gorm.DB {
return tx.Where("status = ?", status)
}
}
func WithUserNameLike(name string) Options {
return func(tx *gorm.DB) *gorm.DB {
if name == "" {
return tx
}
return tx.Where("name like ?", "%"+name+"%")
}
}
func WithUserPhoneLike(phone string) Options {
return func(tx *gorm.DB) *gorm.DB {
if phone == "" {
return tx
}
return tx.Where("phone like ?", "%"+phone+"%")
}
}
func WithUserEmailLike(email string) Options {
return func(tx *gorm.DB) *gorm.DB {
if email == "" {
return tx
}
return tx.Where("email like ?", "%"+email+"%")
}
}
func WithUserStatus(status int) Options {
return func(tx *gorm.DB) *gorm.DB {
return tx.Where("status = ?", status)
}
}
func WithAuditOperatorLike(operator string) Options {
return func(tx *gorm.DB) *gorm.DB {
if operator == "" {
return tx
}
return tx.Where("operator like ?", "%"+operator+"%")
}
}
func WithAuditAction(action string) Options {
return func(tx *gorm.DB) *gorm.DB {
if action == "" {
return tx
}
return tx.Where("action = ?", action)
}
}
func WithAuditObjectType(ot string) Options {
return func(tx *gorm.DB) *gorm.DB {
if ot == "" {
return tx
}
return tx.Where("resource_type = ?", ot)
}
}
func WithAuditStatus(status uint8) Options {
return func(tx *gorm.DB) *gorm.DB {
return tx.Where("status = ?", status)
}
}
func WithAuditCreatedAfter(t time.Time) Options {
return func(tx *gorm.DB) *gorm.DB {
if t.IsZero() {
return tx
}
return tx.Where("gmt_create >= ?", t)
}
}
func WithAuditCluster(cluster string) Options {
return func(tx *gorm.DB) *gorm.DB {
return tx.Where("cluster = ?", cluster)
}
}
func WithPlanNameLike(name string) Options {
return func(tx *gorm.DB) *gorm.DB {
if name == "" {
return tx
}
return tx.Where("name like ?", "%"+name+"%")
}
}
================================================
FILE: pkg/db/plan.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package db
import (
"context"
"time"
"gorm.io/gorm"
"github.com/caoyingjunz/pixiu/pkg/db/model"
"github.com/caoyingjunz/pixiu/pkg/util/errors"
)
type PlanInterface interface {
Create(ctx context.Context, object *model.Plan, opts ...CreatePlanOption) (*model.Plan, error)
Update(ctx context.Context, pid int64, resourceVersion int64, updates map[string]interface{}) error
Delete(ctx context.Context, pid int64) (*model.Plan, error)
Get(ctx context.Context, pid int64) (*model.Plan, error)
List(ctx context.Context, opts ...Options) ([]model.Plan, error)
Count(ctx context.Context, opts ...Options) (int64, error)
CreateNode(ctx context.Context, object *model.Node) (*model.Node, error)
TxCreateNode(ctx context.Context, tx *gorm.DB, object *model.Node) error
UpdateNode(ctx context.Context, nodeId int64, resourceVersion int64, updates map[string]interface{}) error
DeleteNode(ctx context.Context, nodeId int64) (*model.Node, error)
GetNode(ctx context.Context, nodeId int64) (*model.Node, error)
ListNodes(ctx context.Context, pid int64, opts ...Options) ([]model.Node, error)
DeleteNodesByPlan(ctx context.Context, planId int64) error
GetNodeByName(ctx context.Context, planId int64, name string) (*model.Node, error)
DeleteNodesByNames(ctx context.Context, planId int64, names []string) error
CreateConfig(ctx context.Context, object *model.Config) (*model.Config, error)
TxCreateConfig(ctx context.Context, tx *gorm.DB, object *model.Config) error
UpdateConfig(ctx context.Context, cfgId int64, resourceVersion int64, updates map[string]interface{}) error
DeleteConfig(ctx context.Context, cfgId int64) (*model.Config, error)
GetConfig(ctx context.Context, cfgId int64) (*model.Config, error)
ListConfigs(ctx context.Context, opts ...Options) ([]model.Config, error)
DeleteConfigByPlan(ctx context.Context, planId int64) error
GetConfigByPlan(ctx context.Context, planId int64) (*model.Config, error)
CreateTask(ctx context.Context, object *model.Task) (*model.Task, error)
UpdateTask(ctx context.Context, pid int64, name string, updates map[string]interface{}) (*model.Task, error)
DeleteTask(ctx context.Context, pid int64) error
ListTasks(ctx context.Context, pid int64, opts ...Options) ([]model.Task, error)
GetNewestTask(ctx context.Context, pid int64) (*model.Task, error)
GetTaskByName(ctx context.Context, planId int64, name string) (*model.Task, error)
GetTaskById(ctx context.Context, taskId int64) (*model.Task, error)
}
type plan struct {
db *gorm.DB
}
type CreatePlanOption func(plan *model.Plan, tx *gorm.DB) (*gorm.DB, error)
func (p *plan) Create(ctx context.Context, object *model.Plan, opts ...CreatePlanOption) (*model.Plan, error) {
now := time.Now()
object.GmtCreate = now
object.GmtModified = now
if len(opts) == 0 {
// no transaction
if err := p.db.WithContext(ctx).Create(object).Error; err != nil {
return nil, err
}
return object, nil
}
if err := p.db.WithContext(ctx).Transaction(func(tx *gorm.DB) (err error) {
if err = tx.Create(object).Error; err != nil {
return
}
for _, opt := range opts {
if tx, err = opt(object, tx); err != nil {
return
}
}
return
}); err != nil {
return nil, err
}
return object, nil
}
func (p *plan) Update(ctx context.Context, pid int64, resourceVersion int64, updates map[string]interface{}) error {
// 系统维护字段
updates["gmt_modified"] = time.Now()
updates["resource_version"] = resourceVersion + 1
f := p.db.WithContext(ctx).Model(&model.Plan{}).Where("id = ? and resource_version = ?", pid, resourceVersion).Updates(updates)
if f.Error != nil {
return f.Error
}
if f.RowsAffected == 0 {
return errors.ErrRecordNotFound
}
return nil
}
func (p *plan) Delete(ctx context.Context, pid int64) (*model.Plan, error) {
object, err := p.Get(ctx, pid)
if err != nil {
return nil, err
}
if err = p.db.WithContext(ctx).Where("id = ?", pid).Delete(&model.Plan{}).Error; err != nil {
return nil, err
}
return object, nil
}
func (p *plan) Get(ctx context.Context, pid int64) (*model.Plan, error) {
var object model.Plan
if err := p.db.WithContext(ctx).Where("id = ?", pid).First(&object).Error; err != nil {
return nil, err
}
return &object, nil
}
func (p *plan) List(ctx context.Context, opts ...Options) ([]model.Plan, error) {
var objects []model.Plan
tx := p.db.WithContext(ctx)
for _, opt := range opts {
tx = opt(tx)
}
if err := tx.Find(&objects).Error; err != nil {
return nil, err
}
return objects, nil
}
func (p *plan) Count(ctx context.Context, opts ...Options) (int64, error) {
var count int64
tx := p.db.WithContext(ctx).Model(&model.Plan{})
for _, opt := range opts {
tx = opt(tx)
}
if err := tx.Count(&count).Error; err != nil {
return 0, err
}
return count, nil
}
func (p *plan) CreateNode(ctx context.Context, object *model.Node) (*model.Node, error) {
now := time.Now()
object.GmtCreate = now
object.GmtModified = now
if err := p.db.WithContext(ctx).Create(object).Error; err != nil {
return nil, err
}
return object, nil
}
// TxCreateNode creates a node object in the given transaction.
func (p *plan) TxCreateNode(ctx context.Context, tx *gorm.DB, object *model.Node) error {
now := time.Now()
object.GmtCreate = now
object.GmtModified = now
return tx.WithContext(ctx).Create(object).Error
}
func (p *plan) UpdateNode(ctx context.Context, nodeId int64, resourceVersion int64, updates map[string]interface{}) error {
// 系统维护字段
updates["gmt_modified"] = time.Now()
updates["resource_version"] = resourceVersion + 1
f := p.db.WithContext(ctx).Model(&model.Node{}).Where("id = ? and resource_version = ?", nodeId, resourceVersion).Updates(updates)
if f.Error != nil {
return f.Error
}
if f.RowsAffected == 0 {
return errors.ErrRecordNotFound
}
return nil
}
func (p *plan) DeleteNode(ctx context.Context, nodeId int64) (*model.Node, error) {
object, err := p.GetNode(ctx, nodeId)
if err != nil {
return nil, err
}
if err = p.db.WithContext(ctx).Where("id = ?", nodeId).Delete(&model.Node{}).Error; err != nil {
return nil, err
}
return object, nil
}
func (p *plan) DeleteNodesByPlan(ctx context.Context, planId int64) error {
if err := p.db.WithContext(ctx).Where("plan_id = ?", planId).Delete(&model.Node{}).Error; err != nil {
return err
}
return nil
}
func (p *plan) DeleteNodesByNames(ctx context.Context, planId int64, names []string) error {
if err := p.db.WithContext(ctx).Where("plan_id = ? and name in (?)", planId, names).Delete(&model.Node{}).Error; err != nil {
return err
}
return nil
}
func (p *plan) GetNodeByName(ctx context.Context, planId int64, name string) (*model.Node, error) {
var object model.Node
if err := p.db.WithContext(ctx).Where("plan_id = ? and name = ?", planId, name).First(&object).Error; err != nil {
return nil, err
}
return &object, nil
}
func (p *plan) GetNode(ctx context.Context, nodeId int64) (*model.Node, error) {
var object model.Node
if err := p.db.WithContext(ctx).Where("id = ?", nodeId).First(&object).Error; err != nil {
return nil, err
}
return &object, nil
}
func (p *plan) ListNodes(ctx context.Context, pid int64, opts ...Options) ([]model.Node, error) {
var objects []model.Node
tx := p.db.WithContext(ctx).Where("plan_id = ?", pid)
for _, opt := range opts {
tx = opt(tx)
}
if err := tx.Find(&objects).Error; err != nil {
return nil, err
}
return objects, nil
}
func (p *plan) CreateConfig(ctx context.Context, object *model.Config) (*model.Config, error) {
now := time.Now()
object.GmtCreate = now
object.GmtModified = now
if err := p.db.WithContext(ctx).Create(object).Error; err != nil {
return nil, err
}
return object, nil
}
// TxCreateConfig creates a config object in the given transaction.
func (p *plan) TxCreateConfig(ctx context.Context, tx *gorm.DB, object *model.Config) error {
now := time.Now()
object.GmtCreate = now
object.GmtModified = now
return tx.WithContext(ctx).Create(object).Error
}
func (p *plan) UpdateConfig(ctx context.Context, cid int64, resourceVersion int64, updates map[string]interface{}) error {
// 系统维护字段
updates["gmt_modified"] = time.Now()
updates["resource_version"] = resourceVersion + 1
f := p.db.WithContext(ctx).Model(&model.Config{}).Where("id = ? and resource_version = ?", cid, resourceVersion).Updates(updates)
if f.Error != nil {
return f.Error
}
if f.RowsAffected == 0 {
return errors.ErrRecordNotFound
}
return nil
}
func (p *plan) DeleteConfig(ctx context.Context, cid int64) (*model.Config, error) {
object, err := p.GetConfig(ctx, cid)
if err != nil {
return nil, err
}
if err = p.db.WithContext(ctx).Where("id = ?", cid).Delete(&model.Config{}).Error; err != nil {
return nil, err
}
return object, nil
}
func (p *plan) DeleteConfigByPlan(ctx context.Context, planId int64) error {
if err := p.db.WithContext(ctx).Where("plan_id = ?", planId).Delete(&model.Config{}).Error; err != nil {
return err
}
return nil
}
func (p *plan) GetConfig(ctx context.Context, cid int64) (*model.Config, error) {
var object model.Config
if err := p.db.WithContext(ctx).Where("id = ?", cid).First(&object).Error; err != nil {
return nil, err
}
return &object, nil
}
func (p *plan) ListConfigs(ctx context.Context, opts ...Options) ([]model.Config, error) {
var objects []model.Config
tx := p.db.WithContext(ctx)
for _, opt := range opts {
tx = opt(tx)
}
if err := tx.Find(&objects).Error; err != nil {
return nil, err
}
return objects, nil
}
func (p *plan) GetConfigByPlan(ctx context.Context, planId int64) (*model.Config, error) {
var object model.Config
if err := p.db.WithContext(ctx).Where("plan_id = ?", planId).First(&object).Error; err != nil {
return nil, err
}
return &object, nil
}
func (p *plan) CreateTask(ctx context.Context, object *model.Task) (*model.Task, error) {
now := time.Now()
object.GmtCreate = now
object.GmtModified = now
if err := p.db.WithContext(ctx).Create(object).Error; err != nil {
return nil, err
}
return object, nil
}
func (p *plan) UpdateTask(ctx context.Context, pid int64, name string, updates map[string]interface{}) (*model.Task, error) {
f := p.db.WithContext(ctx).Model(&model.Task{}).Where("plan_id = ? and name = ?", pid, name).Updates(updates)
if f.Error != nil {
return nil, f.Error
}
if f.RowsAffected == 0 {
return nil, errors.ErrRecordNotFound
}
return p.GetTaskByName(ctx, pid, name)
}
func (p *plan) DeleteTask(ctx context.Context, pid int64) error {
if err := p.db.WithContext(ctx).Where("plan_id = ?", pid).Delete(&model.Task{}).Error; err != nil {
return err
}
return nil
}
func (p *plan) ListTasks(ctx context.Context, pid int64, opts ...Options) ([]model.Task, error) {
var objects []model.Task
tx := p.db.WithContext(ctx).Where("plan_id = ?", pid)
for _, opt := range opts {
tx = opt(tx)
}
if err := tx.Find(&objects).Error; err != nil {
return nil, err
}
return objects, nil
}
func (p *plan) GetNewestTask(ctx context.Context, pid int64) (*model.Task, error) {
var objects []model.Task
if err := p.db.WithContext(ctx).Where("plan_id = ?", pid).Order("id DESC").Limit(1).Find(&objects).Error; err != nil {
return nil, err
}
if len(objects) == 0 {
return nil, errors.ErrRecordNotFound
}
return &objects[0], nil
}
func (p *plan) GetTaskById(ctx context.Context, taskId int64) (*model.Task, error) {
var object model.Task
if err := p.db.WithContext(ctx).Where("id = ?", taskId).First(&object).Error; err != nil {
return nil, err
}
return &object, nil
}
func (p *plan) GetTaskByName(ctx context.Context, planId int64, name string) (*model.Task, error) {
var object model.Task
if err := p.db.WithContext(ctx).Where("plan_id = ? and name = ?", planId, name).First(&object).Error; err != nil {
return nil, err
}
return &object, nil
}
func newPlan(db *gorm.DB) *plan {
return &plan{db}
}
================================================
FILE: pkg/db/repository.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package db
import (
"context"
"time"
"github.com/caoyingjunz/pixiu/pkg/db/model"
"github.com/caoyingjunz/pixiu/pkg/util/errors"
"gorm.io/gorm"
)
type RepositoryInterface interface {
Create(ctx context.Context, object *model.Repository) (*model.Repository, error)
Update(ctx context.Context, id int64, resourceVersion int64, updates map[string]interface{}) error
Delete(ctx context.Context, id int64) error
Get(ctx context.Context, id int64) (*model.Repository, error)
GetByName(ctx context.Context, name string) (*model.Repository, error)
List(ctx context.Context) ([]*model.Repository, error)
}
type repository struct {
db *gorm.DB
}
func newRepository(db *gorm.DB) RepositoryInterface {
return &repository{db}
}
var _ RepositoryInterface = &repository{}
func (r *repository) Create(ctx context.Context, object *model.Repository) (*model.Repository, error) {
now := time.Now()
object.GmtCreate = now
object.GmtModified = now
if err := r.db.WithContext(ctx).Create(object).Error; err != nil {
return nil, err
}
return object, nil
}
func (r *repository) Update(ctx context.Context, id int64, resourceVersion int64, updates map[string]interface{}) error {
updates["gmt_modified"] = time.Now()
updates["resource_version"] = resourceVersion + 1
f := r.db.WithContext(ctx).Model(&model.Repository{}).Where("id = ? and resource_version = ? ", id, resourceVersion).Updates(updates)
if f.Error != nil {
return f.Error
}
if f.RowsAffected == 0 {
return errors.ErrRecordNotFound
}
return nil
}
func (r *repository) Delete(ctx context.Context, id int64) error {
f := r.db.WithContext(ctx).Where("id = ?", id).Delete(&model.Repository{})
if f.Error != nil {
return f.Error
}
if f.RowsAffected == 0 {
return errors.ErrRecordNotFound
}
return nil
}
func (r *repository) Get(ctx context.Context, id int64) (*model.Repository, error) {
var repo model.Repository
if err := r.db.WithContext(ctx).Where("id = ?", id).First(&repo).Error; err != nil {
return nil, err
}
return &repo, nil
}
func (r *repository) GetByName(ctx context.Context, name string) (*model.Repository, error) {
var repo model.Repository
if err := r.db.WithContext(ctx).Where("name = ?", name).First(&repo).Error; err != nil {
return nil, err
}
return &repo, nil
}
func (r *repository) List(ctx context.Context) ([]*model.Repository, error) {
var repos []*model.Repository
if err := r.db.WithContext(ctx).Find(&repos).Error; err != nil {
return nil, err
}
return repos, nil
}
================================================
FILE: pkg/db/tenant.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package db
import (
"context"
"time"
"gorm.io/gorm"
"github.com/caoyingjunz/pixiu/pkg/db/model"
"github.com/caoyingjunz/pixiu/pkg/util/errors"
)
type TenantInterface interface {
Create(ctx context.Context, object *model.Tenant) (*model.Tenant, error)
Update(ctx context.Context, cid int64, resourceVersion int64, updates map[string]interface{}) error
Delete(ctx context.Context, cid int64) (*model.Tenant, error)
Get(ctx context.Context, cid int64) (*model.Tenant, error)
List(ctx context.Context, opts ...Options) ([]model.Tenant, error)
GetTenantByName(ctx context.Context, name string) (*model.Tenant, error)
}
type tenant struct {
db *gorm.DB
}
func (t *tenant) Create(ctx context.Context, object *model.Tenant) (*model.Tenant, error) {
now := time.Now()
object.GmtCreate = now
object.GmtModified = now
if err := t.db.WithContext(ctx).Create(object).Error; err != nil {
return nil, err
}
return object, nil
}
func (t *tenant) Update(ctx context.Context, tid int64, resourceVersion int64, updates map[string]interface{}) error {
// 系统维护字段
updates["gmt_modified"] = time.Now()
updates["resource_version"] = resourceVersion + 1
f := t.db.WithContext(ctx).Model(&model.Tenant{}).Where("id = ? and resource_version = ?", tid, resourceVersion).Updates(updates)
if f.Error != nil {
return f.Error
}
if f.RowsAffected == 0 {
return errors.ErrRecordNotFound
}
return nil
}
func (t *tenant) Delete(ctx context.Context, tid int64) (*model.Tenant, error) {
object, err := t.Get(ctx, tid)
if err != nil {
return nil, err
}
if object == nil {
return nil, nil
}
if err = t.db.WithContext(ctx).Where("id = ?", tid).Delete(&model.Tenant{}).Error; err != nil {
return nil, err
}
return object, nil
}
func (t *tenant) Get(ctx context.Context, tid int64) (*model.Tenant, error) {
var object model.Tenant
if err := t.db.WithContext(ctx).Where("id = ?", tid).First(&object).Error; err != nil {
if errors.IsRecordNotFound(err) {
return nil, nil
}
return nil, err
}
return &object, nil
}
func (t *tenant) List(ctx context.Context, opts ...Options) ([]model.Tenant, error) {
var objects []model.Tenant
tx := t.db.WithContext(ctx)
for _, opt := range opts {
tx = opt(tx)
}
if err := tx.Find(&objects).Error; err != nil {
return nil, err
}
return objects, nil
}
func (t *tenant) GetTenantByName(ctx context.Context, name string) (*model.Tenant, error) {
var object model.Tenant
if err := t.db.WithContext(ctx).Where("name = ?", name).First(&object).Error; err != nil {
if errors.IsRecordNotFound(err) {
return nil, nil
}
return nil, err
}
return &object, nil
}
func newTenant(db *gorm.DB) *tenant {
return &tenant{db}
}
================================================
FILE: pkg/db/user.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package db
import (
"context"
"time"
"gorm.io/gorm"
"github.com/caoyingjunz/pixiu/pkg/db/model"
"github.com/caoyingjunz/pixiu/pkg/util/errors"
)
type UserInterface interface {
Create(ctx context.Context, object *model.User, fns ...func() error) (*model.User, error)
Update(ctx context.Context, uid int64, resourceVersion int64, updates map[string]interface{}) error
Delete(ctx context.Context, uid int64) error
Get(ctx context.Context, uid int64) (*model.User, error)
GetRoot(ctx context.Context) (*model.User, error)
List(ctx context.Context, opts ...Options) ([]model.User, error)
Count(ctx context.Context, opts ...Options) (int64, error)
GetUserByName(ctx context.Context, userName string) (*model.User, error)
}
type user struct {
db *gorm.DB
}
func (u *user) Create(ctx context.Context, object *model.User, fns ...func() error) (*model.User, error) {
now := time.Now()
object.GmtCreate = now
object.GmtModified = now
if err := u.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := tx.Create(object).Error; err != nil {
return err
}
for _, fn := range fns {
if err := fn(); err != nil {
return err
}
}
return nil
}); err != nil {
return nil, err
}
return object, nil
}
func (u *user) Update(ctx context.Context, uid int64, resourceVersion int64, updates map[string]interface{}) error {
// 系统维护字段
updates["gmt_modified"] = time.Now()
updates["resource_version"] = resourceVersion + 1
f := u.db.WithContext(ctx).Model(&model.User{}).Where("id = ? and resource_version = ?", uid, resourceVersion).Updates(updates)
if f.Error != nil {
return f.Error
}
if f.RowsAffected == 0 {
return errors.ErrRecordNotUpdate
}
return nil
}
func (u *user) Delete(ctx context.Context, uid int64) error {
return u.db.WithContext(ctx).Where("id = ?", uid).Delete(&model.User{}).Error
}
func (u *user) Get(ctx context.Context, uid int64) (*model.User, error) {
var object model.User
if err := u.db.WithContext(ctx).Where("id = ?", uid).First(&object).Error; err != nil {
if errors.IsRecordNotFound(err) {
return nil, nil
}
return nil, err
}
return &object, nil
}
func (u *user) GetRoot(ctx context.Context) (*model.User, error) {
var object model.User
if err := u.db.WithContext(ctx).Where("role = ?", model.RoleRoot).First(&object).Error; err != nil {
if errors.IsRecordNotFound(err) {
return nil, nil
}
return nil, err
}
return &object, nil
}
// List 获取用户列表
// TODO: 暂时不做分页考虑
func (u *user) List(ctx context.Context, opts ...Options) ([]model.User, error) {
var objects []model.User
tx := u.db.WithContext(ctx)
for _, opt := range opts {
tx = opt(tx)
}
if err := tx.Find(&objects).Error; err != nil {
return nil, err
}
return objects, nil
}
func (u *user) Count(ctx context.Context, opts ...Options) (int64, error) {
var total int64
tx := u.db.WithContext(ctx).Model(&model.User{})
for _, opt := range opts {
tx = opt(tx)
}
if err := tx.Count(&total).Error; err != nil {
return 0, err
}
return total, nil
}
func (u *user) GetUserByName(ctx context.Context, userName string) (*model.User, error) {
var object model.User
if err := u.db.WithContext(ctx).Where("name = ?", userName).First(&object).Error; err != nil {
if errors.IsRecordNotFound(err) {
return nil, nil
}
return nil, err
}
return &object, nil
}
func newUser(db *gorm.DB) *user {
return &user{db}
}
================================================
FILE: pkg/jobmanager/audit_cleaner.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package jobmanager
import (
"time"
"github.com/caoyingjunz/pixiu/pkg/db"
logutil "github.com/caoyingjunz/pixiu/pkg/util/log"
)
const (
DefaultSchedule = "0 0 * * 6" // 每周六 0 点执行
DefaultDaysReserved = 30 // 保留 30 天的审计日志
)
type AuditsCleaner struct {
cfg AuditOptions
dao db.ShareDaoFactory
}
type AuditOptions struct {
Schedule string `yaml:"schedule"`
DaysReserved int `yaml:"days_reserved"`
}
func DefaultOptions() AuditOptions {
return AuditOptions{
Schedule: DefaultSchedule,
DaysReserved: DefaultDaysReserved,
}
}
func NewAuditsCleaner(cfg AuditOptions, dao db.ShareDaoFactory) *AuditsCleaner {
return &AuditsCleaner{
cfg: cfg,
dao: dao,
}
}
func (ac *AuditsCleaner) Name() string {
return "audits-cleaner"
}
func (ac *AuditsCleaner) CronSpec() string {
return ac.cfg.Schedule
}
func (ac *AuditsCleaner) LogLevel() logutil.LogLevel {
return logutil.InfoLevel
}
func (ac *AuditsCleaner) Do(ctx *JobContext) (err error) {
resv := ac.cfg.DaysReserved
before := time.Now().AddDate(0, 0, -resv)
entries := map[string]interface{}{
"days_reserved": resv,
"deadline": before,
}
entries["records_deleted"], err = ac.dao.Audit().BatchDelete(ctx, db.WithCreatedBefore(before))
ctx.WithLogFields(entries)
return
}
func (a *AuditOptions) Valid() error {
// TODO
return nil
}
================================================
FILE: pkg/jobmanager/cluster_syncer.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package jobmanager
import (
"context"
"sync"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog/v2"
"github.com/caoyingjunz/pixiu/pkg/client"
"github.com/caoyingjunz/pixiu/pkg/db"
"github.com/caoyingjunz/pixiu/pkg/db/model"
"github.com/caoyingjunz/pixiu/pkg/types"
logutil "github.com/caoyingjunz/pixiu/pkg/util/log"
)
const (
DefaultSyncInterval = "@every 5s"
)
type ClusterSyncer struct {
factory db.ShareDaoFactory
}
func NewClusterSyncer(f db.ShareDaoFactory) *ClusterSyncer {
return &ClusterSyncer{
factory: f,
}
}
func (cs *ClusterSyncer) Name() string {
return "cluster-syncer"
}
func (cs *ClusterSyncer) CronSpec() string {
return DefaultSyncInterval
}
func (cs *ClusterSyncer) LogLevel() logutil.LogLevel {
return logutil.DebugLevel
}
func (cs *ClusterSyncer) Do(ctx *JobContext) (err error) {
clusters, err := cs.factory.Cluster().List(ctx)
if err != nil {
klog.Error("[ClusterSyncer] failed to get clusters: %v", err)
return err
}
diff := len(clusters)
errCh := make(chan error, diff)
var wg sync.WaitGroup
wg.Add(diff)
for _, cluster := range clusters {
go func(c model.Cluster) {
defer wg.Done()
if err = doSync(cs.factory, c); err != nil {
errCh <- err
}
}(cluster)
}
wg.Wait()
select {
case err = <-errCh:
if err != nil {
klog.Error("failed to sync cluster status: %v", err)
}
default:
}
return nil
}
func doSync(f db.ShareDaoFactory, cluster model.Cluster) error {
// 处理自建集群正在部署的集群
if cluster.ClusterType == model.ClusterTypeCustom {
// 自建环境,状态是部署未完成时,则直接不做同步,包含:部署中,等待部署,部署失败
if cluster.ClusterStatus == model.ClusterStatusUnStart ||
cluster.ClusterStatus == model.ClusterStatusDeploy ||
cluster.ClusterStatus == model.ClusterStatusFailed {
return nil
}
}
var (
kubernetesVersion string
nodeData string
err error
)
status := model.ClusterStatusRunning
nodeData, kubernetesVersion, err = getNewestKubeStatus(cluster)
if err != nil {
status = model.ClusterStatusError
}
updates := make(map[string]interface{})
parseStatus(updates, status, kubernetesVersion, nodeData, cluster)
if len(updates) == 0 {
return nil
}
if err = f.Cluster().InternalUpdate(context.TODO(), cluster.Id, updates); err != nil {
klog.Error("failed to update cluster(%s) status: %v", cluster.Name, err)
}
return nil
}
func parseStatus(update map[string]interface{}, status model.ClusterStatus, kubernetesVersion string, nodeData string, cluster model.Cluster) {
if status != cluster.ClusterStatus {
update["status"] = status
}
if kubernetesVersion != cluster.KubernetesVersion {
update["kubernetes_version"] = kubernetesVersion
}
if nodeData != cluster.Nodes {
update["nodes"] = nodeData
}
}
func getNewestKubeStatus(cluster model.Cluster) (string, string, error) {
clusterSet, err := client.NewClusterSet(cluster.KubeConfig)
if err != nil {
return "", "", err
}
nodeList, err := clusterSet.Client.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{})
if err != nil {
return "", "", err
}
nodes := nodeList.Items
kubeNode := &types.KubeNode{Ready: make([]string, 0), NotReady: make([]string, 0)}
// 获取存储状态
for _, node := range nodes {
nodeStatus := parseKubeNodeStatus(&node)
switch nodeStatus {
case "Ready":
kubeNode.Ready = append(kubeNode.Ready, node.Name)
case "NotReady":
kubeNode.NotReady = append(kubeNode.NotReady, node.Name)
}
}
nodeData, err := kubeNode.Marshal()
if err != nil {
return "", "", err
}
var kubernetesVersion string
if len(nodes) != 0 {
kubernetesVersion = nodes[0].Status.NodeInfo.KubeletVersion
}
return nodeData, kubernetesVersion, nil
}
func parseKubeNodeStatus(node *v1.Node) string {
status := "Ready"
for _, condition := range node.Status.Conditions {
if condition.Type != v1.NodeReady {
continue
}
if condition.Status != "True" {
status = "NotReady"
}
}
return status
}
================================================
FILE: pkg/jobmanager/context.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package jobmanager
import (
"context"
"github.com/caoyingjunz/pixiu/pkg/db"
logutil "github.com/caoyingjunz/pixiu/pkg/util/log"
)
type JobContext struct {
context.Context
*logutil.Logger
}
func NewJobContext(name string, cfg *logutil.LogOptions) *JobContext {
jc := &JobContext{
Context: db.WithDBContext(context.Background()),
Logger: logutil.NewLogger(cfg),
}
jc.WithLogField("job", name)
return jc
}
func (c *JobContext) Log(level logutil.LogLevel, err error) {
c.Logger.Log(c.Context, level, err)
}
================================================
FILE: pkg/jobmanager/manager.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package jobmanager
import (
"github.com/robfig/cron/v3"
logutil "github.com/caoyingjunz/pixiu/pkg/util/log"
)
type Job interface {
// Name returns the job name
Name() string
// CronSpec returns the cron expression of the job
// e.g. "* * * * *"
CronSpec() string
// LogLevel returns the log level of the job
LogLevel() logutil.LogLevel
// Do is the job handler
Do(ctx *JobContext) error
}
type Manager struct {
cron *cron.Cron
}
func NewManager(lc *logutil.LogOptions, jobs ...Job) *Manager {
c := cron.New()
for _, job := range jobs {
c.AddFunc(job.CronSpec(), func() {
ctx := NewJobContext(job.Name(), lc)
ctx.Log(job.LogLevel(), job.Do(ctx))
})
}
return &Manager{
c,
}
}
func (m *Manager) Run() {
m.cron.Start()
}
func (m *Manager) Stop() {
ctx := m.cron.Stop()
<-ctx.Done()
}
================================================
FILE: pkg/static/localfile.go
================================================
/*
Copyright 2025 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package static
import (
"github.com/gin-gonic/gin"
"net/http"
"os"
"path"
"strings"
)
type localFileSystem struct {
http.FileSystem
root string
indexes bool
}
func LocalFile(root string, indexes bool) *localFileSystem {
return &localFileSystem{
FileSystem: gin.Dir(root, indexes),
root: root,
indexes: indexes,
}
}
func (l *localFileSystem) Exists(prefix string, file string) bool {
if p := strings.TrimPrefix(file, prefix); len(p) < len(file) {
name := path.Join(l.root, path.Clean(p))
if strings.Contains(name, "\\") || strings.Contains(name, "..") {
return false
}
stats, err := os.Stat(name)
if err != nil {
return false
}
if stats.IsDir() {
if !l.indexes {
_, err := os.Stat(path.Join(name, "index.html"))
if err != nil {
return false
}
}
}
return true
}
return false
}
================================================
FILE: pkg/static/static.go
================================================
/*
Copyright 2025 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package static
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
type ServeFileSystem interface {
http.FileSystem
Exists(prefix string, path string) bool
}
func ServeRoot(urlPrefix, root string) gin.HandlerFunc {
return Serve(urlPrefix, LocalFile(root, false))
}
// Serve returns a middleware handler that serves static files in the given directory.
func Serve(urlPrefix string, fs ServeFileSystem) gin.HandlerFunc {
return ServeCached(urlPrefix, fs, 0)
}
// ServeCached returns a middleware handler that similar as Serve
// but with the Cache-Control Header set as passed in the cacheAge parameter
func ServeCached(urlPrefix string, fs ServeFileSystem, cacheAge uint) gin.HandlerFunc {
fileserver := http.FileServer(fs)
if urlPrefix != "" {
fileserver = http.StripPrefix(urlPrefix, fileserver)
}
return func(c *gin.Context) {
if fs.Exists(urlPrefix, c.Request.URL.Path) {
if cacheAge != 0 {
c.Writer.Header().Add("Cache-Control", fmt.Sprintf("max-age=%d", cacheAge))
}
fileserver.ServeHTTP(c.Writer, c.Request)
c.Abort()
}
}
}
================================================
FILE: pkg/types/helm.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package types
type Release struct {
Name string `json:"name" binding:"required"`
Chart string `json:"chart" binding:"required"`
Version string `json:"version" binding:"required"`
Values map[string]interface{} `json:"values"`
Preview bool `json:"preview"`
}
type RepoId struct {
Id int64 `uri:"id" binding:"required"`
}
type RepoName struct {
Cluster string `uri:"cluster" binding:"required"`
Name string `uri:"name" binding:"required"`
}
type RepoURL struct {
Url string `form:"url" binding:"required"`
}
type ChartValues struct {
Chart string `form:"chart" binding:"required"`
Version string `form:"version" binding:"required"`
}
type ReleaseHistory struct {
Version int `form:"version"`
}
type CreateRepository struct {
Name string `json:"name" binding:"required"`
URL string `json:"url" binding:"required"`
Username string `json:"username"`
Password string `json:"password"`
}
type UpdateRepository struct {
Name string `json:"name" binding:"required"`
URL string `json:"url" binding:"required"`
Username string `json:"username"`
Password string `json:"password"`
ResourceVersion *int64 `json:"resource_version" binding:"required"`
}
================================================
FILE: pkg/types/meta.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package types
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"sync"
"time"
"github.com/gorilla/websocket"
"golang.org/x/crypto/ssh"
appv1 "k8s.io/api/apps/v1"
"k8s.io/api/core/v1"
"k8s.io/client-go/tools/remotecommand"
"k8s.io/klog/v2"
"github.com/caoyingjunz/pixiu/pkg/db/model"
)
const (
timeLayout = "2006-01-02 15:04:05.999999999"
MsgData = '1'
MsgResize = '2'
)
func (c *Cluster) SetId(i int64) {
c.Id = i
}
func (o *KubeObject) SetReplicaSets(replicaSets []appv1.ReplicaSet) {
o.lock.Lock()
defer o.lock.Unlock()
o.ReplicaSets = replicaSets
}
func (o *KubeObject) GetReplicaSets() []appv1.ReplicaSet {
o.lock.Lock()
defer o.lock.Unlock()
return o.ReplicaSets
}
func (o *KubeObject) SetPods(pods []v1.Pod) {
o.lock.Lock()
defer o.lock.Unlock()
o.Pods = pods
}
func (o *KubeObject) GetPods() []v1.Pod {
o.lock.Lock()
defer o.lock.Unlock()
return o.Pods
}
func FormatTime(GmtCreate time.Time, GmtModified time.Time) TimeSpec {
return TimeSpec{
GmtCreate: GmtCreate.Format(timeLayout),
GmtModified: GmtModified.Format(timeLayout),
}
}
// NewTerminalSession 该方法用于升级 http 协议至 websocket,并new一个 TerminalSession 类型的对象返回
func NewTerminalSession(w http.ResponseWriter, r *http.Request) (*TerminalSession, error) {
// 初始化 Upgrader 类型的对象,用于http协议升级为 websocket 协议
upgrader := &websocket.Upgrader{
HandshakeTimeout: time.Second * 2,
// 检测请求来源
CheckOrigin: func(r *http.Request) bool {
return true
},
Subprotocols: []string{r.Header.Get("Sec-WebSocket-Protocol")},
}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return nil, err
}
session := &TerminalSession{
wsConn: conn,
sizeChan: make(chan remotecommand.TerminalSize),
doneChan: make(chan struct{}),
}
return session, nil
}
// 用于读取web端的输入,接收web端输入的指令内容
func (t *TerminalSession) Read(p []byte) (int, error) {
_, message, err := t.wsConn.ReadMessage()
if err != nil {
return copy(p, "\u0004"), err
}
// 反序列化
var msg TerminalMessage
if err = json.Unmarshal(message, &msg); err != nil {
return copy(p, "\u0004"), err
}
// 逻辑判断
switch msg.Operation {
// 如果是标准输入
case "stdin":
return copy(p, msg.Data), nil
// 窗口调整大小
case "resize":
t.sizeChan <- remotecommand.TerminalSize{Width: msg.Cols, Height: msg.Rows}
return 0, nil
// ping 无内容交互
case "ping":
return 0, nil
default:
return copy(p, "\u0004"), fmt.Errorf("unknown message type")
}
}
// 写数据的方法,拿到 api-server 的返回内容,向web端输出
func (t *TerminalSession) Write(p []byte) (int, error) {
msg, err := json.Marshal(TerminalMessage{
Operation: "stdout",
Data: string(p),
})
if err != nil {
return 0, err
}
if err = t.wsConn.WriteMessage(websocket.TextMessage, msg); err != nil {
return 0, err
}
return len(p), nil
}
// Done 标记关闭doneChan,关闭后触发退出终端
func (t *TerminalSession) Done() {
close(t.doneChan)
}
// Close 用于关闭websocket连接
func (t *TerminalSession) Close() error {
return t.wsConn.Close()
}
// Next 获取web端是否resize,以及是否退出终端
func (t *TerminalSession) Next() *remotecommand.TerminalSize {
select {
case size := <-t.sizeChan:
return &size
case <-t.doneChan:
return nil
}
}
func NewTurn(wsConn *websocket.Conn, sshClient *ssh.Client) (*Turn, error) {
session, err := sshClient.NewSession()
if err != nil {
return nil, err
}
stdinPipe, err := session.StdinPipe()
if err != nil {
return nil, err
}
turn := &Turn{StdinPipe: stdinPipe, Session: session, WsConn: wsConn}
session.Stdout = turn
session.Stderr = turn
modes := ssh.TerminalModes{
ssh.ECHO: 1, // disable echo
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
}
if err = session.RequestPty("xterm", 150, 30, modes); err != nil {
return nil, err
}
if err = session.Shell(); err != nil {
return nil, err
}
return turn, nil
}
func (t *Turn) Write(p []byte) (n int, err error) {
writer, err := t.WsConn.NextWriter(websocket.BinaryMessage)
if err != nil {
return 0, err
}
defer writer.Close()
return writer.Write(p)
}
func (t *Turn) Close() error {
if t.Session != nil {
t.Session.Close()
}
return t.WsConn.Close()
}
func (t *Turn) Read(p []byte) (n int, err error) {
for {
msgType, reader, err := t.WsConn.NextReader()
if err != nil {
return 0, err
}
if msgType != websocket.BinaryMessage {
continue
}
return reader.Read(p)
}
}
func (t *Turn) StartLoopRead(ctx context.Context, wg *sync.WaitGroup, logBuff *bytes.Buffer) {
defer wg.Done()
err := t.loopRead(logBuff, ctx)
if err != nil {
klog.Errorf("LoopRead exit, err:%s", err)
}
}
func (t *Turn) loopRead(logBuff *bytes.Buffer, context context.Context) error {
for {
select {
case <-context.Done():
return fmt.Errorf("LoopRead exit")
default:
_, wsData, err := t.WsConn.ReadMessage()
if err != nil {
return fmt.Errorf("reading webSocket message err:%s", err)
}
body := decode(wsData[1:])
switch wsData[0] {
case MsgResize:
if err := t.resizeDo(body); err != nil {
return err
}
case MsgData:
if err := t.dataDo(body, logBuff); err != nil {
return err
}
}
}
}
}
func (t *Turn) dataDo(body []byte, logBuff *bytes.Buffer) error {
if _, err := t.StdinPipe.Write(body); err != nil {
return fmt.Errorf("StdinPipe write err:%s", err)
}
if _, err := logBuff.Write(body); err != nil {
return fmt.Errorf("logBuff write err:%s", err)
}
return nil
}
type Resize struct {
Columns int
Rows int
}
func (t *Turn) resizeDo(body []byte) error {
var args Resize
err := json.Unmarshal(body, &args)
if err != nil {
return fmt.Errorf("ssh pty resize windows err:%s", err)
}
if args.Columns > 0 && args.Rows > 0 {
if err := t.Session.WindowChange(args.Rows, args.Columns); err != nil {
return fmt.Errorf("ssh pty resize windows err:%s", err)
}
}
return nil
}
func (t *Turn) sessionWait() error {
if err := t.Session.Wait(); err != nil {
return err
}
return nil
}
func (t *Turn) StartSessionWait(wg *sync.WaitGroup) {
defer wg.Done()
err := t.sessionWait()
if err != nil {
klog.Errorf("SessionWait exit, err:%s", err)
}
}
func decode(p []byte) []byte {
decodeString, _ := base64.StdEncoding.DecodeString(string(p))
return decodeString
}
func (a *PlanNodeAuth) Marshal() (string, error) {
data, err := json.Marshal(a)
if err != nil {
return "", err
}
return string(data), nil
}
func (a *PlanNodeAuth) Unmarshal(s string) error {
if err := json.Unmarshal([]byte(s), a); err != nil {
return err
}
return nil
}
func (ks *KubernetesSpec) Marshal() (string, error) {
data, err := json.Marshal(ks)
if err != nil {
return "", err
}
return string(data), nil
}
func (ks *KubernetesSpec) Unmarshal(s string) error {
if err := json.Unmarshal([]byte(s), ks); err != nil {
return err
}
return nil
}
func (ns *NetworkSpec) Marshal() (string, error) {
data, err := json.Marshal(ns)
if err != nil {
return "", err
}
return string(data), nil
}
func (ns *NetworkSpec) Unmarshal(s string) error {
if err := json.Unmarshal([]byte(s), ns); err != nil {
return err
}
return nil
}
func (rs *RuntimeSpec) Marshal() (string, error) {
data, err := json.Marshal(rs)
if err != nil {
return "", err
}
return string(data), nil
}
func (rs *RuntimeSpec) Unmarshal(s string) error {
if err := json.Unmarshal([]byte(s), rs); err != nil {
return err
}
return nil
}
func (cs ComponentSpec) Marshal() (string, error) {
data, err := json.Marshal(cs)
if err != nil {
return "", err
}
return string(data), nil
}
func (cs *ComponentSpec) Unmarshal(s string) error {
if err := json.Unmarshal([]byte(s), cs); err != nil {
return err
}
return nil
}
func (rs *RuntimeSpec) IsDocker() bool {
return rs.Runtime == string(model.DockerCRI)
}
func (rs *RuntimeSpec) IsContainerd() bool {
return rs.Runtime == string(model.ContainerdCRI)
}
func (p PageRequest) IsPaged() bool {
return p.Page != 0 && p.Limit != 0
}
func (p PageRequest) Offset(total int) (int, int, error) {
offset := (p.Page - 1) * p.Limit
if offset > total {
return 0, 0, fmt.Errorf("invaild offset")
}
end := offset + p.Limit
if end > total {
end = total
}
return offset, end, nil
}
func (node *KubeNode) Marshal() (string, error) {
data, err := json.Marshal(node)
if err != nil {
return "", err
}
return string(data), nil
}
func (node *KubeNode) Unmarshal(s string) error {
if err := json.Unmarshal([]byte(s), node); err != nil {
return err
}
return nil
}
================================================
FILE: pkg/types/request.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package types
import "github.com/caoyingjunz/pixiu/pkg/db/model"
const AllNamespace = "all_namespaces"
type (
// LoginRequest is the request body struct for user login.
LoginRequest struct {
Name string `json:"name" binding:"required"` // required
Password string `json:"password" binding:"required"` // required
}
CreateUserRequest struct {
Name string `json:"name" binding:"required"` // required
Password string `json:"password" binding:"required,password"` // required
Role model.UserRole `json:"role" binding:"omitempty,oneof=0 1 2"` // optional
Status model.UserStatus `json:"status" binding:"omitempty"`
Email string `json:"email" binding:"omitempty,email"` // optional
Phone string `json:"phone" binding:"omitempty"` // optional
Description string `json:"description" binding:"omitempty"` // optional
}
// UpdateUserRequest
// !Note: if you want to update description only, email also must be provided with current value
UpdateUserRequest struct {
Role model.UserRole `json:"role" binding:"omitempty,oneof=0 1 2"` // required
Status model.UserStatus `json:"status" binding:"omitempty,oneof=0 1 2"` // required
Email string `json:"email" binding:"omitempty,email"` // optional
Phone string `json:"phone" binding:"omitempty"` // optional
Description string `json:"description" binding:"omitempty"` // optional
ResourceVersion *int64 `json:"resource_version" binding:"required"` // required
}
UpdateUserPasswordRequest struct {
New string `json:"new" binding:"required,password"` // required
Old string `json:"old" binding:"required"` // required
ResourceVersion *int64 `json:"resource_version" binding:"required"` // required
Reset bool `json:"reset"`
}
CreateClusterRequest struct {
Name string `json:"name" binding:"omitempty"` // optional
AliasName string `json:"alias_name" binding:"omitempty"` // optional
Type model.ClusterType `json:"cluster_type" binding:"omitempty,oneof=0 1"` // optional
KubeConfig string `json:"kube_config" binding:"required"` // required
Description string `json:"description" binding:"omitempty"` // optional
Protected bool `json:"protected" binding:"omitempty"` // optional
}
UpdateClusterRequest struct {
AliasName *string `json:"alias_name" binding:"omitempty"` // optional
Description *string `json:"description" binding:"omitempty"` // optional
// TODO: put resource version in a common struct for updating request only
ResourceVersion *int64 `json:"resource_version" binding:"required"` // required
}
ProtectClusterRequest struct {
ResourceVersion *int64 `json:"resource_version" binding:"required"` // required
Protected bool `json:"protected" binding:"omitempty"` // optional
}
CreateTenantRequest struct {
Name string `json:"name" binding:"required"` // required
Description *string `json:"description" binding:"omitempty"` // optional
}
UpdateTenantRequest struct {
Name *string `json:"name" binding:"omitempty"` // optional
Description *string `json:"description" binding:"omitempty"` // optional
ResourceVersion *int64 `json:"resource_version" binding:"required"` // required
}
CreatePlanRequest struct {
Name string `json:"name" binding:"required"` // required
Description string `json:"description" binding:"omitempty"` // optional
Config CreatePlanConfigRequest `json:"config"`
Nodes []CreatePlanNodeRequest `json:"nodes"`
}
UpdatePlanRequest struct {
Name string `json:"name" binding:"required"` // required
ResourceVersion *int64 `json:"resource_version" binding:"required"` // required
Description string `json:"description" binding:"omitempty"` // optional
Config CreatePlanConfigRequest `json:"config"`
Nodes []CreatePlanNodeRequest `json:"nodes"`
}
CreatePlanNodeRequest struct {
Name string `json:"name" binding:"omitempty"` // required
PlanId int64 `json:"plan_id"`
Role []string `json:"role"` // k8s 节点的角色,master 和 node
CRI model.CRI `json:"cri"`
Ip string `json:"ip"`
Auth PlanNodeAuth `json:"auth"`
}
UpdatePlanNodeRequest struct {
ResourceVersion int64 `json:"resource_version" binding:"required"` // required
Name string `json:"name" binding:"omitempty"` // required
PlanId int64 `json:"plan_id"`
Role []string `json:"role"` // k8s 节点的角色,master 为 1 和 node 为 0
CRI model.CRI `json:"cri"`
Ip string `json:"ip"`
Auth PlanNodeAuth `json:"auth"`
}
CreatePlanConfigRequest struct {
PlanId int64 `json:"plan_id"`
Region string `json:"region"`
OSImage string `json:"os_image" binding:"required"` // 操作系统
Description string `json:"description" binding:"omitempty"` // optional
Kubernetes KubernetesSpec `json:"kubernetes"`
Network NetworkSpec `json:"network"`
Runtime RuntimeSpec `json:"runtime"`
Component ComponentSpec `json:"component"` // 支持的扩展组件配置
}
UpdatePlanConfigRequest struct {
// TODO:
}
RBACPolicyRequest struct {
// user ID or group name is required
UserId *int64 `json:"user_id" binding:"required_without=GroupName,excluded_with=GroupName"`
GroupName *string `json:"group_name" binding:"required_without=UserId,excluded_with=UserId"`
ObjectType model.ObjectType `json:"object_type" binding:"required,rbac_object"`
SID string `json:"sid" binding:"omitempty,rbac_sid"`
Operation model.Operation `json:"operation" binding:"required,rbac_operation"`
}
ListRBACPolicyRequest struct {
UserId int64 `form:"user_id" binding:"required"`
ObjectType *model.ObjectType `form:"object_type" binding:"omitempty,required_with=UserId,rbac_object"`
SID *string `form:"sid" binding:"omitempty,required_with=ObjectType,rbac_sid"`
Operation *model.Operation `form:"operation" binding:"omitempty,required_with=SID,rbac_operation"`
}
GroupBindingRequest struct {
UserId int64 `json:"user_id" binding:"required"`
GroupName string `json:"group_name" binding:"required"`
}
ListGroupBindingRequest struct {
UserId *int64 `form:"user_id" binding:"omitempty"`
GroupName *string `form:"group_name" binding:"omitempty"`
}
// PageRequest 分页配置
PageRequest struct {
Page int `form:"page" json:"page"` // 页数,表示第几页
Limit int `form:"limit" json:"limit"` // 每页数量
}
// QueryOption 搜索配置
QueryOption struct {
LabelSelector string `form:"labelSelector" json:"labelSelector"` // 标签搜索
NameSelector string `form:"nameSelector" json:"nameSelector"` // 名称搜索
}
// ListClusterRequest 集群列表查询参数
ListClusterRequest struct {
PageRequest `form:",inline"`
QueryOption `form:",inline"`
Status *int `form:"status" json:"status"` // 集群状态过滤,不传则不过滤
}
// ListPlanRequest 部署计划列表查询参数
ListPlanRequest struct {
PageRequest `form:",inline"`
NameSelector string `form:"nameSelector" json:"nameSelector"` // 名称模糊搜索
Step string `form:"step" json:"step"` // 状态过滤,不传则不过滤
}
// ListUserRequest 用户列表查询参数
ListUserRequest struct {
PageRequest `form:",inline"`
UserName string `form:"userName" json:"userName"`
UserPhone string `form:"userPhone" json:"userPhone"`
UserEmail string `form:"userEmail" json:"userEmail"`
Status *int `form:"status" json:"status"`
}
// WebSSHRequest 主机 ssh 跳转请求
WebSSHRequest struct {
Host string `form:"host" json:"host" binding:"required"`
Port int `form:"port" json:"port"`
User string `form:"user" json:"user" binding:"required"`
Password string `form:"password" json:"password"`
}
)
type (
LoginResponse struct {
UserId int64 `json:"user_id"`
UserName string `json:"user_name"`
Token string `json:"token"`
Role model.UserRole `json:"role"`
*model.User `json:"-"`
}
// PageResponse 分页查询返回值
PageResponse struct {
PageRequest `json:",inline"` // 分页请求属性
Total int `json:"total"` // 分页总数
Items interface{} `json:"items"` // 指定页的元素列表
}
)
================================================
FILE: pkg/types/types.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package types
import (
"io"
"sync"
"time"
"github.com/gorilla/websocket"
"golang.org/x/crypto/ssh"
appv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/client-go/tools/remotecommand"
"github.com/caoyingjunz/pixiu/pkg/db/model"
)
type PixiuObjectMeta struct {
Cluster string `uri:"cluster" binding:"required"`
Namespace string `uri:"namespace" binding:"required"`
Name string `uri:"name"`
}
type PixiuMeta struct {
// pixiu 对象 ID
Id int64 `json:"id"`
// Pixiu 对象版本号
ResourceVersion int64 `json:"resource_version"`
}
type TimeMeta struct {
// pixiu 对象创建时间
GmtCreate time.Time `json:"gmt_create"`
// pixiu 对象修改时间
GmtModified time.Time `json:"gmt_modified"`
}
type KubeNode struct {
Ready []string `json:"ready"`
NotReady []string `json:"not_ready"`
}
type Cluster struct {
PixiuMeta `json:",inline"`
Name string `json:"name"`
AliasName string `json:"alias_name"`
Status model.ClusterStatus `json:"status"` // 0: 运行中 1: 部署中 2: 等待部署 3: 部署失败 4: 集群失联,API不可用
// 0: 标准集群 1: 自建集群
ClusterType model.ClusterType `json:"cluster_type"`
PlanId int64 `json:"plan_id"` // 自建集群关联的 PlanId,如果是自建的集群,planId 不为 0
// kubernetes 集群的版本和状态
KubernetesVersion string `json:"kubernetes_version"`
Nodes KubeNode `json:"nodes"`
// 集群删除保护,开启集群删除保护时不允许删除集群
// 0: 关闭集群删除保护 1: 开启集群删除保护
Protected bool `json:"protected"`
// k8s kubeConfig base64 字段
KubeConfig string `json:"kube_config,omitempty"`
// 集群用途描述,可以为空
Description string `json:"description"`
KubernetesMeta `json:",inline"`
TimeMeta `json:",inline"`
}
// KubernetesMeta 记录 kubernetes 集群的数据
type KubernetesMeta struct {
// 集群的版本
KubernetesVersion string `json:"kubernetes_version,omitempty"`
// 节点数量
Nodes int `json:"nodes"`
// The memory and cpu usage
Resources Resources `json:"resources"`
}
// Resources kubernetes 的资源信息
// The memory and cpu usage
type Resources struct {
Cpu string `json:"cpu"`
Memory string `json:"memory"`
}
type User struct {
PixiuMeta `json:",inline"`
Name string `json:"name"` // 用户名称
Password string `json:"password" binding:"required,password"` // 用户密码
Status model.UserStatus `json:"status"` // 用户状态标识
Role model.UserRole `json:"role"` // 用户角色,目前只实现管理员,0: 普通用户 1: 管理员 2: 超级管理员
Email string `json:"email"` // 用户注册邮件
Phone string `json:"phone"` // 用户手机号
Description string `json:"description"` // 用户描述信息
TimeMeta `json:",inline"`
}
type Tenant struct {
PixiuMeta `json:",inline"`
TimeMeta `json:",inline"`
Name string `json:"name"` // 用户名称
Description string `json:"description"` // 用户描述信息
}
type Plan struct {
PixiuMeta `json:",inline"`
TimeMeta `json:",inline"`
Name string `json:"name"` // 用户名称
Step model.TaskStatus `json:"step"`
Description string `json:"description"` // 用户描述信息
KubernetesVersion string `json:"kubernetes_version"` // k8s 版本
NodeCount int `json:"node_count"` // 节点总数
Config PlanConfig `json:"config"`
Nodes []PlanNode `json:"nodes"`
}
type PlanNode struct {
PixiuMeta `json:",inline"`
TimeMeta `json:",inline"`
Name string `json:"name"` // required
PlanId int64 `json:"plan_id,omitempty"`
Role []string `json:"role"` // k8s 节点的角色,master 和 node
CRI model.CRI `json:"cri"`
Ip string `json:"ip"`
Auth PlanNodeAuth `json:"auth,omitempty"`
}
type Audit struct {
PixiuMeta `json:",inline"`
TimeMeta `json:",inline"`
Ip string `json:"ip"`
Action string `json:"action"` // 操作动作
Status model.AuditOperationStatus `json:"status"` // 操作状态
Operator string `json:"operator"` // 操作人
Path string `json:"path"` // 操作路径
ObjectType model.ObjectType `json:"resource_type"` // 资源类型
Duration int64 `json:"duration"` // 请求耗时 ms
ResponseCode int `json:"response_code"` // HTTP 响应码
Cluster string `json:"cluster"` // K8s 集群名
ResourceName string `json:"resource_name"` // 资源名称
ResourceNamespace string `json:"resource_namespace"` // 资源命名空间
}
type AuthType string
const (
NoneAuth AuthType = "none" // 已开启密码
KeyAuth AuthType = "key" // 密钥
PasswordAuth AuthType = "password" // 密码
)
type PlanNodeAuth struct {
Type AuthType `json:"type"` // 节点认证模式,支持 key 和 password
Key *KeySpec `json:"key,omitempty"`
Password *PasswordSpec `json:"password,omitempty"`
}
type PlanTask struct {
PixiuMeta `json:",inline"`
TimeMeta `json:",inline"`
Name string `json:"name"`
PlanId int64 `json:"plan_id" binding:"required"`
Status model.TaskStatus `json:"status"`
Message string `json:"message"`
}
type KeySpec struct {
Data string `json:"data,omitempty"`
File string `json:"-"`
}
type PasswordSpec struct {
User string `json:"user,omitempty"`
Password string `json:"password,omitempty"`
}
type PlanConfig struct {
PixiuMeta `json:",inline"`
TimeMeta `json:",inline"`
PlanId int64 `json:"plan_id,omitempty"` // required
Region string `json:"region"`
OSImage string `json:"os_image"` // 操作系统
Kubernetes KubernetesSpec `json:"kubernetes"`
Network NetworkSpec `json:"network"`
Runtime RuntimeSpec `json:"runtime"`
Component ComponentSpec `json:"component"` // 支持的扩展组件配置
}
// TimeSpec 通用时间规格
type TimeSpec struct {
GmtCreate interface{} `json:"gmt_create,omitempty"`
GmtModified interface{} `json:"gmt_modified,omitempty"`
}
type KubeObject struct {
lock sync.RWMutex
ReplicaSets []appv1.ReplicaSet
Pods []v1.Pod
}
// WebShellOptions ws API 参数定义
type WebShellOptions struct {
Cluster string `form:"cluster"`
Namespace string `form:"namespace"`
Pod string `form:"pod"`
Container string `form:"container"`
Command string `form:"command"`
}
// TerminalMessage 定义了终端和容器 shell 交互内容的格式 Operation 是操作类型
// Data 是具体数据内容 Rows和Cols 可以理解为终端的行数和列数,也就是宽、高
type TerminalMessage struct {
Operation string `json:"operation"`
Data string `json:"data"`
Rows uint16 `json:"rows"`
Cols uint16 `json:"cols"`
}
// TerminalSession 定义 TerminalSession 结构体,实现 PtyHandler 接口
// wsConn 是 websocket 连接
// sizeChan 用来定义终端输入和输出的宽和高
// doneChan 用于标记退出终端
type TerminalSession struct {
wsConn *websocket.Conn
sizeChan chan remotecommand.TerminalSize
doneChan chan struct{}
}
type Turn struct {
StdinPipe io.WriteCloser
Session *ssh.Session
WsConn *websocket.Conn
}
// ListOptions is the query options to a standard REST list call.
type ListOptions struct {
Count bool `form:"count"`
Limit int64 `form:"limit"`
PageRequest `json:",inline"` // 分页请求属性
QueryOption `json:",inline"` // 搜索内容
}
type EventOptions struct {
Uid string `form:"uid"`
Namespace string `form:"namespace"`
Name string `form:"name"`
Kind string `form:"kind"`
Namespaced bool `form:"namespaced"`
Limit int64 `form:"limit"`
}
type PodLogOptions struct {
Container string `form:"container"`
TailLines int64 `form:"tailLines"`
}
type KubernetesSpec struct {
EnablePublicIp bool `json:"enable_public_ip"`
ApiServer string `json:"api_server"`
ApiPort string `json:"api_port"`
KubernetesVersion string `json:"kubernetes_version"`
EnableHA bool `json:"enable_ha"`
Register bool `json:"register"`
}
type NetworkSpec struct {
NetworkInterface string `json:"network_interface"` // 网口,默认 eth0
Cni string `json:"cni"`
PodNetwork string `json:"pod_network"`
ServiceNetwork string `json:"service_network"`
KubeProxy string `json:"kube_proxy"`
}
type RuntimeSpec struct {
Runtime string `json:"runtime"`
}
type ComponentSpec struct {
Helm *Helm `json:"helm,omitempty"` // 忽略,则使用默认值
Prometheus *Prometheus `json:"prometheus,omitempty"`
Grafana *Grafana `json:"grafana,omitempty"`
Haproxy *Haproxy `json:"haproxy,omitempty"`
}
type Helm struct {
Enable bool `json:"enable"`
HelmRelease string `json:"helm_release"`
}
type Prometheus struct {
EnablePrometheus string `json:"enable_prometheus"`
Enable bool `json:"enable"`
}
type Grafana struct {
Enable bool `json:"enable"`
GrafanaAdminUser string `json:"grafana_admin_user"`
GrafanaAdminPassword string `json:"grafana_admin_password"`
}
// Haproxy Options
// This configuration is usually enabled when self-created VMs require high availability.
type Haproxy struct {
Enable bool `json:"enable"` // Enable haproxy and keepalived,
KeepalivedVirtualRouterId string `json:"keepalived_virtual_router_id"` // Arbitrary unique number from 0..255
}
type RBACPolicy struct {
UserName string `json:"username,omitempty"`
GroupName string `json:"groupname,omitempty"`
ObjectType model.ObjectType `json:"resource_type,omitempty"`
StringID string `json:"sid,omitempty"`
Operation model.Operation `json:"operation,omitempty"`
}
// AuditListOptions 审计列表查询选项,支持过滤
type AuditListOptions struct {
ListOptions `json:",inline"`
Operator string `form:"operator"` // 模糊匹配操作人
Action string `form:"action"` // 精确匹配 HTTP 方法(POST/PUT/DELETE/PATCH)
ObjectType string `form:"object_type"` // 资源类型
Cluster string `form:"cluster"` // 集群名称
Status *uint8 `form:"status"` // 操作状态(0:失败 1:成功 2:未知)
StartTime string `form:"start_time"` // 时间范围起(RFC3339,留空忽略)
EndTime string `form:"end_time"` // 时间范围止(RFC3339,留空忽略)
}
================================================
FILE: pkg/util/container/container.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package container
import (
"context"
"fmt"
"io"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
"k8s.io/klog/v2"
"github.com/caoyingjunz/pixiu/pkg/util/errors"
)
type Container struct {
client *client.Client
action string
name string
planId int64
dir string
}
func NewContainer(action string, planId int64, dir string) (*Container, error) {
client, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
return nil, err
}
return &Container{
client: client,
action: action,
name: fmt.Sprintf("%s-%d", action, planId),
planId: planId,
dir: dir}, nil
}
// StartAndWaitForContainer 创建,启动容器,并等待容器退出
func (c *Container) StartAndWaitForContainer(ctx context.Context, image string) error {
// 已经存在,则先删除运行的容器
if err := c.ClearContainer(ctx); err != nil {
return err
}
config := &container.Config{
Labels: map[string]string{
"author": "caoyingjunz",
"pixiuName": c.name,
},
Image: image,
Env: []string{fmt.Sprintf("COMMAND=%s", c.action)},
}
hostConfig := &container.HostConfig{
Binds: []string{fmt.Sprintf("%s/%d:/configs", c.dir, c.planId)},
}
netConfig := &network.NetworkingConfig{}
resp, err := c.client.ContainerCreate(ctx, config, hostConfig, netConfig, nil, c.name)
if err != nil {
return err
}
// 启动容器
if err = c.client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
return err
}
// 等待容器运行完成退出
return c.WaitContainer(ctx, resp.ID, 180)
}
func (c *Container) Close() error {
return c.client.Close()
}
// ClearContainer 清理已存在的老容器
func (c *Container) ClearContainer(ctx context.Context) error {
old, err := c.GetContainer(ctx, c.name)
if err != nil {
// 如果不存在则直接返回
if err == errors.ErrContainerNotFound {
return nil
}
return err
}
containerId := old.ID
timeout := 5 * time.Second
if err = c.client.ContainerStop(ctx, containerId, &timeout); err != nil {
return err
}
return c.client.ContainerRemove(ctx, containerId, types.ContainerRemoveOptions{Force: true})
}
func (c *Container) ListContainers(ctx context.Context) ([]types.Container, error) {
cs, err := c.client.ContainerList(ctx, types.ContainerListOptions{All: true})
if err != nil {
return nil, err
}
return cs, nil
}
func (c *Container) GetContainer(ctx context.Context, containerName string) (*types.Container, error) {
containers, err := c.ListContainers(ctx)
if err != nil {
return nil, err
}
for _, container := range containers {
for _, name := range container.Names {
if name == "/"+containerName {
return &container, nil
}
}
}
return nil, errors.ErrContainerNotFound
}
// WaitContainer
// 等待容器运行退出
// 官方的客户端实现有问题,先通过探针的方式规避,后续优化
// 循环检查容器状态,直到出现异常或符合预期
func (c *Container) WaitContainer(ctx context.Context, containerId string, times int) error {
//_, errCh := c.client.ContainerWait(ctx, resp.ID, container.WaitConditionNextExit)
//if err = <-errCh; err != nil {
// fmt.Println("结束", err)
//
// return err
//}
for i := 0; i < times; i++ {
klog.Infof("waiting for container at %d times", i+1)
// 先等待 5s 再执行,开始等待符合业务场景,且后续的逻辑处理不受影响
time.Sleep(5 * time.Second)
// 实际开始检查
containerInfo, err := c.client.ContainerInspect(ctx, containerId)
if err != nil {
return err
}
if containerInfo.State != nil {
// Can be one of "created", "running", "paused", "restarting", "removing", "exited", or "dead"
state := containerInfo.State
// 容器还在运行,等待下一次检查
if state.Status == "running" && state.Running {
continue
}
// 状态异常,直接退出
if state.Status == "paused" || state.Status == "removing" || state.Status == "dead" {
return fmt.Errorf("容器状态异常(%s),退出等待", state.Status)
}
// 容器已经退出
if state.Status == "exited" {
if state.ExitCode == 0 {
// 正常退出
return nil
} else {
// 异常退出返回错误信息
return fmt.Errorf(state.Error)
}
}
// 其他状态,继续等待
}
}
return fmt.Errorf("等待容器(%s)运行完成超时", containerId)
}
func (c *Container) WatchContainerLog(ctx context.Context, containerId, since string) (io.ReadCloser, error) {
return c.client.ContainerLogs(ctx, containerId, types.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
Follow: true,
Timestamps: false,
})
}
================================================
FILE: pkg/util/errors/errors.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package errors
import (
"errors"
"gorm.io/gorm"
"github.com/go-sql-driver/mysql"
)
var (
ErrRecordNotFound = gorm.ErrRecordNotFound
ErrRecordNotUpdate = errors.New("record not updated")
ErrBusySystem = errors.New("系统繁忙,请稍后再试")
ErrReqParams = errors.New("请求参数错误")
ErrCloudNotRegister = errors.New("cloud 集群未注册")
ErrUserNotFound = errors.New("用户不存在")
ErrNotAcceptable = errors.New("有任务正在执行,请稍后再试")
ErrClusterNotFound = errors.New("集群不存在")
ErrUserPassword = errors.New("密码错误")
ErrInternal = errors.New("服务器内部错误")
ErrTenantNotFound = errors.New("租户不存在")
ErrDuplicatedPassword = errors.New("新密码与旧密码相同")
ErrAuditNotFound = errors.New("审计记录不存在")
ErrContainerNotFound = errors.New("容器不存在")
ParamsError = errors.New("参数错误")
OperateFailed = errors.New("操作失败")
NoPermission = errors.New("无权限")
InnerError = errors.New("内部错误")
NoUserIdError = errors.New("请登录")
UserExistError = errors.New("用户已存在")
RoleExistError = errors.New("角色已存在")
RoleNotExistError = errors.New("角色不存在")
PolicyExistError = errors.New("策略已存在")
PolicyNotExistError = errors.New("策略不存在")
TenantExistError = errors.New("租户已存在")
ErrAuditExists = errors.New("审计记录已存在")
ErrRootAlreadyExists = errors.New("超级管理员已存在")
)
func IsRecordNotFound(err error) bool {
return errors.Is(err, gorm.ErrRecordNotFound)
}
func IsNotUpdated(err error) bool {
return errors.Is(err, ErrRecordNotUpdate)
}
func IsUniqueConstraintError(err error) bool {
mysqlErr, ok := err.(*mysql.MySQLError)
if !ok {
return false
}
// 数据库的 1062 错误码为固定的主键冲突号
return mysqlErr.Number == 1062
}
================================================
FILE: pkg/util/log/log.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package log
import (
"context"
"errors"
"fmt"
"sync"
"time"
klog "github.com/sirupsen/logrus"
"github.com/caoyingjunz/pixiu/pkg/db"
)
var once sync.Once
type LogFormat string
const (
LogFormatJson LogFormat = "json"
LogFormatText LogFormat = "text"
)
var ErrInvalidLogFormat = errors.New("invalid log format")
type LogLevel = klog.Level
// Providing 3 log levels now.
const (
ErrorLevel LogLevel = klog.ErrorLevel
InfoLevel LogLevel = klog.InfoLevel
DebugLevel LogLevel = klog.DebugLevel
)
type LogOptions struct {
LogFormat `yaml:"log_format"`
LogSQL bool `yaml:"log_sql"`
LogLevel `yaml:"log_level"`
}
// DefaultLogOptions returns the default configs.
func DefaultLogOptions() *LogOptions {
return &LogOptions{
LogFormat: LogFormatJson,
LogSQL: false,
LogLevel: InfoLevel,
}
}
func (o *LogOptions) Valid() error {
switch o.LogFormat {
case LogFormatJson, LogFormatText:
return nil
default:
return ErrInvalidLogFormat
}
}
// Init sets the log format only once.
func (o *LogOptions) Init() {
once.Do(func() {
klog.SetLevel(o.LogLevel)
switch o.LogFormat {
case LogFormatJson:
klog.SetFormatter(&klog.JSONFormatter{
TimestampFormat: time.RFC3339Nano,
})
default:
klog.SetFormatter(&klog.TextFormatter{
FullTimestamp: true,
TimestampFormat: time.RFC3339Nano,
})
}
})
}
const (
SuccessMsg = "SUCCESS"
ErrorMsg = "ERROR"
FailMsg = "FAIL"
)
type Logger struct {
startTime time.Time
logSQL bool
logEntry *klog.Entry
}
func NewLogger(cfg *LogOptions) *Logger {
return &Logger{
startTime: time.Now(),
logSQL: cfg.LogSQL,
logEntry: klog.NewEntry(klog.StandardLogger()),
}
}
func (l *Logger) WithLogField(key string, value interface{}) {
l.logEntry = l.logEntry.WithField(key, value)
}
func (l *Logger) WithLogFields(fields map[string]interface{}) {
l.logEntry = l.logEntry.WithFields(fields)
}
func (l *Logger) Log(ctx context.Context, level LogLevel, err error) {
fields := make(map[string]interface{})
if l.logSQL {
if sqls := db.GetSQLs(ctx); len(sqls) > 0 {
fields["sqls"] = sqls
}
}
fields["latency"] = fmt.Sprintf("%dµs", time.Since(l.startTime).Microseconds())
if err != nil {
fields["error"] = err
l.logEntry.WithFields(fields).Error(FailMsg)
return
}
switch level {
case DebugLevel:
l.logEntry.WithFields(fields).Debug(SuccessMsg)
case InfoLevel:
l.logEntry.WithFields(fields).Info(SuccessMsg)
}
}
================================================
FILE: pkg/util/lru/lru.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package lru
import (
"container/list"
"sync"
)
type LRUCache struct {
cap int
evictList *list.List
items map[interface{}]*list.Element
mu sync.RWMutex
}
type entry struct {
key interface{}
value interface{}
}
func NewLRUCache(cap int) *LRUCache {
return &LRUCache{
cap: cap,
evictList: list.New(),
items: make(map[interface{}]*list.Element),
}
}
func (c *LRUCache) Contains(key interface{}) bool {
_, exists := c.items[key]
return exists
}
func (c *LRUCache) Add(key, value interface{}) {
c.mu.Lock()
defer c.mu.Unlock()
// TODO: 重复代码优化
if ent, ok := c.items[key]; ok {
// 当前元素存在, 覆盖当前 value, 并移动到 list 头部
ent.Value.(*entry).value = value
c.evictList.MoveToFront(ent)
} else {
// 当前元素不存在, 并移动到 list 头部
c.items[key] = c.evictList.PushFront(&entry{key, value})
}
// 超出 LRUCache 的容量, 删除 list 尾部元素
if c.evictList.Len() > c.cap {
lastElement := c.evictList.Back()
c.evictList.Remove(lastElement)
delete(c.items, lastElement.Value.(*entry).key)
}
}
func (c *LRUCache) Get(key interface{}) (value interface{}) {
c.mu.RLock()
defer c.mu.RUnlock()
if ent, ok := c.items[key]; ok {
value = ent.Value.(*entry).value
c.evictList.MoveToFront(ent)
}
return
}
func (c *LRUCache) Len() int { return c.evictList.Len() }
================================================
FILE: pkg/util/ssh/ssh.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ssh
import (
"fmt"
"time"
"golang.org/x/crypto/ssh"
"github.com/caoyingjunz/pixiu/pkg/types"
)
func NewSSHClient(sshConfig *types.WebSSHRequest) (*ssh.Client, error) {
// TODO:利用 gin 的解析,直接设置默认值
port := sshConfig.Port
if port == 0 {
port = 22
}
// TODO 补充支持 PrivateKey 场景
return ssh.Dial("tcp", fmt.Sprintf("%s:%d", sshConfig.Host, port), &ssh.ClientConfig{
Timeout: time.Second * 5,
User: sshConfig.User,
Auth: []ssh.AuthMethod{ssh.Password(sshConfig.Password)},
HostKeyCallback: ssh.InsecureIgnoreHostKey(), // 忽略 know_hosts 检查
})
}
================================================
FILE: pkg/util/token/token.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package token
import (
"fmt"
"time"
"github.com/golang-jwt/jwt/v4"
)
type Claims struct {
jwt.RegisteredClaims
Id int64 `json:"id"`
Name string `json:"name"`
Role string `json:"role"`
}
// GenerateToken 生成 token
func GenerateToken(uid int64, name string, jwtKey []byte) (string, error) {
// Generate jwt, 临时有效期 360 分钟
nowTime := time.Now()
expiresTime := nowTime.Add(360 * time.Minute)
claims := &Claims{
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expiresTime), // 过期时间
IssuedAt: jwt.NewNumericDate(nowTime), // 签发时间
NotBefore: jwt.NewNumericDate(nowTime), // 生效时间
},
Id: uid,
Name: name,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(jwtKey)
}
func ParseToken(tokenStr string, jwtKey []byte) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(t *jwt.Token) (interface{}, error) {
return jwtKey, nil
})
if err != nil {
if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors == jwt.ValidationErrorExpired {
return nil, fmt.Errorf("登录已过期,请重新登录")
}
}
return nil, err
}
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
return claims, nil
}
return nil, fmt.Errorf("failed to parse token")
}
================================================
FILE: pkg/util/util.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"fmt"
"math/rand"
"net/http"
"os"
"time"
"github.com/gorilla/websocket"
"golang.org/x/crypto/bcrypt"
)
func init() {
rand.Seed(time.Now().UnixNano())
}
// EncryptUserPassword 生成加密密码
// 前端传的密码为明文,需要加密存储
// TODO: 后续确认是否有必要在前端加密
func EncryptUserPassword(origin string) (string, error) {
pwd, err := bcrypt.GenerateFromPassword([]byte(origin), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(pwd), nil
}
// ValidateUserPassword 验证用户的密码是否正确
func ValidateUserPassword(old, new string) error {
return bcrypt.CompareHashAndPassword([]byte(old), []byte(new))
}
// ValidateStrongPassword validates the password is strong enough.
func ValidateStrongPassword(password string) bool {
if len(password) < 8 {
return false
}
var oneUpper bool
var oneLower bool
var oneNumber bool
for _, l := range password {
if oneUpper && oneLower && oneNumber {
return true
}
if !oneUpper && l >= 'A' && l <= 'Z' {
oneUpper = true
continue
}
if !oneLower && l >= 'a' && l <= 'z' {
oneLower = true
continue
}
if !oneNumber && l >= '0' && l <= '9' {
oneNumber = true
continue
}
}
return oneUpper && oneLower && oneNumber
}
// GenerateRequestID return a request ID string with random suffix.
func GenerateRequestID() string {
return fmt.Sprintf("%s-%06d", time.Now().Format("20060102150405"), rand.Intn(1000000))
}
func IsEmptyS(s string) bool {
return len(s) != 0
}
func IsDirectoryExists(path string) bool {
stat, err := os.Stat(path)
if err != nil {
return false
}
if stat.IsDir() {
return true
}
return false
}
func IsFileExists(path string) bool {
stat, err := os.Stat(path)
if err != nil {
return false
}
if stat.IsDir() {
return false
}
return true
}
func EnsureDirectoryExists(path string) error {
if !IsDirectoryExists(path) {
if err := os.MkdirAll(path, 0755); err != nil {
return err
}
}
return nil
}
func WriteToFile(filename string, data []byte) error {
return os.WriteFile(filename, data, 0600)
}
func BuildWebSocketConnection(w http.ResponseWriter, r *http.Request) (*websocket.Conn, error) {
upgrader := websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true
}}
upgrader.Subprotocols = []string{r.Header.Get("Sec-WebSocket-Protocol")}
return upgrader.Upgrade(w, r, nil)
}
// DeduplicateIntSlice returns a new slice with duplicated elements removed.
func DeduplicateIntSlice(s []int64) (ret []int64) {
ret = make([]int64, 0)
m := make(map[int64]struct{})
for _, v := range s {
if _, ok := m[v]; ok {
continue
}
m[v] = struct{}{}
ret = append(ret, v)
}
return
}
// More returns the larger one.
func More(a, b int) int {
if a > b {
return a
}
return b
}
// Less returns the smaller one.
func Less(a, b int) int {
if a < b {
return a
}
return b
}
================================================
FILE: pkg/util/util_test.go
================================================
/*
Copyright 2024 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"reflect"
"testing"
)
func TestValidateStrongPassword(t *testing.T) {
tests := []struct {
name string
password string
want bool
}{
{
name: "case1",
password: "123456",
want: false,
},
{
name: "case2",
password: "12345678",
want: false,
},
{
name: "case3",
password: "12345678a",
want: false,
},
{
name: "case4",
password: "12345678A",
want: false,
},
{
name: "case5",
password: "12345678aA",
want: true,
},
{
name: "case6",
password: "123456Aa",
want: true,
},
{
name: "case7",
password: "abcdefgh",
want: false,
},
{
name: "case8",
password: "ABCDEFGH",
want: false,
},
{
name: "case9",
password: "abcdef12",
want: false,
},
{
name: "case10",
password: "ABCDEF12",
want: false,
},
{
name: "case11",
password: "Abcdef12",
want: true,
},
{
name: "case12",
password: "aBCDEF12",
want: true,
},
{
name: "case13",
password: "$$$$$$$$",
want: false,
},
{
name: "case14",
password: "$$$$$aA1",
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ValidateStrongPassword(tt.password); got != tt.want {
t.Errorf("ValidateStrongPassword() = %v, want %v", got, tt.want)
}
})
}
}
func TestDeduplicateIntSlice(t *testing.T) {
type args struct {
s []int64
}
tests := []struct {
name string
args args
want []int64
}{
{
name: "case 1",
args: args{
s: []int64{1, 2, 3, 4, 5},
},
want: []int64{1, 2, 3, 4, 5},
},
{
name: "case 2",
args: args{
s: []int64{1, 1, 1, 1, 1},
},
want: []int64{1},
},
{
name: "case 3",
args: args{
s: []int64{1, 1, 2, 3, 4, 5},
},
want: []int64{1, 2, 3, 4, 5},
},
{
name: "case 4",
args: args{
s: []int64{1, 1, 2, 1, 3, 4, 5},
},
want: []int64{1, 2, 3, 4, 5},
},
{
name: "case 5",
args: args{
s: []int64{1, 2, 3, 4, 5, 5, 5, 5},
},
want: []int64{1, 2, 3, 4, 5},
},
{
name: "case 6",
args: args{
s: []int64{5, 1, 4, 2, 3, 3, 2, 4, 1, 5},
},
want: []int64{5, 1, 4, 2, 3},
},
{
name: "case 7",
args: args{
s: []int64{1, 1, 1, 2, 3, 3, 4, 4, 5, 5, 5},
},
want: []int64{1, 2, 3, 4, 5},
},
{
name: "case 8",
args: args{
s: []int64{1, 2, 3, 4, 5, 5, 5, 5, 4, 3, 2, 1},
},
want: []int64{1, 2, 3, 4, 5},
},
{
name: "case 9",
args: args{
s: []int64{1, 2, 3, 4, 5, 5, 5, 5, 4, 3, 2, 1, 1, 1},
},
want: []int64{1, 2, 3, 4, 5},
},
{
name: "case 10",
args: args{
s: []int64{1, 2, 3, 4, 5, 5, 5, 5, 4, 3, 2, 1, 1, 1, 2, 3, 4, 5},
},
want: []int64{1, 2, 3, 4, 5},
},
{
name: "case 11",
args: args{
s: []int64{},
},
want: []int64{},
},
{
name: "case 12",
args: args{
s: nil,
},
want: []int64{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := DeduplicateIntSlice(tt.args.s); !reflect.DeepEqual(got, tt.want) {
t.Errorf("DeduplicateIntSlice() = %v, want %v", got, tt.want)
}
})
}
}
================================================
FILE: pkg/util/uuid/uuid.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package uuid
import (
"math/rand"
"github.com/google/uuid"
)
const (
namePrefix = "pixiu-"
)
func NewUUID() string {
return uuid.New().String()
}
func NewRandName(length int) string {
chars := []rune("abcdefghijklmnopqrstuvwxyz")
rs := make([]rune, length)
for i := 0; i < length; i++ {
rs[i] = chars[rand.Intn(len(chars))]
}
return namePrefix + string(rs)
}
================================================
FILE: template/globals.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (phe "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package template
const GlobalsTemplate = `# Render below by Pixiu
---
{{- if .Kubernetes.EnableHA }}
enable_kubernetes_ha: "yes"
{{- end }}
{{- if .Kubernetes.EnablePublicIp }}
kube_vip_address: "{{ .Kubernetes.ApiServer }}"
{{- end }}
{{- if .Kubernetes.ApiPort }}
kube_vip_port: "{{ .Kubernetes.ApiPort }}"
{{- end }}
kube_release: {{ .Kubernetes.KubernetesVersion }}
cluster_cidr: "{{ .Network.PodNetwork }}"
service_cidr: "{{ .Network.ServiceNetwork }}"
network_interface: "{{ .Network.NetworkInterface }}"
{{- if and .Component.Haproxy .Component.Haproxy.Enable }}
enable_haproxy: "yes"
{{- if .Component.Haproxy.KeepalivedVirtualRouterId }}
keepalived_virtual_router_id: "{{ .Component.Haproxy.KeepalivedVirtualRouterId }}"
{{- end }}
{{- end }}
{{- if eq .Network.Cni "calico" }}
enable_calico: "yes"
{{- end }}
enable_nfs: "no"
`
================================================
FILE: template/hosts.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (phe "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package template
const HostTemplate = `# Render below by Pixiu engine
127.0.0.1 localhost
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
{{- range .Nodes }}
{{ .Ip }} {{ .Name }}
{{- end }}
`
================================================
FILE: template/multinode.go
================================================
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (phe "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package template
const MultiModeTemplate = `# Render below by Pixiu engine
[docker-master]
{{- range .DockerMaster }}
{{- if eq .Auth.Type "password" }}
{{ .Name }} ansible_ssh_user={{ .Auth.Password.User }} ansible_ssh_pass={{ .Auth.Password.Password }}
{{- end }}
{{- if eq .Auth.Type "key" }}
{{ .Name }} ansible_ssh_user=root ansible_ssh_private_key_file={{ .Auth.Key.File }}
{{- end }}
{{- end }}
[docker-node]
{{- range .DockerNode }}
{{- if eq .Auth.Type "password" }}
{{ .Name }} ansible_ssh_user={{ .Auth.Password.User }} ansible_ssh_pass={{ .Auth.Password.Password }}
{{- end }}
{{- if eq .Auth.Type "key" }}
{{ .Name }} ansible_ssh_user=root ansible_ssh_private_key_file={{ .Auth.Key.File }}
{{- end }}
{{- end }}
[containerd-master]
{{- range .ContainerdMaster }}
{{- if eq .Auth.Type "password" }}
{{ .Name }} ansible_ssh_user={{ .Auth.Password.User }} ansible_ssh_pass={{ .Auth.Password.Password }}
{{- end }}
{{- if eq .Auth.Type "key" }}
{{ .Name }} ansible_ssh_user=root ansible_ssh_private_key_file={{ .Auth.Key.File }}
{{- end }}
{{- end }}
[containerd-node]
{{- range .ContainerdNode }}
{{- if eq .Auth.Type "password" }}
{{ .Name }} ansible_ssh_user={{ .Auth.Password.User }} ansible_ssh_pass={{ .Auth.Password.Password }}
{{- end }}
{{- if eq .Auth.Type "key" }}
{{ .Name }} ansible_ssh_user=root ansible_ssh_private_key_file={{ .Auth.Key.File }}
{{- end }}
{{- end }}
[storage]
# Don't change the bellow groups
[kube-master:children]
docker-master
containerd-master
[kube-node:children]
docker-node
containerd-node
[baremetal:children]
kube-master
kube-node
storage
[kubernetes:children]
kube-master
kube-node
[nfs-server:children]
storage
[haproxy:children]
kube-master
`