!94 实现devstar可以在对应配置的k8s上添加和删除runner

* 修复runners.go中关于ctx的编译报错
* Merge branch 'main' of gitee.com:devstar/devstar into feature/runner
* 实现devstar可以在对应配置的k8s上添加和删除runner
* 实现了修改runner的标签的功能
* 单机部署环境下实现Web界面上启动和删除runner
* 启动时能够自动启动act_runner
This commit is contained in:
vecmatex
2025-08-10 03:29:09 +00:00
repo.diff.committed_by 孟宁
repo.diff.parent 68512a67c8
repo.diff.commit b9ff967366
repo.diff.stats_desc%!(EXTRA int=17, int=807, int=0)

31
go.mod
repo.diff.view_file

@@ -134,6 +134,34 @@ require (
xorm.io/xorm v1.3.9
)
require (
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/distribution/reference v0.5.0 // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect
golang.org/x/term v0.32.0 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/apimachinery v0.33.3 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)
require (
cloud.google.com/go/compute/metadata v0.6.0 // indirect
dario.cat/mergo v1.0.1 // indirect
@@ -190,6 +218,7 @@ require (
github.com/davidmz/go-pageant v1.0.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/docker/docker v24.0.9+incompatible
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
@@ -279,6 +308,8 @@ require (
google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/api v0.33.3
k8s.io/client-go v0.33.3
)
replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1

66
go.sum
repo.diff.view_file

@@ -60,6 +60,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.6.0 h1:UXT0o77lXQrikd1kgwIPQOUect7EoR/+sbP4wQKdzxM=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0/go.mod h1:cTvi54pg19DoT07ekoeMgE/taAwNtCShVeZqA+Iv2xI=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
@@ -238,6 +240,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=
@@ -248,6 +252,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.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/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.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0=
github.com/docker/docker v24.0.9+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/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=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
@@ -266,6 +278,8 @@ github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTe
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 h1:oP4q0fw+fOSWn3DfFi4EXdT+B+gTtzx8GC9xsc26Znk=
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/ethantkoenig/rupture v1.0.1 h1:6aAXghmvtnngMgQzy7SMGdicMvkV86V4n9fT0meE5E4=
@@ -321,6 +335,16 @@ github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU=
github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-redis/redis/v7 v7.4.1 h1:PASvf36gyUpr2zdOUS/9Zqc80GbM+9BDyiJSJDDOrTI=
@@ -345,6 +369,8 @@ github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs=
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 h1:UjoPNDAQ5JPCjlxoJd6K8ALZqSDDhk2ymieAZOVaDg0=
@@ -382,11 +408,14 @@ github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl76
github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q=
github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
@@ -478,6 +507,8 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
@@ -545,11 +576,15 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
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.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=
@@ -726,6 +761,7 @@ github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3i
github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js=
github.com/yohcop/openid-go v1.0.1/go.mod h1:b/AvD03P0KHj4yuihb+VtLD6bYYgsy0zqBzPCRjkCNs=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
@@ -846,6 +882,7 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -893,8 +930,10 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200928182047-19e03678916f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
@@ -923,7 +962,11 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
@@ -941,6 +984,20 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8=
k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE=
k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA=
k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA=
k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
@@ -965,6 +1022,15 @@ mvdan.cc/xurls/v2 v2.6.0 h1:3NTZpeTxYVWNSokW3MKeyVkz/j7uYXYiMtXRUfmjbgI=
mvdan.cc/xurls/v2 v2.6.0/go.mod h1:bCvEZ1XvdA6wDnxY7jPPjEmigDtvtvPXAD/Exa9IMSk=
pgregory.net/rapid v0.4.2 h1:lsi9jhvZTYvzVpeG93WWgimPRmiJQfGFRNTEZh1dtY0=
pgregory.net/rapid v0.4.2/go.mod h1:UYpPVyjFHzYBGHIxLFoupi8vwk6rXNzRY9OMvVxFIOU=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 h1:mUcz5b3FJbP5Cvdq7Khzn6J9OCUQJaBwgBkCR+MOwSs=
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:FJGmPh3vz9jSos1L/F91iAgnC/aejc0wIIrF2ZwJxdY=
xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo=

repo.diff.view_file

@@ -0,0 +1,117 @@
package docker
import (
"context"
"io"
"os"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
)
// CreateDockerClient 创建Docker客户端
func CreateDockerClient(ctx context.Context) (*client.Client, error) {
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
return nil, err
}
return cli, nil
}
// GetDockerSocketPath 获取Docker Socket路径
func GetDockerSocketPath() (string, error) {
// 检查常见的Docker socket路径
socketPaths := []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",
}
for _, path := range socketPaths {
if _, err := os.Stat(path); err == nil {
return path, nil
}
}
// 如果找不到,返回默认路径
return "/var/run/docker.sock", nil
}
// PullImage 拉取Docker镜像
func PullImage(cli *client.Client, dockerHost, imageName string) error {
ctx := context.Background()
reader, err := cli.ImagePull(ctx, imageName, types.ImagePullOptions{})
if err != nil {
return err
}
defer reader.Close()
// 读取并丢弃输出,确保拉取完成
_, err = io.Copy(io.Discard, reader)
return err
}
// CreateAndStartContainer 创建并启动容器
func CreateAndStartContainer(cli *client.Client, imageName string, cmd []string, env []string, binds []string, ports map[string]string, containerName string) error {
ctx := context.Background()
// 配置容器
config := &container.Config{
Image: imageName,
Env: env,
}
if cmd != nil {
config.Cmd = cmd
}
hostConfig := &container.HostConfig{
Binds: binds,
}
// 如果有端口映射配置
if ports != nil && len(ports) > 0 {
// 这里可以根据需要添加端口映射逻辑
// hostConfig.PortBindings = portBindings
}
// 创建容器
resp, err := cli.ContainerCreate(ctx, config, hostConfig, nil, nil, containerName)
if err != nil {
return err
}
// 启动容器
return cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
}
// DeleteContainer 停止并删除指定名称的容器
func DeleteContainer(cli *client.Client, containerName string) error {
ctx := context.Background()
// 首先尝试停止容器
timeout := 10
err := cli.ContainerStop(ctx, containerName, container.StopOptions{
Timeout: &timeout,
})
if err != nil {
// 如果容器已经停止或不存在,继续执行删除操作
// 这里不返回错误,因为我们的目标是删除容器
}
// 删除容器
err = cli.ContainerRemove(ctx, containerName, types.ContainerRemoveOptions{
Force: true, // 强制删除,即使容器正在运行
})
if err != nil {
return err
}
return nil
}

