Merge branch 'main' into feature/appstore
把modules/appstore移到service/appstore
This commit is contained in:
45
.gitea/workflows/devstar-studio-autotest.yaml
Normal file
45
.gitea/workflows/devstar-studio-autotest.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
# DevStar 自动化测试工作流定义
|
||||
#
|
||||
# Artifact命名规则:
|
||||
# 1. ${{ vars.DOCKER_REGISTRY_ADDRESS }}/${{ vars.DOCKER_REPOSITORY_ARTIFACT}}:latest
|
||||
# e.g., devstar.cn/devstar/devstar-studio:latest
|
||||
# 2. ${{ vars.DOCKER_REGISTRY_ADDRESS }}/${{ vars.DOCKER_REPOSITORY_ARTIFACT}}:rootless-dev-${{ gitea.sha }}
|
||||
# e.g., devstar.cn/devstar/devstar-studio:rootless-dev-0047d315a3f73cca0c18c641d24b0347456618d5
|
||||
# 其中,
|
||||
# - rootless 表示非 root 权限容器
|
||||
# - dev 表示开发版本
|
||||
# - ${{ gitea.sha }} 表示触发 CI Workflow 的 commit SHA
|
||||
#
|
||||
# 构建参数设置
|
||||
# 点击仓库 > 设置 > Actions > 密钥:
|
||||
# - ${{ secrets.DOCKER_REGISTRY_USERNAME }}: Docker Registry 用户名
|
||||
# - ${{ secrets.DOCKER_REGISTRY_PASSWORD }}: Docker Registry 密码
|
||||
# 点击仓库 > 设置 > Actions > 变量:
|
||||
# - ${{ vars.DOCKER_REGISTRY_ADDRESS }}: Docker Registry 域名, e.g., `devstar.cn`
|
||||
# - ${{ vars.DOCKER_REPOSITORY_ARTIFACT}}: 制品名称, e.g., `devstar/devstar-studio`
|
||||
#
|
||||
|
||||
name: DevStar Studio Auto Test Pipeline
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build-and-push-x86-64-docker-image:
|
||||
# Actual runs-on image: docker.io/library/gitea/runner_image:ubuntu-latest
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 🔍 Check out repository code
|
||||
uses: https://devstar.cn/actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
- name: 🔧 Test Codes and Build an Artifact
|
||||
run: |
|
||||
echo "Prepare to build repository code ${{ gitea.repository }}:${{ gitea.ref }}."
|
||||
make test
|
||||
make devstar
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# devstar-studio-dev-ci.yaml
|
||||
# DevStar 测试并构建制品 CI 工作流定义
|
||||
# DevStar 构建制品 CI/CD 工作流定义
|
||||
#
|
||||
# Artifact命名规则:
|
||||
# 1. ${{ vars.DOCKER_REGISTRY_ADDRESS }}/${{ vars.DOCKER_REPOSITORY_ARTIFACT}}:latest
|
||||
@@ -20,7 +19,7 @@
|
||||
# - ${{ vars.DOCKER_REPOSITORY_ARTIFACT}}: 制品名称, e.g., `devstar/devstar-studio`
|
||||
#
|
||||
|
||||
name: DevStar Studio CI Pipeline
|
||||
name: DevStar Studio CI/CD Pipeline
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -41,11 +40,11 @@ jobs:
|
||||
- name: 🔧 Test Codes and Build an Artifact
|
||||
run: |
|
||||
echo "Prepare to build repository code ${{ gitea.repository }}:${{ gitea.ref }}."
|
||||
docker build -t gitea/gitea:latest .
|
||||
make devstar
|
||||
- name: 🚀 Push Artifact to devstar.cn and docker.io Registry
|
||||
run: |
|
||||
docker tag gitea/gitea:latest ${{ vars.DOCKER_REGISTRY_ADDRESS }}/${{ vars.DOCKER_REPOSITORY_ARTIFACT}}:rootless-dev-${{ gitea.sha }}
|
||||
docker tag gitea/gitea:latest ${{ vars.DOCKER_REGISTRY_ADDRESS }}/${{ vars.DOCKER_REPOSITORY_ARTIFACT}}:latest
|
||||
docker tag devstar-studio:latest ${{ vars.DOCKER_REGISTRY_ADDRESS }}/${{ vars.DOCKER_REPOSITORY_ARTIFACT}}:rootless-dev-${{ gitea.sha }}
|
||||
docker tag devstar-studio:latest ${{ vars.DOCKER_REGISTRY_ADDRESS }}/${{ vars.DOCKER_REPOSITORY_ARTIFACT}}:latest
|
||||
echo "${{ secrets.DOCKER_REGISTRY_PASSWORD }}" | docker login -u ${{ secrets.DOCKER_REGISTRY_USERNAME }} ${{ vars.DOCKER_REGISTRY_ADDRESS }} --password-stdin
|
||||
docker push ${{ vars.DOCKER_REGISTRY_ADDRESS }}/${{ vars.DOCKER_REPOSITORY_ARTIFACT}}:rootless-dev-${{ gitea.sha }}
|
||||
docker push ${{ vars.DOCKER_REGISTRY_ADDRESS }}/${{ vars.DOCKER_REPOSITORY_ARTIFACT}}:latest
|
||||
4
Makefile
4
Makefile
@@ -938,6 +938,10 @@ controller-manager-debug: go-check
|
||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o controller-manager-debug modules/k8s/cmd/controller-manager/controller-manager.go
|
||||
|
||||
|
||||
.PHONY: devstar
|
||||
devstar:
|
||||
docker build -t devstar-studio:latest -f docker/Dockerfile.devstar .
|
||||
|
||||
.PHONY: docker
|
||||
docker:
|
||||
docker build --disable-content-trust=false -t $(DOCKER_REF) .
|
||||
|
||||
10
README.md
10
README.md
@@ -70,8 +70,8 @@ After building, a binary file named `gitea` will be generated in the root of the
|
||||
Start from Container Image:
|
||||
|
||||
```
|
||||
make docker
|
||||
public/assets/install.sh start --image=gitea/gitea:latest
|
||||
make devstar
|
||||
public/assets/install.sh start --image=devstar-studio:latest
|
||||
|
||||
# 查看日志
|
||||
public/assets/install.sh logs
|
||||
@@ -95,8 +95,8 @@ wsl --install -d Ubuntu-20.04 && wsl --setdefault Ubuntu-20.04
|
||||
|
||||
```bash
|
||||
# download and install go
|
||||
wget -c https://go.dev/dl/go1.23.3.linux-amd64.tar.gz
|
||||
sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.23.3.linux-amd64.tar.gz
|
||||
wget -c https://go.dev/dl/go1.24.6.linux-amd64.tar.gz
|
||||
sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.24.6.linux-amd64.tar.gz
|
||||
export PATH=$PATH:/usr/local/go/bin
|
||||
go version
|
||||
|
||||
@@ -127,7 +127,7 @@ git commit -m "commit log"
|
||||
git push
|
||||
```
|
||||
|
||||
在DevStar Git仓库发起Pull Request,合并代码后会自动触发CI流水线完成容器镜像的构建并上传到devstar.cn/devstar/devstar-studio:latest
|
||||
在DevStar Git仓库发起Pull Request,合并代码后会自动触发CI流水线完成容器镜像的构建并上传到 mengning997/devstar-studio:latest 和 devstar.cn/devstar/devstar-studio:latest
|
||||
|
||||
```
|
||||
public/assets/install.sh start
|
||||
|
||||
17
docker/Dockerfile.devContainer
Normal file
17
docker/Dockerfile.devContainer
Normal file
@@ -0,0 +1,17 @@
|
||||
# prepare base dev environment for Gitea
|
||||
|
||||
FROM docker.io/library/golang:1.24-alpine3.22 AS build-env
|
||||
|
||||
# Build deps
|
||||
RUN apk --no-cache add \
|
||||
build-base \
|
||||
git \
|
||||
icu-data-full \
|
||||
nodejs \
|
||||
npm \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
# To acquire Gitea dev container:
|
||||
# $ docker build -t devstar.cn/devstar/devstar-dev-container:latest -f docker/Dockerfile.devContainer .
|
||||
# $ docker login devstar.cn
|
||||
# $ docker push devstar.cn/devstar/devstar-dev-container:latest
|
||||
91
docker/Dockerfile.devstar
Normal file
91
docker/Dockerfile.devstar
Normal file
@@ -0,0 +1,91 @@
|
||||
# Build stage
|
||||
# FROM docker/Dockerfile.devContainer
|
||||
FROM devstar.cn/devstar/devstar-dev-container:latest AS build-env
|
||||
|
||||
ARG GOPROXY="https://goproxy.cn"
|
||||
ENV GOPROXY=${GOPROXY:-direct}
|
||||
|
||||
ARG GITEA_VERSION
|
||||
ARG TAGS="sqlite sqlite_unlock_notify"
|
||||
ENV TAGS="bindata timetzdata $TAGS"
|
||||
ARG CGO_EXTRA_CFLAGS
|
||||
|
||||
#Build deps
|
||||
# RUN apk --no-cache add \
|
||||
# build-base \
|
||||
# git \
|
||||
# nodejs \
|
||||
# npm \
|
||||
# && rm -rf /var/cache/apk/*
|
||||
|
||||
# Setup repo
|
||||
COPY . ${GOPATH}/src/code.gitea.io/gitea
|
||||
WORKDIR ${GOPATH}/src/code.gitea.io/gitea
|
||||
|
||||
# Checkout version if set
|
||||
RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \
|
||||
&& make clean-all build
|
||||
|
||||
# Begin env-to-ini build
|
||||
RUN go build contrib/environment-to-ini/environment-to-ini.go
|
||||
|
||||
# Copy local files
|
||||
COPY docker/rootless /tmp/local
|
||||
|
||||
# Set permissions
|
||||
RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
|
||||
/tmp/local/usr/local/bin/docker-setup.sh \
|
||||
/tmp/local/usr/local/bin/gitea \
|
||||
/go/src/code.gitea.io/gitea/gitea \
|
||||
/go/src/code.gitea.io/gitea/environment-to-ini
|
||||
|
||||
# FROM docker/Dockerfile.runtimeContainer
|
||||
FROM devstar.cn/devstar/devstar-runtime-container:latest
|
||||
LABEL maintainer="contact@mengning.com.cn"
|
||||
|
||||
EXPOSE 2222 3000
|
||||
|
||||
# RUN apk --no-cache add \
|
||||
# bash \
|
||||
# ca-certificates \
|
||||
# dumb-init \
|
||||
# gettext \
|
||||
# git \
|
||||
# curl \
|
||||
# gnupg \
|
||||
# openssh-keygen \
|
||||
# && rm -rf /var/cache/apk/*
|
||||
|
||||
RUN addgroup \
|
||||
-S -g 1000 \
|
||||
git && \
|
||||
adduser \
|
||||
-S -H -D \
|
||||
-h /var/lib/gitea/git \
|
||||
-s /bin/bash \
|
||||
-u 1000 \
|
||||
-G git \
|
||||
git
|
||||
|
||||
RUN mkdir -p /var/lib/gitea /etc/gitea
|
||||
RUN chown git:git /var/lib/gitea /etc/gitea
|
||||
|
||||
COPY --from=build-env /tmp/local /
|
||||
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
|
||||
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini
|
||||
|
||||
# git:git
|
||||
USER 1000:1000
|
||||
ENV GITEA_WORK_DIR=/var/lib/gitea
|
||||
ENV GITEA_CUSTOM=/var/lib/gitea/custom
|
||||
ENV GITEA_TEMP=/tmp/gitea
|
||||
ENV TMPDIR=/tmp/gitea
|
||||
|
||||
# TODO add to docs the ability to define the ini to load (useful to test and revert a config)
|
||||
ENV GITEA_APP_INI=/etc/gitea/app.ini
|
||||
ENV HOME="/var/lib/gitea/git"
|
||||
VOLUME ["/var/lib/gitea", "/etc/gitea"]
|
||||
WORKDIR /var/lib/gitea
|
||||
|
||||
ENTRYPOINT ["/usr/bin/dumb-init", "--", "/usr/local/bin/docker-entrypoint.sh"]
|
||||
CMD []
|
||||
24
docker/Dockerfile.runtimeContainer
Normal file
24
docker/Dockerfile.runtimeContainer
Normal file
@@ -0,0 +1,24 @@
|
||||
# prepare base runtime environment for Gitea
|
||||
|
||||
FROM docker.io/library/alpine:3.22
|
||||
|
||||
RUN apk --no-cache add \
|
||||
bash \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gettext \
|
||||
git \
|
||||
linux-pam \
|
||||
openssh \
|
||||
s6 \
|
||||
dumb-init \
|
||||
sqlite \
|
||||
su-exec \
|
||||
gnupg \
|
||||
docker-cli \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
# To acquire Gitea base runtime container:
|
||||
# $ docker build -t devstar.cn/devstar/devstar-runtime-container:latest -f docker/Dockerfile.runtimeContainer .
|
||||
# $ docker login devstar.cn
|
||||
# $ docker push devstar.cn/devstar/devstar-runtime-container:latest
|
||||
45
go.mod
45
go.mod
@@ -146,6 +146,32 @@ 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/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 (
|
||||
cel.dev/expr v0.20.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||
@@ -154,7 +180,6 @@ require (
|
||||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
|
||||
github.com/ArtisanCloud/PowerSocialite/v3 v3.0.8 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||
github.com/DataDog/zstd v1.5.7 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect
|
||||
@@ -205,8 +230,8 @@ 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/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
@@ -218,14 +243,9 @@ require (
|
||||
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.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/go-webauthn/x v0.1.20 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||
@@ -235,8 +255,6 @@ require (
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/cel-go v0.23.2 // indirect
|
||||
github.com/google/flatbuffers v25.2.10+incompatible // indirect
|
||||
github.com/google/gnostic-models v0.6.9 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/go-tpm v0.9.3 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
@@ -312,25 +330,16 @@ require (
|
||||
go.uber.org/zap/exp v0.3.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
||||
golang.org/x/mod v0.25.0 // indirect
|
||||
golang.org/x/term v0.32.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
golang.org/x/tools v0.33.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.33.0 // indirect
|
||||
k8s.io/apiserver v0.33.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // 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
|
||||
)
|
||||
|
||||
replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
|
||||
|
||||
18
go.sum
18
go.sum
@@ -62,8 +62,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-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
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=
|
||||
@@ -251,6 +251,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=
|
||||
@@ -261,6 +263,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=
|
||||
@@ -606,6 +616,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||
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=
|
||||
@@ -1035,6 +1047,8 @@ 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=
|
||||
istio.io/api v1.26.3 h1:/TiA7bJi24yBQSgpLy5vHhFkobf4DWS1L+CuUxNk4os=
|
||||
istio.io/api v1.26.3/go.mod h1:DTVGH6CLXj5W8FF9JUD3Tis78iRgT1WeuAnxfTz21Wg=
|
||||
istio.io/client-go v1.26.3 h1:ryF4+Nyz5wDO4mVCzXcm2W+fqbnekY88Z36hTcv5fnw=
|
||||
|
||||
117
modules/docker/docker_api.go
Normal file
117
modules/docker/docker_api.go
Normal 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
14
modules/setting/k8s.go
Normal 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("")
|
||||
}
|
||||
14
modules/setting/runners.go
Normal file
14
modules/setting/runners.go
Normal 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")
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -3880,6 +3884,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
|
||||
|
||||
@@ -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 端口
|
||||
@@ -3867,6 +3871,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=成功启动并注册一个运行器
|
||||
|
||||
@@ -3,7 +3,7 @@ package v1
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/modules/appstore"
|
||||
"code.gitea.io/gitea/services/appstore"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
@@ -169,13 +170,14 @@ func Create(ctx *context.Context) {
|
||||
ctx.Data["private"] = getRepoPrivate(ctx)
|
||||
ctx.Data["default_branch"] = setting.Repository.DefaultBranch
|
||||
ctx.Data["repo_template_name"] = ctx.Tr("repo.template_select")
|
||||
|
||||
ctx.Data["devstar_template_name"] = ctx.Tr("repo.template_select")
|
||||
templateID := ctx.FormInt64("template_id")
|
||||
if templateID > 0 {
|
||||
templateRepo, err := repo_model.GetRepositoryByID(ctx, templateID)
|
||||
if err == nil && access_model.CheckRepoUnitUser(ctx, templateRepo, ctxUser, unit.TypeCode) {
|
||||
ctx.Data["repo_template"] = templateID
|
||||
ctx.Data["repo_template_name"] = templateRepo.Name
|
||||
ctx.Data["repo_template_name"] = templateRepo.FullName
|
||||
ctx.Data["devstar_template_name"] = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,6 +216,79 @@ func handleCreateError(ctx *context.Context, owner *user_model.User, err error,
|
||||
}
|
||||
}
|
||||
|
||||
// isValidCommitUrl checks whether a given URL is a valid Git URL with a commit reference.
|
||||
func isValidCommitUrl(url string) bool {
|
||||
// 正则表达式用于匹配包含 commit 的 Git URL
|
||||
regex := `^https?:\/\/([a-zA-Z0-9.-]+)\/([a-zA-Z0-9._-]+)\/([a-zA-Z0-9._-]+)(\/commit\/([a-f0-9]{40}))?$`
|
||||
|
||||
// 编译正则表达式
|
||||
pattern, err := regexp.Compile(regex)
|
||||
if err != nil {
|
||||
fmt.Println("Invalid regex for commit URL:", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// 测试 URL 是否匹配正则表达式
|
||||
return pattern.MatchString(url)
|
||||
}
|
||||
|
||||
// isValidGitPath checks whether a given URL is a valid Git URL ending with .git.
|
||||
func isValidGitPath(url string) bool {
|
||||
// 正则表达式用于匹配以 .git 结尾的 Git URL
|
||||
regexWithGit := `^https?:\/\/([a-zA-Z0-9.-]+)\/([a-zA-Z0-9._-]+)\/([a-zA-Z0-9._-]+)\.git$`
|
||||
|
||||
// 编译正则表达式
|
||||
pattern, err := regexp.Compile(regexWithGit)
|
||||
if err != nil {
|
||||
fmt.Println("Invalid regex for .git URL:", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// 测试 URL 是否匹配正则表达式
|
||||
return pattern.MatchString(url)
|
||||
}
|
||||
|
||||
// ExtractCommitID 从给定的 URL 中提取 commitID
|
||||
func ExtractCommitID(url string) (string, error) {
|
||||
// 正则表达式匹配 commit URL 中的 commitID
|
||||
regex := `^https?:\/\/[a-zA-Z0-9.-]+\/[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+\/commit\/([a-f0-9]{40})$`
|
||||
|
||||
// 编译正则表达式
|
||||
commitPattern, err := regexp.Compile(regex)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 尝试匹配 URL
|
||||
if commitPattern.MatchString(url) {
|
||||
// 提取 commitID
|
||||
matches := commitPattern.FindStringSubmatch(url)
|
||||
if len(matches) >= 2 {
|
||||
return matches[1], nil // 返回 commitID
|
||||
}
|
||||
}
|
||||
|
||||
// 如果匹配失败,返回错误
|
||||
return "", fmt.Errorf("URL does not match expected format: %s", url)
|
||||
}
|
||||
|
||||
// ReplaceCommitWithGit 将 commit URL 中的 /commit/<commitID> 替换为 .git
|
||||
func ReplaceCommitWithGit(url string) (string, error) {
|
||||
// 定义正则表达式匹配 URL 中的 /commit/<commitID> 部分
|
||||
regex := `(/commit/[a-f0-9]{40})$`
|
||||
|
||||
// 编译正则表达式
|
||||
commitPattern, err := regexp.Compile(regex)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 替换为 .git
|
||||
replacedURL := commitPattern.ReplaceAllString(url, ".git")
|
||||
|
||||
return replacedURL, nil
|
||||
}
|
||||
|
||||
// CreatePost response for creating repository
|
||||
func CreatePost(ctx *context.Context) {
|
||||
createCommon(ctx)
|
||||
@@ -292,6 +367,54 @@ func CreatePost(ctx *context.Context) {
|
||||
})
|
||||
if err == nil {
|
||||
log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name)
|
||||
|
||||
if strings.TrimSpace(repo.DefaultBranch) == "" {
|
||||
repo.DefaultBranch = setting.Repository.DefaultBranch
|
||||
}
|
||||
|
||||
if isValidGitPath(form.DevstarTemplate) {
|
||||
log.Info("init repo from %s", form.DevstarTemplate)
|
||||
repo, err = repo_service.InitRepoFromGitURL(ctx, ctx.Doer, ctxUser, form.DevstarTemplate, "", repo)
|
||||
if err != nil {
|
||||
log.Error("Error initializing repo from DevstarTemplate: %v", err)
|
||||
return
|
||||
}
|
||||
if repo == nil {
|
||||
log.Error("repo is nil after initializing from DevstarTemplate")
|
||||
}
|
||||
}
|
||||
|
||||
if isValidGitPath(form.GitUrlTemplate) {
|
||||
log.Info("init repo from %s", form.GitUrlTemplate)
|
||||
repo, err = repo_service.InitRepoFromGitURL(ctx, ctx.Doer, ctxUser, form.GitUrlTemplate, "", repo)
|
||||
if err != nil {
|
||||
log.Error("Error initializing repo from GitUrlTemplate: %v", err)
|
||||
return
|
||||
}
|
||||
if repo == nil {
|
||||
log.Error("repo is nil after initializing from GitUrlTemplate")
|
||||
}
|
||||
}
|
||||
|
||||
if isValidCommitUrl(form.GitUrlTemplate) {
|
||||
log.Info("init repo from %s", form.GitUrlTemplate)
|
||||
commitID, err := ExtractCommitID(form.GitUrlTemplate)
|
||||
if err != nil {
|
||||
log.Error("Error:", err)
|
||||
}
|
||||
gitURL, err := ReplaceCommitWithGit(form.GitUrlTemplate)
|
||||
if err != nil {
|
||||
log.Error("Error:", err)
|
||||
}
|
||||
repo, err = repo_service.InitRepoFromGitURL(ctx, ctx.Doer, ctxUser, gitURL, commitID, repo)
|
||||
if err != nil {
|
||||
log.Error("Error initializing repo from GitUrlTemplate (commit): %v", err)
|
||||
return
|
||||
}
|
||||
if repo == nil {
|
||||
log.Error("repo is nil after initializing from GitUrlTemplate (commit)")
|
||||
}
|
||||
}
|
||||
ctx.Redirect(repo.Link())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
"strings"
|
||||
|
||||
appstore_model "code.gitea.io/gitea/models/appstore"
|
||||
"code.gitea.io/gitea/modules/appstore"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/services/appstore"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -32,6 +32,8 @@ type CreateRepoForm struct {
|
||||
Readme string
|
||||
Template bool
|
||||
|
||||
DevstarTemplate string
|
||||
GitUrlTemplate string
|
||||
RepoTemplate int64
|
||||
GitContent bool
|
||||
Topics bool
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
// EditRunnerForm form for admin to create runner
|
||||
type EditRunnerForm struct {
|
||||
Description string
|
||||
AgentLabels []string
|
||||
}
|
||||
|
||||
// Validate validates form fields
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -160,7 +160,7 @@ func initRepository(ctx context.Context, u *user_model.User, repo *repo_model.Re
|
||||
}
|
||||
|
||||
// Apply changes and commit.
|
||||
if err = initRepoCommit(ctx, tmpDir, repo, u, opts.DefaultBranch); err != nil {
|
||||
if err = initRepoCommit(ctx, tmpDir, repo, u, opts.DefaultBranch, "Initial commit"); err != nil {
|
||||
return fmt.Errorf("initRepoCommit: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
@@ -250,7 +251,7 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r
|
||||
defaultBranch = templateRepo.DefaultBranch
|
||||
}
|
||||
|
||||
return initRepoCommit(ctx, tmpDir, repo, repo.Owner, defaultBranch)
|
||||
return initRepoCommit(ctx, tmpDir, repo, repo.Owner, defaultBranch, "Initial commit")
|
||||
}
|
||||
|
||||
// GenerateGitContent generates git content from a template repository
|
||||
@@ -292,6 +293,81 @@ func GenerateGitContent(ctx context.Context, templateRepo, generateRepo *repo_mo
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateGitContent generates git content from a Git URL
|
||||
func GenerateGitContentFromGitURL(ctx context.Context, gitURL, commitID string, repo *repo_model.Repository) error {
|
||||
tmpDir, err := os.MkdirTemp(os.TempDir(), "gitea-"+repo.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to create temp dir for repository %s: %w", gitURL, err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := util.RemoveAll(tmpDir); err != nil {
|
||||
log.Error("RemoveAll: %v", err)
|
||||
}
|
||||
}()
|
||||
// generateRepoCommit
|
||||
commitTimeStr := time.Now().Format(time.RFC3339)
|
||||
authorSig := repo.Owner.NewGitSig()
|
||||
|
||||
// Because this may call hooks we should pass in the environment
|
||||
env := append(os.Environ(),
|
||||
"GIT_AUTHOR_NAME="+authorSig.Name,
|
||||
"GIT_AUTHOR_EMAIL="+authorSig.Email,
|
||||
"GIT_AUTHOR_DATE="+commitTimeStr,
|
||||
"GIT_COMMITTER_NAME="+authorSig.Name,
|
||||
"GIT_COMMITTER_EMAIL="+authorSig.Email,
|
||||
"GIT_COMMITTER_DATE="+commitTimeStr,
|
||||
)
|
||||
|
||||
// Clone to temporary path with a specified depth.
|
||||
if err := git.Clone(ctx, gitURL, tmpDir, git.CloneRepoOptions{
|
||||
Depth: 1, // 仍然可以保持深度克隆以优化性能
|
||||
}); err != nil {
|
||||
return fmt.Errorf("git clone: %w", err)
|
||||
}
|
||||
// Change to the specified commit ID after cloning
|
||||
if commitID != "" {
|
||||
if err := git.NewCommand("checkout").AddDynamicArguments(commitID).Run(ctx, &git.RunOpts{Dir: tmpDir}); err != nil {
|
||||
return fmt.Errorf("git checkout %s: %w", commitID, err)
|
||||
}
|
||||
}
|
||||
// Get the current SHA1 version of the working directory
|
||||
var sha1Buffer bytes.Buffer
|
||||
if err := git.NewCommand("rev-parse", "HEAD").Run(ctx, &git.RunOpts{
|
||||
Dir: tmpDir,
|
||||
Stdout: &sha1Buffer, // 使用 bytes.Buffer
|
||||
}); err != nil {
|
||||
return fmt.Errorf("git rev-parse HEAD: %w", err)
|
||||
}
|
||||
// 获取SHA1字符串并修整空白
|
||||
sha1 := strings.TrimSpace(sha1Buffer.String())
|
||||
|
||||
if err := util.RemoveAll(path.Join(tmpDir, ".git")); err != nil {
|
||||
return fmt.Errorf("remove git dir: %w", err)
|
||||
}
|
||||
|
||||
if err := git.InitRepository(ctx, tmpDir, false, repo.ObjectFormatName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repoPath := repo.RepoPath()
|
||||
if stdout, _, err := git.NewCommand("remote", "add", "origin").AddDynamicArguments(repoPath).
|
||||
//SetDescription(fmt.Sprintf("generateRepoCommit (git remote add): %s to %s", gitURL, tmpDir)).
|
||||
RunStdString(ctx, &git.RunOpts{Dir: tmpDir, Env: env}); err != nil {
|
||||
log.Error("Unable to add %v as remote origin to temporary repo to %s: stdout %s\nError: %v", repo, tmpDir, stdout, err)
|
||||
return fmt.Errorf("git remote add: %w", err)
|
||||
}
|
||||
commitMessage := "Initial commit from " + gitURL + " ( " + sha1 + " ) "
|
||||
initRepoCommit(ctx, tmpDir, repo, repo.Owner, repo.DefaultBranch, commitMessage)
|
||||
|
||||
if err := repo_module.UpdateRepoSize(ctx, repo); err != nil {
|
||||
return fmt.Errorf("failed to update size for repository: %w", err)
|
||||
}
|
||||
|
||||
log.Info("GenerateGitContentFromGitURL init repo from %s : %s", gitURL, commitID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateRepoOptions contains the template units to generate
|
||||
type GenerateRepoOptions struct {
|
||||
Name string
|
||||
|
||||
@@ -19,9 +19,9 @@ import (
|
||||
)
|
||||
|
||||
// initRepoCommit temporarily changes with work directory.
|
||||
func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Repository, u *user_model.User, defaultBranch string) (err error) {
|
||||
func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Repository, u *user_model.User, defaultBranch string, commitMessage string) (err error) {
|
||||
commitTimeStr := time.Now().Format(time.RFC3339)
|
||||
|
||||
commitMsg := "--message=" + commitMessage
|
||||
sig := u.NewGitSig()
|
||||
// Because this may call hooks we should pass in the environment
|
||||
env := append(os.Environ(),
|
||||
@@ -39,7 +39,7 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi
|
||||
return fmt.Errorf("git add --all: %w", err)
|
||||
}
|
||||
|
||||
cmd := git.NewCommand("commit", "--message=Initial commit").
|
||||
cmd := git.NewCommand("commit").AddOptionFormat(commitMsg).
|
||||
AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email)
|
||||
|
||||
sign, key, signer, _ := asymkey_service.SignInitialCommit(ctx, tmpPath, u)
|
||||
|
||||
@@ -192,3 +192,17 @@ func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templ
|
||||
|
||||
return generateRepo, nil
|
||||
}
|
||||
|
||||
// InitRepoFromGitURL for a repository from a Git URL
|
||||
func InitRepoFromGitURL(ctx context.Context, doer, owner *user_model.User, gitURL, commitID string, repo *repo_model.Repository) (_ *repo_model.Repository, err error) {
|
||||
if !doer.IsAdmin {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
err = GenerateGitContentFromGitURL(ctx, gitURL, commitID, repo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
435
services/runners/runners.go
Normal file
435
services/runners/runners.go
Normal 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
|
||||
}
|
||||
@@ -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}}">
|
||||
|
||||
@@ -61,17 +61,31 @@
|
||||
<label for="description">{{ctx.Locale.Tr "repo.repo_desc"}}</label>
|
||||
<textarea id="description" rows="2" name="description" placeholder="{{ctx.Locale.Tr "repo.repo_desc_helper"}}" maxlength="2048">{{.description}}</textarea>
|
||||
</div>
|
||||
|
||||
{{if .devstar_template_name}}
|
||||
<div id="devstar_template_area" class="inline field">
|
||||
<label>{{ctx.Locale.Tr "repo.devstar_template"}}</label>
|
||||
<div id="devstar_template_search" class="ui search selection dropdown">
|
||||
<input type="hidden" id="devstar_template" name="devstar_template" value="">
|
||||
<div id="devstar_template_name" class="default text">{{.devstar_template_name}}</div>
|
||||
<div class="menu"></div>
|
||||
</div>
|
||||
<span class="help">{{ctx.Locale.Tr "repo.devstar_template_desc" "https://DevStar.cn/"}}</span>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div id="repo_template_area">
|
||||
<div class="inline field">
|
||||
<label>{{ctx.Locale.Tr "repo.template"}}</label>
|
||||
<div id="repo_template_search" class="ui search selection dropdown">
|
||||
<input type="hidden" id="repo_template" name="repo_template" value="{{or .repo_template ""}}">
|
||||
<div class="default text">{{.repo_template_name}}</div>
|
||||
<div id="repo_template_name" class="default text">{{.repo_template_name}}</div>
|
||||
<div class="menu">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="template_units" class="tw-hidden">
|
||||
<div id="template_units" {{if not .repo_template}}class="tw-hidden"{{end}}>
|
||||
<div class="inline field">
|
||||
<label>{{ctx.Locale.Tr "repo.template.items"}}</label>
|
||||
<div class="ui checkbox">
|
||||
@@ -113,8 +127,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="non_template">
|
||||
<div id="non_template" {{if .repo_template}}class="tw-hidden"{{end}}>
|
||||
<div class="inline field">
|
||||
<label>{{ctx.Locale.Tr "repo.issue_labels"}}</label>
|
||||
<div class="ui search selection dropdown">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
187
web_src/js/features/repo-template.ts
Normal file
187
web_src/js/features/repo-template.ts
Normal file
@@ -0,0 +1,187 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
import { htmlEscape } from '../utils/html.ts';
|
||||
import { hideElem, showElem } from '../utils/dom.ts';
|
||||
|
||||
const {appSubUrl} = window.config;
|
||||
|
||||
export function initRepoTemplateSearch() {
|
||||
|
||||
function isValidGitUrl(url) {
|
||||
// 正则表达式支持两种格式的 Git URL
|
||||
const regex = /^https:\/\/([a-zA-Z0-9.-]+)\/([a-zA-Z0-9._-]+)\/([a-zA-Z0-9._-]+)(\/commit\/([a-f0-9]{40}))?$/;
|
||||
// 或者,以 .git 结尾
|
||||
const regexWithGit = /^https:\/\/([a-zA-Z0-9.-]+)\/([a-zA-Z0-9._-]+)\/([a-zA-Z0-9._-]+)\.git$/;
|
||||
return regex.test(url) || regexWithGit.test(url);
|
||||
}
|
||||
|
||||
function getRepoNameFromGitUrl(url) {
|
||||
// 正则表达式支持两种格式的 Git URL
|
||||
const regexWithCommit = /^https:\/\/([a-zA-Z0-9.-]+)\/([a-zA-Z0-9._-]+)\/([a-zA-Z0-9._-]+)\/commit\/([a-f0-9]{40})$/; // 针对 commit ID 的 URL
|
||||
const regexWithoutCommit = /^https:\/\/([a-zA-Z0-9.-]+)\/([a-zA-Z0-9._-]+)\/([a-zA-Z0-9._-]+)\.git$/; // 针对 .git 结尾的 URL
|
||||
// 尝试匹配 commit URL
|
||||
let match = url.match(regexWithCommit);
|
||||
if (match) {
|
||||
return match[3]; // 返回 repo-name
|
||||
}
|
||||
// 尝试匹配 .git URL
|
||||
match = url.match(regexWithoutCommit);
|
||||
if (match) {
|
||||
return match[3]; // 返回 repo-name
|
||||
}
|
||||
return null; // 如果没有匹配,返回 null
|
||||
}
|
||||
|
||||
const $repoTemplate = $('#repo_template');
|
||||
const $devstarTemplate = $('#devstar_template');
|
||||
const $gitURLTemplate = $('#git_url_template');
|
||||
const $templateUnits = $('#template_units');
|
||||
const $nonTemplate = $('#non_template');
|
||||
const $repoTemplateArea = $('#repo_template_area');
|
||||
const $gitURLTemplateArea = $('#git_url_template_area');
|
||||
const $devstarTemplateArea = $('#devstar_template_area');
|
||||
|
||||
const isDevstar = /^(https?:\/\/)?(www\.)?(devstar\.cn)(:\d+)?(\/.*)?$/i.test(document.URL);
|
||||
if (isDevstar) {
|
||||
hideElem($devstarTemplateArea);
|
||||
$devstarTemplateArea.remove();
|
||||
}
|
||||
|
||||
const GitURL = document.URL.match(/[?&]template_url=([^&]*)/);
|
||||
if (GitURL) {
|
||||
if (isValidGitUrl(GitURL[1])) {
|
||||
$gitURLTemplate.val(GitURL[1]);
|
||||
}
|
||||
$devstarTemplateArea.val('');
|
||||
hideElem($devstarTemplateArea);
|
||||
hideElem($repoTemplateArea);
|
||||
}
|
||||
|
||||
const template_id = document.URL.match(/[?&]template_id=([^&]*)/);
|
||||
if (template_id) {
|
||||
$devstarTemplateArea.val('');
|
||||
hideElem($devstarTemplateArea);
|
||||
$gitURLTemplate.val("");
|
||||
hideElem($gitURLTemplateArea);
|
||||
}
|
||||
|
||||
const checkTemplate = function () {
|
||||
if ($repoTemplate.val() !== '' && $repoTemplate.val() !== '0') {
|
||||
showElem($templateUnits);
|
||||
hideElem($nonTemplate);
|
||||
hideElem($gitURLTemplateArea);
|
||||
$devstarTemplateArea.val('');
|
||||
hideElem($devstarTemplateArea);
|
||||
} else {
|
||||
showElem($devstarTemplateArea);
|
||||
hideElem($templateUnits);
|
||||
showElem($nonTemplate);
|
||||
showElem($gitURLTemplateArea);
|
||||
}
|
||||
};
|
||||
$repoTemplate.on('change', checkTemplate);
|
||||
checkTemplate();
|
||||
|
||||
const checkGitURLTemplate = function () {
|
||||
if ($gitURLTemplate.val() !== '' && $gitURLTemplate.val() !== '0') {
|
||||
if ($('#repo_name').val() == '') {
|
||||
$('#repo_name').val(getRepoNameFromGitUrl($gitURLTemplate.val()));
|
||||
}
|
||||
if ($('#description').val() == '') {
|
||||
$('#description').val("init repo from " + $gitURLTemplate.val());
|
||||
}
|
||||
hideElem($repoTemplateArea);
|
||||
hideElem($nonTemplate);
|
||||
$devstarTemplateArea.val('');
|
||||
hideElem($devstarTemplateArea);
|
||||
} else {
|
||||
showElem($devstarTemplateArea);
|
||||
showElem($repoTemplateArea);
|
||||
}
|
||||
};
|
||||
if($gitURLTemplate.length){
|
||||
$gitURLTemplate.on('change', checkGitURLTemplate);
|
||||
checkGitURLTemplate();
|
||||
}
|
||||
|
||||
const checkDevStarTemplate = function () {
|
||||
if ($devstarTemplate.val() !== '' && $devstarTemplate.val() !== '0') {
|
||||
if ($('#repo_name').val() == '') {
|
||||
$('#repo_name').val(getRepoNameFromGitUrl($devstarTemplate.val()));
|
||||
}
|
||||
if ($('#description').val() == '') {
|
||||
$('#description').val("init repo from " + $devstarTemplate.val());
|
||||
}
|
||||
$repoTemplate.val('');
|
||||
hideElem($gitURLTemplateArea);
|
||||
hideElem($repoTemplateArea);
|
||||
hideElem($nonTemplate);
|
||||
} else {
|
||||
showElem($repoTemplateArea);
|
||||
showElem($gitURLTemplateArea);
|
||||
hideElem($templateUnits);
|
||||
showElem($nonTemplate);
|
||||
}
|
||||
};
|
||||
if($devstarTemplate.length){
|
||||
$devstarTemplate.on('change', checkDevStarTemplate);
|
||||
}
|
||||
|
||||
const initDevStarTemplateSearch = function () {
|
||||
$('#devstar_template_search')
|
||||
.dropdown({
|
||||
apiSettings: {
|
||||
url: `https://devstar.cn/api/v1/repos/search?q={query}&template=true`,
|
||||
onResponse(response) {
|
||||
console.log(response.data);
|
||||
const filteredResponse = { success: true, results: [] };
|
||||
filteredResponse.results.push({
|
||||
name: '-----',
|
||||
value: '',
|
||||
});
|
||||
// Parse the response from the api to work with our dropdown
|
||||
$.each(response.data, (_r, repo) => {
|
||||
filteredResponse.results.push({
|
||||
name: htmlEscape("DevStar.cn/" + repo.full_name),
|
||||
value: repo.clone_url,
|
||||
});
|
||||
});
|
||||
return filteredResponse;
|
||||
},
|
||||
cache: true,
|
||||
},
|
||||
|
||||
fullTextSearch: true,
|
||||
});
|
||||
};
|
||||
initDevStarTemplateSearch();
|
||||
|
||||
const changeOwner = function () {
|
||||
$('#repo_template_search')
|
||||
.dropdown({
|
||||
apiSettings: {
|
||||
url: `${appSubUrl}/repo/search?q={query}&template=true&priority_owner_id=${$('#uid').val()}`,
|
||||
onResponse(response) {
|
||||
const filteredResponse = { success: true, results: [] };
|
||||
filteredResponse.results.push({
|
||||
name: '-----',
|
||||
value: '',
|
||||
});
|
||||
// Parse the response from the api to work with our dropdown
|
||||
$.each(response.data, (_r, repo) => {
|
||||
filteredResponse.results.push({
|
||||
name: htmlEscape(repo.repository.full_name),
|
||||
value: repo.repository.id,
|
||||
});
|
||||
});
|
||||
return filteredResponse;
|
||||
},
|
||||
cache: false,
|
||||
},
|
||||
|
||||
fullTextSearch: true,
|
||||
});
|
||||
};
|
||||
$('#uid').on('change', changeOwner);
|
||||
changeOwner();
|
||||
}
|
||||
@@ -66,6 +66,7 @@ import {initGlobalButtonClickOnEnter, initGlobalButtons, initGlobalDeleteButton}
|
||||
import {initGlobalComboMarkdownEditor, initGlobalEnterQuickSubmit, initGlobalFormDirtyLeaveConfirm} from './features/common-form.ts';
|
||||
import {callInitFunctions} from './modules/init.ts';
|
||||
import {initRepoViewFileTree} from './features/repo-view-file-tree.ts';
|
||||
import {initRepoTemplateSearch} from './features/repo-template.ts';
|
||||
|
||||
const initStartTime = performance.now();
|
||||
const initPerformanceTracer = callInitFunctions([
|
||||
@@ -164,6 +165,7 @@ const initPerformanceTracer = callInitFunctions([
|
||||
initOAuth2SettingsDisableCheckbox,
|
||||
|
||||
initRepoFileView,
|
||||
initRepoTemplateSearch,
|
||||
]);
|
||||
|
||||
// it must be the last one, then the "querySelectorAll" only needs to be executed once for global init functions.
|
||||
|
||||
Reference in New Issue
Block a user