chore: 统一本地服务器与远程仓库源码

- 合并 main 与 master 历史并以服务器结构为基线
- 保留迁移、数据保留、AWS 账单与部署安全能力
- 建立 SDD 规格和 CI 验证闭环
This commit is contained in:
Logan.Li committed 2026-08-05 20:03:25 +08:00
commit fd60361d93
460 files changed
+37492 -23871

No files matched your search

+143 -64
View File
@@ -6,8 +6,9 @@ import (
"strconv"
"time"
"ezops/model"
"ezops/middleware"
"ezops/model"
"ezops/pkg/crypto"
ezredis "ezops/pkg/redis"
"ezops/pkg/toolinstaller"
@@ -57,11 +58,12 @@ type PipelineHandler struct {
db *gorm.DB
cache ezredis.Cache
toolInstaller *toolinstaller.ToolInstaller
crypt *crypto.SecretBox
}
// NewPipelineHandler 创建流水线处理器
func NewPipelineHandler(db *gorm.DB, cache ezredis.Cache, ti *toolinstaller.ToolInstaller) *PipelineHandler {
h := &PipelineHandler{db: db, cache: cache, toolInstaller: ti}
func NewPipelineHandler(db *gorm.DB, cache ezredis.Cache, ti *toolinstaller.ToolInstaller, masterKey string) *PipelineHandler {
h := &PipelineHandler{db: db, cache: cache, toolInstaller: ti, crypt: crypto.NewSecretBox(masterKey)}
h.ensureBuildMasterConfig()
return h
}
@@ -69,7 +71,7 @@ func NewPipelineHandler(db *gorm.DB, cache ezredis.Cache, ti *toolinstaller.Tool
// PipelineListItem 列表项(含最近一次运行摘要)// Logan.Li
type PipelineListItem struct {
model.Pipeline
LastRunStages string `json:"last_run_stages,omitempty"` // JSON: [{name,status}]
LastRunStages string `json:"last_run_stages,omitempty"` // JSON: [{name,status}]
LastImageTag string `json:"last_image_tag,omitempty"`
LastCommitID string `json:"last_commit_id,omitempty"`
LastRunDuration int `json:"last_duration,omitempty"`
@@ -112,7 +114,11 @@ func (h *PipelineHandler) ListPipelines(c *gin.Context) {
db.Count(&total)
var pipelines []model.Pipeline
db.Preload("Environment").Offset(query.GetOffset()).Limit(query.GetLimit()).Order("updated_at DESC").Find(&pipelines)
db.Preload("Environment").
Offset(query.GetOffset()).
Limit(query.GetLimit()).
Order("CASE WHEN last_run_at IS NULL THEN 1 ELSE 0 END ASC, last_run_at DESC, updated_at DESC").
Find(&pipelines)
// 批量获取每个流水线最近一次运行的摘要(阶段状态 + 镜像 + 耗时)// Logan.Li
items := make([]PipelineListItem, len(pipelines))
@@ -123,7 +129,7 @@ func (h *PipelineHandler) ListPipelines(c *gin.Context) {
}
// 子查询:每个 pipeline_id 的最新 run id
var latestRuns []model.PipelineRun
h.db.Where("id IN (SELECT MAX(id) FROM ops_pipeline_runs WHERE pipeline_id IN ? AND status IN ('success','failed','running','pending') GROUP BY pipeline_id)", pids).
h.db.Where("id IN (SELECT MAX(id) FROM deploy_pipeline_runs WHERE pipeline_id IN ? AND status IN ('success','failed','running','pending') GROUP BY pipeline_id)", pids).
Find(&latestRuns)
runMap := map[uint]*model.PipelineRun{}
for i := range latestRuns {
@@ -147,8 +153,8 @@ func (h *PipelineHandler) ListPipelines(c *gin.Context) {
}
c.JSON(http.StatusOK, model.Response{
Code: 200,
Data: model.ListResponse{List: items, Total: total},
Code: 200,
Data: model.ListResponse{List: items, Total: total},
})
}
@@ -211,30 +217,29 @@ func (h *PipelineHandler) CreatePipeline(c *gin.Context) {
}
pipeline := model.Pipeline{
Name: req.Name,
Code: req.Code,
JobName: req.JobName,
Description: req.Description,
Type: req.Type,
Source: req.Source,
Branch: req.Branch,
Dockerfile: req.Dockerfile,
Stages: req.Stages,
Variables: req.Variables,
DeployMode: req.DeployMode,
DeployTarget: req.DeployTarget,
ServerIDs: req.ServerIDs,
DeployRemotePath: req.DeployRemotePath,
DeployScript: req.DeployScript,
K8sClusterID: req.K8sClusterID,
K8sNamespace: req.K8sNamespace,
K8sDeployment: req.K8sDeployment,
K8sContainerName: req.K8sContainerName,
RepoID: req.RepoID,
RegistryID: req.RegistryID,
Name: req.Name,
Code: req.Code,
JobName: req.JobName,
Description: req.Description,
Type: req.Type,
CIProvider: normalizeCIProvider(req.CIProvider),
Source: req.Source,
Branch: req.Branch,
Dockerfile: req.Dockerfile,
Stages: req.Stages,
Variables: req.Variables,
DeployMode: req.DeployMode,
DeployTarget: req.DeployTarget,
ServerIDs: req.ServerIDs,
DeployRemotePath: req.DeployRemotePath,
DeployScript: req.DeployScript,
K8sClusterID: req.K8sClusterID,
K8sNamespace: req.K8sNamespace,
K8sDeployment: req.K8sDeployment,
K8sContainerName: req.K8sContainerName,
GitCredentialID: req.GitCredentialID,
RegistryCredentialID: req.RegistryCredentialID,
EnvironmentID: req.EnvironmentID,
EnvironmentID: req.EnvironmentID,
// 触发配置
TriggerMode: req.TriggerMode,
WatchBranches: req.WatchBranches,
@@ -242,11 +247,13 @@ func (h *PipelineHandler) CreatePipeline(c *gin.Context) {
CronExpr: req.CronExpr,
WebhookToken: req.WebhookToken,
// 缓存
CachePaths: req.CachePaths,
CachePaths: req.CachePaths,
JenkinsJobName: req.JenkinsJobName,
JenkinsJobURL: req.JenkinsJobURL,
// 并发控制
MaxExecutors: req.MaxExecutors,
BuildNodeID: req.BuildNodeID,
Enabled: true,
Enabled: true,
}
if err := h.db.Create(&pipeline).Error; err != nil {
@@ -301,38 +308,105 @@ func (h *PipelineHandler) UpdatePipeline(c *gin.Context) {
}
updates := map[string]interface{}{}
if req.Name != "" { updates["name"] = req.Name }
if req.JobName != "" { updates["job_name"] = req.JobName }
if req.Description != "" { updates["description"] = req.Description }
if req.Type != "" { updates["type"] = req.Type }
if req.Source != "" { updates["source"] = req.Source }
if req.Branch != "" { updates["branch"] = req.Branch }
if req.Dockerfile != "" { updates["dockerfile"] = req.Dockerfile }
if req.Stages != "" { updates["stages"] = req.Stages }
if req.Variables != "" { updates["variables"] = req.Variables }
if req.DeployMode != "" { updates["deploy_mode"] = req.DeployMode }
if req.DeployTarget != "" { updates["deploy_target"] = req.DeployTarget }
if req.ServerIDs != "" { updates["server_ids"] = req.ServerIDs }
if req.DeployRemotePath != "" { updates["deploy_remote_path"] = req.DeployRemotePath }
if req.DeployScript != "" { updates["deploy_script"] = req.DeployScript }
if req.K8sNamespace != "" { updates["k8s_namespace"] = req.K8sNamespace }
if req.K8sDeployment != "" { updates["k8s_deployment"] = req.K8sDeployment }
if req.K8sContainerName != "" { updates["k8s_container_name"] = req.K8sContainerName }
if req.K8sClusterID != nil { updates["k8s_cluster_id"] = req.K8sClusterID }
if req.RepoID != nil { updates["repo_id"] = req.RepoID }
if req.RegistryID != nil { updates["registry_id"] = req.RegistryID }
if req.GitCredentialID != nil { updates["git_credential_id"] = req.GitCredentialID }
if req.RegistryCredentialID != nil { updates["registry_credential_id"] = req.RegistryCredentialID }
if req.EnvironmentID != nil { updates["environment_id"] = req.EnvironmentID }
if req.TriggerMode != "" { updates["trigger_mode"] = req.TriggerMode }
if req.WatchBranches != "" { updates["watch_branches"] = req.WatchBranches }
if req.ExcludeBranch != "" { updates["exclude_branch"] = req.ExcludeBranch }
if req.CronExpr != "" { updates["cron_expr"] = req.CronExpr }
if req.WebhookToken != "" { updates["webhook_token"] = req.WebhookToken }
if req.CachePaths != "" { updates["cache_paths"] = req.CachePaths }
if req.Enabled != nil { updates["enabled"] = req.Enabled }
if req.MaxExecutors > 0 { updates["max_executors"] = req.MaxExecutors }
if req.BuildNodeID != nil { updates["build_node_id"] = req.BuildNodeID }
if req.Name != "" {
updates["name"] = req.Name
}
if req.JobName != "" {
updates["job_name"] = req.JobName
}
if req.Description != "" {
updates["description"] = req.Description
}
if req.Type != "" {
updates["type"] = req.Type
}
if req.CIProvider != "" {
updates["ci_provider"] = normalizeCIProvider(req.CIProvider)
}
if req.Source != "" {
updates["source"] = req.Source
}
if req.Branch != "" {
updates["branch"] = req.Branch
}
if req.Dockerfile != "" {
updates["dockerfile"] = req.Dockerfile
}
if req.Stages != "" {
updates["stages"] = req.Stages
}
if req.Variables != "" {
updates["variables"] = req.Variables
}
if req.DeployMode != "" {
updates["deploy_mode"] = req.DeployMode
}
if req.DeployTarget != "" {
updates["deploy_target"] = req.DeployTarget
}
if req.ServerIDs != "" {
updates["server_ids"] = req.ServerIDs
}
if req.DeployRemotePath != "" {
updates["deploy_remote_path"] = req.DeployRemotePath
}
if req.DeployScript != "" {
updates["deploy_script"] = req.DeployScript
}
if req.K8sNamespace != "" {
updates["k8s_namespace"] = req.K8sNamespace
}
if req.K8sDeployment != "" {
updates["k8s_deployment"] = req.K8sDeployment
}
if req.K8sContainerName != "" {
updates["k8s_container_name"] = req.K8sContainerName
}
if req.K8sClusterID != nil {
updates["k8s_cluster_id"] = req.K8sClusterID
}
if req.GitCredentialID != nil {
updates["git_credential_id"] = req.GitCredentialID
}
if req.RegistryCredentialID != nil {
updates["registry_credential_id"] = req.RegistryCredentialID
}
if req.EnvironmentID != nil {
updates["environment_id"] = req.EnvironmentID
}
if req.TriggerMode != "" {
updates["trigger_mode"] = req.TriggerMode
}
if req.WatchBranches != "" {
updates["watch_branches"] = req.WatchBranches
}
if req.ExcludeBranch != "" {
updates["exclude_branch"] = req.ExcludeBranch
}
if req.CronExpr != "" {
updates["cron_expr"] = req.CronExpr
}
if req.WebhookToken != "" {
updates["webhook_token"] = req.WebhookToken
}
if req.CachePaths != "" {
updates["cache_paths"] = req.CachePaths
}
if req.JenkinsJobName != "" {
updates["jenkins_job_name"] = req.JenkinsJobName
}
if req.JenkinsJobURL != "" {
updates["jenkins_job_url"] = req.JenkinsJobURL
}
if req.Enabled != nil {
updates["enabled"] = req.Enabled
}
if req.MaxExecutors > 0 {
updates["max_executors"] = req.MaxExecutors
}
if req.BuildNodeID != nil {
updates["build_node_id"] = req.BuildNodeID
}
if err := h.db.Model(&pipeline).Updates(updates).Error; err != nil {
c.JSON(http.StatusInternalServerError, model.Response{
@@ -404,6 +478,11 @@ func (h *PipelineHandler) TriggerPipeline(c *gin.Context) {
var req model.TriggerPipelineRequest
c.ShouldBindJSON(&req)
if pipeline.CIProvider == "jenkins" {
h.triggerJenkinsPipeline(c, pipeline, req)
return
}
var maxBuildNumber int
h.db.Model(&model.PipelineRun{}).Where("pipeline_id = ?", id).
Select("COALESCE(MAX(build_number), 0)").Scan(&maxBuildNumber)