14
modules/setting/k8s.go Normal file
repo.diff.view_file

@@ -0,0 +1,14 @@
package setting
var K8sConfig = struct {
Enable bool
Url string
Token string
}{}
func loadK8sSettingsFrom(rootCfg ConfigProvider) {
sec := rootCfg.Section("k8s")
K8sConfig.Enable = sec.Key("ENABLE").MustBool(false)
K8sConfig.Url = sec.Key("URL").MustString("")
K8sConfig.Token = sec.Key("TOKEN").MustString("")
}

repo.diff.view_file

@@ -0,0 +1,14 @@
package setting
var Runner = struct {
AutoStart bool
Count int
Image string
}{}
func loadRunnerSettingsFrom(rootCfg ConfigProvider) {
sec := rootCfg.Section("runners")
Runner.AutoStart = sec.Key("AUTO_START").MustBool(true)
Runner.Count = sec.Key("RUNNER_COUNT").MustInt(1)
Runner.Image = sec.Key("RUNNER_IMAGE").MustString("devstar.cn/devstar/act_runner:latest")
}

repo.diff.view_file

@@ -217,6 +217,8 @@ func LoadSettings() {
loadProjectFrom(CfgProvider)
loadMimeTypeMapFrom(CfgProvider)
loadFederationFrom(CfgProvider)
loadRunnerSettingsFrom(CfgProvider)
loadK8sSettingsFrom(CfgProvider)
loadWechatSettingsFrom(CfgProvider)
}
@@ -225,6 +227,8 @@ func LoadSettingsForInstall() {
loadDBSetting(CfgProvider)
loadServiceFrom(CfgProvider)
loadMailerFrom(CfgProvider)
loadRunnerSettingsFrom(CfgProvider)
loadK8sSettingsFrom(CfgProvider)
loadWechatSettingsFrom(CfgProvider)
}

