├── .gitignore
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── aigc-dashboard
├── .eslintrc.cjs
├── .gitignore
├── .pnp.cjs
├── .pnp.loader.mjs
├── .prettierrc.json
├── README.md
├── index.html
├── package-lock.json
├── package.json
├── public
│ └── favicon.ico
├── src
│ ├── App.vue
│ ├── assets
│ │ ├── base.css
│ │ ├── logo.svg
│ │ └── main.css
│ ├── components
│ │ ├── engine.vue
│ │ └── engines.vue
│ ├── main.js
│ ├── router
│ │ └── index.js
│ ├── stores
│ │ └── counter.js
│ └── views
│ │ ├── dashboard.vue
│ │ └── welcome.vue
├── vite.config.js
├── vitest.config.js
└── yarn.lock
├── deploy
├── helm
│ ├── aigc-gateway
│ │ ├── .helmignore
│ │ ├── Chart.yaml
│ │ ├── templates
│ │ │ └── aigc-gateway.yaml
│ │ └── values.yaml
│ └── logto
│ │ ├── .helmignore
│ │ ├── Chart.yaml
│ │ ├── templates
│ │ ├── logto.yaml
│ │ └── postgress.yaml
│ │ └── values.yaml
└── yaml
│ ├── aigc-gateway.yaml
│ ├── logto.yaml
│ └── postgres.yaml
├── docs
├── Dashboard介绍.md
├── en
│ └── README.md
├── images
│ ├── Oops-wrong.png
│ ├── SD-dash.png
│ ├── Traditional-Web-console.png
│ ├── Traditional-Web-setting.jpeg
│ ├── admin-login.png
│ ├── arch.png
│ ├── auth.png
│ ├── console.png
│ ├── customdata-delete-1.png
│ ├── customdata-delete-2.png
│ ├── dashboard-installed.png
│ ├── dashboard-login.png
│ ├── dashboard-recover.png
│ ├── dashboard-uninstalled.png
│ ├── logout.png
│ ├── m2m-console.png
│ ├── m2m-role-assign.png
│ ├── m2m-role-setting.png
│ ├── m2m-setting.png
│ ├── quick-setup-realse.png
│ └── user-login.png
├── 安装部署.md
├── 常见问题.md
├── 架构原理.md
└── 模版管理.md
├── go.mod
├── go.sum
├── main.go
└── pkg
├── controller
└── controller.go
├── resources
├── error.go
├── error_test.go
├── resource.go
├── resource_manager.go
└── resource_manager_test.go
├── routers
├── resource.go
└── sign.go
├── session
└── session.go
├── user
└── user.go
└── utils
├── config.go
├── config_test.go
├── string.go
├── string_test.go
└── test.config
/.gitignore:
--------------------------------------------------------------------------------
1 | # If you prefer the allow list template instead of the deny list, see community template:
2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
3 | #
4 | # Binaries for programs and plugins
5 | *.exe
6 | *.exe~
7 | *.dll
8 | *.so
9 | *.dylib
10 | **/.DS_Store
11 |
12 | ### IntelliJ IDEA ###
13 | .idea
14 | *.iws
15 | *.iml
16 | *.ipr
17 | .mvn
18 | mvnw*
19 |
20 | # vscode
21 | .vscode
22 |
23 | # Test binary, built with `go test -c`
24 | *.test
25 |
26 | # Output of the go coverage tool, specifically when used with LiteIDE
27 | *.out
28 |
29 | # Dependency directories (remove the comment below to include it)
30 | # vendor/
31 |
32 | # Go workspace file
33 | go.work
34 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # syntax=docker/dockerfile:1
2 | FROM golang:1.19 as builder
3 |
4 | # Set destination for COPY
5 | WORKDIR /app
6 |
7 | # Download Go modules
8 | COPY go.mod go.sum ./
9 | RUN go mod download
10 |
11 | # Copy the source code. Note the slash at the end, as explained in
12 | # https://docs.docker.com/engine/reference/builder/#copy
13 | COPY . ./
14 |
15 | # Build
16 | #RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o manager main.go
17 |
18 | RUN CGO_ENABLED=0 GOOS=linux go build -a -o aigc-gateway main.go
19 |
20 | # Optional:
21 | # To bind to a TCP port, runtime parameters must be supplied to the docker command.
22 | # But we can document in the Dockerfile what ports
23 | # the application is going to listen on by default.
24 | # https://docs.docker.com/engine/reference/builder/#expose
25 |
26 | FROM alpine:3.17
27 |
28 | RUN apk add --no-cache ca-certificates bash expat curl \
29 | && rm -rf /var/cache/apk/*
30 |
31 | WORKDIR /app
32 | COPY --from=builder /app/aigc-gateway .
33 | #COPY ./aigc-gateway .
34 | COPY ./aigc-dashboard ./aigc-dashboard
35 |
36 | # Run
37 | CMD ["./aigc-gateway"]
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CloudNativeGame/aigc-gateway/355383cb419bc08e945935a5b3a321695f00240c/Makefile
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AIGC-Gateway
2 |
3 | 中文 | [English](./docs/en/README.md)
4 |
5 | ## AIGC-Gateway概述
6 |
7 | 2023年,AIGC的潮流引爆了泛娱乐内容的生产力革命。文生图、文生音频、文生视频等AIGC范式成为了各大公司争相投入和落地生产的主要场景。目前AIGC领域主要分为两个大的场景,一个是面向终端客户的场景,例如:Midjourney或者Hugging Face;另外一个是面向内容生产者的场景,例如:Stable Diffusion。前者更多的是面向AIGC的服务化与商业化,而后者更多的面向企业内部数字资产生成与提效。大部分企业会通过第二种形态在企业内部落地AIGC的能力。
8 |
9 | 相比传统的互联网型业务而言,AIGC引擎的使用与运维具有如下特性:
10 |
11 | - **运行时间碎片且弹性**
12 |
13 | 通常 AIGC引擎的使用时间与内容生产者的使用周期是强相关的。因为目前为止,AIGC引擎生成内容的质量需要人进行评估与交互式的调整。这会导致从单个内容生产者的角度而言,使用的时间会非常碎片,而且使用的时间周期性区间会非常确定。一般情况下,只有在工作日的4-6个小时是AIGC引擎集中使用的时间,而有效的使用时长会更短暂,工作日的晚上与双休日正常情况下也是没有任何使用时长的。
14 |
15 |
16 | - **数字资产隔离与安全**
17 |
18 | 对于很多以内容生产为主体的行业与公司而言,AIGC引擎中的自训练模型是数字核心资产。不同的内容生产者之间是非主动不自动共享模型的。而且不同的内容生产者之间,生成的内容需要做到访问隔离、防止篡改、数据隔离。
19 |
20 |
21 | - **多种资源类型与弹性**
22 |
23 | AIGC引擎对GPU的异构算力是强依赖。但是,GPU的种类丰富,不同类型的GPU在成本和效率上的差异非常大。以Stable Diffusion为例,A100 40G显存与A100 80G显存在生成图片的效率上相差1s左右,但是成本上会相差1倍以上。此外,很多早期就涉足AIGC的公司在本地机房中都会有比较多的消费卡(例如:NV3090)。在企业内部落地AIGC引擎的时候,需要考虑如何将这些资源利用起来,并统一管理和提效。
24 |
25 |
26 | - **引擎种类与版本繁多**
27 |
28 | AIGC引擎的迭代速度非常快,不同版本之间会存在一定的不兼容。而作为数字资产生成的工具,通常为了保障模型的稳定运行,一旦投入生产,大部分时间不会轻易变更版本。而不同的内容生产者之间依赖的版本极有可能也会有所不同。所以,多个版本如何统一的管理和升级,也是企业内部落地AIGC的时候需要考虑的事情。
29 |
30 |
31 | ## AIGC-Gateway特性
32 |
33 | 为了解决企业内部落地AIGC引擎的通用性问题,阿里云与行者AI一起开源了AIGC-Gateway项目,降低企业内部AIGC落地的难度与费用,真正做到开箱即用,即开即用。
34 |
35 | AIGC-Gateway整体架构如下:
36 |
37 | 
38 |
39 | AIGC-Gateway具有如下特性:
40 | - **动态拉起按需释放**
41 |
42 | 对于任意一种AIGC引擎,AIGC-Gateway都可以做到,访问的时候拉起引擎,退出或者离开的时候,释放引擎所需的GPU资源,并保留数据存储。当下一次同一个用户访问的时候,快速拉起新的资源并挂载归属于这个客户的数据存储,实现GPU卡的分时复用,降低整体的成本投入。
43 |
44 |
45 | - **租户独享访问隔离**
46 |
47 | 通过AIGC-Gateway拉起的AIGC引擎资源独享,数据隔离。除此之外,访问地址通过OIDC的方式进行了安全防护,对任意一种AIGC引擎都可以无侵入式地实现访问的权限控制。此外,AIGC-Gateway通过与开源认证体系Logto的集成,实现了企业内部账号体系的自动单点登录与认证,简化了
48 |
49 |
50 | - **统一GPU资源管理**
51 |
52 | AIGC-Gateway是一个与云厂商无关的开源项目,支持企业的IT工程师进行本地部署、混合云部署、多云部署。对于不同类型的GPU资源,例如:本地GPU、云上单卡GPU、云上多卡GPU、云上显存共享GPU都有完善的支持。
53 |
54 |
55 | - **版本/模型统一管理**
56 |
57 | AIGC-Gateway支持同时部署多种、多套、多版本的AIGC引擎。并且,可以对同一个版本的AIGC引擎进行简单便捷的批量管理。并且支持在不同版本的AIGC引擎进行数据的迁移与切换。
58 |
59 |
60 | - **成本核算与费用分摊**
61 |
62 | 在阿里云上,可以通过可视化大盘和API的方式,实现组户级、实例级使用成本的核算,便于内部的成本核算以及成本单价的估算。
63 |
64 |
65 | ## 下一步
66 |
67 | - [AIGC-Gateway Dashboard介绍](./docs/Dashboard介绍.md)
68 | - [安装并配置AIGC-Gateway](./docs/安装部署.md)
69 | - [如何部署多种AIGC引擎模版](./docs/模版管理.md)
70 | - [AIGC-Gateway的架构与原理](./docs/架构原理.md)
71 | - [常见问题与排查](./docs/常见问题.md)
72 |
73 | ## 上游项目
74 |
75 | - [OpenKruiseGame](https://github.com/openkruise/kruise-game)
76 | - [logto](https://github.com/logto-io/logto/)
77 |
--------------------------------------------------------------------------------
/aigc-dashboard/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | require('@rushstack/eslint-patch/modern-module-resolution')
3 |
4 | module.exports = {
5 | root: true,
6 | 'extends': [
7 | 'plugin:vue/vue3-essential',
8 | 'eslint:recommended',
9 | '@vue/eslint-config-prettier/skip-formatting'
10 | ],
11 | parserOptions: {
12 | ecmaVersion: 'latest'
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/aigc-dashboard/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | .DS_Store
12 | dist
13 | dist-ssr
14 | coverage
15 | *.local
16 |
17 | /cypress/videos/
18 | /cypress/screenshots/
19 |
20 | # Editor directories and files
21 | .vscode/*
22 | !.vscode/extensions.json
23 | .idea
24 | *.suo
25 | *.ntvs*
26 | *.njsproj
27 | *.sln
28 | *.sw?
29 |
--------------------------------------------------------------------------------
/aigc-dashboard/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/prettierrc",
3 | "semi": false,
4 | "tabWidth": 2,
5 | "singleQuote": true,
6 | "printWidth": 100,
7 | "trailingComma": "none"
8 | }
--------------------------------------------------------------------------------
/aigc-dashboard/README.md:
--------------------------------------------------------------------------------
1 | # aigc-dashboard
2 |
3 | This template should help get you started developing with Vue 3 in Vite.
4 |
5 | ## Recommended IDE Setup
6 |
7 | [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
8 |
9 | ## Customize configuration
10 |
11 | See [Vite Configuration Reference](https://vitejs.dev/config/).
12 |
13 | ## Project Setup
14 |
15 | ```sh
16 | npm install
17 | ```
18 |
19 | ### Compile and Hot-Reload for Development
20 |
21 | ```sh
22 | npm run dev
23 | ```
24 |
25 | ### Compile and Minify for Production
26 |
27 | ```sh
28 | npm run build
29 | ```
30 |
31 | ### Run Unit Tests with [Vitest](https://vitest.dev/)
32 |
33 | ```sh
34 | npm run test:unit
35 | ```
36 |
37 | ### Lint with [ESLint](https://eslint.org/)
38 |
39 | ```sh
40 | npm run lint
41 | ```
42 |
--------------------------------------------------------------------------------
/aigc-dashboard/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | AIGC-Gateway
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/aigc-dashboard/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aigc-dashboard",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "preview": "vite preview",
9 | "test:unit": "vitest",
10 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore",
11 | "format": "prettier --write src/"
12 | },
13 | "dependencies": {
14 | "@fortawesome/fontawesome-free": "^6.4.0",
15 | "@mdi/font": "^7.2.96",
16 | "axios": "^1.4.0",
17 | "material-design-icons-iconfont": "^6.7.0",
18 | "pinia": "^2.0.35",
19 | "vue": "^3.2.47",
20 | "vue-axios": "^3.5.2",
21 | "vue-router": "^4.1.6",
22 | "vuetify": "^3.2.3"
23 | },
24 | "devDependencies": {
25 | "@rushstack/eslint-patch": "^1.2.0",
26 | "@vitejs/plugin-vue": "^4.2.1",
27 | "@vue/eslint-config-prettier": "^7.1.0",
28 | "@vue/test-utils": "^2.3.2",
29 | "eslint": "^8.39.0",
30 | "eslint-plugin-vue": "^9.11.0",
31 | "jsdom": "^22.0.0",
32 | "prettier": "^2.8.8",
33 | "vite": "^4.3.5",
34 | "vitest": "^0.31.0"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/aigc-dashboard/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CloudNativeGame/aigc-gateway/355383cb419bc08e945935a5b3a321695f00240c/aigc-dashboard/public/favicon.ico
--------------------------------------------------------------------------------
/aigc-dashboard/src/App.vue:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/aigc-dashboard/src/assets/base.css:
--------------------------------------------------------------------------------
1 | /* color palette from */
2 | :root {
3 | --vt-c-white: #ffffff;
4 | --vt-c-white-soft: #f8f8f8;
5 | --vt-c-white-mute: #f2f2f2;
6 |
7 | --vt-c-black: #181818;
8 | --vt-c-black-soft: #222222;
9 | --vt-c-black-mute: #282828;
10 |
11 | --vt-c-indigo: #2c3e50;
12 |
13 | --vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
14 | --vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
15 | --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
16 | --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
17 |
18 | --vt-c-text-light-1: var(--vt-c-indigo);
19 | --vt-c-text-light-2: rgba(60, 60, 60, 0.66);
20 | --vt-c-text-dark-1: var(--vt-c-white);
21 | --vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
22 | }
23 |
24 | /* semantic color variables for this project */
25 | :root {
26 | --color-background: var(--vt-c-white);
27 | --color-background-soft: var(--vt-c-white-soft);
28 | --color-background-mute: var(--vt-c-white-mute);
29 |
30 | --color-border: var(--vt-c-divider-light-2);
31 | --color-border-hover: var(--vt-c-divider-light-1);
32 |
33 | --color-heading: var(--vt-c-text-light-1);
34 | --color-text: var(--vt-c-text-light-1);
35 |
36 | --section-gap: 160px;
37 | }
38 |
39 | @media (prefers-color-scheme: dark) {
40 | :root {
41 | --color-background: var(--vt-c-black);
42 | --color-background-soft: var(--vt-c-black-soft);
43 | --color-background-mute: var(--vt-c-black-mute);
44 |
45 | --color-border: var(--vt-c-divider-dark-2);
46 | --color-border-hover: var(--vt-c-divider-dark-1);
47 |
48 | --color-heading: var(--vt-c-text-dark-1);
49 | --color-text: var(--vt-c-text-dark-2);
50 | }
51 | }
52 |
53 | *,
54 | *::before,
55 | *::after {
56 | box-sizing: border-box;
57 | margin: 0;
58 | position: relative;
59 | font-weight: normal;
60 | }
61 |
62 | body {
63 | min-height: 100vh;
64 | color: var(--color-text);
65 | background: var(--color-background);
66 | transition: color 0.5s, background-color 0.5s;
67 | line-height: 1.6;
68 | font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
69 | Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
70 | font-size: 15px;
71 | text-rendering: optimizeLegibility;
72 | -webkit-font-smoothing: antialiased;
73 | -moz-osx-font-smoothing: grayscale;
74 | }
75 |
--------------------------------------------------------------------------------
/aigc-dashboard/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
12 |
13 |
14 |
18 |
19 |
21 |
23 |
25 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/aigc-dashboard/src/assets/main.css:
--------------------------------------------------------------------------------
1 | @import './base.css';
--------------------------------------------------------------------------------
/aigc-dashboard/src/components/engine.vue:
--------------------------------------------------------------------------------
1 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 | {{ engine.metadata.name }}
174 |
175 |
176 |
177 |
178 |
179 |
180 |
186 |
187 |
188 |
189 |
190 |
192 | Install
193 |
194 |
195 |
196 |
197 | Recover
198 |
199 |
200 |
201 |
202 |
203 | Visit
204 |
205 |
206 |
207 |
208 | Pause
209 |
210 |
211 |
212 |
213 | Uninstall
214 |
215 |
216 |
217 |
218 | Restart
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
230 |
231 |
232 | Installation will create new resource,would you like to do that.
233 |
234 |
235 |
236 | OK
237 | CLOSE
238 |
239 |
240 |
241 |
242 |
--------------------------------------------------------------------------------
/aigc-dashboard/src/components/engines.vue:
--------------------------------------------------------------------------------
1 |
3 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/aigc-dashboard/src/main.js:
--------------------------------------------------------------------------------
1 | import './assets/main.css'
2 | import 'vuetify/styles'
3 | import {createVuetify} from 'vuetify'
4 | import * as components from 'vuetify/components'
5 | import * as directives from 'vuetify/directives'
6 | import 'material-design-icons-iconfont/dist/material-design-icons.css'
7 | import '@mdi/font/css/materialdesignicons.css'
8 | import '@fortawesome/fontawesome-free/css/all.css'
9 | import {createApp} from 'vue'
10 | import {createPinia} from 'pinia'
11 |
12 | import App from './App.vue'
13 | import router from './router'
14 |
15 | import axios from 'axios'
16 | import VueAxios from 'vue-axios'
17 |
18 |
19 | const vuetify = createVuetify({
20 | components,
21 | directives,
22 | theme: {
23 | defaultTheme: 'dark'
24 | }
25 | })
26 |
27 | const app = createApp(App)
28 |
29 | app.use(createPinia())
30 | app.use(router)
31 | app.use(vuetify)
32 | app.use(VueAxios, axios)
33 |
34 | app.mount('#app')
35 |
--------------------------------------------------------------------------------
/aigc-dashboard/src/router/index.js:
--------------------------------------------------------------------------------
1 | import {createRouter, createWebHashHistory} from 'vue-router'
2 | import WelcomeView from '../views/welcome.vue'
3 | import DashboardView from '../views/dashboard.vue'
4 |
5 | const router = createRouter({
6 | history: createWebHashHistory(),
7 | routes: [
8 | {
9 | path: '/',
10 | name: 'welcome',
11 | component: WelcomeView
12 | },
13 | {
14 | path: '/dashboard',
15 | name: 'dashboard',
16 | // route level code-splitting
17 | // this generates a separate chunk (About.[hash].js) for this route
18 | // which is lazy-loaded when the route is visited.
19 | component: DashboardView,
20 | }
21 | ]
22 | })
23 |
24 |
25 | router.beforeEach(async (to, from) => {
26 | if (!login && to.name != "welcome") {
27 | // redirect the user to the login page
28 | return {name: 'welcome'}
29 | }
30 |
31 | if (login && to.name == "welcome") {
32 | return {name: 'dashboard'}
33 | }
34 | })
35 |
36 | export default router
37 |
--------------------------------------------------------------------------------
/aigc-dashboard/src/stores/counter.js:
--------------------------------------------------------------------------------
1 | import { ref, computed } from 'vue'
2 | import { defineStore } from 'pinia'
3 |
4 | export const useCounterStore = defineStore('counter', () => {
5 | const count = ref(0)
6 | const doubleCount = computed(() => count.value * 2)
7 | function increment() {
8 | count.value++
9 | }
10 |
11 | return { count, doubleCount, increment }
12 | })
13 |
--------------------------------------------------------------------------------
/aigc-dashboard/src/views/dashboard.vue:
--------------------------------------------------------------------------------
1 |
3 |
24 |
25 |
26 |
30 |
31 |
36 |
37 |
38 |
39 |
40 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/aigc-dashboard/src/views/welcome.vue:
--------------------------------------------------------------------------------
1 |
3 |
4 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
AIGC Gateway
29 |
OpenSource AIGC engine on kubernetes.
30 |
31 |
32 | Start to Use
33 |
34 |
35 |
36 |
37 |
38 |
39 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/aigc-dashboard/vite.config.js:
--------------------------------------------------------------------------------
1 | import { fileURLToPath, URL } from 'node:url'
2 |
3 | import { defineConfig } from 'vite'
4 | import vue from '@vitejs/plugin-vue'
5 |
6 | // https://vitejs.dev/config/
7 | export default defineConfig({
8 | plugins: [vue()],
9 | resolve: {
10 | alias: {
11 | '@': fileURLToPath(new URL('./src', import.meta.url))
12 | }
13 | }
14 | })
15 |
--------------------------------------------------------------------------------
/aigc-dashboard/vitest.config.js:
--------------------------------------------------------------------------------
1 | import { fileURLToPath } from 'node:url'
2 | import { mergeConfig } from 'vite'
3 | import { configDefaults, defineConfig } from 'vitest/config'
4 | import viteConfig from './vite.config'
5 |
6 | export default mergeConfig(
7 | viteConfig,
8 | defineConfig({
9 | test: {
10 | environment: 'jsdom',
11 | exclude: [...configDefaults.exclude, 'e2e/*'],
12 | root: fileURLToPath(new URL('./', import.meta.url))
13 | }
14 | })
15 | )
16 |
--------------------------------------------------------------------------------
/deploy/helm/aigc-gateway/.helmignore:
--------------------------------------------------------------------------------
1 | # Patterns to ignore when building packages.
2 | # This supports shell glob matching, relative path matching, and
3 | # negation (prefixed with !). Only one pattern per line.
4 | .DS_Store
5 | # Common VCS dirs
6 | .git/
7 | .gitignore
8 | .bzr/
9 | .bzrignore
10 | .hg/
11 | .hgignore
12 | .svn/
13 | # Common backup files
14 | *.swp
15 | *.bak
16 | *.tmp
17 | *.orig
18 | *~
19 | # Various IDEs
20 | .project
21 | .idea/
22 | *.tmproj
23 | .vscode/
--------------------------------------------------------------------------------
/deploy/helm/aigc-gateway/Chart.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | name: aigc-gateway
3 | description: Helm chart for aigc-gateway running on k8s
4 | version: 1.1.0
5 | appVersion: 1.1.0
6 | kubeVersion: ">= 1.16.0-0"
7 |
--------------------------------------------------------------------------------
/deploy/helm/aigc-gateway/templates/aigc-gateway.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: aigc-gateway
5 | namespace: {{ .Values.installation.namespace }}
6 | labels:
7 | app: aigc-gateway
8 | spec:
9 | replicas: 1
10 | selector:
11 | matchLabels:
12 | app: aigc-gateway
13 | template:
14 | metadata:
15 | labels:
16 | app: aigc-gateway
17 | spec:
18 | containers:
19 | - name: aigc-gateway
20 | image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
21 | command:
22 | - "./aigc-gateway"
23 | env:
24 | - name: App_Id
25 | value: {{ .Values.appId }}
26 | - name: App_Secret
27 | value: {{ .Values.appSecret }}
28 | - name: M2M_Id
29 | value: {{ .Values.m2mId }}
30 | - name: M2M_Secret
31 | value: {{ .Values.m2mSecret }}
32 | - name: Redirect_Url
33 | value: https://{{ .Values.host }}/
34 | - name: Endpoint
35 | value: {{ .Values.endpoint }}
36 | ports:
37 | - containerPort: 8090
38 | name: dashboard
39 | volumeMounts:
40 | - name: config-volume
41 | mountPath: "/etc/config" # 挂载点路径
42 | readOnly: true
43 | volumes:
44 | - name: config-volume
45 | configMap:
46 | name: aigc-gateway-config
47 | items:
48 | - key: config.json
49 | path: "config.json" # 映射为容器内的文件名
50 | serviceAccountName: aigc-gateway
51 | ---
52 | apiVersion: v1
53 | kind: ConfigMap
54 | metadata:
55 | name: aigc-gateway-config
56 | namespace: {{ .Values.installation.namespace }}
57 | data:
58 | config.json: |
59 | {
60 | "namespaces": ["default"],
61 | "gss_labels": {}
62 | }
63 | ---
64 | apiVersion: v1
65 | kind: Service
66 | metadata:
67 | name: aigc-gateway
68 | namespace: {{ .Values.installation.namespace }}
69 | labels:
70 | app: aigc-gateway
71 | spec:
72 | selector:
73 | app: aigc-gateway
74 | ports:
75 | - protocol: TCP
76 | name: dashboard
77 | port: 8090
78 | targetPort: 8090
79 | type: ClusterIP
80 | ---
81 | apiVersion: networking.k8s.io/v1
82 | kind: Ingress
83 | metadata:
84 | name: aigc-gateway
85 | namespace: {{ .Values.installation.namespace }}
86 | annotations:
87 | "nginx.ingress.kubernetes.io/ssl-redirect": "true"
88 | spec:
89 | ingressClassName: nginx
90 | tls:
91 | - hosts:
92 | - {{ .Values.host }}
93 | secretName: {{ .Values.secretName }}
94 | rules:
95 | - host: {{ .Values.host }}
96 | http:
97 | paths:
98 | - backend:
99 | service:
100 | name: aigc-gateway
101 | port:
102 | number: 8090
103 | pathType: ImplementationSpecific
104 | path: "/"
105 | ---
106 | apiVersion: v1
107 | kind: ServiceAccount
108 | metadata:
109 | name: aigc-gateway
110 | namespace: {{ .Values.installation.namespace }}
111 | ---
112 | apiVersion: rbac.authorization.k8s.io/v1
113 | kind: ClusterRole
114 | metadata:
115 | name: aigc-gateway
116 | rules:
117 | - apiGroups:
118 | - game.kruise.io
119 | resources:
120 | - gameservers
121 | verbs:
122 | - create
123 | - delete
124 | - get
125 | - list
126 | - patch
127 | - update
128 | - watch
129 | - apiGroups:
130 | - game.kruise.io
131 | resources:
132 | - gameservers/finalizers
133 | verbs:
134 | - update
135 | - apiGroups:
136 | - game.kruise.io
137 | resources:
138 | - gameservers/status
139 | verbs:
140 | - get
141 | - patch
142 | - update
143 | - apiGroups:
144 | - game.kruise.io
145 | resources:
146 | - gameserversets
147 | verbs:
148 | - create
149 | - delete
150 | - get
151 | - list
152 | - patch
153 | - update
154 | - watch
155 | - apiGroups:
156 | - game.kruise.io
157 | resources:
158 | - gameserversets/finalizers
159 | verbs:
160 | - update
161 | - apiGroups:
162 | - game.kruise.io
163 | resources:
164 | - gameserversets/status
165 | verbs:
166 | - get
167 | - patch
168 | - update
169 | - apiGroups:
170 | - ""
171 | resources:
172 | - persistentvolumeclaims
173 | verbs:
174 | - delete
175 | - get
176 | - apiGroups:
177 | - ""
178 | resources:
179 | - pods
180 | verbs:
181 | - get
182 | - delete
183 | ---
184 | apiVersion: rbac.authorization.k8s.io/v1
185 | kind: ClusterRoleBinding
186 | metadata:
187 | name: aigc-gateway
188 | roleRef:
189 | apiGroup: rbac.authorization.k8s.io
190 | kind: ClusterRole
191 | name: aigc-gateway
192 | subjects:
193 | - kind: ServiceAccount
194 | name: aigc-gateway
195 | namespace: {{ .Values.installation.namespace }}
196 |
--------------------------------------------------------------------------------
/deploy/helm/aigc-gateway/values.yaml:
--------------------------------------------------------------------------------
1 | installation:
2 | namespace: aigc-gateway
3 | image:
4 | repository: registry-cn-hangzhou.ack.aliyuncs.com/acs/aigc-gateway
5 | tag: v1.3.0
6 | appId: "32qh5wp6c1q3jofy2myw"
7 | appSecret: "35mpn2dk62brjxaduy70"
8 | m2mId: "ezxyaol30ld99c7ns5ur"
9 | m2mSecret: "g7hly3nwq2omligpyh3k"
10 | host: "dashboard.c5464a5f2c39341d3b3eda6e2dd37b55.cn-hangzhou.alicontainer.com"
11 | endpoint: "https://logto.c5464a5f2c39341d3b3eda6e2dd37b55.cn-hangzhou.alicontainer.com/"
12 | secretName: "tls-logto"
--------------------------------------------------------------------------------
/deploy/helm/logto/.helmignore:
--------------------------------------------------------------------------------
1 | # Patterns to ignore when building packages.
2 | # This supports shell glob matching, relative path matching, and
3 | # negation (prefixed with !). Only one pattern per line.
4 | .DS_Store
5 | # Common VCS dirs
6 | .git/
7 | .gitignore
8 | .bzr/
9 | .bzrignore
10 | .hg/
11 | .hgignore
12 | .svn/
13 | # Common backup files
14 | *.swp
15 | *.bak
16 | *.tmp
17 | *.orig
18 | *~
19 | # Various IDEs
20 | .project
21 | .idea/
22 | *.tmproj
23 | .vscode/
--------------------------------------------------------------------------------
/deploy/helm/logto/Chart.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | name: logto
3 | description: Helm chart for logto running on k8s
4 | version: 0.1.0
5 | appVersion: 0.1.0
6 | kubeVersion: ">= 1.16.0-0"
7 |
--------------------------------------------------------------------------------
/deploy/helm/logto/templates/logto.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: logto
5 | namespace: {{ .Values.installation.namespace }}
6 | labels:
7 | app: logto
8 | spec:
9 | replicas: 1
10 | selector:
11 | matchLabels:
12 | app: logto
13 | template:
14 | metadata:
15 | labels:
16 | app: logto
17 | spec:
18 | containers:
19 | - name: logto
20 | image: svhd/logto:1.4 # replace it with your exactly
21 | command:
22 | - "sh"
23 | - "-c"
24 | - "npm run cli db seed -- --swe && npm start"
25 | env:
26 | - name: TRUST_PROXY_HEADER
27 | value: "1"
28 | - name: DB_URL
29 | value: "postgres://postgres:p0stgr3s@postgres.{{ .Values.installation.namespace }}.svc.cluster.local:5432/logto"
30 | - name: ADMIN_ENDPOINT
31 | value: https://{{ .Values.adminEndpoint }}
32 | - name: ENDPOINT
33 | value: https://{{ .Values.endpoint }}
34 | ports:
35 | - containerPort: 3001
36 | name: signin
37 | - containerPort: 3002
38 | name: console
39 | ---
40 | apiVersion: v1
41 | kind: Service
42 | metadata:
43 | name: logto
44 | namespace: {{ .Values.installation.namespace }}
45 | labels:
46 | app: logto
47 | spec:
48 | selector:
49 | app: logto
50 | ports:
51 | - protocol: TCP
52 | name: signin
53 | port: 3001
54 | targetPort: 3001
55 | - protocol: TCP
56 | name: console
57 | port: 3002
58 | targetPort: 3002
59 | type: ClusterIP
60 | ---
61 | apiVersion: networking.k8s.io/v1
62 | kind: Ingress
63 | metadata:
64 | name: logto
65 | namespace: {{ .Values.installation.namespace }}
66 | annotations:
67 | "nginx.ingress.kubernetes.io/ssl-redirect": "true"
68 | spec:
69 | ingressClassName: nginx
70 | tls:
71 | - hosts:
72 | - {{ .Values.endpoint }}
73 | secretName: {{ .Values.secretName }}
74 | rules:
75 | - host: {{ .Values.endpoint }}
76 | http:
77 | paths:
78 | - backend:
79 | service:
80 | name: logto
81 | port:
82 | number: 3001
83 | pathType: ImplementationSpecific
84 | path: "/"
85 | ---
86 | apiVersion: networking.k8s.io/v1
87 | kind: Ingress
88 | metadata:
89 | name: logto-admin
90 | namespace: {{ .Values.installation.namespace }}
91 | annotations:
92 | "nginx.ingress.kubernetes.io/ssl-redirect": "true"
93 | spec:
94 | ingressClassName: nginx
95 | tls:
96 | - hosts:
97 | - {{ .Values.adminEndpoint }}
98 | secretName: {{ .Values.secretName }}
99 | rules:
100 | - host: {{ .Values.adminEndpoint }}
101 | http:
102 | paths:
103 | - backend:
104 | service:
105 | name: logto
106 | port:
107 | number: 3002
108 | pathType: ImplementationSpecific
109 | path: "/"
--------------------------------------------------------------------------------
/deploy/helm/logto/templates/postgress.yaml:
--------------------------------------------------------------------------------
1 | {{- if .Values.installation.createNamespace }}
2 | apiVersion: v1
3 | kind: Namespace
4 | metadata:
5 | name: {{ .Values.installation.namespace }}
6 | {{- end }}
7 | ---
8 | apiVersion: apps/v1
9 | kind: StatefulSet
10 | metadata:
11 | name: postgres
12 | namespace: {{ .Values.installation.namespace }}
13 | spec:
14 | selector:
15 | matchLabels:
16 | app: postgres
17 | serviceName: "postgres"
18 | replicas: 1
19 | template:
20 | metadata:
21 | labels:
22 | app: postgres
23 | spec:
24 | containers:
25 | - name: postgres
26 | securityContext:
27 | runAsUser: 70
28 | runAsGroup: 70
29 | runAsNonRoot: true
30 | env:
31 | - name: POSTGRES_USER
32 | value: postgres
33 | - name: POSTGRES_PASSWORD
34 | value: p0stgr3s
35 | image: postgres:14-alpine
36 | ports:
37 | - containerPort: 5432
38 | name: postgres
39 | livenessProbe:
40 | exec:
41 | command:
42 | - "pg_isready"
43 | failureThreshold: 5
44 | periodSeconds: 10
45 | timeoutSeconds: 5
46 | ---
47 |
48 | apiVersion: v1
49 | kind: Service
50 | metadata:
51 | name: postgres
52 | namespace: {{ .Values.installation.namespace }}
53 | labels:
54 | app: postgres
55 | spec:
56 | ports:
57 | - port: 5432
58 | name: postgres
59 | clusterIP: None
60 | selector:
61 | app: postgres
--------------------------------------------------------------------------------
/deploy/helm/logto/values.yaml:
--------------------------------------------------------------------------------
1 | adminEndpoint: "logto-admin.c5464a5f2c39341d3b3eda6e2dd37b55.cn-hangzhou.alicontainer.com"
2 | endpoint: "logto.c5464a5f2c39341d3b3eda6e2dd37b55.cn-hangzhou.alicontainer.com"
3 | secretName: "tls-logto"
4 | installation:
5 | namespace: aigc-gateway
6 | createNamespace: false
7 |
--------------------------------------------------------------------------------
/deploy/yaml/aigc-gateway.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: aigc-gateway
5 | labels:
6 | app: aigc-gateway
7 | spec:
8 | replicas: 1
9 | selector:
10 | matchLabels:
11 | app: aigc-gateway
12 | template:
13 | metadata:
14 | labels:
15 | app: aigc-gateway
16 | spec:
17 | containers:
18 | - name: aigc-gateway
19 | image: registry.cn-beijing.aliyuncs.com/acs/aigc-gateway:v0.1.0 # replace it with your exactly
20 | command:
21 | - "./aigc-gateway"
22 | env:
23 | - name: App_Id
24 | value: "550tqtqmoxfgc2efcs0hg"
25 | - name: App_Secret
26 | value: "os1dow3fzqrukv8ngvoat"
27 | - name: M2M_Id
28 | value: ""
29 | - name: M2M_Secret
30 | value: ""
31 | - name: Basic_Auth_Token
32 | value: "eHp3ZnB3cjR3cGhhdmVrcDY4ZGVnOjJjazF3YnY5cWdvbmw1ZjZtNWdjdw=="
33 | - name: Redirect_Url
34 | value: "https://dashboard.c5464a5f2c39341d3b3eda6e2dd37b55.cn-hangzhou.alicontainer.com/"
35 | - name: Endpoint
36 | value: "https://logto.c5464a5f2c39341d3b3eda6e2dd37b55.cn-hangzhou.alicontainer.com/"
37 | ports:
38 | - containerPort: 8090
39 | name: dashboard
40 | serviceAccountName: aigc-gateway
41 | ---
42 | apiVersion: v1
43 | kind: Service
44 | metadata:
45 | name: aigc-gateway
46 | labels:
47 | app: aigc-gateway
48 | spec:
49 | selector:
50 | app: aigc-gateway
51 | ports:
52 | - protocol: TCP
53 | name: dashboard
54 | port: 8090
55 | targetPort: 8090
56 | type: ClusterIP
57 | ---
58 | apiVersion: networking.k8s.io/v1
59 | kind: Ingress
60 | metadata:
61 | name: aigc-gateway
62 | annotations:
63 | "nginx.ingress.kubernetes.io/ssl-redirect": "true"
64 | spec:
65 | ingressClassName: nginx
66 | tls:
67 | - hosts:
68 | - "dashboard.c5464a5f2c39341d3b3eda6e2dd37b55.cn-hangzhou.alicontainer.com"
69 | secretName: tls-logto
70 | rules:
71 | - host: "dashboard.c5464a5f2c39341d3b3eda6e2dd37b55.cn-hangzhou.alicontainer.com"
72 | http:
73 | paths:
74 | - backend:
75 | service:
76 | name: aigc-gateway
77 | port:
78 | number: 8090
79 | pathType: ImplementationSpecific
80 | path: "/"
81 | ---
82 | apiVersion: v1
83 | kind: ServiceAccount
84 | metadata:
85 | name: aigc-gateway
86 | namespace: default
87 | ---
88 | apiVersion: rbac.authorization.k8s.io/v1
89 | kind: ClusterRole
90 | metadata:
91 | name: aigc-gateway
92 | rules:
93 | - apiGroups:
94 | - game.kruise.io
95 | resources:
96 | - gameservers
97 | verbs:
98 | - create
99 | - delete
100 | - get
101 | - list
102 | - patch
103 | - update
104 | - watch
105 | - apiGroups:
106 | - game.kruise.io
107 | resources:
108 | - gameservers/finalizers
109 | verbs:
110 | - update
111 | - apiGroups:
112 | - game.kruise.io
113 | resources:
114 | - gameservers/status
115 | verbs:
116 | - get
117 | - patch
118 | - update
119 | - apiGroups:
120 | - game.kruise.io
121 | resources:
122 | - gameserversets
123 | verbs:
124 | - create
125 | - delete
126 | - get
127 | - list
128 | - patch
129 | - update
130 | - watch
131 | - apiGroups:
132 | - game.kruise.io
133 | resources:
134 | - gameserversets/finalizers
135 | verbs:
136 | - update
137 | - apiGroups:
138 | - game.kruise.io
139 | resources:
140 | - gameserversets/status
141 | verbs:
142 | - get
143 | - patch
144 | - update
145 | ---
146 | apiVersion: rbac.authorization.k8s.io/v1
147 | kind: ClusterRoleBinding
148 | metadata:
149 | name: aigc-gateway
150 | roleRef:
151 | apiGroup: rbac.authorization.k8s.io
152 | kind: ClusterRole
153 | name: aigc-gateway
154 | subjects:
155 | - kind: ServiceAccount
156 | name: aigc-gateway
157 | namespace: default
--------------------------------------------------------------------------------
/deploy/yaml/logto.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: logto
5 | labels:
6 | app: logto
7 | spec:
8 | replicas: 1
9 | selector:
10 | matchLabels:
11 | app: logto
12 | template:
13 | metadata:
14 | labels:
15 | app: logto
16 | spec:
17 | containers:
18 | - name: logto
19 | image: svhd/logto:latest # replace it with your exactly
20 | command:
21 | - "sh"
22 | - "-c"
23 | - "npm run cli db seed -- --swe && npm start"
24 | env:
25 | - name: TRUST_PROXY_HEADER
26 | value: "1"
27 | - name: DB_URL
28 | value: "postgres://postgres:p0stgr3s@postgres.default.svc.cluster.local:5432/logto"
29 | - name: ADMIN_ENDPOINT
30 | value: "https://logto-admin.c5464a5f2c39341d3b3eda6e2dd37b55.cn-hangzhou.alicontainer.com"
31 | - name: ENDPOINT
32 | value: "https://logto.c5464a5f2c39341d3b3eda6e2dd37b55.cn-hangzhou.alicontainer.com"
33 | ports:
34 | - containerPort: 3001
35 | name: signin
36 | - containerPort: 3002
37 | name: console
38 | ---
39 | apiVersion: v1
40 | kind: Service
41 | metadata:
42 | name: logto
43 | labels:
44 | app: logto
45 | spec:
46 | selector:
47 | app: logto
48 | ports:
49 | - protocol: TCP
50 | name: signin
51 | port: 3001
52 | targetPort: 3001
53 | - protocol: TCP
54 | name: console
55 | port: 3002
56 | targetPort: 3002
57 | type: ClusterIP
58 | ---
59 | apiVersion: networking.k8s.io/v1
60 | kind: Ingress
61 | metadata:
62 | name: logto
63 | annotations:
64 | "nginx.ingress.kubernetes.io/ssl-redirect": "true"
65 | spec:
66 | ingressClassName: nginx
67 | tls:
68 | - hosts:
69 | - "logto.c5464a5f2c39341d3b3eda6e2dd37b55.cn-hangzhou.alicontainer.com"
70 | secretName: tls-logto
71 | rules:
72 | - host: "logto.c5464a5f2c39341d3b3eda6e2dd37b55.cn-hangzhou.alicontainer.com"
73 | http:
74 | paths:
75 | - backend:
76 | service:
77 | name: logto
78 | port:
79 | number: 3001
80 | pathType: ImplementationSpecific
81 | path: "/"
82 | ---
83 | apiVersion: networking.k8s.io/v1
84 | kind: Ingress
85 | metadata:
86 | name: logto-admin
87 | annotations:
88 | "nginx.ingress.kubernetes.io/ssl-redirect": "true"
89 | spec:
90 | ingressClassName: nginx
91 | tls:
92 | - hosts:
93 | - "logto-admin.c5464a5f2c39341d3b3eda6e2dd37b55.cn-hangzhou.alicontainer.com"
94 | secretName: tls-logto
95 | rules:
96 | - host: "logto-admin.c5464a5f2c39341d3b3eda6e2dd37b55.cn-hangzhou.alicontainer.com"
97 | http:
98 | paths:
99 | - backend:
100 | service:
101 | name: logto
102 | port:
103 | number: 3002
104 | pathType: ImplementationSpecific
105 | path: "/"
--------------------------------------------------------------------------------
/deploy/yaml/postgres.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: StatefulSet
3 | metadata:
4 | name: postgres
5 | spec:
6 | selector:
7 | matchLabels:
8 | app: postgres
9 | serviceName: "postgres"
10 | replicas: 1
11 | template:
12 | metadata:
13 | labels:
14 | app: postgres
15 | spec:
16 | containers:
17 | - name: postgres
18 | securityContext:
19 | runAsUser: 70
20 | runAsGroup: 70
21 | runAsNonRoot: true
22 | env:
23 | - name: POSTGRES_USER
24 | value: postgres
25 | - name: POSTGRES_PASSWORD
26 | value: p0stgr3s
27 | image: postgres:14-alpine
28 | ports:
29 | - containerPort: 5432
30 | name: postgres
31 | livenessProbe:
32 | exec:
33 | command:
34 | - "pg_isready"
35 | failureThreshold: 5
36 | periodSeconds: 10
37 | timeoutSeconds: 5
38 | ---
39 |
40 | apiVersion: v1
41 | kind: Service
42 | metadata:
43 | name: postgres
44 | labels:
45 | app: postgres
46 | spec:
47 | ports:
48 | - port: 5432
49 | name: postgres
50 | clusterIP: None
51 | selector:
52 | app: postgres
--------------------------------------------------------------------------------
/docs/Dashboard介绍.md:
--------------------------------------------------------------------------------
1 |
2 | ## 初始页面
3 |
4 | 访问AIGC-Gateway地址,进入初始页面。点击START TO USE,将进入用户个人控制台。
5 |
6 | 
7 |
8 | ## 用户认证页面
9 |
10 | 当用户未处于登录态,需要登录帐号进行身份认证。
11 |
12 | 
13 |
14 | ## 用户个人控制台
15 |
16 | ### 安装
17 |
18 | 新用户登录后不存在属于自己的AIGC实例,可以选择对应模版,安装所需实例。
19 |
20 | 
21 |
22 | ### 使用
23 |
24 | 当实例完成安装,用户可以点击VISIT访问对应实例
25 |
26 | 
27 |
28 | 本例中使用 Stable-Diffusion,点击VISIT后跳转至SD页面
29 |
30 | 
31 |
32 | ### 停止与恢复
33 |
34 | 当用户使用完成,可以点击PAUSE释放实例。当实例完成释放,用户可以点击RECOVER来重载该实例,实例的持久化数据会再次加载进来,不会丢失。
35 |
36 | 
37 |
38 |
39 | ### 登出
40 |
41 | 用户完成使用,可以点击左边栏中Log Out,退出登录态
42 |
43 | 
--------------------------------------------------------------------------------
/docs/en/README.md:
--------------------------------------------------------------------------------
1 | # AIGC-Gateway
2 | [中文](../../README.md) | English
3 |
4 | ## Overview of AIGC-Gateway
5 |
6 | In 2023, The trend of AIGC (Artificial Intelligence Generated Content) ignited a productivity revolution in the production of pan-entertainment content. AIGC paradigms such as AIGC graphics, AIGC audio, and AIGC video became the main scene for major companies to invest in and land production. Currently, the AIGC field is mainly divided into two major scenes: one is the scene for end customers, such as Midjourney or Hugging Face; the other is the scene for content producers, such as Stable Diffusion. The former is more focused on the service and commercialization of AIGC, while the latter is more focused on generating digital assets and improving efficiency within the enterprise. Most companies will use the second form to land AIGC capabilities within the enterprise.
7 |
8 | Compared to traditional internet-based businesses, the use and operation of AIGC engines have the following characteristics:
9 |
10 | - **Fragmented and Elastic Runtime**
11 |
12 | Usually, the usage time of AIGC engines is strongly related to the usage cycle of content producers. So far, the quality of AIGC engine-generated content requires manual evaluation and interactive adjustment. This will result in very fragmented usage time from the perspective of a single content producer, and the usage time interval will be very certain. Generally, the AIGC engine is only used for 4-6 hours on working days, and the effective usage time will be even shorter. There is usually no usage time on weekday evenings and weekends.
13 |
14 |
15 | - **Digital Asset Isolation and Security**
16 |
17 | For many industries and companies that are mainly content producers, the self-trained models in AIGC engines are digital core assets. Different content producers do not actively share models with each other. In addition, the content generated by different content producers needs to achieve access isolation, prevent tampering, and data isolation.
18 |
19 |
20 | - **Multiple Resource Types and Elasticity**
21 |
22 | AIGC engines strongly depend on the heterogeneous computing power of GPUs. However, there are many types of GPUs, and the cost and efficiency differences between different types of GPUs are very large. For example, in Stable Diffusion, the efficiency of generating images with A100 40G VRAM and A100 80G VRAM is about 1 second different, but the cost is more than twice as expensive for A100 80G VRAM. In addition, many companies that have been involved in AIGC for a long time will have quite a few consumer cards (such as NV3090) in their local data centers. When landing AIGC engines within the enterprise, it is necessary to consider how to utilize these resources and unify management and improve efficiency.
23 |
24 |
25 | - **Various Types and Versions of Engines**
26 |
27 | The iteration speed of AIGC engines is very fast, and there may be some incompatibilities between different versions. As a tool for generating digital assets, it is usually difficult to change the version once it is put into production to ensure the stable operation of the model. The versions that different content producers rely on may also differ significantly. Therefore, how to manage and upgrade multiple versions uniformly is also something that needs to be considered when landing AIGC within the enterprise.
28 |
29 |
30 | ## Features of AIGC-Gateway
31 |
32 | In order to solve the universality problem of landing AIGC engines within the enterprise, Alibaba Cloud and Xingzhe AI (Longyuan Games) have open-sourced the AIGC-Gateway project to reduce the difficulty and cost of landing AIGC within the enterprise, and truly achieve plug-and-play.
33 |
34 | 
35 |
36 | The AIGC-Gateway has the following features:
37 |
38 | - **Dynamic Launch and On-Demand Release**
39 |
40 | AIGC-Gateway can launch any AIGC engine on demand and release the required GPU resources while preserving the data storage when exiting or leaving. When the same user accesses again, AIGC-Gateway can quickly launch new resources and mount the data storage belonging to this customer, achieving time-sharing reuse of GPU cards and reducing overall cost investment.
41 |
42 |
43 | - **Tenant Exclusive Access Isolation**
44 |
45 | The AIGC engine resources launched by AIGC-Gateway are exclusive to the tenant and data is isolated. In addition, access addresses are secured through OIDC for non-intrusive access control of any AIGC engine. Additionally, AIGC-Gateway integrates with the open-source authentication system Logto to achieve automatic single sign-on and authentication for enterprise internal account systems, simplifying the process.
46 |
47 |
48 | - **Unified GPU Resource Management**
49 |
50 | AIGC-Gateway is an open-source project that is cloud vendor independent and supports local deployment, hybrid cloud deployment, and multi-cloud deployment by enterprise IT engineers. It fully supports different types of GPU resources, such as local GPU, cloud single-card GPU, cloud multi-card GPU, and cloud-shared memory GPU.
51 |
52 |
53 | - **Version/Model Unified Management**
54 |
55 | AIGC-Gateway supports simultaneous deployment of multiple, multiple sets, and multiple versions of AIGC engines. It can also perform simple and convenient batch management for AIGC models of the same version and support data migration and switching between different versions of AIGC engines.
56 |
57 |
58 | - **Cost Accounting and Cost Allocation**
59 |
60 | On Alibaba Cloud, cost accounting for user and instance levels can be achieved through a visual dashboard and API, facilitating internal cost accounting and cost unit price estimation.
61 |
62 |
63 | ## What's Next
64 |
65 | - [Introduction of AIGC-Gateway Dashboard](../../docs/Dashboard介绍.md)
66 | - [Install & Config AIGC-Gateway](../../docs/安装部署.md)
67 | - [Deploy multi AIGC Engine Templates](../../docs/模版管理.md)
68 | - [Architecture & Principle of AIGC-Gateway](../../docs/架构原理.md)
69 | - [Q&A](../../docs/常见问题.md)
70 |
71 | ## Upstream Project
72 |
73 | - [OpenKruiseGame](https://github.com/openkruise/kruise-game)
74 | - [logto](https://github.com/logto-io/logto/)
--------------------------------------------------------------------------------
/docs/images/Oops-wrong.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CloudNativeGame/aigc-gateway/355383cb419bc08e945935a5b3a321695f00240c/docs/images/Oops-wrong.png
--------------------------------------------------------------------------------
/docs/images/SD-dash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CloudNativeGame/aigc-gateway/355383cb419bc08e945935a5b3a321695f00240c/docs/images/SD-dash.png
--------------------------------------------------------------------------------
/docs/images/Traditional-Web-console.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CloudNativeGame/aigc-gateway/355383cb419bc08e945935a5b3a321695f00240c/docs/images/Traditional-Web-console.png
--------------------------------------------------------------------------------
/docs/images/Traditional-Web-setting.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CloudNativeGame/aigc-gateway/355383cb419bc08e945935a5b3a321695f00240c/docs/images/Traditional-Web-setting.jpeg
--------------------------------------------------------------------------------
/docs/images/admin-login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CloudNativeGame/aigc-gateway/355383cb419bc08e945935a5b3a321695f00240c/docs/images/admin-login.png
--------------------------------------------------------------------------------
/docs/images/arch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CloudNativeGame/aigc-gateway/355383cb419bc08e945935a5b3a321695f00240c/docs/images/arch.png
--------------------------------------------------------------------------------
/docs/images/auth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CloudNativeGame/aigc-gateway/355383cb419bc08e945935a5b3a321695f00240c/docs/images/auth.png
--------------------------------------------------------------------------------
/docs/images/console.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CloudNativeGame/aigc-gateway/355383cb419bc08e945935a5b3a321695f00240c/docs/images/console.png
--------------------------------------------------------------------------------
/docs/images/customdata-delete-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CloudNativeGame/aigc-gateway/355383cb419bc08e945935a5b3a321695f00240c/docs/images/customdata-delete-1.png
--------------------------------------------------------------------------------
/docs/images/customdata-delete-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CloudNativeGame/aigc-gateway/355383cb419bc08e945935a5b3a321695f00240c/docs/images/customdata-delete-2.png
--------------------------------------------------------------------------------
/docs/images/dashboard-installed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CloudNativeGame/aigc-gateway/355383cb419bc08e945935a5b3a321695f00240c/docs/images/dashboard-installed.png
--------------------------------------------------------------------------------
/docs/images/dashboard-login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CloudNativeGame/aigc-gateway/355383cb419bc08e945935a5b3a321695f00240c/docs/images/dashboard-login.png
--------------------------------------------------------------------------------
/docs/images/dashboard-recover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CloudNativeGame/aigc-gateway/355383cb419bc08e945935a5b3a321695f00240c/docs/images/dashboard-recover.png
--------------------------------------------------------------------------------
/docs/images/dashboard-uninstalled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CloudNativeGame/aigc-gateway/355383cb419bc08e945935a5b3a321695f00240c/docs/images/dashboard-uninstalled.png
--------------------------------------------------------------------------------
/docs/images/logout.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CloudNativeGame/aigc-gateway/355383cb419bc08e945935a5b3a321695f00240c/docs/images/logout.png
--------------------------------------------------------------------------------
/docs/images/m2m-console.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CloudNativeGame/aigc-gateway/355383cb419bc08e945935a5b3a321695f00240c/docs/images/m2m-console.png
--------------------------------------------------------------------------------
/docs/images/m2m-role-assign.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CloudNativeGame/aigc-gateway/355383cb419bc08e945935a5b3a321695f00240c/docs/images/m2m-role-assign.png
--------------------------------------------------------------------------------
/docs/images/m2m-role-setting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CloudNativeGame/aigc-gateway/355383cb419bc08e945935a5b3a321695f00240c/docs/images/m2m-role-setting.png
--------------------------------------------------------------------------------
/docs/images/m2m-setting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CloudNativeGame/aigc-gateway/355383cb419bc08e945935a5b3a321695f00240c/docs/images/m2m-setting.png
--------------------------------------------------------------------------------
/docs/images/quick-setup-realse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CloudNativeGame/aigc-gateway/355383cb419bc08e945935a5b3a321695f00240c/docs/images/quick-setup-realse.png
--------------------------------------------------------------------------------
/docs/images/user-login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CloudNativeGame/aigc-gateway/355383cb419bc08e945935a5b3a321695f00240c/docs/images/user-login.png
--------------------------------------------------------------------------------
/docs/安装部署.md:
--------------------------------------------------------------------------------
1 | 本文介绍如何将AIGC-Gateway部署至Kubernetes集群
2 |
3 | AIGC-Gateway 包含两个组件
4 | - logto。用作用户身份认证,实现单点登录。
5 | - aigc-gateway。计算资源管理,同时维护用户与实例的映射关系。
6 |
7 | 完整地安装AIGC-Gateway需要经历以下步骤:
8 | 1. 生成logto证书,并在k8s集群中安装logto
9 | 2. 在logto admin控制台配置logto参数,并获取应用ID/secret等
10 | 3. 根据第二步中获取的信息,填写aigc-gateway相关参数,并安装
11 |
12 | ## 1. 安装logto
13 |
14 | 此步骤将会部署一个logto pod与一个postgress pod。logto用作用户鉴权,postgress用作logto对应数据库。建议生产环境采用独立的高可用数据库或云提供商数据库。
15 |
16 | ### 1.1 生成证书,并创建对应secret对象
17 |
18 | AIGC-Gateway 通过logto进行用户身份认证,默认通过 HTTPS 访问,因此需要提前准备证书,以下我们通过OpenSSL生成自定义证书用于测试,正式生产用途建议申请受信任的企业证书。
19 |
20 | ```bash
21 | openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=c5464a5f2c39341d3b3eda6e2dd37b505.cn-hangzhou.alicontainer.com/O=c5464a5f2c39341d3b3eda6e2dd37b505.cn-hangzhou.alicontainer.com"
22 |
23 | kubectl create ns aigc-gateway
24 | kubectl -n aigc-gateway create secret tls tls-logto --key tls.key --cert tls.crt
25 | ```
26 |
27 | ### 1.2 编辑./deploy/helm/logto/values.yaml
28 |
29 | ```yaml
30 | adminEndpoint: "logto-admin.c5464a5f2c39341d3b3eda6e2dd37b505.cn-hangzhou.alicontainer.com" #logto admin控制台域名
31 | endpoint: "logto.c5464a5f2c39341d3b3eda6e2dd37b505.cn-hangzhou.alicontainer.com" #logto域名
32 | secretName: "tls-logto" #第1.1步中生成的secret的名称
33 | installation:
34 | namespace: aigc-gateway #与第1.1步中生成的secret同命名空间
35 | createNamespace: false #如若存在该ns,则不需要创建新的
36 | ```
37 |
38 | ### 1.3 安装 logto helm chart
39 | ```bash
40 | helm install logto ./deploy/helm/logto
41 | ```
42 |
43 | ## 2. 配置logto参数
44 |
45 | ### 2.1 登录logto admin控制台
46 |
47 | 访问logto admin控制台,进行logto相关参数配置。首次登录时创建admin账户,生成管理员
48 |
49 | 
50 |
51 | ### 2.2 创建Traditional Web应用
52 | 输入用户名密码后,进入admin控制台,点击Application,点击Traditional Web,输入应用名称为aigc-gateway,创建应用。该配置页记录了AIGC-Gateway应用与logto交互的信息,其生成的ID与Secret将作为AIGC-Gateway的参数。
53 |
54 | 
55 |
56 | 跳过应用指引,在配置页面输入aigc-gateway域名(例如 dashboard.c5464a5f2c39341d3b3eda6e2dd37b505.cn-hangzhou.alicontainer.com)相关的两处URIs:
57 |
58 | - Redirect URIs:https://{aigc-gateway域名}/sign-in-callback
59 | - 例如:https://dashboard.c5464a5f2c39341d3b3eda6e2dd37b505.cn-hangzhou.alicontainer.com/sign-in-callback
60 | - Post Sign-out URIs:https://{aigc-gateway域名}/
61 | - 例如:https://dashboard.c5464a5f2c39341d3b3eda6e2dd37b505.cn-hangzhou.alicontainer.com/
62 |
63 | 点击保存更改
64 |
65 | 
66 |
67 | ### 2.3 创建Machine-to-Machine应用
68 | Machine-to-Machine将赋予AIGC-Gateway管理登录用户与后端资源实例信息映射关系的权限。点击Application,点击Machine-to-Machine,输入自定义应用名称,点击创建。
69 |
70 | 
71 |
72 | 在配置页面点击右下方Enable admin access按钮,点击保存更改(logto v1.4版本)
73 |
74 | 
75 |
76 | 在logto最新版(v1.14)中,m2m应用的admin权限需要通过设置role来实现,具体操作如下图所示。点击左侧导航栏Roles。创建role,点击show more options,填入对应信息。接下来将创建的role分配给m2m应用,完成权限绑定。
77 |
78 | 
79 |
80 | 
81 |
82 |
83 | ## 3. 安装AIGC-Gateway
84 |
85 | ### 3.1 编辑./deploy/helm/aigc-gateway/values.yaml
86 |
87 | ```yaml
88 | installation:
89 | namespace: aigc-gateway #建议与logto命名空间保持一致
90 | image:
91 | repository: registry-cn-hangzhou.ack.aliyuncs.com/acs/aigc-gateway
92 | tag: v1.3.0
93 | appId: "32qh5wp6c1q3jofy2myww" #在Traditional Web配置页中找到AppID并填入
94 | appSecret: "35mpn2dk62brjxaduy7x0" #在Traditional Web配置页中找到AppSecret并填入
95 | m2mId: "ezxyaol30ld99c7ns5u2r" #在Machine-to-Machine配置页中找到AppID并填入
96 | m2mSecret: "g7hly3nwq2omligpyh3ck" #在Machine-to-Machine配置页中找到AppSecret并填入
97 | host: "dashboard.c5464a5f2c39341d3b3eda6e2dd37b505.cn-hangzhou.alicontainer.com" #输入AIGC-Gateway配置的域名,该域名也是用户的访问端点
98 | endpoint: "https://logto.c5464a5f2c39341d3b3eda6e2dd37b505.cn-hangzhou.alicontainer.com/" #输入由logto域名组成的对应端点
99 | secretName: "tls-logto" #输入AIGC-Gateway对应的证书secret(本例中AIGC-Gateway证书与logto证书一致)
100 | ```
101 |
102 | ### 3.2 安装 aigc-gateway helm chart
103 | ```bash
104 | helm install aigc-gateway ./deploy/helm/aigc-gateway
105 | ```
106 |
107 | ---
108 |
109 | ## What's next
110 |
111 | - [模版管理](./模版管理.md)
112 |
113 |
--------------------------------------------------------------------------------
/docs/常见问题.md:
--------------------------------------------------------------------------------
1 | ## 常见问题与处理
2 |
3 | ### 浏览器页面出现:post_logout_redirect_uri not registered
4 |
5 | 用户logout的时候报错:
6 | ```json
7 | {"message":"OIDC 内部错误: invalid_request","code":"oidc.provider_error","data":{"error_description":"post_logout_redirect_uri not registered"}}
8 | ```
9 | 在logto admin控制台查看Tradition APP中设置的Post Sign-out Redirect URIs是否与集群中aigc-gatway的环境变量Redirect_Url一致。
10 | 通常情况下是因为,Post Sign-out Redirect URIs 的结尾缺少或多填写了 “/”
11 |
12 | ### 点击AIGC引擎的Install/Recover后出现转圈圈,刷新后无法访问
13 |
14 | 造成该情况出现有两个可能原因:
15 | a. 实例pod创建失败。可通过kebectl describe po {实例名称} -n {实例命名空间} 来查看创建失败原因。
16 | b. 实例访问网络创建失败。kebectl get gs {实例名称} -n {实例命名空间} -oyaml 找到network Status字段,查看网络是否Ready。
17 |
18 | ### 点击visit后无法访问
19 |
20 | - 首先,确定访问路径与登录用户是否匹配,该用户是否具有对应实例的访问权限。
21 | - 若匹配,kebectl get ingress {实例名称} -n {实例命名空间} 查看ingress是否存在且状态正常。查看对应annotation是否(如模版管理一节中示例所示)设置正确。
22 | - 若实例ingress正常,在浏览器端检查名为logto-session 的 cookie 对应domain是否是如下格式
23 | ```
24 | .{aigc-gateway根域名}
25 |
26 | #若aigc-gateway域名为dashboard.c5464a5f2c39341d3b3eda6e2dd37b505.cn-hangzhou.alicontainer.com
27 | #cookie对应domain应为 .c5464a5f2c39341d3b3eda6e2dd37b505.cn-hangzhou.alicontainer.com
28 | ```
29 |
30 | ### 点击Start To Use进入失败,登录超时
31 |
32 | 例如浏览器点击后出现以下类似错误
33 | ```
34 | Get "https://logto.cdadd9cd45d964c2694c15d4a4ad8fe37.ap-northeast-1.alicontainer.com/oidc/.well-known/openid-configuration": dial tcp 47.74.14.253:443: connect: connection timed out
35 | ```
36 |
37 | - 问题表因:aigc-gateway 访问 logto失败,无法跳转登录页面
38 | - 可能根因:Kubernetes集群内访问集群LoadBalancer暴露的SLB地址不通。可尝试在aigc-gateway容器中访问nginx对应SLB地址确认问题。
39 | - 解决方法:面对阿里云ACK的使用者,可以通过升级Terway到最新版本解决。具体操作为:ACK集群控制台 -> 组件管理 -> 网络 -> 先升级terway-eniip -> 再升级terway-controlplane
40 |
41 | ### logto admin dashboard 出现 Oops! Something went wrong
42 |
43 | 例如下图所示情况
44 |
45 | 
46 |
47 | - 问题原因:使用自签证书时,浏览器还没有授予logto域名的访问权限。
48 | - 解决方法:手动访问logto域名,点击信任即可。
--------------------------------------------------------------------------------
/docs/架构原理.md:
--------------------------------------------------------------------------------
1 | ## AIGC-Gateway是如何实现快速拉起和释放的?
2 |
3 | 
4 |
5 | AIGC-Gateway是通过容器的方式拉起和释放AIGC实例的,在有镜像缓存和模型异步加载的情况下,可以做到秒级的启动。此外,AIGC引擎的管理是通过开源项目OpenKruiseGame来进行管理的,可以通过配置服务质量的方式配置自定义的下线策略,实现企业对用户登出行为的自定义。拓展阅读:https://openkruise.io/zh/kruisegame/user-manuals/autoscale/
6 |
7 | ## AIGC-Gateway是如何如何实现用户访问控制的?
8 |
9 | 
10 |
11 | 默认AIGC-Gateway的网络是通过Ingress的方式进行透出的。AIGC-Gateway提供了增强的OAuth接口,支持在访问的时候认证登录态的同时,还可以校验资源和用户的归属。
12 |
13 | ## AIGC-Gateway是如何实现AIGC引擎管理的?
14 |
15 | 不同类型、不同版本、不同资源的AIGC引擎在AIGC-Gateway中是通过不同配置的Yaml模版来进行抽象的,增加、修改、变更一个AIGC引擎的配置,只需要修改对应的模版即可。AIGC-Gateway是通过Kubernetes中的接口反查获取的,无任何的内置和绑定,对开发者而言是完全开放的。
16 |
17 | ## AIGC-Gateway是如何实现GPU资源管理的?
18 |
19 | 不同的GPU资源,可以通过在负载中设置不同的Resource与调度策略的方式来实现,如果需要资源混合,例如:云下有3090的消费卡,云上有A100的数据中心卡,可以通过配置Kubernetes中的Affinity的方式实现优先使用云下资源,云下资源没有再使用云上资源。
20 |
21 | ## 如何配置自动拉起和自动释放的策略
22 |
23 | AIGC-Gateway可以通过配置探测脚本的方式标注用户离开的状态,例如:可以以无网络访问来判断用户的离开,并结合keda实现自动伸缩。拓展阅读:https://openkruise.io/zh/kruisegame/user-manuals/service-qualities
24 |
25 | ## 如何查看生成的图片与文件
26 | AIGC-Gateway可以通过在Sidecar中配置filebrowser的方式,实现web控制台的文件访问。
--------------------------------------------------------------------------------
/docs/模版管理.md:
--------------------------------------------------------------------------------
1 | AIGC-Gateway支持管理员自定义创建多种AIGC模版,模版初始部署在集群中时,集群不存在对应实例。管理员每部署一个模版,用户Dashboard视图中都会出现一个新的待安装的AIGC模型,当用户选择安装对应模版时,AIGC-Gateway会为该用户创建对应新的实例。
2 |
3 | ## 前置依赖
4 |
5 | 管理员需在Kubernetes集群中安装AIGC实例管理组件OKG(OpenKruiseGame),具体信息请参考:https://openkruise.io/zh/kruisegame/installation
6 |
7 | ```yaml
8 | $ helm repo add openkruise https://openkruise.github.io/charts/
9 |
10 | $ helm repo update
11 |
12 | $ helm install kruise openkruise/kruise --version 1.4.0
13 |
14 | $ helm install kruise-game openkruise/kruise-game --version 0.4.1
15 | ```
16 |
17 | ## 模版部署
18 |
19 | AIGC-Gateway中模版的概念等同于OKG中的GameServerSet对象,安装OKG后,管理员可在集群中部署多套AIGC示例模版(多组GameServerSet)。
20 |
21 | 以下是部署一个模版(GameServerSet)的示例。其中关键字段的说明如下:
22 | - spec.replicas 副本数目一定设置为0。因为模版下发后,不应存在任何实例,实例是由用户选择创建。
23 | - spec.gameServerTemplate.volumeClaimTemplates 用作创建属于用户个人的数据盘。该存储对象与实例生命周期解耦,也就是说,用户在释放计算实例后,个人数据并不丢失,实例恢复时数据重新加载。
24 | - spec.network 用于声明实例的访问网络。具体字段填写说明可参考示例中注释。
25 |
26 | ```
27 | cat <区分。如实例0的域名为instances0...; 实例1的域名为instances1...
85 | #注意,该域名需要与aigc-gateway具有相同根域名,此例中为c5464a5f2c39341d3b3eda6e2dd37b505.cn-hangzhou.alicontainer.com
86 | value: instances-cpu.c5464a5f2c39341d3b3eda6e2dd37b505.cn-hangzhou.alicontainer.com
87 | - name: PathType
88 | value: ImplementationSpecific
89 | - name: Path
90 | value: /
91 | - name: Annotation
92 | #用作实例访问鉴权,特定实例的访问权限只属于特定用户。下面的域名替换为AIGC-Gateway实际域名
93 | value: 'nginx.ingress.kubernetes.io/auth-url: https://dashboard.c5464a5f2c39341d3b3eda6e2dd37b505.cn-hangzhou.alicontainer.com/auth'
94 | - name: Annotation
95 | #实例访问鉴权失败后进行登录认证。下面的域名替换为AIGC-Gateway实际域名
96 | value: 'nginx.ingress.kubernetes.io/auth-signin: https://dashboard.c5464a5f2c39341d3b3eda6e2dd37b505.cn-hangzhou.alicontainer.com/sign-in'
97 | EOF
98 | ```
99 |
100 | 以上模版为SD使用CPU的例子。通常,可以将不同的AIGC模型部署为不同的模版,使用不同GPU计算规格的模型部署成不同的模版,甚至可以将不同版本的模型部署成不同模版,AIGC-Gateway管理员可自行定义。
101 |
102 | 例如,部署单卡GPU的模版如下所示(以下 #... 代表省略的部分)
103 |
104 | ```yaml
105 | #...
106 | gameServerTemplate:
107 | spec:
108 | containers:
109 | - #...
110 | resources:
111 | limits:
112 | nvidia.com/gpu: 1 #申请一张GPU卡
113 | #...
114 | ```
115 |
116 | 例如,部署共享GPU的模版如下所示,该模版生成的实例将使用3GiB显存的GPU
117 |
118 | ```yaml
119 | #...
120 | gameServerTemplate:
121 | spec:
122 | containers:
123 | - #...
124 | resources:
125 | limits:
126 | # 单位为GiB,该Pod总共申请了3 GiB显存。
127 | aliyun.com/gpu-mem: 3 # 设置GPU显存大小。
128 | #...
129 | ```
130 |
131 | 例如,ECI-CPU模版如下所示
132 |
133 | ```yaml
134 | #...
135 | gameServerTemplate:
136 | metadata:
137 | labels:
138 | alibabacloud.com/eci: "true"
139 | spec:
140 | containers:
141 | - #...
142 | resources:
143 | limits:
144 | #生成实例将使用2核4G计算资源
145 | cpu: "2"
146 | memory: 4Gi
147 | #...
148 | ```
149 |
150 | 例如,ECI-GPU模版如下所示
151 |
152 | ```yaml
153 | #...
154 | gameServerTemplate:
155 | metadata:
156 | labels:
157 | alibabacloud.com/eci: "true"
158 | annotations:
159 | k8s.aliyun.com/eci-use-specs: ecs.gn7i-c8g1.2xlarge #在此实例指定规格
160 | spec:
161 | containers:
162 | - #...
163 | resources:
164 | limits:
165 | nvidia.com/gpu: 1 #申请一张GPU卡
166 | #...
167 | ```
168 |
169 | ## 模版更新
170 |
171 | 当平台决定更新模版镜像、规格等参数时,管理员可以操作对应GameServerSet对象:
172 |
173 | ```shell
174 | kubectl edit gss stable-diffusion-cpu -n default
175 | ```
176 |
177 | 需要注意的是:
178 |
179 | - 避免更改`replicas`与`reserveGameServerIds`字段。
180 | - `gameServerTemplate`下的字段更新后,该模版下的实例将重启,会对正在使用的用户造成影响。【若存在无感升级的需求可以发布issue,我们将后续补充相关功能】
181 |
182 | ## 模版删除
183 |
184 | 当平台决定下线某版本模型时,可进行模版删除。
185 | 需要注意的是,删除模版后,对应的所有实例也将删除。
186 | 此外,管理员需要手动进行用户数据清理动作。
187 | 具体操作如下:
188 |
189 | 1. 在Kubernetes集群中删除GameServerSet对象,如删除上述部署在`default`命名空间的`stable-diffusion-cpu`
190 |
191 | ```shell
192 | kubectl delete gss stable-diffusion-cpu -n default
193 | ```
194 |
195 | 2. 清理所有用户相关元数据
196 |
197 | 登录logto admin Dashboard,进入用户管理(User Management),依次遍历所有用户,找到Custom data,删除对应模版下的所有信息,点击保存。如图所示:
198 |
199 | 
200 | 
201 |
202 | 【若存在自动清理的需求可以发布issue,我们将后续补充相关功能】
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/CloudNativeGame/aigc-gateway
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/gin-contrib/sessions v0.0.5
7 | github.com/gin-contrib/static v0.0.1
8 | github.com/gin-gonic/gin v1.9.0
9 | github.com/logto-io/go v1.0.1
10 | github.com/openkruise/kruise-game v0.3.0
11 | k8s.io/apimachinery v0.27.1
12 | k8s.io/client-go v0.27.1
13 | k8s.io/utils v0.0.0-20230209194617-a36077c30491
14 | sigs.k8s.io/controller-runtime v0.12.1
15 | )
16 |
17 | require (
18 | github.com/bytedance/sonic v1.8.0 // indirect
19 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
20 | github.com/davecgh/go-spew v1.1.1 // indirect
21 | github.com/emicklei/go-restful/v3 v3.9.0 // indirect
22 | github.com/evanphx/json-patch v4.12.0+incompatible // indirect
23 | github.com/gin-contrib/sse v0.1.0 // indirect
24 | github.com/go-logr/logr v1.2.3 // indirect
25 | github.com/go-openapi/jsonpointer v0.19.6 // indirect
26 | github.com/go-openapi/jsonreference v0.20.1 // indirect
27 | github.com/go-openapi/swag v0.22.3 // indirect
28 | github.com/go-playground/locales v0.14.1 // indirect
29 | github.com/go-playground/universal-translator v0.18.1 // indirect
30 | github.com/go-playground/validator/v10 v10.11.2 // indirect
31 | github.com/goccy/go-json v0.10.0 // indirect
32 | github.com/gogo/protobuf v1.3.2 // indirect
33 | github.com/golang/protobuf v1.5.3 // indirect
34 | github.com/google/gnostic v0.5.7-v3refs // indirect
35 | github.com/google/go-cmp v0.5.9 // indirect
36 | github.com/google/gofuzz v1.1.0 // indirect
37 | github.com/google/uuid v1.3.0 // indirect
38 | github.com/gorilla/context v1.1.1 // indirect
39 | github.com/gorilla/securecookie v1.1.1 // indirect
40 | github.com/gorilla/sessions v1.2.1 // indirect
41 | github.com/imdario/mergo v0.3.12 // indirect
42 | github.com/josharian/intern v1.0.0 // indirect
43 | github.com/json-iterator/go v1.1.12 // indirect
44 | github.com/klauspost/cpuid/v2 v2.0.9 // indirect
45 | github.com/leodido/go-urn v1.2.1 // indirect
46 | github.com/mailru/easyjson v0.7.7 // indirect
47 | github.com/mattn/go-isatty v0.0.17 // indirect
48 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
49 | github.com/modern-go/reflect2 v1.0.2 // indirect
50 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
51 | github.com/openkruise/kruise-api v1.3.0 // indirect
52 | github.com/pelletier/go-toml/v2 v2.0.6 // indirect
53 | github.com/pkg/errors v0.9.1 // indirect
54 | github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b // indirect
55 | github.com/spf13/pflag v1.0.5 // indirect
56 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
57 | github.com/ugorji/go/codec v1.2.9 // indirect
58 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
59 | golang.org/x/crypto v0.5.0 // indirect
60 | golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
61 | golang.org/x/net v0.8.0 // indirect
62 | golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
63 | golang.org/x/sys v0.6.0 // indirect
64 | golang.org/x/term v0.6.0 // indirect
65 | golang.org/x/text v0.8.0 // indirect
66 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
67 | google.golang.org/appengine v1.6.7 // indirect
68 | google.golang.org/protobuf v1.28.1 // indirect
69 | gopkg.in/inf.v0 v0.9.1 // indirect
70 | gopkg.in/square/go-jose.v2 v2.6.0 // indirect
71 | gopkg.in/yaml.v2 v2.4.0 // indirect
72 | gopkg.in/yaml.v3 v3.0.1 // indirect
73 | k8s.io/api v0.27.1 // indirect
74 | k8s.io/klog/v2 v2.90.1 // indirect
75 | k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a // indirect
76 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
77 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
78 | sigs.k8s.io/yaml v1.3.0 // indirect
79 | )
80 |
81 | replace github.com/logto-io/go v1.0.1 => github.com/CloudNativeGame/logto-go v0.0.1
82 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
9 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
10 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
11 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
12 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
13 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
14 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
15 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
16 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
17 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
18 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
19 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
20 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
21 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
22 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
23 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
24 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
25 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
26 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
27 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
28 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
29 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
30 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
31 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
32 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
33 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
34 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
35 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
36 | github.com/CloudNativeGame/logto-go v0.0.1 h1:obbHUPFneigxB+Z02AP41YMjfE0u7P1L66XczPV8fFk=
37 | github.com/CloudNativeGame/logto-go v0.0.1/go.mod h1:kygmGSqVB8i9Dl6GOc8bPLGuPygaCID3ky5/UDOBljo=
38 | github.com/agiledragon/gomonkey/v2 v2.9.0 h1:PDiKKybR596O6FHW+RVSG0Z7uGCBNbmbUXh3uCNQ7Hc=
39 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
40 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
41 | github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA=
42 | github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
43 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
44 | github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
45 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
46 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
47 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
48 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
49 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
50 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
51 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
52 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
53 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
54 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
55 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
56 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
57 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
58 | github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE=
59 | github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
60 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
61 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
62 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
63 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
64 | github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
65 | github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
66 | github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
67 | github.com/gin-contrib/sessions v0.0.5 h1:CATtfHmLMQrMNpJRgzjWXD7worTh7g7ritsQfmF+0jE=
68 | github.com/gin-contrib/sessions v0.0.5/go.mod h1:vYAuaUPqie3WUSsft6HUlCjlwwoJQs97miaG2+7neKY=
69 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
70 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
71 | github.com/gin-contrib/static v0.0.1 h1:JVxuvHPuUfkoul12N7dtQw7KRn/pSMq7Ue1Va9Swm1U=
72 | github.com/gin-contrib/static v0.0.1/go.mod h1:CSxeF+wep05e0kCOsqWdAWbSszmc31zTIbD8TvWl7Hs=
73 | github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
74 | github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
75 | github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
76 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
77 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
78 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
79 | github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
80 | github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
81 | github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
82 | github.com/go-logr/zapr v1.2.0 h1:n4JnPI1T3Qq1SFEi/F8rwLrZERp2bso19PJZDB9dayk=
83 | github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
84 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
85 | github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8=
86 | github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
87 | github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
88 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
89 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
90 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
91 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
92 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
93 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
94 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
95 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
96 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
97 | github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
98 | github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
99 | github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
100 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
101 | github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
102 | github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
103 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
104 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
105 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
106 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
107 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
108 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
109 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
110 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
111 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
112 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
113 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
114 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
115 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
116 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
117 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
118 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
119 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
120 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
121 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
122 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
123 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
124 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
125 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
126 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
127 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
128 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
129 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
130 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
131 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
132 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
133 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
134 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
135 | github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54=
136 | github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ=
137 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
138 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
139 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
140 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
141 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
142 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
143 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
144 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
145 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
146 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
147 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
148 | github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
149 | github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
150 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
151 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
152 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
153 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
154 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
155 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
156 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
157 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
158 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
159 | github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
160 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
161 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
162 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
163 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
164 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
165 | github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
166 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
167 | github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
168 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
169 | github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
170 | github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
171 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
172 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
173 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
174 | github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
175 | github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
176 | github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc=
177 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
178 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
179 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
180 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
181 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
182 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
183 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
184 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
185 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
186 | github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
187 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
188 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
189 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
190 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
191 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
192 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
193 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
194 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
195 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
196 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
197 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
198 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
199 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
200 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
201 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
202 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
203 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
204 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
205 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
206 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
207 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
208 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
209 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
210 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
211 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
212 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
213 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
214 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
215 | github.com/onsi/ginkgo/v2 v2.9.1 h1:zie5Ly042PD3bsCvsSOPvRnFwyo3rKe64TJlD6nu0mk=
216 | github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E=
217 | github.com/openkruise/kruise-api v1.3.0 h1:yfEy64uXgSuX/5RwePLbwUK/uX8RRM8fHJkccel5ZIQ=
218 | github.com/openkruise/kruise-api v1.3.0/go.mod h1:9ZX+ycdHKNzcA5ezAf35xOa2Mwfa2BYagWr0lKgi5dU=
219 | github.com/openkruise/kruise-game v0.3.0 h1:/bP5FFIoVF4zaegUEnPVoSARbj1VvxURMoV8WNcuXhM=
220 | github.com/openkruise/kruise-game v0.3.0/go.mod h1:cqcBfUyuOJFo2lBHUNY9Dvz1HOEa1pWsmWzDQCvrp9I=
221 | github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
222 | github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
223 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
224 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
225 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
226 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
227 | github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
228 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
229 | github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
230 | github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
231 | github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
232 | github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b h1:aUNXCGgukb4gtY99imuIeoh8Vr0GSwAlYxPAhqZrpFc=
233 | github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
234 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
235 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
236 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
237 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
238 | github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
239 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
240 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
241 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
242 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
243 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
244 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
245 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
246 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
247 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
248 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
249 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
250 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
251 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
252 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
253 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
254 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
255 | github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
256 | github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
257 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
258 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
259 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
260 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
261 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
262 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
263 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
264 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
265 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
266 | go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
267 | go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
268 | go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI=
269 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
270 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
271 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
272 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
273 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
274 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
275 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
276 | golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
277 | golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
278 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
279 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
280 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
281 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
282 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
283 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
284 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
285 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
286 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
287 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
288 | golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
289 | golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
290 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
291 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
292 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
293 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
294 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
295 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
296 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
297 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
298 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
299 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
300 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
301 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
302 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
303 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
304 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
305 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
306 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
307 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
308 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
309 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
310 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
311 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
312 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
313 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
314 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
315 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
316 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
317 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
318 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
319 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
320 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
321 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
322 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
323 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
324 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
325 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
326 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
327 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
328 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
329 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
330 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
331 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
332 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
333 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
334 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
335 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
336 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
337 | golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
338 | golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
339 | golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
340 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
341 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
342 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
343 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
344 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
345 | golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg=
346 | golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
347 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
348 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
349 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
350 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
351 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
352 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
353 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
354 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
355 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
356 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
357 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
358 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
359 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
360 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
361 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
362 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
363 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
364 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
365 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
366 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
367 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
368 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
369 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
370 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
371 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
372 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
373 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
374 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
375 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
376 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
377 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
378 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
379 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
380 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
381 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
382 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
383 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
384 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
385 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
386 | golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
387 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
388 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
389 | golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
390 | golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
391 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
392 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
393 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
394 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
395 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
396 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
397 | golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
398 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
399 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
400 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
401 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
402 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
403 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
404 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
405 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
406 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
407 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
408 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
409 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
410 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
411 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
412 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
413 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
414 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
415 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
416 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
417 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
418 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
419 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
420 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
421 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
422 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
423 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
424 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
425 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
426 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
427 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
428 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
429 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
430 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
431 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
432 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
433 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
434 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
435 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
436 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
437 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
438 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
439 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
440 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
441 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
442 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
443 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
444 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
445 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
446 | golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
447 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
448 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
449 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
450 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
451 | gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY=
452 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
453 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
454 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
455 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
456 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
457 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
458 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
459 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
460 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
461 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
462 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
463 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
464 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
465 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
466 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
467 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
468 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
469 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
470 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
471 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
472 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
473 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
474 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
475 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
476 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
477 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
478 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
479 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
480 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
481 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
482 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
483 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
484 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
485 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
486 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
487 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
488 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
489 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
490 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
491 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
492 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
493 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
494 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
495 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
496 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
497 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
498 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
499 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
500 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
501 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
502 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
503 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
504 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
505 | google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
506 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
507 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
508 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
509 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
510 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
511 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
512 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
513 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
514 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
515 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
516 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
517 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
518 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
519 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
520 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
521 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
522 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
523 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
524 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
525 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
526 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
527 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
528 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
529 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
530 | google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
531 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
532 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
533 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
534 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
535 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
536 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
537 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
538 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
539 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
540 | gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
541 | gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
542 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
543 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
544 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
545 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
546 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
547 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
548 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
549 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
550 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
551 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
552 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
553 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
554 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
555 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
556 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
557 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
558 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
559 | k8s.io/api v0.27.1 h1:Z6zUGQ1Vd10tJ+gHcNNNgkV5emCyW+v2XTmn+CLjSd0=
560 | k8s.io/api v0.27.1/go.mod h1:z5g/BpAiD+f6AArpqNjkY+cji8ueZDU/WV1jcj5Jk4E=
561 | k8s.io/apiextensions-apiserver v0.24.0 h1:JfgFqbA8gKJ/uDT++feAqk9jBIwNnL9YGdQvaI9DLtY=
562 | k8s.io/apimachinery v0.27.1 h1:EGuZiLI95UQQcClhanryclaQE6xjg1Bts6/L3cD7zyc=
563 | k8s.io/apimachinery v0.27.1/go.mod h1:5ikh59fK3AJ287GUvpUsryoMFtH9zj/ARfWCo3AyXTM=
564 | k8s.io/client-go v0.27.1 h1:oXsfhW/qncM1wDmWBIuDzRHNS2tLhK3BZv512Nc59W8=
565 | k8s.io/client-go v0.27.1/go.mod h1:f8LHMUkVb3b9N8bWturc+EDtVVVwZ7ueTVquFAJb2vA=
566 | k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw=
567 | k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
568 | k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a h1:gmovKNur38vgoWfGtP5QOGNOA7ki4n6qNYoFAgMlNvg=
569 | k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a/go.mod h1:y5VtZWM9sHHc2ZodIH/6SHzXj+TPU5USoA8lcIeKEKY=
570 | k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY=
571 | k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
572 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
573 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
574 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
575 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
576 | sigs.k8s.io/controller-runtime v0.12.1 h1:4BJY01xe9zKQti8oRjj/NeHKRXthf1YkYJAgLONFFoI=
577 | sigs.k8s.io/controller-runtime v0.12.1/go.mod h1:BKhxlA4l7FPK4AQcsuL4X6vZeWnKDXez/vp1Y8dxTU0=
578 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
579 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
580 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
581 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
582 | sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
583 | sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
584 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/CloudNativeGame/aigc-gateway/pkg/routers"
5 | "github.com/CloudNativeGame/aigc-gateway/pkg/utils"
6 | "github.com/gin-contrib/sessions"
7 | "github.com/gin-contrib/sessions/memstore"
8 | "github.com/gin-contrib/static"
9 | "github.com/gin-gonic/gin"
10 | "github.com/logto-io/go/client"
11 | "os"
12 | )
13 |
14 | func main() {
15 | router := gin.Default()
16 | // load templates
17 | router.Delims("{[{", "}]}")
18 | router.LoadHTMLGlob("aigc-dashboard/dist/*.html")
19 | router.Use(static.Serve("/assets", static.LocalFile("aigc-dashboard/dist/assets", true)))
20 |
21 | endpoint := os.Getenv("Endpoint")
22 | logtoConfig := &client.LogtoConfig{
23 |
24 | Endpoint: endpoint,
25 | AppId: os.Getenv("App_Id"),
26 | AppSecret: os.Getenv("App_Secret"),
27 | Scopes: []string{"email", "custom_data"},
28 | }
29 | // We use memory-based session in this example
30 | store := memstore.NewStore([]byte("your session secret"))
31 | store.Options(sessions.Options{
32 | Domain: utils.GetDomainFromEndpoint(endpoint),
33 | Path: "/",
34 | MaxAge: 604800,
35 | })
36 | router.Use(sessions.Sessions("logto-session", store))
37 | routers.RegisterSignRouters(router, logtoConfig)
38 | routers.RegisterResourceRouters(router, logtoConfig)
39 |
40 | router.Run(":8090")
41 | }
42 |
--------------------------------------------------------------------------------
/pkg/controller/controller.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | // add informer policy to update resources.
4 |
--------------------------------------------------------------------------------
/pkg/resources/error.go:
--------------------------------------------------------------------------------
1 | package resources
2 |
3 | import "fmt"
4 |
5 | type ResourceErrorType string
6 |
7 | const (
8 | ApiCallError ResourceErrorType = "ApiCallError"
9 | InternalError ResourceErrorType = "InternalError"
10 | ParameterError ResourceErrorType = "ParameterError"
11 | NotFoundError ResourceErrorType = "NotFoundError"
12 | )
13 |
14 | type ErrorReasonType string
15 |
16 | const (
17 | PauseReason = "Instance paused"
18 | NotExistReason = "Instance not existed"
19 | ResourceMetaNullReason = "ResourceMeta is Null"
20 | IdNullReason = "ID is Null"
21 | IdNotIntegerReason = "ID is not Integer"
22 | NameNullReason = "Name is Null"
23 | NamespaceNullReason = "Namespace is Null"
24 | )
25 |
26 | type ResourceError interface {
27 | Error() string
28 |
29 | Reason() ErrorReasonType
30 |
31 | Type() ResourceErrorType
32 | }
33 |
34 | type ResourceErrorImplErrorImpl struct {
35 | errorType ResourceErrorType
36 | msg string
37 | reason ErrorReasonType
38 | }
39 |
40 | func (r ResourceErrorImplErrorImpl) Error() string {
41 | return r.msg
42 | }
43 |
44 | func (r ResourceErrorImplErrorImpl) Reason() ErrorReasonType {
45 | return r.reason
46 | }
47 |
48 | func (r ResourceErrorImplErrorImpl) Type() ResourceErrorType {
49 | return r.errorType
50 | }
51 |
52 | func IsResourceError(err error) bool {
53 | _, is := err.(ResourceErrorImplErrorImpl)
54 | return is
55 | }
56 |
57 | func IsNotFoundError(err error) bool {
58 | re, is := err.(ResourceErrorImplErrorImpl)
59 | if !is {
60 | return false
61 | }
62 | if re.Type() == NotFoundError {
63 | return true
64 | }
65 | return false
66 | }
67 |
68 | func IsApiCallError(err error) bool {
69 | re, is := err.(ResourceErrorImplErrorImpl)
70 | if !is {
71 | return false
72 | }
73 | if re.Type() == ApiCallError {
74 | return true
75 | }
76 | return false
77 | }
78 |
79 | func IsInternalError(err error) bool {
80 | re, is := err.(ResourceErrorImplErrorImpl)
81 | if !is {
82 | return false
83 | }
84 | if re.Type() == InternalError {
85 | return true
86 | }
87 | return false
88 | }
89 |
90 | func IsParameterError(err error) bool {
91 | re, is := err.(ResourceErrorImplErrorImpl)
92 | if !is {
93 | return false
94 | }
95 | if re.Type() == ParameterError {
96 | return true
97 | }
98 | return false
99 | }
100 |
101 | func GetErrorType(err error) ResourceErrorType {
102 | return err.(ResourceErrorImplErrorImpl).Type()
103 | }
104 |
105 | func GetErrorReason(err error) ErrorReasonType {
106 | return err.(ResourceErrorImplErrorImpl).Reason()
107 | }
108 |
109 | func NewResourceError(errorType ResourceErrorType, errorReason ErrorReasonType, msg string, args ...interface{}) ResourceError {
110 | return ResourceErrorImplErrorImpl{
111 | errorType: errorType,
112 | reason: errorReason,
113 | msg: fmt.Sprintf(msg, args...),
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/pkg/resources/error_test.go:
--------------------------------------------------------------------------------
1 | package resources
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | )
7 |
8 | func TestIsResourceError(t *testing.T) {
9 | tests := []struct {
10 | err error
11 | is bool
12 | }{
13 | {
14 | err: NewResourceError("", "", ""),
15 | is: true,
16 | },
17 | {
18 | err: fmt.Errorf("xxx"),
19 | is: false,
20 | },
21 | }
22 |
23 | for caseI, test := range tests {
24 | actual := IsResourceError(test.err)
25 | expect := test.is
26 | if actual != expect {
27 | t.Errorf("case %d: whether test err is ResourceError should be %v", caseI, expect)
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/pkg/resources/resource.go:
--------------------------------------------------------------------------------
1 | package resources
2 |
3 | import v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
4 |
5 | type Resource interface {
6 | v1.Object
7 | }
8 |
9 | type ResourceMeta struct {
10 | Name string
11 | Namespace string
12 | ID string
13 | Type string
14 | }
15 |
--------------------------------------------------------------------------------
/pkg/resources/resource_manager.go:
--------------------------------------------------------------------------------
1 | package resources
2 |
3 | import (
4 | "context"
5 | gamekruiseiov1alpha1 "github.com/openkruise/kruise-game/apis/v1alpha1"
6 | "github.com/openkruise/kruise-game/pkg/util"
7 | v1 "k8s.io/api/core/v1"
8 | "k8s.io/apimachinery/pkg/api/errors"
9 | "k8s.io/apimachinery/pkg/labels"
10 | "k8s.io/apimachinery/pkg/types"
11 | "k8s.io/client-go/kubernetes/scheme"
12 | "k8s.io/utils/pointer"
13 | "sigs.k8s.io/controller-runtime/pkg/client"
14 | "sigs.k8s.io/controller-runtime/pkg/client/config"
15 | "strconv"
16 | )
17 |
18 | // ResourceManager conteroller
19 | type ResourceManager struct {
20 | client.Client
21 | }
22 |
23 | func (rm *ResourceManager) ListResources(namespaces []string, resourcesLabels map[string]string) ([]Resource, error) {
24 | var resources []Resource
25 |
26 | if len(namespaces) == 0 {
27 | gssList := &gamekruiseiov1alpha1.GameServerSetList{}
28 | err := rm.List(context.Background(), gssList, &client.ListOptions{
29 | LabelSelector: labels.SelectorFromSet(resourcesLabels),
30 | })
31 | if err != nil {
32 | return nil, NewResourceError(ApiCallError, "", err.Error())
33 | }
34 |
35 | for _, gss := range gssList.Items {
36 | resources = append(resources, gss.DeepCopy())
37 | }
38 | }
39 |
40 | for _, ns := range namespaces {
41 | listOptions := client.ListOptions{
42 | Namespace: ns,
43 | LabelSelector: labels.SelectorFromSet(resourcesLabels),
44 | }
45 | gssList := &gamekruiseiov1alpha1.GameServerSetList{}
46 | err := rm.List(context.Background(), gssList, &listOptions)
47 | if err != nil {
48 | return nil, NewResourceError(ApiCallError, "", err.Error())
49 | }
50 | for _, gss := range gssList.Items {
51 | resources = append(resources, gss.DeepCopy())
52 | }
53 | }
54 |
55 | return resources, nil
56 | }
57 |
58 | func (rm *ResourceManager) GetResource(meta *ResourceMeta) (Resource, error) {
59 | if err := checkResourceMeta(meta, &metaNeed{ID: true, Name: true, Namespace: true}); err != nil {
60 | return nil, err
61 | }
62 |
63 | gs := &gamekruiseiov1alpha1.GameServer{}
64 | err := rm.Get(context.Background(), types.NamespacedName{
65 | Name: meta.Name + "-" + meta.ID,
66 | Namespace: meta.Namespace,
67 | }, gs)
68 | if err != nil {
69 | if errors.IsNotFound(err) {
70 | gss := &gamekruiseiov1alpha1.GameServerSet{}
71 | err := rm.Get(context.Background(), types.NamespacedName{
72 | Name: meta.Name,
73 | Namespace: meta.Namespace,
74 | }, gss)
75 | if err != nil {
76 | return nil, NewResourceError(ApiCallError, "", err.Error())
77 | }
78 | idInt, _ := strconv.Atoi(meta.ID)
79 | if util.IsNumInList(idInt, gss.Spec.ReserveGameServerIds) {
80 | return nil, NewResourceError(NotFoundError, PauseReason, "")
81 | } else {
82 | return nil, NewResourceError(NotFoundError, NotExistReason, "")
83 | }
84 | }
85 | return nil, NewResourceError(ApiCallError, "", err.Error())
86 | }
87 |
88 | return gs, nil
89 | }
90 |
91 | func (rm *ResourceManager) GetResourceEndpoint(meta *ResourceMeta) (string, error) {
92 | gs := &gamekruiseiov1alpha1.GameServer{}
93 | err := rm.Get(context.Background(), types.NamespacedName{
94 | Name: meta.Name + "-" + meta.ID,
95 | Namespace: meta.Namespace,
96 | }, gs)
97 | if err != nil {
98 | if errors.IsNotFound(err) {
99 | return "", nil
100 | }
101 | return "", NewResourceError(ApiCallError, "", err.Error())
102 | }
103 | if len(gs.Status.NetworkStatus.ExternalAddresses) == 0 {
104 | return "", nil
105 | }
106 |
107 | return gs.Status.NetworkStatus.ExternalAddresses[0].EndPoint, nil
108 | }
109 |
110 | func (rm *ResourceManager) CreateResource(meta *ResourceMeta) (*ResourceMeta, error) {
111 | if err := checkResourceMeta(meta, &metaNeed{Name: true, Namespace: true}); err != nil {
112 | return nil, err
113 | }
114 |
115 | // get GameServerSet
116 | gss := &gamekruiseiov1alpha1.GameServerSet{}
117 | err := rm.Get(context.Background(), types.NamespacedName{
118 | Name: meta.Name,
119 | Namespace: meta.Namespace,
120 | }, gss)
121 | if err != nil {
122 | return nil, NewResourceError(ApiCallError, "", err.Error())
123 | }
124 |
125 | newId := len(gss.Spec.ReserveGameServerIds) + int(*gss.Spec.Replicas)
126 |
127 | // update GameServerSet
128 | gss.Spec.Replicas = pointer.Int32(*gss.Spec.Replicas + 1)
129 | err = rm.Update(context.Background(), gss)
130 | if err != nil {
131 | return nil, NewResourceError(ApiCallError, "", err.Error())
132 | }
133 |
134 | return &ResourceMeta{
135 | Namespace: meta.Namespace,
136 | Name: meta.Name,
137 | ID: strconv.Itoa(newId),
138 | }, nil
139 | }
140 |
141 | func (rm *ResourceManager) PauseResource(meta *ResourceMeta) error {
142 | if err := checkResourceMeta(meta, &metaNeed{ID: true, Name: true, Namespace: true}); err != nil {
143 | return err
144 | }
145 |
146 | // get GameServerSet
147 | gss := &gamekruiseiov1alpha1.GameServerSet{}
148 | err := rm.Get(context.Background(), types.NamespacedName{
149 | Name: meta.Name,
150 | Namespace: meta.Namespace,
151 | }, gss)
152 | if err != nil {
153 | return NewResourceError(ApiCallError, "", err.Error())
154 | }
155 |
156 | idInt, _ := strconv.Atoi(meta.ID)
157 | // check if already paused
158 | if util.IsNumInList(idInt, gss.Spec.ReserveGameServerIds) {
159 | return NewResourceError(NotFoundError, PauseReason, "")
160 | }
161 |
162 | // update GameServerSet
163 | gss.Spec.Replicas = pointer.Int32(*gss.Spec.Replicas - 1)
164 | gss.Spec.ReserveGameServerIds = append(gss.Spec.ReserveGameServerIds, []int{idInt}...)
165 | err = rm.Update(context.Background(), gss)
166 | if err != nil {
167 | return err
168 | }
169 |
170 | return nil
171 | }
172 |
173 | func (rm *ResourceManager) RecoverResource(meta *ResourceMeta) (Resource, error) {
174 | if err := checkResourceMeta(meta, &metaNeed{ID: true, Name: true, Namespace: true}); err != nil {
175 | return nil, err
176 | }
177 |
178 | // get GameServer
179 | gs := &gamekruiseiov1alpha1.GameServer{}
180 | err := rm.Get(context.Background(), types.NamespacedName{
181 | Name: meta.Name + "-" + meta.ID,
182 | Namespace: meta.Namespace,
183 | }, gs)
184 | if err != nil {
185 | if errors.IsNotFound(err) {
186 | // get GameServerSet
187 | gss := &gamekruiseiov1alpha1.GameServerSet{}
188 | err = rm.Get(context.Background(), types.NamespacedName{
189 | Name: meta.Name,
190 | Namespace: meta.Namespace,
191 | }, gss)
192 | if err != nil {
193 | return nil, NewResourceError(ApiCallError, "", err.Error())
194 | }
195 |
196 | idInt, _ := strconv.Atoi(meta.ID)
197 | if util.IsNumInList(idInt, gss.Spec.ReserveGameServerIds) {
198 | // update GameServerSet
199 | gss.Spec.Replicas = pointer.Int32(*gss.Spec.Replicas + 1)
200 | idInt, _ := strconv.Atoi(meta.ID)
201 | gss.Spec.ReserveGameServerIds = util.GetSliceInANotInB(gss.Spec.ReserveGameServerIds, []int{idInt})
202 | err = rm.Update(context.Background(), gss)
203 | if err != nil {
204 | return nil, NewResourceError(ApiCallError, "", err.Error())
205 | }
206 | return nil, NewResourceError(NotFoundError, PauseReason, "")
207 | }
208 |
209 | return nil, NewResourceError(NotFoundError, NotExistReason, "")
210 | }
211 |
212 | return nil, NewResourceError(ApiCallError, "", err.Error())
213 | }
214 |
215 | return gs, nil
216 | }
217 |
218 | func (rm *ResourceManager) DeleteResource(meta *ResourceMeta) error {
219 | if err := checkResourceMeta(meta, &metaNeed{ID: true, Name: true, Namespace: true}); err != nil {
220 | return err
221 | }
222 |
223 | // get GameServerSet
224 | gss := &gamekruiseiov1alpha1.GameServerSet{}
225 | err := rm.Get(context.Background(), types.NamespacedName{
226 | Name: meta.Name,
227 | Namespace: meta.Namespace,
228 | }, gss)
229 | if err != nil {
230 | return NewResourceError(ApiCallError, "", err.Error())
231 | }
232 |
233 | idInt, _ := strconv.Atoi(meta.ID)
234 | // check if gs exist or not
235 | if !util.IsNumInList(idInt, gss.Spec.ReserveGameServerIds) {
236 | // update GameServerSet to delete gs
237 | gss.Spec.Replicas = pointer.Int32(*gss.Spec.Replicas - 1)
238 | gss.Spec.ReserveGameServerIds = append(gss.Spec.ReserveGameServerIds, []int{idInt}...)
239 | err = rm.Update(context.Background(), gss)
240 | if err != nil {
241 | return err
242 | }
243 | }
244 |
245 | // delete pvcs related to gss
246 | for _, vct := range gss.Spec.GameServerTemplate.VolumeClaimTemplates {
247 | pvc := &v1.PersistentVolumeClaim{}
248 | err = rm.Get(context.Background(), types.NamespacedName{
249 | Name: vct.GetName() + "-" + meta.Name + "-" + meta.ID,
250 | Namespace: meta.Namespace,
251 | }, pvc)
252 | if err != nil {
253 | if errors.IsNotFound(err) {
254 | return nil
255 | }
256 | return NewResourceError(ApiCallError, "", err.Error())
257 | }
258 | err = rm.Delete(context.Background(), pvc)
259 | if err != nil && !errors.IsNotFound(err) {
260 | return NewResourceError(ApiCallError, "", err.Error())
261 | }
262 | }
263 |
264 | return nil
265 | }
266 |
267 | func (rm *ResourceManager) RestartResource(meta *ResourceMeta) error {
268 | if err := checkResourceMeta(meta, &metaNeed{ID: true, Name: true, Namespace: true}); err != nil {
269 | return err
270 | }
271 |
272 | // delete pod
273 | pod := &v1.Pod{}
274 | err := rm.Get(context.Background(), types.NamespacedName{
275 | Name: meta.Name + "-" + meta.ID,
276 | Namespace: meta.Namespace,
277 | }, pod)
278 | if err != nil {
279 | if errors.IsNotFound(err) {
280 | return nil
281 | }
282 | return NewResourceError(ApiCallError, "", err.Error())
283 | }
284 | err = rm.Delete(context.Background(), pod)
285 | if err != nil && !errors.IsNotFound(err) {
286 | return NewResourceError(ApiCallError, "", err.Error())
287 | }
288 |
289 | return nil
290 | }
291 |
292 | type metaNeed struct {
293 | ID bool
294 | Name bool
295 | Namespace bool
296 | }
297 |
298 | func checkResourceMeta(meta *ResourceMeta, metaNeed *metaNeed) error {
299 | if meta == nil {
300 | return NewResourceError(ParameterError, ResourceMetaNullReason, "%s: %s", ParameterError, ResourceMetaNullReason)
301 | }
302 |
303 | if metaNeed.Namespace && meta.Namespace == "" {
304 | return NewResourceError(ParameterError, NamespaceNullReason, "%s: %s", ParameterError, NamespaceNullReason)
305 | }
306 |
307 | if metaNeed.Name && meta.Name == "" {
308 | return NewResourceError(ParameterError, NameNullReason, "%s: %s", ParameterError, NameNullReason)
309 | }
310 |
311 | if metaNeed.ID {
312 | if meta.ID == "" {
313 | return NewResourceError(ParameterError, IdNullReason, "%s: %s", ParameterError, IdNullReason)
314 | }
315 | _, err := strconv.Atoi(meta.ID)
316 | if err != nil {
317 | return NewResourceError(ParameterError, IdNotIntegerReason, "%s: %s", ParameterError, IdNotIntegerReason)
318 | }
319 | }
320 |
321 | return nil
322 | }
323 |
324 | var rm *ResourceManager
325 |
326 | func NewResourceManager() *ResourceManager {
327 | return rm
328 | }
329 |
330 | func init() {
331 | cfg := config.GetConfigOrDie()
332 |
333 | c, err := client.New(cfg, client.Options{})
334 | if err != nil {
335 | panic(err)
336 | }
337 | rm = &ResourceManager{
338 | Client: c,
339 | }
340 | gamekruiseiov1alpha1.AddToScheme(scheme.Scheme)
341 | }
342 |
--------------------------------------------------------------------------------
/pkg/resources/resource_manager_test.go:
--------------------------------------------------------------------------------
1 | package resources
2 |
3 | import (
4 | "context"
5 | gameKruiseV1alpha1 "github.com/openkruise/kruise-game/apis/v1alpha1"
6 | "github.com/openkruise/kruise-game/pkg/util"
7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8 | "k8s.io/apimachinery/pkg/runtime"
9 | "k8s.io/apimachinery/pkg/types"
10 | utilruntime "k8s.io/apimachinery/pkg/util/runtime"
11 | "k8s.io/utils/pointer"
12 | "reflect"
13 | "sigs.k8s.io/controller-runtime/pkg/client"
14 | "sigs.k8s.io/controller-runtime/pkg/client/fake"
15 | "testing"
16 | )
17 |
18 | var (
19 | schemeTest = runtime.NewScheme()
20 | )
21 |
22 | func init() {
23 | utilruntime.Must(gameKruiseV1alpha1.AddToScheme(schemeTest))
24 | }
25 |
26 | func TestResourceManager_checkResourceMeta(t *testing.T) {
27 | tests := []struct {
28 | meta *ResourceMeta
29 | metaNeed *metaNeed
30 | err error
31 | }{
32 | {
33 | meta: &ResourceMeta{
34 | Name: "gss-name",
35 | Namespace: "gss-ns",
36 | ID: "6",
37 | },
38 | metaNeed: &metaNeed{
39 | Name: true,
40 | Namespace: true,
41 | ID: true,
42 | },
43 | err: nil,
44 | },
45 | {
46 | meta: &ResourceMeta{
47 | Name: "gss-name",
48 | Namespace: "gss-ns",
49 | ID: "f",
50 | },
51 | metaNeed: &metaNeed{
52 | Name: true,
53 | Namespace: true,
54 | ID: true,
55 | },
56 | err: NewResourceError(ParameterError, IdNotIntegerReason, "%s: %s", ParameterError, IdNotIntegerReason),
57 | },
58 | {
59 | meta: &ResourceMeta{
60 | Name: "gss-name",
61 | Namespace: "gss-ns",
62 | },
63 | metaNeed: &metaNeed{
64 | Name: true,
65 | Namespace: true,
66 | },
67 | err: nil,
68 | },
69 | }
70 |
71 | for i, test := range tests {
72 | err := checkResourceMeta(test.meta, test.metaNeed)
73 | if !reflect.DeepEqual(test.err, err) {
74 | t.Errorf("case %d: expect err is: %s, but actual err is: %s", i, test.err.Error(), err.Error())
75 | }
76 | }
77 | }
78 |
79 | func TestResourceManager_ListResources(t *testing.T) {
80 | tests := []struct {
81 | namespaces []string
82 | resourcesLabels map[string]string
83 | gssList []*gameKruiseV1alpha1.GameServerSet
84 | selectedNum []int
85 | }{
86 | {
87 | gssList: []*gameKruiseV1alpha1.GameServerSet{
88 | {
89 | ObjectMeta: metav1.ObjectMeta{
90 | Namespace: "xxx",
91 | Name: "gss-0",
92 | ResourceVersion: "999",
93 | Labels: map[string]string{"types": "cv"},
94 | },
95 | Spec: gameKruiseV1alpha1.GameServerSetSpec{
96 | Replicas: pointer.Int32(3),
97 | },
98 | },
99 | },
100 | selectedNum: []int{0},
101 | },
102 | {
103 | namespaces: []string{"xx"},
104 | gssList: []*gameKruiseV1alpha1.GameServerSet{
105 | {
106 | ObjectMeta: metav1.ObjectMeta{
107 | Namespace: "xxx",
108 | Name: "gss-0",
109 | ResourceVersion: "999",
110 | Labels: map[string]string{"types": "cv"},
111 | },
112 | Spec: gameKruiseV1alpha1.GameServerSetSpec{
113 | Replicas: pointer.Int32(3),
114 | },
115 | },
116 | },
117 | selectedNum: nil,
118 | },
119 | {
120 | namespaces: []string{"xxx"},
121 | resourcesLabels: map[string]string{"types": "cv"},
122 | gssList: []*gameKruiseV1alpha1.GameServerSet{
123 | {
124 | ObjectMeta: metav1.ObjectMeta{
125 | Namespace: "xxx",
126 | Name: "gss-0",
127 | ResourceVersion: "999",
128 | Labels: map[string]string{"types": "cv"},
129 | },
130 | Spec: gameKruiseV1alpha1.GameServerSetSpec{
131 | Replicas: pointer.Int32(3),
132 | },
133 | },
134 | {
135 | ObjectMeta: metav1.ObjectMeta{
136 | Namespace: "xxx",
137 | Name: "gss-1",
138 | ResourceVersion: "999",
139 | Labels: map[string]string{"types": "nlp"},
140 | },
141 | Spec: gameKruiseV1alpha1.GameServerSetSpec{
142 | Replicas: pointer.Int32(3),
143 | },
144 | },
145 | {
146 | ObjectMeta: metav1.ObjectMeta{
147 | Namespace: "xxx",
148 | Name: "gss-2",
149 | ResourceVersion: "999",
150 | Labels: map[string]string{"types": "cv"},
151 | },
152 | Spec: gameKruiseV1alpha1.GameServerSetSpec{
153 | Replicas: pointer.Int32(3),
154 | },
155 | },
156 | },
157 | selectedNum: []int{0, 2},
158 | },
159 | {
160 | resourcesLabels: map[string]string{"types": "cv"},
161 | gssList: []*gameKruiseV1alpha1.GameServerSet{
162 | {
163 | ObjectMeta: metav1.ObjectMeta{
164 | Namespace: "xxx",
165 | Name: "gss-0",
166 | ResourceVersion: "999",
167 | Labels: map[string]string{"types": "cv"},
168 | },
169 | Spec: gameKruiseV1alpha1.GameServerSetSpec{
170 | Replicas: pointer.Int32(3),
171 | },
172 | },
173 | {
174 | ObjectMeta: metav1.ObjectMeta{
175 | Namespace: "xx",
176 | Name: "gss-1",
177 | ResourceVersion: "999",
178 | Labels: map[string]string{"types": "nlp"},
179 | },
180 | Spec: gameKruiseV1alpha1.GameServerSetSpec{
181 | Replicas: pointer.Int32(3),
182 | },
183 | },
184 | {
185 | ObjectMeta: metav1.ObjectMeta{
186 | Namespace: "xx",
187 | Name: "gss-2",
188 | ResourceVersion: "999",
189 | Labels: map[string]string{"types": "cv"},
190 | },
191 | Spec: gameKruiseV1alpha1.GameServerSetSpec{
192 | Replicas: pointer.Int32(3),
193 | },
194 | },
195 | },
196 | selectedNum: []int{0, 2},
197 | },
198 | }
199 |
200 | for caseNum, test := range tests {
201 | var objs []client.Object
202 | for _, gss := range test.gssList {
203 | objs = append(objs, gss)
204 | }
205 | c := fake.NewClientBuilder().WithScheme(schemeTest).WithObjects(objs...).Build()
206 | rm := &ResourceManager{Client: c}
207 | actualResources, _ := rm.ListResources(test.namespaces, test.resourcesLabels)
208 | var actualNum []int
209 | for _, resource := range actualResources {
210 | num := util.GetIndexFromGsName(resource.GetName())
211 | actualNum = append(actualNum, num)
212 | }
213 | if !util.IsSliceEqual(actualNum, test.selectedNum) {
214 | t.Errorf("case %d: expect resources: %v, but actual resources: %v", caseNum, test.selectedNum, actualNum)
215 | }
216 | }
217 | }
218 |
219 | func TestResourceManager_GetResource(t *testing.T) {
220 | tests := []struct {
221 | meta *ResourceMeta
222 | gs *gameKruiseV1alpha1.GameServer
223 | isError bool
224 | }{
225 | {
226 | meta: &ResourceMeta{
227 | Namespace: "xxx",
228 | Name: "xxx",
229 | ID: "1",
230 | },
231 | gs: &gameKruiseV1alpha1.GameServer{
232 | ObjectMeta: metav1.ObjectMeta{
233 | Namespace: "xxx",
234 | Name: "xxx-0",
235 | Labels: map[string]string{
236 | gameKruiseV1alpha1.GameServerOwnerGssKey: "xxx",
237 | },
238 | },
239 | },
240 | isError: true,
241 | },
242 | {
243 | meta: &ResourceMeta{
244 | Namespace: "xxx",
245 | Name: "xxx",
246 | },
247 | gs: &gameKruiseV1alpha1.GameServer{
248 | ObjectMeta: metav1.ObjectMeta{
249 | Namespace: "xxx",
250 | Name: "xxx-0",
251 | Labels: map[string]string{
252 | gameKruiseV1alpha1.GameServerOwnerGssKey: "xxx",
253 | },
254 | },
255 | },
256 | isError: true,
257 | },
258 | {
259 | meta: &ResourceMeta{
260 | Namespace: "xxx",
261 | Name: "xxx",
262 | ID: "0",
263 | },
264 | gs: &gameKruiseV1alpha1.GameServer{
265 | ObjectMeta: metav1.ObjectMeta{
266 | Namespace: "xxx",
267 | Name: "xxx-0",
268 | Labels: map[string]string{
269 | gameKruiseV1alpha1.GameServerOwnerGssKey: "xxx",
270 | },
271 | },
272 | },
273 | isError: false,
274 | },
275 | }
276 |
277 | for caseNum, test := range tests {
278 | objs := []client.Object{test.gs}
279 | c := fake.NewClientBuilder().WithScheme(schemeTest).WithObjects(objs...).Build()
280 | rm := &ResourceManager{Client: c}
281 | _, err := rm.GetResource(test.meta)
282 | if !reflect.DeepEqual(test.isError, err != nil) {
283 | t.Errorf("case %d: errs not equal", caseNum)
284 | }
285 | }
286 | }
287 |
288 | func TestResourceManager_CreateResource(t *testing.T) {
289 | tests := []struct {
290 | meta *ResourceMeta
291 | gssBefore *gameKruiseV1alpha1.GameServerSet
292 | newID string
293 | gssAfter *gameKruiseV1alpha1.GameServerSet
294 | }{
295 | {
296 | meta: &ResourceMeta{
297 | Namespace: "xxx",
298 | Name: "case-0",
299 | },
300 | gssBefore: &gameKruiseV1alpha1.GameServerSet{
301 | ObjectMeta: metav1.ObjectMeta{
302 | Namespace: "xxx",
303 | Name: "case-0",
304 | ResourceVersion: "999",
305 | Annotations: map[string]string{gameKruiseV1alpha1.GameServerSetReserveIdsKey: "1"},
306 | },
307 | Spec: gameKruiseV1alpha1.GameServerSetSpec{
308 | Replicas: pointer.Int32(3),
309 | ReserveGameServerIds: []int{1},
310 | },
311 | },
312 | newID: "4",
313 | gssAfter: &gameKruiseV1alpha1.GameServerSet{
314 | TypeMeta: metav1.TypeMeta{
315 | APIVersion: "game.kruise.io/v1alpha1",
316 | Kind: "GameServerSet",
317 | },
318 | ObjectMeta: metav1.ObjectMeta{
319 | Namespace: "xxx",
320 | Name: "case-0",
321 | ResourceVersion: "1000",
322 | Annotations: map[string]string{gameKruiseV1alpha1.GameServerSetReserveIdsKey: "1"},
323 | },
324 | Spec: gameKruiseV1alpha1.GameServerSetSpec{
325 | Replicas: pointer.Int32(4),
326 | ReserveGameServerIds: []int{1},
327 | },
328 | },
329 | },
330 | {
331 | meta: &ResourceMeta{
332 | Namespace: "xxx",
333 | Name: "case-1",
334 | },
335 | gssBefore: &gameKruiseV1alpha1.GameServerSet{
336 | ObjectMeta: metav1.ObjectMeta{
337 | Namespace: "xxx",
338 | Name: "case-1",
339 | ResourceVersion: "999",
340 | Annotations: map[string]string{gameKruiseV1alpha1.GameServerSetReserveIdsKey: "1,2,4"},
341 | },
342 | Spec: gameKruiseV1alpha1.GameServerSetSpec{
343 | Replicas: pointer.Int32(7),
344 | ReserveGameServerIds: []int{1, 2, 4},
345 | },
346 | },
347 | newID: "10",
348 | gssAfter: &gameKruiseV1alpha1.GameServerSet{
349 | TypeMeta: metav1.TypeMeta{
350 | APIVersion: "game.kruise.io/v1alpha1",
351 | Kind: "GameServerSet",
352 | },
353 | ObjectMeta: metav1.ObjectMeta{
354 | Namespace: "xxx",
355 | Name: "case-1",
356 | ResourceVersion: "1000",
357 | Annotations: map[string]string{gameKruiseV1alpha1.GameServerSetReserveIdsKey: "1,2,4"},
358 | },
359 | Spec: gameKruiseV1alpha1.GameServerSetSpec{
360 | Replicas: pointer.Int32(8),
361 | ReserveGameServerIds: []int{1, 2, 4},
362 | },
363 | },
364 | },
365 | }
366 |
367 | for caseNum, test := range tests {
368 | objs := []client.Object{test.gssBefore}
369 | c := fake.NewClientBuilder().WithScheme(schemeTest).WithObjects(objs...).Build()
370 | rm := &ResourceManager{Client: c}
371 | actualMeta, err := rm.CreateResource(test.meta)
372 | if err != nil {
373 | t.Errorf("case %d: %s", caseNum, err.Error())
374 | }
375 | if actualMeta.ID != test.newID {
376 | t.Errorf("case %d: expect return ID is %s, but get %s", caseNum, test.newID, actualMeta.ID)
377 | }
378 | actualGss := &gameKruiseV1alpha1.GameServerSet{}
379 | if err := rm.Get(context.Background(), types.NamespacedName{
380 | Name: test.gssBefore.GetName(),
381 | Namespace: test.gssBefore.GetNamespace(),
382 | }, actualGss); err != nil {
383 | t.Error(err)
384 | }
385 | if !reflect.DeepEqual(test.gssAfter, actualGss) {
386 | t.Errorf("case %d: expect gss %v but got %v", caseNum, test.gssAfter, actualGss)
387 | }
388 | }
389 | }
390 |
391 | func TestResourceManager_PauseResource(t *testing.T) {
392 | tests := []struct {
393 | meta *ResourceMeta
394 | gssBefore *gameKruiseV1alpha1.GameServerSet
395 | gssAfter *gameKruiseV1alpha1.GameServerSet
396 | isError bool
397 | }{
398 | {
399 | meta: &ResourceMeta{
400 | Namespace: "xxx",
401 | Name: "case-0",
402 | ID: "1",
403 | },
404 | gssBefore: &gameKruiseV1alpha1.GameServerSet{
405 | ObjectMeta: metav1.ObjectMeta{
406 | Namespace: "xxx",
407 | Name: "case-0",
408 | ResourceVersion: "999",
409 | },
410 | Spec: gameKruiseV1alpha1.GameServerSetSpec{
411 | Replicas: pointer.Int32(3),
412 | ReserveGameServerIds: []int{},
413 | },
414 | },
415 | gssAfter: &gameKruiseV1alpha1.GameServerSet{
416 | TypeMeta: metav1.TypeMeta{
417 | APIVersion: "game.kruise.io/v1alpha1",
418 | Kind: "GameServerSet",
419 | },
420 | ObjectMeta: metav1.ObjectMeta{
421 | Namespace: "xxx",
422 | Name: "case-0",
423 | ResourceVersion: "1000",
424 | },
425 | Spec: gameKruiseV1alpha1.GameServerSetSpec{
426 | Replicas: pointer.Int32(2),
427 | ReserveGameServerIds: []int{1},
428 | },
429 | },
430 | },
431 | {
432 | meta: &ResourceMeta{
433 | Namespace: "xxx",
434 | Name: "case-1",
435 | },
436 | gssBefore: &gameKruiseV1alpha1.GameServerSet{
437 | ObjectMeta: metav1.ObjectMeta{
438 | Namespace: "xxx",
439 | Name: "case-1",
440 | ResourceVersion: "999",
441 | },
442 | Spec: gameKruiseV1alpha1.GameServerSetSpec{
443 | Replicas: pointer.Int32(3),
444 | ReserveGameServerIds: []int{},
445 | },
446 | },
447 | isError: true,
448 | },
449 | }
450 |
451 | for caseNum, test := range tests {
452 | objs := []client.Object{test.gssBefore}
453 | c := fake.NewClientBuilder().WithScheme(schemeTest).WithObjects(objs...).Build()
454 | rm := &ResourceManager{Client: c}
455 | err := rm.PauseResource(test.meta)
456 |
457 | if !reflect.DeepEqual(test.isError, err != nil) {
458 | t.Errorf("case %d: errs not equal", caseNum)
459 | continue
460 | }
461 |
462 | if !test.isError {
463 | actualGss := &gameKruiseV1alpha1.GameServerSet{}
464 | if err := rm.Get(context.Background(), types.NamespacedName{
465 | Name: test.gssBefore.GetName(),
466 | Namespace: test.gssBefore.GetNamespace(),
467 | }, actualGss); err != nil {
468 | t.Error(err)
469 | }
470 | if !reflect.DeepEqual(test.gssAfter, actualGss) {
471 | t.Errorf("case %d: expect gss %v but got %v", caseNum, test.gssAfter, actualGss)
472 | }
473 | }
474 | }
475 | }
476 |
--------------------------------------------------------------------------------
/pkg/routers/resource.go:
--------------------------------------------------------------------------------
1 | package routers
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 |
7 | "github.com/CloudNativeGame/aigc-gateway/pkg/resources"
8 | mem "github.com/CloudNativeGame/aigc-gateway/pkg/session"
9 | "github.com/CloudNativeGame/aigc-gateway/pkg/user"
10 | "github.com/CloudNativeGame/aigc-gateway/pkg/utils"
11 | "github.com/gin-contrib/sessions"
12 | "github.com/gin-gonic/gin"
13 | "github.com/logto-io/go/client"
14 | "github.com/logto-io/go/core"
15 | )
16 |
17 | func RegisterResourceRouters(router *gin.Engine, logtoConfig *client.LogtoConfig) {
18 | // Add a link to perform a sign-in request on the home page
19 | router.GET("/resources", func(ctx *gin.Context) {
20 | rm := resources.NewResourceManager()
21 | config, err := utils.ParseConfig("/etc/config/config.json")
22 | if err != nil {
23 | ctx.String(400, err.Error())
24 | }
25 |
26 | resources, err := rm.ListResources(config.Namespaces, config.GssLabels)
27 | if err != nil {
28 | ctx.String(400, err.Error())
29 | }
30 | ctx.JSON(200, resources)
31 | })
32 |
33 | router.GET("/resource/:namespace/:name/:id", func(ctx *gin.Context) {
34 | name := ctx.Param("name")
35 | namespace := ctx.Param("namespace")
36 | id := ctx.Param("id")
37 |
38 | session := sessions.Default(ctx)
39 | logtoClient := client.NewLogtoClient(
40 | logtoConfig,
41 | &mem.SessionStorage{Session: session},
42 | )
43 |
44 | userInfo, err := logtoClient.FetchUserInfo()
45 | if err != nil {
46 | ctx.Error(err)
47 |
48 | return
49 | }
50 | cm := userInfo.CustomData
51 | key := fmt.Sprintf("%s-%s", namespace, name)
52 |
53 | value := cm[key]
54 | if value == nil {
55 | ctx.String(403, "have no privilege to get the instance with ID %d", id)
56 | return
57 | }
58 |
59 | rm := &resources.ResourceMeta{}
60 | valueBytes, err := interfaceToBytes(value)
61 | if err != nil {
62 | ctx.Error(err)
63 |
64 | return
65 | }
66 | err = json.Unmarshal(valueBytes, rm)
67 | if err != nil {
68 | ctx.Error(err)
69 |
70 | return
71 | }
72 |
73 | if id != rm.ID || name != rm.Name || namespace != rm.Namespace {
74 | ctx.String(403, "have no privilege to get the instance with ID %d", id)
75 | return
76 | }
77 |
78 | // add json wrapper
79 | resourceManager := resources.NewResourceManager()
80 | resource, err := resourceManager.GetResource(rm)
81 |
82 | if err != nil {
83 | if resources.GetErrorReason(err) == resources.PauseReason {
84 | ctx.Status(423)
85 | return
86 | }
87 | ctx.String(400, err.Error())
88 |
89 | return
90 | }
91 |
92 | ctx.JSON(200, resource)
93 | })
94 |
95 | // create a new one
96 | router.PUT("/resource/:namespace/:name", func(ctx *gin.Context) {
97 | name := ctx.Param("name")
98 | namespace := ctx.Param("namespace")
99 |
100 | session := sessions.Default(ctx)
101 | logtoClient := client.NewLogtoClient(
102 | logtoConfig,
103 | &mem.SessionStorage{Session: session},
104 | )
105 |
106 | userInfo, err := logtoClient.FetchUserInfo()
107 |
108 | if err != nil {
109 | ctx.Error(err)
110 | }
111 |
112 | cm := userInfo.CustomData
113 |
114 | if cm == nil {
115 | cm = make(map[string]interface{})
116 | }
117 |
118 | key := fmt.Sprintf("%s-%s", namespace, name)
119 |
120 | // check if already installed
121 | if cm[key] != nil {
122 | ctx.String(400, "already installed")
123 | return
124 | }
125 |
126 | rm := &resources.ResourceMeta{
127 | Name: name,
128 | Namespace: namespace,
129 | }
130 |
131 | // add json wrapper
132 | resourceManager := resources.NewResourceManager()
133 | meta, err := resourceManager.CreateResource(rm)
134 | if err != nil {
135 | ctx.Error(err)
136 |
137 | return
138 | }
139 |
140 | cm[key] = meta
141 |
142 | err = user.UpdateUserMetaData(userInfo.Sub, cm)
143 | if err != nil {
144 | ctx.Error(err)
145 |
146 | return
147 | }
148 |
149 | ctx.JSON(200, meta)
150 |
151 | })
152 |
153 | router.POST("/resource/:namespace/:name/pause", func(ctx *gin.Context) {
154 | name := ctx.Param("name")
155 | namespace := ctx.Param("namespace")
156 |
157 | session := sessions.Default(ctx)
158 | logtoClient := client.NewLogtoClient(
159 | logtoConfig,
160 | &mem.SessionStorage{Session: session},
161 | )
162 |
163 | userInfo, err := logtoClient.FetchUserInfo()
164 |
165 | if err != nil {
166 | ctx.Error(err)
167 |
168 | return
169 | }
170 |
171 | cm := userInfo.CustomData
172 |
173 | key := fmt.Sprintf("%s-%s", namespace, name)
174 | value := cm[key]
175 |
176 | valueBytes, err := interfaceToBytes(value)
177 |
178 | if err != nil {
179 | ctx.Error(err)
180 |
181 | return
182 | }
183 |
184 | rm := &resources.ResourceMeta{}
185 |
186 | err = json.Unmarshal(valueBytes, rm)
187 |
188 | if err != nil {
189 | ctx.Error(err)
190 |
191 | return
192 | }
193 |
194 | // add json wrapper
195 | resourceManager := resources.NewResourceManager()
196 | err = resourceManager.PauseResource(rm)
197 |
198 | if err != nil {
199 | ctx.String(400, err.Error())
200 |
201 | return
202 | }
203 | ctx.Status(200)
204 | })
205 |
206 | router.POST("/resource/:namespace/:name/recover", func(ctx *gin.Context) {
207 | name := ctx.Param("name")
208 | namespace := ctx.Param("namespace")
209 |
210 | session := sessions.Default(ctx)
211 | logtoClient := client.NewLogtoClient(
212 | logtoConfig,
213 | &mem.SessionStorage{Session: session},
214 | )
215 |
216 | userInfo, err := logtoClient.FetchUserInfo()
217 |
218 | if err != nil {
219 | ctx.Error(err)
220 |
221 | return
222 | }
223 |
224 | cm := userInfo.CustomData
225 |
226 | key := fmt.Sprintf("%s-%s", namespace, name)
227 | value := cm[key]
228 |
229 | if value == nil {
230 | ctx.Status(200)
231 | return
232 | }
233 |
234 | valueBytes, err := interfaceToBytes(value)
235 |
236 | if err != nil {
237 | ctx.Error(err)
238 |
239 | return
240 | }
241 |
242 | rm := &resources.ResourceMeta{}
243 |
244 | json.Unmarshal(valueBytes, rm)
245 |
246 | // add json wrapper
247 | resourceManager := resources.NewResourceManager()
248 | _, err = resourceManager.RecoverResource(rm)
249 |
250 | if err != nil {
251 | ctx.String(400, err.Error())
252 |
253 | return
254 | }
255 | ctx.Status(200)
256 | })
257 |
258 | router.DELETE("/resource/:namespace/:name", func(ctx *gin.Context) {
259 | name := ctx.Param("name")
260 | namespace := ctx.Param("namespace")
261 |
262 | session := sessions.Default(ctx)
263 | logtoClient := client.NewLogtoClient(
264 | logtoConfig,
265 | &mem.SessionStorage{Session: session},
266 | )
267 |
268 | userInfo, err := logtoClient.FetchUserInfo()
269 |
270 | if err != nil {
271 | ctx.Error(err)
272 | return
273 | }
274 |
275 | cm := userInfo.CustomData
276 |
277 | key := fmt.Sprintf("%s-%s", namespace, name)
278 | value := cm[key]
279 | if value == nil {
280 | ctx.Status(400)
281 | return
282 | }
283 | valueBytes, err := interfaceToBytes(value)
284 | if err != nil {
285 | ctx.Error(err)
286 | return
287 | }
288 |
289 | rm := &resources.ResourceMeta{}
290 | err = json.Unmarshal(valueBytes, rm)
291 | if err != nil {
292 | ctx.Error(err)
293 | return
294 | }
295 |
296 | // add json wrapper
297 | resourceManager := resources.NewResourceManager()
298 | err = resourceManager.DeleteResource(rm)
299 | if err != nil {
300 | ctx.Error(err)
301 | }
302 |
303 | cm[key] = nil
304 |
305 | err = user.UpdateUserMetaData(userInfo.Sub, cm)
306 | if err != nil {
307 | ctx.Error(err)
308 | return
309 | }
310 |
311 | ctx.Status(200)
312 | return
313 | })
314 |
315 | router.POST("/resource/:namespace/:name/restart", func(ctx *gin.Context) {
316 | name := ctx.Param("name")
317 | namespace := ctx.Param("namespace")
318 |
319 | session := sessions.Default(ctx)
320 | logtoClient := client.NewLogtoClient(
321 | logtoConfig,
322 | &mem.SessionStorage{Session: session},
323 | )
324 |
325 | userInfo, err := logtoClient.FetchUserInfo()
326 |
327 | if err != nil {
328 | ctx.Error(err)
329 | return
330 | }
331 |
332 | cm := userInfo.CustomData
333 |
334 | key := fmt.Sprintf("%s-%s", namespace, name)
335 | value := cm[key]
336 | if value == nil {
337 | ctx.Status(400)
338 | return
339 | }
340 | valueBytes, err := interfaceToBytes(value)
341 | if err != nil {
342 | ctx.Error(err)
343 | return
344 | }
345 |
346 | rm := &resources.ResourceMeta{}
347 | err = json.Unmarshal(valueBytes, rm)
348 | if err != nil {
349 | ctx.Error(err)
350 | return
351 | }
352 |
353 | // add json wrapper
354 | resourceManager := resources.NewResourceManager()
355 | err = resourceManager.RestartResource(rm)
356 | if err != nil {
357 | ctx.Error(err)
358 | return
359 | }
360 |
361 | ctx.Status(200)
362 | return
363 | })
364 | }
365 |
366 | func interfaceToBytes(data interface{}) ([]byte, error) {
367 | bytes, err := json.Marshal(data)
368 | if err != nil {
369 | return nil, err
370 | }
371 | return bytes, nil
372 | }
373 |
374 | func getUserInfoFromSession(logtoConfig *client.LogtoConfig, ctx *gin.Context) (core.IdTokenClaims, error) {
375 | // Init LogtoClient
376 | session := sessions.Default(ctx)
377 | logtoClient := client.NewLogtoClient(
378 | logtoConfig,
379 | &mem.SessionStorage{Session: session},
380 | )
381 |
382 | tc, err := logtoClient.GetIdTokenClaims()
383 |
384 | if err != nil {
385 | return core.IdTokenClaims{}, err
386 | }
387 |
388 | return tc, nil
389 | }
390 |
--------------------------------------------------------------------------------
/pkg/routers/sign.go:
--------------------------------------------------------------------------------
1 | package routers
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/CloudNativeGame/aigc-gateway/pkg/resources"
6 | mem "github.com/CloudNativeGame/aigc-gateway/pkg/session"
7 | "github.com/gin-contrib/sessions"
8 | "github.com/gin-gonic/gin"
9 | "github.com/logto-io/go/client"
10 | "net/http"
11 | "net/url"
12 | "os"
13 | "strings"
14 | )
15 |
16 | var redirectURL = os.Getenv("Redirect_Url")
17 |
18 | func RegisterSignRouters(router *gin.Engine, logtoConfig *client.LogtoConfig) {
19 | // Add a link to perform a sign-in request on the home page
20 | router.GET("/", func(ctx *gin.Context) {
21 | // Init LogtoClient
22 | session := sessions.Default(ctx)
23 | logtoClient := client.NewLogtoClient(
24 | logtoConfig,
25 | &mem.SessionStorage{Session: session},
26 | )
27 |
28 | resp, err := logtoClient.FetchUserInfo()
29 |
30 | authState := false
31 |
32 | if err == nil {
33 | authState = true
34 | }
35 |
36 | ctx.HTML(http.StatusOK, "index.html", gin.H{
37 | "title": "Main website",
38 | "authState": authState,
39 | "userInfo": resp,
40 | })
41 | })
42 |
43 | router.GET("/auth", func(ctx *gin.Context) {
44 | session := sessions.Default(ctx)
45 | logtoClient := client.NewLogtoClient(
46 | logtoConfig,
47 | &mem.SessionStorage{Session: session},
48 | )
49 |
50 | userInfo, err := logtoClient.FetchUserInfo()
51 | if err != nil {
52 | ctx.String(http.StatusUnauthorized, err.Error())
53 | return
54 | }
55 |
56 | originUrl := ctx.GetHeader("X-Original-Url")
57 |
58 | resourceManager := resources.NewResourceManager()
59 |
60 | for _, info := range userInfo.CustomData {
61 | if info == nil {
62 | continue
63 | }
64 | valueBytes, err := interfaceToBytes(info)
65 | if err != nil {
66 | ctx.Error(err)
67 | }
68 |
69 | rm := &resources.ResourceMeta{}
70 |
71 | err = json.Unmarshal(valueBytes, rm)
72 |
73 | if err != nil {
74 | ctx.Error(err)
75 | }
76 |
77 | endpoint, err := resourceManager.GetResourceEndpoint(rm)
78 | if err != nil {
79 | ctx.String(http.StatusInternalServerError, err.Error())
80 | }
81 |
82 | if endpoint != "" && strings.Contains(originUrl, endpoint) {
83 | ctx.String(http.StatusOK, "")
84 | return
85 | }
86 | }
87 |
88 | ctx.String(http.StatusUnauthorized, "User and Access Endpoint Mismatch")
89 | return
90 | })
91 |
92 | // Add a route for handling sign-in requests
93 | router.GET("/sign-in", func(ctx *gin.Context) {
94 | session := sessions.Default(ctx)
95 | logtoClient := client.NewLogtoClient(
96 | logtoConfig,
97 | &mem.SessionStorage{Session: session},
98 | )
99 |
100 | signInUri, _ := url.JoinPath(redirectURL, "sign-in-callback")
101 |
102 | // The sign-in request is handled by Logto.
103 | // The user will be redirected to the Redirect URI on signed in.
104 | signInUri, err := logtoClient.SignIn(signInUri)
105 | if err != nil {
106 | ctx.String(http.StatusInternalServerError, err.Error())
107 | return
108 | }
109 |
110 | // Redirect the user to the Logto sign-in page.
111 | ctx.Redirect(http.StatusTemporaryRedirect, signInUri)
112 | })
113 |
114 | // Add a route for handling signing out requests
115 | router.GET("/sign-out", func(ctx *gin.Context) {
116 | session := sessions.Default(ctx)
117 | logtoClient := client.NewLogtoClient(
118 | logtoConfig,
119 | &mem.SessionStorage{Session: session},
120 | )
121 |
122 | // The sign-out request is handled by Logto.
123 | // The user will be redirected to the Post Sign-out Redirect URI on signed out.
124 | signOutUri, signOutErr := logtoClient.SignOut(redirectURL)
125 |
126 | if signOutErr != nil {
127 | ctx.String(http.StatusOK, signOutErr.Error())
128 | return
129 | }
130 |
131 | ctx.Redirect(http.StatusTemporaryRedirect, signOutUri)
132 | })
133 |
134 | // Add a route for handling sign-in callback requests
135 | router.GET("/sign-in-callback", func(ctx *gin.Context) {
136 | session := sessions.Default(ctx)
137 | logtoClient := client.NewLogtoClient(
138 | logtoConfig,
139 | &mem.SessionStorage{Session: session},
140 | )
141 |
142 | // The sign-in callback request is handled by Logto
143 | err := logtoClient.HandleSignInCallback(ctx.Request)
144 | if err != nil {
145 | ctx.String(http.StatusInternalServerError, err.Error())
146 | return
147 | }
148 |
149 | // Jump to the page specified by the developer.
150 | // This example takes the user back to the home page.
151 | ctx.Redirect(http.StatusTemporaryRedirect, "/")
152 | })
153 | }
154 |
--------------------------------------------------------------------------------
/pkg/session/session.go:
--------------------------------------------------------------------------------
1 | package session
2 |
3 | import (
4 | "github.com/gin-contrib/sessions"
5 | )
6 |
7 | type SessionStorage struct {
8 | Session sessions.Session
9 | }
10 |
11 | func (storage *SessionStorage) GetItem(key string) string {
12 | value := storage.Session.Get(key)
13 | if value == nil {
14 | return ""
15 | }
16 | return value.(string)
17 | }
18 |
19 | func (storage *SessionStorage) SetItem(key, value string) {
20 | storage.Session.Set(key, value)
21 | storage.Session.Save()
22 | }
23 |
--------------------------------------------------------------------------------
/pkg/user/user.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "bytes"
5 | "crypto/tls"
6 | "encoding/json"
7 | "fmt"
8 | "io/ioutil"
9 | "net/http"
10 | "net/url"
11 | "os"
12 | "strings"
13 | )
14 |
15 | type CustomData map[string]interface{}
16 |
17 | type PatchBody struct {
18 | CustomData CustomData `json:"customData"`
19 | }
20 |
21 | type tokenAuth struct {
22 | AccessToken string `json:"access_token"`
23 | ExpiresIn int `json:"expires_in"`
24 | TokenType string `json:"token_type"`
25 | }
26 |
27 | type User struct {
28 | Id string `json:"id"`
29 | Username string `json:"username"`
30 | PrimaryEmail string `json:"primaryEmail"`
31 | PrimaryPhone string `json:"primaryPhone"`
32 | Name string `json:"name"`
33 | Avatar string `json:"avatar"`
34 | CustomData map[string]interface{} `json:"customData"`
35 | LastSignInAt int `json:"lastSignInAt"`
36 | CreatedAt int `json:"createdAt"`
37 | ApplicationId string `json:"applicationId"`
38 | IsSuspended bool `json:"isSuspended"`
39 | HasPassword bool `json:"hasPassword"`
40 | }
41 |
42 | var M2MID = os.Getenv("M2M_Id")
43 | var M2MSecret = os.Getenv("M2M_Secret")
44 | var Endpoint = os.Getenv("Endpoint")
45 |
46 | func UpdateUserMetaData(userId string, customData map[string]interface{}) error {
47 |
48 | c := http.Client{
49 | Transport: &http.Transport{
50 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
51 | },
52 | }
53 | form := url.Values{}
54 | form.Add("grant_type", "client_credentials")
55 | form.Add("resource", "https://default.logto.app/api")
56 | form.Add("scope", "all")
57 |
58 | tokenRequest, err := http.NewRequest(http.MethodPost, Endpoint+"oidc/token", strings.NewReader(form.Encode()))
59 |
60 | if err != nil {
61 | return err
62 | }
63 | tokenRequest.SetBasicAuth(M2MID, M2MSecret)
64 | tokenRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded")
65 | resp, err := c.Do(tokenRequest)
66 | if err != nil {
67 | return err
68 | }
69 |
70 | defer resp.Body.Close()
71 |
72 | b, _ := ioutil.ReadAll(resp.Body)
73 |
74 | au := &tokenAuth{}
75 | err = json.Unmarshal(b, au)
76 |
77 | if err != nil {
78 | return err
79 | }
80 |
81 | patchBody := &PatchBody{}
82 | patchBody.CustomData = customData
83 |
84 | customDataBytes, err := json.Marshal(patchBody)
85 |
86 | patchRequest, err := http.NewRequest(http.MethodPatch, fmt.Sprintf(Endpoint+"api/users/%s/custom-data", userId), bytes.NewBuffer(customDataBytes))
87 | if err != nil {
88 | return err
89 | }
90 | patchRequest.Header.Set("Authorization", "Bearer "+au.AccessToken)
91 | patchRequest.Header.Set("Content-Type", "application/json")
92 |
93 | patchResponse, err := c.Do(patchRequest)
94 | defer patchResponse.Body.Close()
95 | if err != nil {
96 | return err
97 | }
98 | return nil
99 | }
100 |
101 | func GetUserProfile(userId string) (*User, error) {
102 | c := http.Client{}
103 | form := url.Values{}
104 | form.Add("grant_type", "client_credentials")
105 | form.Add("resource", "https://default.logto.app/api")
106 | form.Add("scope", "all")
107 |
108 | tokenRequest, err := http.NewRequest(http.MethodPost, Endpoint+"oidc/token", strings.NewReader(form.Encode()))
109 | if err != nil {
110 | return nil, err
111 | }
112 | tokenRequest.SetBasicAuth(M2MID, M2MSecret)
113 | tokenRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded")
114 | resp, err := c.Do(tokenRequest)
115 | if err != nil {
116 | return nil, err
117 | }
118 |
119 | defer resp.Body.Close()
120 |
121 | b, _ := ioutil.ReadAll(resp.Body)
122 |
123 | au := &tokenAuth{}
124 | err = json.Unmarshal(b, au)
125 |
126 | if err != nil {
127 | return nil, err
128 | }
129 |
130 | userResp, err := http.Get(Endpoint + "api/users/" + userId)
131 |
132 | defer userResp.Body.Close()
133 |
134 | if err != nil {
135 | return nil, err
136 | }
137 | userBytes, err := ioutil.ReadAll(resp.Body)
138 | if err != nil {
139 | return nil, err
140 | }
141 |
142 | u := &User{}
143 |
144 | err = json.Unmarshal(userBytes, u)
145 |
146 | return u, err
147 |
148 | }
149 |
--------------------------------------------------------------------------------
/pkg/utils/config.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io/ioutil"
7 | "os"
8 | )
9 |
10 | type AigcGatewayConfig struct {
11 | Namespaces []string `json:"namespaces"`
12 | GssLabels map[string]string `json:"gss_labels"`
13 | }
14 |
15 | func ParseConfig(name string) (*AigcGatewayConfig, error) {
16 | // 打开 JSON 文件
17 | file, err := os.Open(name)
18 | if err != nil {
19 | fmt.Printf("Error opening file: %s\n", err)
20 | return nil, err
21 | }
22 | defer file.Close()
23 |
24 | // 读取文件内容
25 | bytes, err := ioutil.ReadAll(file)
26 | if err != nil {
27 | fmt.Printf("Error reading file: %s\n", err)
28 | return nil, err
29 | }
30 |
31 | // 将 JSON 数据解析到 Config 结构体中
32 | config := &AigcGatewayConfig{}
33 | if err := json.Unmarshal(bytes, config); err != nil {
34 | fmt.Printf("Error parsing JSON: %s\n", err)
35 | return nil, err
36 | }
37 |
38 | // 输出解析结果
39 | fmt.Printf("Namespaces: %s\n", config.Namespaces)
40 | fmt.Printf("GSS Labels: %+v\n", config.GssLabels)
41 | return config, nil
42 | }
43 |
--------------------------------------------------------------------------------
/pkg/utils/config_test.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "testing"
4 |
5 | func TestParseConfig(t *testing.T) {
6 | ParseConfig("./test.config")
7 | }
8 |
--------------------------------------------------------------------------------
/pkg/utils/string.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "strings"
4 |
5 | func GetDomainFromEndpoint(endpoint string) string {
6 | var domain string
7 | tail := 0
8 | index := strings.Index(endpoint, ".")
9 | if endpoint[len(endpoint)-1] == '/' {
10 | tail = 1
11 | }
12 | if index != -1 {
13 | domain = endpoint[index : len(endpoint)-tail]
14 | }
15 | return domain
16 | }
17 |
--------------------------------------------------------------------------------
/pkg/utils/string_test.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "testing"
4 |
5 | func TestGetDomainFromEndpoint(t *testing.T) {
6 | tests := []struct {
7 | endpoint string
8 | domain string
9 | }{
10 | {
11 | endpoint: "https://logto.c5464a5f2c39341d3b3eda6e2dd37b505.cn-hangzhou.alicontainer.com/",
12 | domain: ".c5464a5f2c39341d3b3eda6e2dd37b505.cn-hangzhou.alicontainer.com",
13 | },
14 | {
15 | endpoint: "https://logto.c5464a5f2c39341d3b3eda6e2dd37b505.cn-hangzhou.alicontainer.com",
16 | domain: ".c5464a5f2c39341d3b3eda6e2dd37b505.cn-hangzhou.alicontainer.com",
17 | },
18 | }
19 |
20 | for _, test := range tests {
21 | actual := GetDomainFromEndpoint(test.endpoint)
22 | expect := test.domain
23 | if actual != expect {
24 | t.Errorf("expect domain: %s, but actual got %v", expect, actual)
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/pkg/utils/test.config:
--------------------------------------------------------------------------------
1 | {
2 | "namespaces": ["xxx"],
3 | "gss_labels": {
4 | "xxx": "xxx"
5 | }
6 | }
--------------------------------------------------------------------------------