!16 [Feature] DevContainer 适配 Docker
* [Chore] format code style * [Chore] go mod tidy * remove deprecated config * Update services/devstar_devcontainer/docker_agent/AssignDevcontainerCr…
This commit is contained in:
repo.diff.committed_by
戴明辰
repo.diff.parent
c92eeecf06
repo.diff.commit
8d5b34e07e
95
README_ZH.md
95
README_ZH.md
@@ -3,49 +3,61 @@
|
||||
DevStar Studio 是 Gitea 发行版
|
||||
|
||||
## 1. 快速开始
|
||||
编译、打包成为镜像:代码目录执行 `make docker` 命令
|
||||
### 1.1 单机版
|
||||
|
||||
### 1.1 单机版部署
|
||||
#### 1.1.1 部署
|
||||
|
||||
使用docker-compose部署DevStar Studio需要参考官方网站:
|
||||
https://docs.gitea.com/zh-cn/next/installation/install-with-docker-rootless
|
||||
**Linux部署**
|
||||
|
||||
- 数据库准备(可选,可使用sqlite)参考网址:https://docs.gitea.com/zh-cn/installation/database-prep
|
||||
- 环境准备:docker、git
|
||||
- 下载Linux可执行文件压缩包(-linux) **tar -xzvf** 解压缩得到可执行文件,Linux部署命令参考网址:https://docs.gitea.com/zh-cn/installation/install-from-binary
|
||||
|
||||
**Docker镜像部署**
|
||||
|
||||
- 数据库准备(可选,可使用sqlite)参考网址:https://docs.gitea.com/zh-cn/installation/database-prep
|
||||
- 环境准备:docker、git
|
||||
- 下载镜像文件压缩包(-docker) **tar -xzvf** 解压缩得到镜像文件
|
||||
|
||||
下列以SQLite db本地最小安装为例,首先初次启动,复制配置信息:
|
||||
```bash
|
||||
docker run \
|
||||
--restart=always \
|
||||
--name devstar-studio \
|
||||
-d \
|
||||
-p 3000:3000 \
|
||||
devstar-studio:latest
|
||||
# 加载镜像
|
||||
docker load -i <镜像文件名>
|
||||
|
||||
# Windows: docker desktop需打开Expose daemon on tcp://localhost:2375 without TLS
|
||||
docker run --restart=always --name <容器名> -d -p 3000:3000 devstar-studio:latest
|
||||
|
||||
|
||||
# linux: docker运行容器,后面修改linux主机 /var/run/docker.sock 权限为 666
|
||||
docker run --restart=always --name <容器名> -d -p 3000:3000 -v /var/run/docker.sock:/var/run/docker.sock devstar-studio:latest
|
||||
sudo chmod 666 /var/run/docker.sock
|
||||
```
|
||||
|
||||
浏览器打开 http://localhost:3000,执行安装操作,记录下列路径名记录下来,准备后续映射文件夹
|
||||
需要复制的配置信息如下:
|
||||
- 数据库文件路径名字
|
||||
- 仓库根路径名字
|
||||
- LFS根目录名字
|
||||
- 日志路径名字
|
||||
#### 1.1.2 安装和配置
|
||||
|
||||
**!先运行并通过首页安装,完成后再进行配置!**
|
||||
|
||||
|
||||
|
||||
将容器内 `/etc/gitea/app.ini` 复制出来,参考[官网文档](https://docs.gitea.com/administration/config-cheat-sheet) 进行自定义
|
||||
```bash
|
||||
# linux docker 需要把配置文件复制出来修改,Windows: docker desktop在容器界面-file找到/etc/gitea/app.ini修改
|
||||
docker cp devstar-studio:/etc/gitea/app.ini ./app.ini
|
||||
docker cp ./app.ini devstar-studio:/etc/gitea/app.ini
|
||||
```
|
||||
|
||||
然后 添加/修改 下列小节键:
|
||||
```ini
|
||||
[wechat]
|
||||
WECHAT_OFFICIAL_ACCOUNT_TEMP_QR_EXPIRE_SECONDS=60
|
||||
WECHAT_OFFICIAL_ACCOUNT_APP_ID=<微信公众号APPID>
|
||||
WECHAT_OFFICIAL_ACCOUNT_APP_SECRET=<微信公众号SECRET>
|
||||
WECHAT_OFFICIAL_ACCOUNT_MESSAGE_TOKEN = <微信公众号自定义Token>
|
||||
WECHAT_OFFICIAL_ACCOUNT_MESSAGE_AES_KEY = <微信公众号AES加密密钥>
|
||||
添加/修改 下列小节键:
|
||||
|
||||
```ini
|
||||
[database]
|
||||
CHARSET_COLLATION = utf8mb4_bin
|
||||
|
||||
[devstar.ssh_key_pair]
|
||||
KEY_SIZE = <写入希望生成的SSH密钥长度,比如 4096 ,默认值 2048>
|
||||
|
||||
[cors]
|
||||
CONTENT_SECURITY_POLICY = default-src 'self' data: 'unsafe-inline' https://mp.weixin.qq.com; img-src * data:
|
||||
ENABLED = true
|
||||
ALLOW_DOMAIN = *
|
||||
METHODS = GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS
|
||||
MAX_AGE = 10m
|
||||
|
||||
[ui.admin]
|
||||
;; Dev Container 分页参数(每页展示 DevContainer 个数),若未指定,默认值 50
|
||||
@@ -55,28 +67,21 @@ DEV_CONTAINERS_PAGING_NUM = 50
|
||||
ENABLED = true
|
||||
AGENT = docker
|
||||
TIMEOUT_SECONDS = 120
|
||||
HOST = 127.0.0.1
|
||||
|
||||
[devstar.ssh_key_pair]
|
||||
KEY_SIZE = <写入希望生成的SSH密钥长度,比如 4096 ,默认值 2048>
|
||||
;; 修改为部署host地址
|
||||
HOST = 192.168.88.88
|
||||
;; docker desktop必须添加,linux docker可删除
|
||||
DOCKER_HOST = tcp://host.docker.internal:2375
|
||||
|
||||
```
|
||||
|
||||
正式部署单机版
|
||||
```bash
|
||||
docker stop devstar-studio && docker rm devstar-studio
|
||||
#### 1.1.3 单机版使用
|
||||
|
||||
1. 添加publickey:用户-设置-SSH / GPG 密钥-管理 SSH 密钥,windows一般在C:/用户/用户名/.ssh目录下
|
||||
2. 创建仓库,创建.devcontainer/devcontainer.json,并且 **添加 image 字段**,例如:"image":"mcr.microsoft.com/devcontainers/base:dev-ubuntu-20.04"
|
||||
3. 仓库内-开发容器-创建开发容器
|
||||
|
||||
```bash
|
||||
|
||||
docker run \
|
||||
--restart=always \
|
||||
--name devstar-studio \
|
||||
-d \
|
||||
-p 3000:3000 \
|
||||
-p 2222:2222 \
|
||||
-v 本地数据库文件路径:容器内数据库文件路径 \
|
||||
-v 本地仓库根路径:容器内仓库根路径 \
|
||||
-v 本地LFS根目录:容器内LFS路径 \
|
||||
-v 本地日志路径:容器内日志路径 \
|
||||
devstar-studio:latest
|
||||
```
|
||||
|
||||
### 1.2 Kubernetes 集群部署
|
||||
|
||||
10
go.mod
10
go.mod
@@ -30,6 +30,8 @@ require (
|
||||
github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21
|
||||
github.com/djherbis/buffer v1.2.0
|
||||
github.com/djherbis/nio/v3 v3.0.1
|
||||
github.com/docker/docker v24.0.7+incompatible
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.2
|
||||
@@ -131,6 +133,14 @@ require (
|
||||
xorm.io/xorm v1.3.8
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||
github.com/distribution/reference v0.5.0 // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/ArtisanCloud/PowerSocialite/v3 v3.0.7 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
|
||||
19
go.sum
19
go.sum
@@ -94,6 +94,8 @@ github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occ
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2 h1:YUUxeiOWgdAQE3pXt2H7QXzZs0q8UBjgRbl56qo8GYM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2/go.mod h1:dmXQgZuiSubAecswZE+Sm8jkvEa7kQgTPVRvwL/nd0E=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
|
||||
@@ -289,6 +291,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21 h1:PdsjTl0Cg+ZJgOx/CFV5NNgO1ThTreqdgKYiDCMHJwA=
|
||||
github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21/go.mod h1:xJvkyD6Y2rZapGvPJLYo9dyx1s5dxBEDPa8T3YTuOk0=
|
||||
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
|
||||
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/djherbis/buffer v1.1.0/go.mod h1:VwN8VdFkMY0DCALdY8o00d3IZ6Amz/UNVMWcSaJT44o=
|
||||
github.com/djherbis/buffer v1.2.0 h1:PH5Dd2ss0C7CRRhQCZ2u7MssF+No9ide8Ye71nPHcrQ=
|
||||
github.com/djherbis/buffer v1.2.0/go.mod h1:fjnebbZjCUpPinBRD+TDwXSOeNQ7fPQWLfGQqiAiUyE=
|
||||
@@ -299,6 +303,14 @@ github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55k
|
||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
|
||||
github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
||||
@@ -738,12 +750,16 @@ github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
|
||||
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd h1:aY7OQNf2XqY/JQ6qREWamhI/81os/agb2BAGpcx5yWI=
|
||||
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 h1:j2kD3MT1z4PXCiUllUJF9mWUESr9TWKS7iEKsQ/IipM=
|
||||
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
|
||||
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
|
||||
@@ -1202,6 +1218,7 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -1455,6 +1472,8 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
@@ -59,6 +59,7 @@ type DevcontainerType struct {
|
||||
|
||||
DefaultGitBranchName string
|
||||
DefaultDevcontainerImageName string
|
||||
DockerHost string
|
||||
}
|
||||
|
||||
type SSHKeyPairType struct {
|
||||
|
||||
@@ -727,6 +727,15 @@ func apiAuth(authMethod auth.Method) func(*context.APIContext) {
|
||||
// verifyAuthWithOptions checks authentication according to options
|
||||
func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.APIContext) {
|
||||
return func(ctx *context.APIContext) {
|
||||
// 检查是否为 OPTIONS 请求
|
||||
if ctx.Req.Method == "OPTIONS" {
|
||||
// 设置 CORS 响应头
|
||||
ctx.Resp.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
ctx.Resp.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
|
||||
ctx.Resp.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
|
||||
ctx.Resp.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
// Check prohibit login users.
|
||||
if ctx.IsSigned {
|
||||
if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm {
|
||||
|
||||
@@ -18,6 +18,15 @@ import (
|
||||
)
|
||||
|
||||
func goGet(ctx *context.Context) {
|
||||
// 检查是否为 OPTIONS 请求
|
||||
if ctx.Req.Method == "OPTIONS" {
|
||||
// 设置 CORS 响应头
|
||||
ctx.Resp.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
ctx.Resp.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
|
||||
ctx.Resp.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
|
||||
ctx.Resp.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
if ctx.Req.Method != "GET" || len(ctx.Req.URL.RawQuery) < 8 || ctx.FormString("go-get") != "1" {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -40,7 +40,8 @@ func OpenDevcontainerService(ctx *gitea_web_context.Context, opts *devcontainer_
|
||||
}
|
||||
}
|
||||
openDevcontainerAbstractAgentVO.NodePortAssigned = devcontainerApp.Status.NodePortAssigned
|
||||
|
||||
case setting.DEVCONTAINER_AGENT_NAME_DOCKER:
|
||||
openDevcontainerAbstractAgentVO.NodePortAssigned = opts.Port
|
||||
default:
|
||||
return nil, devcontainer_service_errors.ErrOperateDevcontainer{
|
||||
Action: "Open DevContainer",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package devstar_devcontainer
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/services/devstar_devcontainer/docker_agent"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -276,6 +277,8 @@ func purgeDevcontainersResource(ctx *context.Context, devcontainersList *[]devst
|
||||
switch setting.Devstar.Devcontainer.Agent {
|
||||
case setting.DEVCONTAINER_AGENT_NAME_K8S:
|
||||
return devcontainer_k8s_agent_service.AssignDevcontainerDeletion2K8sOperator(ctx, devcontainersList)
|
||||
case setting.DEVCONTAINER_AGENT_NAME_DOCKER:
|
||||
return docker_agent.AssignDevcontainerDeletionDockerOperator(ctx, devcontainersList)
|
||||
default:
|
||||
// 未知 Agent,直接报错
|
||||
return devstar_devcontainer_models.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
@@ -300,6 +303,8 @@ func claimDevcontainerResource(ctx *context.Context, newDevContainer *devcontain
|
||||
case setting.DEVCONTAINER_AGENT_NAME_K8S:
|
||||
// k8s Operator
|
||||
return devcontainer_k8s_agent_service.AssignDevcontainerCreation2K8sOperator(ctx, newDevContainer)
|
||||
case setting.DEVCONTAINER_AGENT_NAME_DOCKER:
|
||||
return docker_agent.AssignDevcontainerCreationDockerOperator(ctx, newDevContainer)
|
||||
default:
|
||||
// 未知 Agent,直接报错
|
||||
return devstar_devcontainer_models.ErrFailedToOperateDevstarDevcontainerDB{
|
||||
|
||||
@@ -55,6 +55,7 @@ func OpenDevcontainerAPIService(ctx *gitea_web_context.Context, opts *devcontain
|
||||
// 2. 调用抽象层获取 DevContainer 最新状态(需要根据用户传入的 wait 参数决定是否要阻塞等待 DevContainer 就绪)
|
||||
optsOpenDevcontainer := &devcontainer_service_options.OpenDevcontainerAppDispatcherOptions{
|
||||
Name: devcontainerDetails.DevContainerName,
|
||||
Port: devcontainerDetails.DevContainerPort,
|
||||
Wait: opts.Wait,
|
||||
}
|
||||
openDevcontainerAbstractAgentVO, err := DevcontainersService.OpenDevcontainerService(ctx, optsOpenDevcontainer)
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
package docker_agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
devcontainer_dto "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent/dto"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
devcontainer_service_dto "code.gitea.io/gitea/services/devstar_devcontainer/dto"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/go-connections/nat"
|
||||
apimachinery_meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
var commonSocketPaths = []string{
|
||||
"/var/run/docker.sock",
|
||||
"/run/podman/podman.sock",
|
||||
"$HOME/.colima/docker.sock",
|
||||
"$XDG_RUNTIME_DIR/docker.sock",
|
||||
"$XDG_RUNTIME_DIR/podman/podman.sock",
|
||||
`\\.\pipe\docker_engine`,
|
||||
"$HOME/.docker/run/docker.sock",
|
||||
}
|
||||
|
||||
func AssignDevcontainerCreationDockerOperator(ctx *context.Context, newDevContainer *devcontainer_service_dto.CreateDevcontainerDTO) error {
|
||||
log.Info("Docker create container.....")
|
||||
// 1. 创建docker client
|
||||
cli, err := CreateDockerClient(ctx)
|
||||
defer cli.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. 创建容器
|
||||
opts := &devcontainer_dto.CreateDevcontainerOptions{
|
||||
Name: newDevContainer.Name,
|
||||
CreateOptions: apimachinery_meta_v1.CreateOptions{},
|
||||
Image: newDevContainer.Image,
|
||||
CommandList: []string{
|
||||
"sh",
|
||||
"-c",
|
||||
"rm -f /etc/ssh/ssh_host_*; ssh-keygen -A ; service ssh start ; while true; do sleep 60; done",
|
||||
},
|
||||
SSHPublicKeyList: newDevContainer.SSHPublicKeyList,
|
||||
GitRepositoryURL: newDevContainer.GitRepositoryURL,
|
||||
}
|
||||
|
||||
// 2. 创建成功,取回集群中的 DevContainer
|
||||
port, err := CreateDevcontainer(ctx, cli, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 3. 将分配的 NodePort Service 写回 newDevcontainer,供写入数据库进行下一步操作
|
||||
uint16Value, _ := strconv.ParseUint(port, 10, 16)
|
||||
newDevContainer.DevcontainerPort = uint16(uint16Value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateDevcontainer(ctx *context.Context, cli *client.Client, opts *devcontainer_dto.CreateDevcontainerOptions) (string, error) {
|
||||
if ctx == nil || opts == nil {
|
||||
return "", fmt.Errorf("拉取镜像失败:环境为空")
|
||||
}
|
||||
// 拉取镜像
|
||||
err := pullImage(cli, opts.Image)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("拉取镜像失败:%v", err)
|
||||
}
|
||||
// 创建并启动容器
|
||||
port, err := createAndStartContainer(cli, opts)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("创建或启动容器失败:%v", err)
|
||||
}
|
||||
return port, nil
|
||||
}
|
||||
|
||||
// pullImage 用于拉取指定的 Docker 镜像
|
||||
func pullImage(cli *client.Client, image string) error {
|
||||
ctx := context.Background()
|
||||
resp, err := cli.ImagePull(ctx, image, types.ImagePullOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Close()
|
||||
|
||||
_, err = io.Copy(os.Stdout, resp)
|
||||
return err
|
||||
}
|
||||
|
||||
func createAndStartContainer(cli *client.Client, opts *devcontainer_dto.CreateDevcontainerOptions) (string, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
// 创建容器配置
|
||||
config := &container.Config{
|
||||
Image: opts.Image,
|
||||
Cmd: opts.CommandList,
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
Tty: true,
|
||||
OpenStdin: true,
|
||||
ExposedPorts: nat.PortSet{
|
||||
"22/tcp": struct{}{},
|
||||
},
|
||||
}
|
||||
|
||||
// 设置容器主机配置
|
||||
hostConfig := &container.HostConfig{
|
||||
// PortBindings: nat.PortMap{
|
||||
// "22/tcp": []nat.PortBinding{{HostIP: "0.0.0.0", HostPort: strconv.Itoa(int(opts.ServicePort))}}, // 将容器的22端口映射到宿主机的8080端口
|
||||
// },
|
||||
ExtraHosts: []string{"host.docker.internal:" + setting.Devstar.Devcontainer.Host},
|
||||
PublishAllPorts: true,
|
||||
}
|
||||
|
||||
// 创建容器
|
||||
resp, err := cli.ContainerCreate(ctx, config, hostConfig, nil, nil, opts.Name)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("创建或启动容器失败:%v", err)
|
||||
}
|
||||
|
||||
// 启动容器
|
||||
err = cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("创建或启动容器失败:%v", err)
|
||||
}
|
||||
// 将公钥数组转换为字符串
|
||||
keysString := strings.Join(opts.SSHPublicKeyList, "\n")
|
||||
log.Info(fmt.Sprintf("keysString %s", keysString))
|
||||
// 解析原始 URL
|
||||
parsedURL, err := url.Parse(opts.GitRepositoryURL)
|
||||
if err != nil {
|
||||
fmt.Printf("Error parsing URL: %v\n", err)
|
||||
return "", fmt.Errorf("error pull code from git: %v", err)
|
||||
}
|
||||
// 获取主机和端口
|
||||
hostParts := strings.Split(parsedURL.Host, ":")
|
||||
|
||||
port := ""
|
||||
if len(hostParts) > 1 {
|
||||
port = hostParts[1]
|
||||
}
|
||||
// 修改主机部分
|
||||
newHost := "host.docker.internal"
|
||||
if port != "" {
|
||||
newHost += ":" + port
|
||||
}
|
||||
|
||||
// 设置新的主机
|
||||
parsedURL.Host = newHost
|
||||
|
||||
// 生成新的 URL
|
||||
newURL := parsedURL.String()
|
||||
cmd := []string{
|
||||
"sh", "-c",
|
||||
"if [ ! -d '/data/workspace' ]; then git clone " + newURL + " /data/workspace && echo \"Git Repository cloned.\"; else echo \"Folder already exists.\"; fi; mkdir -p ~/test; mkdir -p ~/.ssh ; chmod 700 ~/.ssh; echo \"" + keysString + "\" > ~/.ssh/authorized_keys ; chmod 600 ~/.ssh/authorized_keys; ",
|
||||
}
|
||||
// 创建 exec 实例
|
||||
ex, err := cli.ContainerExecCreate(context.Background(), resp.ID, types.ExecConfig{
|
||||
Cmd: cmd,
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("启动执行器失败:%v", err)
|
||||
}
|
||||
execResp, err := cli.ContainerExecAttach(context.Background(), ex.ID, types.ExecStartCheck{})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("执行命令失败:%v", err)
|
||||
}
|
||||
output, err := ioutil.ReadAll(execResp.Reader)
|
||||
log.Info("Command output:\n%s\n", output)
|
||||
// 获取容器详细信息
|
||||
containerJSON, err := cli.ContainerInspect(context.Background(), resp.ID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("获取容器信息失败:%v", err)
|
||||
}
|
||||
|
||||
// 获取端口映射信息
|
||||
portBindings := containerJSON.NetworkSettings.Ports
|
||||
for containerPort, bindings := range portBindings {
|
||||
for _, binding := range bindings {
|
||||
log.Info("Container Port %s is mapped to Host Port %s on IP %s\n", containerPort, binding.HostPort, binding.HostIP)
|
||||
if containerPort.Port() == "22" {
|
||||
return binding.HostPort, nil
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("未找到映射的端口")
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package docker_agent
|
||||
|
||||
import (
|
||||
devstar_devcontainer_models "code.gitea.io/gitea/models/devstar_devcontainer"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"context"
|
||||
"github.com/docker/docker/api/types"
|
||||
)
|
||||
|
||||
func AssignDevcontainerDeletionDockerOperator(ctx *context.Context, devcontainersList *[]devstar_devcontainer_models.DevstarDevcontainer) error {
|
||||
log.Info("Docker delete container")
|
||||
// 1. 创建docker client
|
||||
cli, err := CreateDockerClient(ctx)
|
||||
defer cli.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 获取容器 ID
|
||||
containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{All: true})
|
||||
if err != nil {
|
||||
log.Info("获取容器列表失败: %v", err)
|
||||
}
|
||||
|
||||
var containerIDList []string
|
||||
for _, c := range containers {
|
||||
log.Info("containersIDList: %v", c.ID)
|
||||
for _, name := range c.Names {
|
||||
log.Info("containerNameList: %v", name)
|
||||
for _, devcontainer := range *devcontainersList {
|
||||
log.Info("devcontainerName: %v", devcontainer.Name)
|
||||
if name == "/"+devcontainer.Name {
|
||||
containerIDList = append(containerIDList, c.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 删除容器
|
||||
options := types.ContainerRemoveOptions{
|
||||
Force: true, // 强制删除正在运行的容器
|
||||
RemoveVolumes: true, // 删除数据卷
|
||||
RemoveLinks: false, // 删除链接(已弃用)
|
||||
}
|
||||
for _, id := range containerIDList {
|
||||
if err := cli.ContainerRemove(context.Background(), id, options); err != nil {
|
||||
log.Info("删除%s容器失败: %v", id, err)
|
||||
}
|
||||
log.Info("容器 %s 已成功删除\n", id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
68
services/devstar_devcontainer/docker_agent/DockerUtils.go
Normal file
68
services/devstar_devcontainer/docker_agent/DockerUtils.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package docker_agent
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/docker/docker/client"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func CreateDockerClient(ctx *context.Context) (*client.Client, error) {
|
||||
log.Info("检查 Docker 环境")
|
||||
// 1. 检查 Docker 环境
|
||||
dockerSocketPath, err := GetDockerSocketPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Info("dockerSocketPath: %s", dockerSocketPath)
|
||||
cli, err := CheckIfDockerRunning(*ctx, dockerSocketPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cli, nil
|
||||
}
|
||||
|
||||
/*
|
||||
Docker环境路径优先级: 配置文件、环境变量
|
||||
*/
|
||||
func GetDockerSocketPath() (string, error) {
|
||||
if setting.Devstar.Devcontainer.DockerHost != "" {
|
||||
return setting.Devstar.Devcontainer.DockerHost, nil
|
||||
}
|
||||
socket, found := os.LookupEnv("DOCKER_HOST")
|
||||
if found {
|
||||
return socket, nil
|
||||
}
|
||||
for _, p := range commonSocketPaths {
|
||||
if _, err := os.Lstat(os.ExpandEnv(p)); err == nil {
|
||||
if strings.HasPrefix(p, `\\.\`) {
|
||||
return "npipe://" + filepath.ToSlash(os.ExpandEnv(p)), nil
|
||||
}
|
||||
return "unix://" + filepath.ToSlash(os.ExpandEnv(p)), nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("daemon Docker Engine socket not found and docker_host config was invalid")
|
||||
}
|
||||
func CheckIfDockerRunning(ctx context.Context, configDockerHost string) (*client.Client, error) {
|
||||
opts := []client.Opt{
|
||||
client.FromEnv,
|
||||
}
|
||||
|
||||
if configDockerHost != "" {
|
||||
opts = append(opts, client.WithHost(configDockerHost))
|
||||
}
|
||||
|
||||
cli, err := client.NewClientWithOpts(opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = cli.Ping(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot ping the docker daemon, is it running? %w", err)
|
||||
}
|
||||
return cli, nil
|
||||
}
|
||||
@@ -3,4 +3,5 @@ package options
|
||||
type OpenDevcontainerAppDispatcherOptions struct {
|
||||
Name string `json:"name"`
|
||||
Wait bool `json:"wait"`
|
||||
Port uint16
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user