repo.diff.view_file

@@ -303,6 +303,10 @@ log_root_path = Log Path
log_root_path_helper = Log files will be written to this directory.
optional_title = Optional Settings
k8s_title = Kubernetes Settings
k8s_enable = Enable Kubernetes
k8s_url = Kubernetes API URL
k8s_token = Kubernetes Token
email_title = Email Settings
smtp_addr = SMTP Host
smtp_port = SMTP Port
@@ -3879,6 +3883,8 @@ runners.status.active = Active
runners.status.offline = Offline
runners.version = Version
runners.reset_registration_token = Reset registration token
runners.regist_runner = Register a new runner
runners.regist_runner_success = Register a new runner successfully
runners.reset_registration_token_confirm = Would you like to invalidate the current token and generate a new one?
runners.regist_runner = Register a new runner
runners.regist_runner_success = Register a new runner successfully

repo.diff.view_file

@@ -298,6 +298,10 @@ log_root_path=日志路径
log_root_path_helper=日志文件将写入此目录。
optional_title=可选设置
k8s_title = Kubernetes设置
k8s_enable = 启用 Kubernetes
k8s_url = Kubernetes API 地址
k8s_token = Kubernetes 访问令牌
email_title=电子邮箱设置
smtp_addr=SMTP 主机地址
smtp_port=SMTP 端口
@@ -3866,6 +3870,8 @@ runners.status.active=启用
runners.status.offline=离线
runners.version=版本
runners.reset_registration_token=重置注册令牌
runners.regist_runner=启动并注册一个运行器
runners.regist_runner_success=成功启动并注册一个运行器
runners.reset_registration_token_confirm=是否吊销当前令牌并生成一个新令牌?
runners.regist_runner=启动并注册一个运行器
runners.regist_runner_success=成功启动并注册一个运行器

repo.diff.view_file

