Add runner capability detection and reporting
Some checks failed
release-nightly / release-image (map[tag_suffix: target:basic]) (push) Failing after 0s
release-nightly / release-image (map[tag_suffix:-dind-rootless target:dind-rootless]) (push) Failing after 0s
release-tag / goreleaser (push) Failing after 2s
release-tag / release-image (push) Failing after 0s
checks / check and test (push) Failing after 2s
release-nightly / goreleaser (push) Failing after 3s
release-nightly / release-image (map[tag_suffix:-dind target:dind]) (push) Failing after 0s
Some checks failed
release-nightly / release-image (map[tag_suffix: target:basic]) (push) Failing after 0s
release-nightly / release-image (map[tag_suffix:-dind-rootless target:dind-rootless]) (push) Failing after 0s
release-tag / goreleaser (push) Failing after 2s
release-tag / release-image (push) Failing after 0s
checks / check and test (push) Failing after 2s
release-nightly / goreleaser (push) Failing after 3s
release-nightly / release-image (map[tag_suffix:-dind target:dind]) (push) Failing after 0s
This change adds automatic detection of runner capabilities including: - OS and architecture - Docker/Podman availability and version - Docker Compose support - Available shells (bash, sh, pwsh, etc.) - Installed development tools (Node, Go, Python, Java, .NET, Rust) - Feature support flags (cache, services, composite actions) - Known limitations (artifact v4 not supported) Capabilities are detected on startup and sent to Gitea via the CapabilitiesJson field in DeclareRequest. This enables AI tools to query runner capabilities before generating workflows. Uses GitCaddy fork of actions-proto-go with capability support. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
5
go.mod
5
go.mod
@@ -5,7 +5,7 @@ go 1.24.0
|
|||||||
toolchain go1.24.11
|
toolchain go1.24.11
|
||||||
|
|
||||||
require (
|
require (
|
||||||
code.gitea.io/actions-proto-go v0.4.1
|
code.gitea.io/actions-proto-go v0.5.0
|
||||||
code.gitea.io/gitea-vet v0.2.3
|
code.gitea.io/gitea-vet v0.2.3
|
||||||
connectrpc.com/connect v1.16.2
|
connectrpc.com/connect v1.16.2
|
||||||
github.com/avast/retry-go/v4 v4.6.0
|
github.com/avast/retry-go/v4 v4.6.0
|
||||||
@@ -108,3 +108,6 @@ replace github.com/go-git/go-git/v5 => github.com/go-git/go-git/v5 v5.16.2
|
|||||||
|
|
||||||
// Remove after
|
// Remove after
|
||||||
replace github.com/distribution/reference v0.6.0 => github.com/distribution/reference v0.5.0
|
replace github.com/distribution/reference v0.6.0 => github.com/distribution/reference v0.5.0
|
||||||
|
|
||||||
|
// Use GitCaddy fork with capability support
|
||||||
|
replace code.gitea.io/actions-proto-go => git.marketally.com/gitcaddy/actions-proto-go v0.5.0
|
||||||
|
|||||||
18
go.sum
18
go.sum
@@ -1,5 +1,3 @@
|
|||||||
code.gitea.io/actions-proto-go v0.4.1 h1:l0EYhjsgpUe/1VABo2eK7zcoNX2W44WOnb0MSLrKfls=
|
|
||||||
code.gitea.io/actions-proto-go v0.4.1/go.mod h1:mn7Wkqz6JbnTOHQpot3yDeHx+O5C9EGhMEE+htvHBas=
|
|
||||||
code.gitea.io/gitea-vet v0.2.3 h1:gdFmm6WOTM65rE8FUBTRzeQZYzXePKSSB1+r574hWwI=
|
code.gitea.io/gitea-vet v0.2.3 h1:gdFmm6WOTM65rE8FUBTRzeQZYzXePKSSB1+r574hWwI=
|
||||||
code.gitea.io/gitea-vet v0.2.3/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
|
code.gitea.io/gitea-vet v0.2.3/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
|
||||||
connectrpc.com/connect v1.16.2 h1:ybd6y+ls7GOlb7Bh5C8+ghA6SvCBajHwxssO2CGFjqE=
|
connectrpc.com/connect v1.16.2 h1:ybd6y+ls7GOlb7Bh5C8+ghA6SvCBajHwxssO2CGFjqE=
|
||||||
@@ -8,6 +6,8 @@ cyphar.com/go-pathrs v0.2.1 h1:9nx1vOgwVvX1mNBWDu93+vaceedpbsDqo+XuBGL40b8=
|
|||||||
cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc=
|
cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc=
|
||||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||||
|
git.marketally.com/gitcaddy/actions-proto-go v0.5.0 h1:D2loMnqTXiaJL6TMfOOUJz4/3Vpv0AnMDSJVuiqMNrM=
|
||||||
|
git.marketally.com/gitcaddy/actions-proto-go v0.5.0/go.mod h1:li5RzZsj1sV8a0SXzXWsGNwv0dYw7Wj829AgloZqF5o=
|
||||||
gitea.com/gitea/act v0.261.7-0.20251202193638-5417d3ac6742 h1:ulcquQluJbmNASkh6ina70LvcHEa9eWYfQ+DeAZ0VEE=
|
gitea.com/gitea/act v0.261.7-0.20251202193638-5417d3ac6742 h1:ulcquQluJbmNASkh6ina70LvcHEa9eWYfQ+DeAZ0VEE=
|
||||||
gitea.com/gitea/act v0.261.7-0.20251202193638-5417d3ac6742/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok=
|
gitea.com/gitea/act v0.261.7-0.20251202193638-5417d3ac6742/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok=
|
||||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
|
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
|
||||||
@@ -51,8 +51,6 @@ github.com/docker/cli v25.0.3+incompatible h1:KLeNs7zws74oFuVhgZQ5ONGZiXUUdgsdy6
|
|||||||
github.com/docker/cli v25.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v25.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
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/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
github.com/docker/docker v25.0.6+incompatible h1:5cPwbwriIcsua2REJe8HqQV+6WlWc1byg2QSXzBxBGg=
|
|
||||||
github.com/docker/docker v25.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
|
||||||
github.com/docker/docker v25.0.13+incompatible h1:YeBrkUd3q0ZoRDNoEzuopwCLU+uD8GZahDHwBdsTnkU=
|
github.com/docker/docker v25.0.13+incompatible h1:YeBrkUd3q0ZoRDNoEzuopwCLU+uD8GZahDHwBdsTnkU=
|
||||||
github.com/docker/docker v25.0.13+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v25.0.13+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
|
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
|
||||||
@@ -233,8 +231,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
|||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
|
||||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
|
||||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||||
@@ -248,8 +244,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
|
||||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
|
||||||
golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
|
golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
|
||||||
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -271,20 +265,16 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
|
||||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
|
||||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
|
|
||||||
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
|
|
||||||
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
||||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
|||||||
@@ -136,8 +136,19 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu
|
|||||||
|
|
||||||
runner := run.NewRunner(cfg, reg, cli)
|
runner := run.NewRunner(cfg, reg, cli)
|
||||||
|
|
||||||
|
// Detect runner capabilities for AI-friendly workflow generation
|
||||||
|
dockerHost := cfg.Container.DockerHost
|
||||||
|
if dockerHost == "" {
|
||||||
|
if dh, err := getDockerSocketPath(""); err == nil {
|
||||||
|
dockerHost = dh
|
||||||
|
}
|
||||||
|
}
|
||||||
|
capabilities := envcheck.DetectCapabilities(ctx, dockerHost)
|
||||||
|
capabilitiesJson := capabilities.ToJSON()
|
||||||
|
log.Infof("detected capabilities: %s", capabilitiesJson)
|
||||||
|
|
||||||
// declare the labels of the runner before fetching tasks
|
// declare the labels of the runner before fetching tasks
|
||||||
resp, err := runner.Declare(ctx, ls.Names())
|
resp, err := runner.Declare(ctx, ls.Names(), capabilitiesJson)
|
||||||
if err != nil && connect.CodeOf(err) == connect.CodeUnimplemented {
|
if err != nil && connect.CodeOf(err) == connect.CodeUnimplemented {
|
||||||
log.Errorf("Your Gitea version is too old to support runner declare, please upgrade to v1.21 or later")
|
log.Errorf("Your Gitea version is too old to support runner declare, please upgrade to v1.21 or later")
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -249,9 +249,10 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
|
|||||||
return execErr
|
return execErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Runner) Declare(ctx context.Context, labels []string) (*connect.Response[runnerv1.DeclareResponse], error) {
|
func (r *Runner) Declare(ctx context.Context, labels []string, capabilitiesJson string) (*connect.Response[runnerv1.DeclareResponse], error) {
|
||||||
return r.client.Declare(ctx, connect.NewRequest(&runnerv1.DeclareRequest{
|
return r.client.Declare(ctx, connect.NewRequest(&runnerv1.DeclareRequest{
|
||||||
Version: ver.Version(),
|
Version: ver.Version(),
|
||||||
Labels: labels,
|
Labels: labels,
|
||||||
|
CapabilitiesJson: capabilitiesJson,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
312
internal/pkg/envcheck/capabilities.go
Normal file
312
internal/pkg/envcheck/capabilities.go
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package envcheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RunnerCapabilities represents the capabilities of a runner for AI consumption
|
||||||
|
type RunnerCapabilities struct {
|
||||||
|
OS string `json:"os"`
|
||||||
|
Arch string `json:"arch"`
|
||||||
|
Docker bool `json:"docker"`
|
||||||
|
DockerCompose bool `json:"docker_compose"`
|
||||||
|
ContainerRuntime string `json:"container_runtime,omitempty"`
|
||||||
|
Shell []string `json:"shell,omitempty"`
|
||||||
|
Tools map[string][]string `json:"tools,omitempty"`
|
||||||
|
Features *CapabilityFeatures `json:"features,omitempty"`
|
||||||
|
Limitations []string `json:"limitations,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CapabilityFeatures represents feature support flags
|
||||||
|
type CapabilityFeatures struct {
|
||||||
|
ArtifactsV4 bool `json:"artifacts_v4"`
|
||||||
|
Cache bool `json:"cache"`
|
||||||
|
Services bool `json:"services"`
|
||||||
|
CompositeActions bool `json:"composite_actions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DetectCapabilities detects the runner's capabilities
|
||||||
|
func DetectCapabilities(ctx context.Context, dockerHost string) *RunnerCapabilities {
|
||||||
|
cap := &RunnerCapabilities{
|
||||||
|
OS: runtime.GOOS,
|
||||||
|
Arch: runtime.GOARCH,
|
||||||
|
Tools: make(map[string][]string),
|
||||||
|
Shell: detectShells(),
|
||||||
|
Features: &CapabilityFeatures{
|
||||||
|
ArtifactsV4: false, // Gitea doesn't support v4 artifacts
|
||||||
|
Cache: true,
|
||||||
|
Services: true,
|
||||||
|
CompositeActions: true,
|
||||||
|
},
|
||||||
|
Limitations: []string{
|
||||||
|
"actions/upload-artifact@v4 not supported (use v3 or direct API upload)",
|
||||||
|
"actions/download-artifact@v4 not supported (use v3)",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect Docker
|
||||||
|
cap.Docker, cap.ContainerRuntime = detectDocker(ctx, dockerHost)
|
||||||
|
if cap.Docker {
|
||||||
|
cap.DockerCompose = detectDockerCompose(ctx)
|
||||||
|
cap.Features.Services = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect common tools
|
||||||
|
detectTools(ctx, cap)
|
||||||
|
|
||||||
|
return cap
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToJSON converts capabilities to JSON string for transmission
|
||||||
|
func (c *RunnerCapabilities) ToJSON() string {
|
||||||
|
data, err := json.Marshal(c)
|
||||||
|
if err != nil {
|
||||||
|
return "{}"
|
||||||
|
}
|
||||||
|
return string(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectShells() []string {
|
||||||
|
shells := []string{}
|
||||||
|
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
if _, err := exec.LookPath("pwsh"); err == nil {
|
||||||
|
shells = append(shells, "pwsh")
|
||||||
|
}
|
||||||
|
if _, err := exec.LookPath("powershell"); err == nil {
|
||||||
|
shells = append(shells, "powershell")
|
||||||
|
}
|
||||||
|
shells = append(shells, "cmd")
|
||||||
|
case "darwin":
|
||||||
|
if _, err := exec.LookPath("zsh"); err == nil {
|
||||||
|
shells = append(shells, "zsh")
|
||||||
|
}
|
||||||
|
if _, err := exec.LookPath("bash"); err == nil {
|
||||||
|
shells = append(shells, "bash")
|
||||||
|
}
|
||||||
|
shells = append(shells, "sh")
|
||||||
|
default: // linux and others
|
||||||
|
if _, err := exec.LookPath("bash"); err == nil {
|
||||||
|
shells = append(shells, "bash")
|
||||||
|
}
|
||||||
|
shells = append(shells, "sh")
|
||||||
|
}
|
||||||
|
|
||||||
|
return shells
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectDocker(ctx context.Context, dockerHost string) (bool, string) {
|
||||||
|
opts := []client.Opt{client.FromEnv}
|
||||||
|
if dockerHost != "" {
|
||||||
|
opts = append(opts, client.WithHost(dockerHost))
|
||||||
|
}
|
||||||
|
|
||||||
|
cli, err := client.NewClientWithOpts(opts...)
|
||||||
|
if err != nil {
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
defer cli.Close()
|
||||||
|
|
||||||
|
timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_, err = cli.Ping(timeoutCtx)
|
||||||
|
if err != nil {
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's podman or docker
|
||||||
|
info, err := cli.Info(timeoutCtx)
|
||||||
|
if err == nil {
|
||||||
|
if strings.Contains(strings.ToLower(info.Name), "podman") {
|
||||||
|
return true, "podman"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, "docker"
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectDockerCompose(ctx context.Context) bool {
|
||||||
|
// Check for docker compose v2 (docker compose)
|
||||||
|
cmd := exec.CommandContext(ctx, "docker", "compose", "version")
|
||||||
|
if err := cmd.Run(); err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for docker-compose v1
|
||||||
|
if _, err := exec.LookPath("docker-compose"); err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectTools(ctx context.Context, cap *RunnerCapabilities) {
|
||||||
|
toolDetectors := map[string]func(context.Context) []string{
|
||||||
|
"node": detectNodeVersions,
|
||||||
|
"go": detectGoVersions,
|
||||||
|
"python": detectPythonVersions,
|
||||||
|
"java": detectJavaVersions,
|
||||||
|
"dotnet": detectDotnetVersions,
|
||||||
|
"rust": detectRustVersions,
|
||||||
|
}
|
||||||
|
|
||||||
|
for tool, detector := range toolDetectors {
|
||||||
|
if versions := detector(ctx); len(versions) > 0 {
|
||||||
|
cap.Tools[tool] = versions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectNodeVersions(ctx context.Context) []string {
|
||||||
|
return detectToolVersion(ctx, "node", "--version", "v")
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectGoVersions(ctx context.Context) []string {
|
||||||
|
return detectToolVersion(ctx, "go", "version", "go")
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectPythonVersions(ctx context.Context) []string {
|
||||||
|
versions := []string{}
|
||||||
|
|
||||||
|
// Try python3 first
|
||||||
|
if v := detectToolVersion(ctx, "python3", "--version", "Python "); len(v) > 0 {
|
||||||
|
versions = append(versions, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also try python
|
||||||
|
if v := detectToolVersion(ctx, "python", "--version", "Python "); len(v) > 0 {
|
||||||
|
// Avoid duplicates
|
||||||
|
for _, ver := range v {
|
||||||
|
found := false
|
||||||
|
for _, existing := range versions {
|
||||||
|
if existing == ver {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
versions = append(versions, ver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return versions
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectJavaVersions(ctx context.Context) []string {
|
||||||
|
cmd := exec.CommandContext(ctx, "java", "-version")
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Java version output goes to stderr and looks like: openjdk version "17.0.1" or java version "1.8.0_301"
|
||||||
|
lines := strings.Split(string(output), "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
if strings.Contains(line, "version") {
|
||||||
|
// Extract version from quotes
|
||||||
|
start := strings.Index(line, "\"")
|
||||||
|
end := strings.LastIndex(line, "\"")
|
||||||
|
if start != -1 && end > start {
|
||||||
|
version := line[start+1 : end]
|
||||||
|
// Simplify version (e.g., "17.0.1" -> "17")
|
||||||
|
parts := strings.Split(version, ".")
|
||||||
|
if len(parts) > 0 {
|
||||||
|
if parts[0] == "1" && len(parts) > 1 {
|
||||||
|
return []string{parts[1]} // Java 8 style: 1.8 -> 8
|
||||||
|
}
|
||||||
|
return []string{parts[0]}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectDotnetVersions(ctx context.Context) []string {
|
||||||
|
cmd := exec.CommandContext(ctx, "dotnet", "--list-sdks")
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
versions := []string{}
|
||||||
|
lines := strings.Split(string(output), "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Format: "8.0.100 [/path/to/sdk]"
|
||||||
|
parts := strings.Split(line, " ")
|
||||||
|
if len(parts) > 0 {
|
||||||
|
version := parts[0]
|
||||||
|
// Simplify to major version
|
||||||
|
major := strings.Split(version, ".")[0]
|
||||||
|
// Avoid duplicates
|
||||||
|
found := false
|
||||||
|
for _, v := range versions {
|
||||||
|
if v == major {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
versions = append(versions, major)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return versions
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectRustVersions(ctx context.Context) []string {
|
||||||
|
return detectToolVersion(ctx, "rustc", "--version", "rustc ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectToolVersion(ctx context.Context, cmd string, args string, prefix string) []string {
|
||||||
|
timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
c := exec.CommandContext(timeoutCtx, cmd, args)
|
||||||
|
output, err := c.Output()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
line := strings.TrimSpace(string(output))
|
||||||
|
if prefix != "" {
|
||||||
|
if idx := strings.Index(line, prefix); idx != -1 {
|
||||||
|
line = line[idx+len(prefix):]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get just the version number
|
||||||
|
parts := strings.Fields(line)
|
||||||
|
if len(parts) > 0 {
|
||||||
|
version := parts[0]
|
||||||
|
// Clean up version string
|
||||||
|
version = strings.TrimPrefix(version, "v")
|
||||||
|
// Return major.minor or just major
|
||||||
|
vparts := strings.Split(version, ".")
|
||||||
|
if len(vparts) >= 2 {
|
||||||
|
return []string{vparts[0] + "." + vparts[1]}
|
||||||
|
}
|
||||||
|
return []string{vparts[0]}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user