@@ -36,6 +36,7 @@ import (
auth_service "code.gitea.io/gitea/services/auth"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
runners_service "code.gitea.io/gitea/services/runners"
"code.gitea.io/gitea/services/versioned_migration"
"gitea.com/go-chi/session"
@@ -123,6 +124,10 @@ func Install(ctx *context.Context) {
form.AppURL = setting.AppURL
form.LogRootPath = setting.Log.RootPath
form.K8sEnable = setting.K8sConfig.Enable
form.K8sUrl = setting.K8sConfig.Url
form.K8sToken = setting.K8sConfig.Token
// E-mail service settings
if setting.MailService != nil {
form.SMTPAddr = setting.MailService.SMTPAddr
@@ -450,6 +455,20 @@ func SubmitInstall(ctx *context.Context) {
cfg.Section("wechat").Key("ENABLED_WECHAT_QR_SIGNIN").SetValue("false")
}
if form.K8sEnable {
ctx.Data["K8sEnable"] = form.K8sEnable
cfg.Section("k8s").Key("ENABLE").SetValue("true")
cfg.Section("k8s").Key("URL").SetValue(form.K8sUrl)
cfg.Section("k8s").Key("TOKEN").SetValue(form.K8sToken)
} else {
ctx.Data["K8sEnable"] = form.K8sEnable
cfg.Section("k8s").Key("ENABLE").SetValue("false")
}
cfg.Section("runners").Key("AUTO_START").SetValue("true")
cfg.Section("runners").Key("RUNNER_COUNT").SetValue("1")
cfg.Section("runners").Key("RUNNER_IMAGE").SetValue("devstar.cn/devstar/act_runner:latest")
cfg.Section("openid").Key("ENABLE_OPENID_SIGNIN").SetValue(strconv.FormatBool(form.EnableOpenIDSignIn))
cfg.Section("openid").Key("ENABLE_OPENID_SIGNUP").SetValue(strconv.FormatBool(form.EnableOpenIDSignUp))
cfg.Section("service").Key("DISABLE_REGISTRATION").SetValue(strconv.FormatBool(form.DisableRegistration))
@@ -594,6 +613,8 @@ func SubmitInstall(ctx *context.Context) {
}
}
runners_service.RegistGlobalRunner(ctx)
setting.ClearEnvConfigKeys()
log.Info("First-time run install finished!")
InstallDone(ctx)

repo.diff.view_file

@@ -7,6 +7,7 @@ import (
"errors"
"net/http"
"net/url"
"strings"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
@@ -18,6 +19,7 @@ import (
shared_user "code.gitea.io/gitea/routers/web/shared/user"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
runners_services "code.gitea.io/gitea/services/runners"
)
const (
@@ -253,6 +255,13 @@ func RunnersEditPost(ctx *context.Context) {
runner.Description = form.Description
err = actions_model.UpdateRunner(ctx, runner, "description")
agentLabelsStr := ctx.Req.FormValue("agentlabels")
form.AgentLabels = strings.Split(agentLabelsStr, ",")
for i := range form.AgentLabels {
form.AgentLabels[i] = strings.TrimSpace(form.AgentLabels[i])
}
runner.AgentLabels = form.AgentLabels
err = actions_model.UpdateRunner(ctx, runner, "description", "agent_labels")
if err != nil {
log.Warn("RunnerDetailsEditPost.UpdateRunner failed: %v, url: %s", err, ctx.Req.URL)
ctx.Flash.Warning(ctx.Tr("actions.runners.update_runner_failed"))
@@ -285,6 +294,31 @@ func ResetRunnerRegistrationToken(ctx *context.Context) {
ctx.JSONRedirect(redirectTo)
}
func RegisterARunner(ctx *context.Context) {
rCtx, err := getRunnersCtx(ctx)
if err != nil {
ctx.ServerError("getRunnersCtx", err)
return
}
token, err := actions_model.NewRunnerToken(ctx, rCtx.OwnerID, rCtx.RepoID)
if err != nil {
ctx.ServerError("NewRunnerToken", err)
return
}
regToken := token.Token
requestCtx := ctx.Req.Context()
err = runners_services.RegistRunner(requestCtx, regToken)
if err != nil {
log.Warn("RegistRunner failed: %v, url: %s", err, ctx.Req.URL)
ctx.Flash.Warning(ctx.Tr("actions.runners.regist_runner_failed"))
ctx.Redirect(rCtx.RedirectLink)
return
}
ctx.Flash.Success(ctx.Tr("actions.runners.regist_runner_success"))
ctx.Redirect(rCtx.RedirectLink)
}
// RunnerDeletePost response for deleting runner
func RunnerDeletePost(ctx *context.Context) {
rCtx, err := getRunnersCtx(ctx)
@@ -306,6 +340,17 @@ func RunnerDeletePost(ctx *context.Context) {
successRedirectTo := rCtx.RedirectLink
failedRedirectTo := rCtx.RedirectLink + url.PathEscape(ctx.PathParam("runnerid"))
// 删除对应的Docker容器
if runner.Name != "" {
requestCtx := ctx.Req.Context()
if err := runners_services.DeleteRunnerByName(requestCtx, runner.Name); err != nil {
log.Warn("DeleteRunnerByName failed: %v, runner name: %s, url: %s", err, runner.Name, ctx.Req.URL)
// 即使删除容器失败,我们仍然继续删除数据库记录
} else {
log.Info("Successfully deleted Docker container for runner: %s", runner.Name)
}
}
if err := actions_model.DeleteRunner(ctx, runner.ID); err != nil {
log.Warn("DeleteRunnerPost.UpdateRunner failed: %v, url: %s", err, ctx.Req.URL)
ctx.Flash.Warning(ctx.Tr("actions.runners.delete_runner_failed"))

repo.diff.view_file

@@ -473,6 +473,16 @@ func registerWebRoutes(m *web.Router) {
})
}
addSettingsRunnersRegRoutes := func() {
m.Group("/runners", func() {
m.Get("", shared_actions.Runners)
m.Combo("/{runnerid}").Get(shared_actions.RunnersEdit).
Post(web.Bind(forms.EditRunnerForm{}), shared_actions.RunnersEditPost)
m.Post("/{runnerid}/delete", shared_actions.RunnerDeletePost)
m.Get("/regist_runner", shared_actions.RegisterARunner)
})
}
// FIXME: not all routes need go through same middleware.
// Especially some AJAX requests, we can reduce middleware number to improve performance.
@@ -668,6 +678,7 @@ func registerWebRoutes(m *web.Router) {
m.Group("/actions", func() {
m.Get("", user_setting.RedirectToDefaultSetting)
addSettingsRunnersRoutes()
addSettingsRunnersRegRoutes()
addSettingsSecretsRoutes()
addSettingsVariablesRoutes()
}, actions.MustEnableActions)
@@ -823,6 +834,7 @@ func registerWebRoutes(m *web.Router) {
m.Group("/actions", func() {
m.Get("", admin.RedirectToDefaultSetting)
addSettingsRunnersRoutes()
addSettingsRunnersRegRoutes()
addSettingsVariablesRoutes()
})
}, adminReq, ctxDataSet("EnableOAuth2", setting.OAuth2.Enabled, "EnablePackages", setting.Packages.Enabled))
@@ -971,6 +983,7 @@ func registerWebRoutes(m *web.Router) {
m.Group("/actions", func() {
m.Get("", org_setting.RedirectToDefaultSetting)
addSettingsRunnersRoutes()
addSettingsRunnersRegRoutes()
addSettingsSecretsRoutes()
addSettingsVariablesRoutes()
}, actions.MustEnableActions)
@@ -1163,6 +1176,7 @@ func registerWebRoutes(m *web.Router) {
m.Group("/actions", func() {
m.Get("", shared_actions.RedirectToDefaultSetting)
addSettingsRunnersRoutes()
addSettingsRunnersRegRoutes()
addSettingsSecretsRoutes()
addSettingsVariablesRoutes()
}, actions.MustEnableActions)

repo.diff.view_file

@@ -15,6 +15,7 @@ import (
// EditRunnerForm form for admin to create runner
type EditRunnerForm struct {
Description string
AgentLabels []string
}
// Validate validates form fields

repo.diff.view_file

@@ -37,6 +37,10 @@ type InstallForm struct {
AppURL string `binding:"Required"`
LogRootPath string `binding:"Required"`
K8sEnable bool
K8sUrl string
K8sToken string
SMTPAddr string
SMTPPort string
SMTPFrom string

435
services/runners/runners.go Normal file
repo.diff.view_file

@@ -0,0 +1,435 @@
package runners
import (
"context"
"fmt"
"net"
"strings"
"time"
actions_module "code.gitea.io/gitea/models/actions"
docker_module "code.gitea.io/gitea/modules/docker"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
func RegistGlobalRunner(ctx context.Context) error {
log.Info("获取全局RunnerToken...")
actionRunnerToken, err := actions_module.NewRunnerToken(ctx, 0, 0)
if err != nil {
return fmt.Errorf("获取全局RunnerToken失败:%v", err)
}
runnerCount := setting.Runner.Count
for i := 0; i < runnerCount; i++ {
err := RegistRunner(ctx, actionRunnerToken.Token)
if err != nil {
return fmt.Errorf("注册Runner失败:%v", err)
}
}
return nil
}
func checkK8sIsEnable() bool {
return setting.K8sConfig.Enable
}
func RegistRunner(ctx context.Context, token string) error {
log.Info("开始注册Runner...")
var err error
if checkK8sIsEnable() {
err = registK8sRunner(ctx, token)
} else {
err = registDockerRunner(ctx, token)
}
if err != nil {
return fmt.Errorf("注册Runner失败:%v", err)
}
log.Info("Runner注册成功: %s", token)
return nil
}
func registDockerRunner(ctx context.Context, token string) error {
log.Info("开始注册Runner...")
cli, err := docker_module.CreateDockerClient(ctx)
if err != nil {
return err
}
defer cli.Close()
//拉取act_runner镜像
dockerHost, err := docker_module.GetDockerSocketPath()
if err != nil {
return fmt.Errorf("获取docker socket路径失败:%v", err)
}
// 拉取镜像
err = docker_module.PullImage(cli, dockerHost, setting.Runner.Image)
if err != nil {
return fmt.Errorf("拉取act_runner镜像失败:%v", err)
}
//获取本机IP
ips, err := getLocalIP()
if err != nil {
return fmt.Errorf("获取本机IP失败:%v", err)
}
//获取InstanceUrl
conntype := strings.Split(setting.AppURL, "://")[0]
port := setting.HTTPPort
instanceURL := conntype + "://" + ips[0] + ":" + port
timestamp := time.Now().Format("20060102150405")
//Runner配置
env := []string{
"GITEA_INSTANCE_URL=" + instanceURL,
"GITEA_RUNNER_REGISTRATION_TOKEN=" + token,
"GITEA_RUNNER_NAME=runner-" + timestamp,
}
binds := []string{
"/var/run/docker.sock:/var/run/docker.sock",
}
containerName := "runner-" + timestamp
//创建并启动Runner容器
err = docker_module.CreateAndStartContainer(cli, setting.Runner.Image, nil, env, binds, nil, containerName)
if err != nil {
return fmt.Errorf("创建并注册Runner失败:%v", err)
}
return nil
}
func DeleteRunnerByName(ctx context.Context, runnerName string) error {
log.Info("开始停止并删除容器: %s", runnerName)
var err error
if checkK8sIsEnable() {
err = deleteK8sRunnerByName(ctx, runnerName)
} else {
err = deleteDockerRunnerByName(ctx, runnerName)
}
if err != nil {
return fmt.Errorf("删除Runner失败:%v", err)
}
log.Info("Runner删除成功: %s", runnerName)
return nil
}
func deleteDockerRunnerByName(ctx context.Context, runnerName string) error {
log.Info("开始停止并删除容器: %s", runnerName)
// 创建Docker客户端
cli, err := docker_module.CreateDockerClient(ctx)
if err != nil {
return fmt.Errorf("Docker client创建失败:%v", err)
}
log.Info("[StopAndRemoveContainer]Docker client创建成功")
defer cli.Close()
err = docker_module.DeleteContainer(cli, runnerName)
if err != nil {
return fmt.Errorf("Runner创建失败:%v", err)
}
return nil
}
func getLocalIP() ([]string, error) {
var ips []string
interfaces, err := net.Interfaces()
if err != nil {
return nil, err
}
for _, iface := range interfaces {
if iface.Flags&net.FlagUp == 0 ||
iface.Flags&net.FlagLoopback != 0 {
continue
}
addrs, err := iface.Addrs()
if err != nil {
continue
}
// 遍历地址列表
for _, addr := range addrs {
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if ip == nil || ip.IsLoopback() {
continue
}
ip = ip.To4()
if ip == nil {
continue // 非IPv4地址
}
ips = append(ips, ip.String())
}
}
if len(ips) == 0 {
return nil, fmt.Errorf("no valid IP address found")
}
return ips, nil
}
func getK8sUrlAndToken() (string, string, error) {
if !checkK8sIsEnable() {
return "", "", fmt.Errorf("K8s未启用")
}
k8sUrl := setting.K8sConfig.Url
k8sToken := setting.K8sConfig.Token
if k8sUrl == "" || k8sToken == "" {
return "", "", fmt.Errorf("K8s配置不完整")
}
return k8sUrl, k8sToken, nil
}
func registK8sRunner(ctx context.Context, token string) error {
log.Info("开始注册Kubernetes Runner: %s", token)
k8sURL, k8sToken, err := getK8sUrlAndToken()
if err != nil {
return fmt.Errorf("获取K8s配置失败: %v", err)
}
// 测试连接
err = testKubernetesConnection(k8sURL, k8sToken)
if err != nil {
return fmt.Errorf("Kubernetes连接测试失败: %v", err)
}
// 创建K8s客户端
clientset, err := createKubernetesClient(k8sURL, k8sToken)
if err != nil {
return fmt.Errorf("创建Kubernetes客户端失败: %v", err)
}
// 获取实例URL
instanceURL, err := getInstanceURL()
if err != nil {
return fmt.Errorf("获取实例URL失败: %v", err)
}
// 创建Runner Deployment
deployment, err := createRunnerDeployment(token, instanceURL)
if err != nil {
return fmt.Errorf("创建Runner Deployment配置失败: %v", err)
}
// 部署到Kubernetes
//namespace := setting.K8sConfig.Namespace
var namespace string
if namespace == "" {
namespace = "act-runner"
}
_, err = clientset.AppsV1().Deployments(namespace).Create(ctx, deployment, metav1.CreateOptions{})
if err != nil {
return fmt.Errorf("在Kubernetes中创建Runner Deployment失败: %v", err)
}
log.Info("成功在Kubernetes中创建Runner: %s", deployment.Name)
return nil
}
func getInstanceURL() (string, error) {
// 如果AppURL是公网地址直接使用
if setting.AppURL != "" &&
!strings.Contains(setting.AppURL, "127.0.0.1") &&
!strings.Contains(setting.AppURL, "localhost") {
log.Info("使用配置的AppURL: %s", setting.AppURL)
return setting.AppURL, nil
}
// 否则构建URL
ips, err := getLocalIP()
if err != nil {
return "", fmt.Errorf("获取本机IP失败: %v", err)
}
if len(ips) == 0 {
return "", fmt.Errorf("没有找到有效的IP地址")
}
// 使用第一个IP构建URL
conntype := "http"
if strings.Contains(setting.AppURL, "https://") {
conntype = "https"
}
port := setting.HTTPPort
instanceURL := conntype + "://" + ips[0] + ":" + port
log.Info("构建的实例URL: %s", instanceURL)
return instanceURL, nil
}
func deleteK8sRunnerByName(ctx context.Context, runnerName string) error {
log.Info("开始删除K8s Runner: %s", runnerName)
// 创建Kubernetes客户端
clientset, err := createKubernetesClient(setting.K8sConfig.Url, setting.K8sConfig.Token)
if err != nil {
return fmt.Errorf("创建Kubernetes客户端失败: %v", err)
}
// 设置namespace与创建时保持一致
namespace := "act-runner"
// 删除Deployment
err = clientset.AppsV1().Deployments(namespace).Delete(ctx, runnerName, metav1.DeleteOptions{})
if err != nil {
return fmt.Errorf("删除K8s Runner Deployment失败: %v", err)
}
log.Info("成功删除K8s Runner Deployment: %s", runnerName)
return nil
}
func createKubernetesClient(k8sURL, token string) (*kubernetes.Clientset, error) {
config := &rest.Config{
Host: k8sURL,
BearerToken: token,
TLSClientConfig: rest.TLSClientConfig{
Insecure: true,
},
}
// 创建客户端
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("创建Kubernetes客户端失败: %v", err)
}
return clientset, nil
}
func testKubernetesConnection(k8sURL, token string) error {
clientset, err := createKubernetesClient(k8sURL, token)
if err != nil {
return err
}
// 尝试获取节点列表来测试连接
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_, err = clientset.CoreV1().Nodes().List(ctx, metav1.ListOptions{Limit: 1})
if err != nil {
return fmt.Errorf("无法连接到Kubernetes集群: %v", err)
}
log.Info("Kubernetes连接测试成功")
return nil
}
func createRunnerDeployment(token, instanceURL string) (*appsv1.Deployment, error) {
timestamp := time.Now().Format("20060102150405")
name := "act-runner-" + timestamp
labels := map[string]string{
"app": "act-runner",
"type": "runner",
"version": "1.0",
}
// 副本数从配置获取
replicas := int32(1)
// 创建Deployment配置
deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Labels: labels,
},
Spec: appsv1.DeploymentSpec{
Replicas: &replicas,
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "act-runner",
},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "act-runner",
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "act-runner", // 匹配现有配置中的容器名
Image: setting.Runner.Image,
Ports: []corev1.ContainerPort{
{
Name: "http-0",
ContainerPort: 3000,
Protocol: corev1.ProtocolTCP,
},
},
Env: []corev1.EnvVar{
{
Name: "GITEA_INSTANCE_URL",
Value: instanceURL,
},
{
Name: "GITEA_RUNNER_REGISTRATION_TOKEN",
Value: token,
},
{
Name: "GITEA_RUNNER_NAME", // 可选如果需要设置runner名称
Value: name,
},
},
// 移除资源限制以匹配现有配置(现有配置中 resources: {}
Resources: corev1.ResourceRequirements{},
// 挂载Docker socket
VolumeMounts: []corev1.VolumeMount{
{
Name: "docker-sock",
MountPath: "/var/run/docker.sock",
},
},
ImagePullPolicy: corev1.PullIfNotPresent,
},
},
// Docker socket卷
Volumes: []corev1.Volume{
{
Name: "docker-sock",
VolumeSource: corev1.VolumeSource{
HostPath: &corev1.HostPathVolumeSource{
Path: "/var/run/docker.sock",
},
},
},
},
RestartPolicy: corev1.RestartPolicyAlways,
ServiceAccountName: "default", // 匹配现有配置
DNSPolicy: corev1.DNSClusterFirst,
// 添加节点选择器(如果需要)
NodeSelector: map[string]string{
"kubernetes.io/hostname": "node1", // 可以从配置中读取
},
// 添加容忍度
Tolerations: []corev1.Toleration{
{
Key: "node.kubernetes.io/not-ready",
Operator: corev1.TolerationOpExists,
Effect: corev1.TaintEffectNoExecute,
TolerationSeconds: func() *int64 { i := int64(300); return &i }(),
},
{
Key: "node.kubernetes.io/unreachable",
Operator: corev1.TolerationOpExists,
Effect: corev1.TaintEffectNoExecute,
TolerationSeconds: func() *int64 { i := int64(300); return &i }(),
},
},
},
},
},
}
return deployment, nil
}

repo.diff.view_file

@@ -158,6 +158,27 @@
<!-- Optional Settings -->
<h4 class="ui dividing header">{{ctx.Locale.Tr "install.optional_title"}}</h4>
<div>
<!-- k8s -->
<details class="optional field">
<summary class="right-content tw-py-2{{if .Err_SMTP}} text red{{end}}">
{{ctx.Locale.Tr "install.k8s_title"}}
</summary>
<div class="inline field">
<div class="ui checkbox" id="enable-k8s">
<label data-tooltip-content="{{ctx.Locale.Tr "install.k8s_enable"}}">{{ctx.Locale.Tr "install.k8s_enable"}}</label>
<input name="k8s_enable" type="checkbox" {{if .k8s_enable}}checked{{end}}>
</div>
</div>
<div class="inline field">
<label for="k8s_url">{{ctx.Locale.Tr "install.k8s_url"}}</label>
<input id="k8s_url" name="k8s_url" value="{{.k8s_url}}">
</div>
<div class="inline field">
<label for="k8s_token">{{ctx.Locale.Tr "install.k8s_token"}}</label>
<input id="k8s_token" name="k8s_token" value="{{.k8s_token}}">
</div>
</div>
</details>
<!-- Email -->
<details class="optional field">
<summary class="right-content tw-py-2{{if .Err_SMTP}} text red{{end}}">

repo.diff.view_file

@@ -36,6 +36,11 @@
<input id="description" name="description" value="{{.Runner.Description}}">
</div>
<div class="field">
<label for="agentlabels">{{ctx.Locale.Tr "actions.runners.labels"}}</label>
<input id="agentlabels" name="agentlabels" value="{{range $i, $label := .Runner.AgentLabels}}{{if $i}}, {{end}}{{$label}}{{end}}">
</div>
<div class="divider"></div>
<div class="field">

repo.diff.view_file

@@ -9,6 +9,9 @@
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</button>
<div class="menu">
<div class="item">
<a href="{{$.Link}}/regist_runner">{{ctx.Locale.Tr "actions.runners.regist_runner"}}</a>
</div>
<div class="item">
<a href="https://docs.gitea.com/usage/actions/act-runner">{{ctx.Locale.Tr "actions.runners.new_notice"}}</a>
</div>