mirror of
https://gitee.com/goploy/goploy.git
synced 2024-12-01 19:57:34 +08:00
Changed: file struct
This commit is contained in:
parent
e2cf9c6b11
commit
4d8b3d44c9
@ -17,7 +17,7 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/zhenorzz/goploy/cmd/server/api"
|
||||
"github.com/zhenorzz/goploy/cmd/server/api/middleware"
|
||||
"github.com/zhenorzz/goploy/cmd/server/task"
|
||||
"github.com/zhenorzz/goploy/cmd/server/task/deploy"
|
||||
"github.com/zhenorzz/goploy/config"
|
||||
"github.com/zhenorzz/goploy/internal/model"
|
||||
"github.com/zhenorzz/goploy/internal/pipeline/docker"
|
||||
@ -806,7 +806,7 @@ func (Deploy) Rebuild(gp *server.Goploy) server.Response {
|
||||
if err != nil {
|
||||
return response.JSON{Code: response.Error, Message: err.Error()}
|
||||
}
|
||||
task.AddDeployTask(task.Gsync{
|
||||
deploy.AddTask(deploy.Gsync{
|
||||
UserInfo: gp.UserInfo,
|
||||
Project: project,
|
||||
ProjectServers: projectServers,
|
||||
@ -937,7 +937,7 @@ func (Deploy) Webhook(gp *server.Goploy) server.Response {
|
||||
if err != nil {
|
||||
return response.JSON{Code: response.Error, Message: err.Error()}
|
||||
}
|
||||
task.AddDeployTask(task.Gsync{
|
||||
deploy.AddTask(deploy.Gsync{
|
||||
UserInfo: gp.UserInfo,
|
||||
Project: project,
|
||||
ProjectServers: projectServers,
|
||||
@ -1015,7 +1015,7 @@ func projectDeploy(gp *server.Goploy, project model.Project, commitID string, br
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
task.AddDeployTask(task.Gsync{
|
||||
deploy.AddTask(deploy.Gsync{
|
||||
UserInfo: gp.UserInfo,
|
||||
Project: project,
|
||||
ProjectServers: projectServers,
|
||||
|
@ -5,6 +5,8 @@
|
||||
package role
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"github.com/zhenorzz/goploy/cmd/server/api"
|
||||
"github.com/zhenorzz/goploy/cmd/server/api/middleware"
|
||||
"github.com/zhenorzz/goploy/config"
|
||||
@ -195,7 +197,7 @@ func (Role) Remove(gp *server.Goploy) server.Response {
|
||||
}
|
||||
|
||||
namespaceUser, err := (model.NamespaceUser{RoleID: reqData.ID}).GetDataByRoleID()
|
||||
if err != nil {
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return response.JSON{Code: response.Error, Message: err.Error()}
|
||||
}
|
||||
if namespaceUser.ID > 0 {
|
||||
|
@ -1,900 +0,0 @@
|
||||
// Copyright 2022 The Goploy Authors. All rights reserved.
|
||||
// Use of this source code is governed by a GPLv3-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package task
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"container/list"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/zhenorzz/goploy/cmd/server/ws"
|
||||
"github.com/zhenorzz/goploy/config"
|
||||
"github.com/zhenorzz/goploy/internal/model"
|
||||
"github.com/zhenorzz/goploy/internal/pipeline/docker"
|
||||
"github.com/zhenorzz/goploy/internal/pkg"
|
||||
"github.com/zhenorzz/goploy/internal/pkg/cmd"
|
||||
"github.com/zhenorzz/goploy/internal/repo"
|
||||
"github.com/zhenorzz/goploy/internal/transmitter"
|
||||
"gopkg.in/yaml.v3"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
var deployList = list.New()
|
||||
var deployTick = time.Tick(time.Millisecond)
|
||||
|
||||
type Gsync struct {
|
||||
UserInfo model.User
|
||||
Project model.Project
|
||||
ProjectServers model.ProjectServers
|
||||
PublishTrace model.PublishTrace
|
||||
CommitInfo repo.CommitInfo
|
||||
CommitID string
|
||||
Branch string
|
||||
}
|
||||
|
||||
type syncMessage struct {
|
||||
serverName string
|
||||
projectID int64
|
||||
detail string
|
||||
state int
|
||||
}
|
||||
|
||||
type deployMessage struct {
|
||||
ProjectID int64 `json:"projectId"`
|
||||
ProjectName string `json:"projectName"`
|
||||
State uint8 `json:"state"`
|
||||
Message string `json:"message"`
|
||||
Ext interface{} `json:"ext"`
|
||||
}
|
||||
|
||||
const (
|
||||
Queue = iota
|
||||
Deploying
|
||||
Success
|
||||
Fail
|
||||
)
|
||||
|
||||
func (deployMessage) CanSendTo(*ws.Client) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var projectLogFormat = "projectID: %d %s"
|
||||
|
||||
func startDeployTask() {
|
||||
atomic.AddInt32(&counter, 1)
|
||||
var deployingNumber int32
|
||||
var wg sync.WaitGroup
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-deployTick:
|
||||
if atomic.LoadInt32(&deployingNumber) < config.Toml.APP.DeployLimit {
|
||||
atomic.AddInt32(&deployingNumber, 1)
|
||||
if deployElem := deployList.Front(); deployElem != nil {
|
||||
wg.Add(1)
|
||||
go func(gsync *Gsync) {
|
||||
gsync.Exec()
|
||||
atomic.AddInt32(&deployingNumber, -1)
|
||||
wg.Done()
|
||||
}(deployList.Remove(deployElem).(*Gsync))
|
||||
} else {
|
||||
atomic.AddInt32(&deployingNumber, -1)
|
||||
}
|
||||
}
|
||||
case <-stop:
|
||||
wg.Wait()
|
||||
atomic.AddInt32(&counter, -1)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func AddDeployTask(gsync Gsync) {
|
||||
ws.Send(ws.Data{
|
||||
Type: ws.TypeProject,
|
||||
Message: deployMessage{
|
||||
ProjectID: gsync.Project.ID,
|
||||
ProjectName: gsync.Project.Name,
|
||||
State: Queue,
|
||||
Message: "Task waiting",
|
||||
Ext: struct {
|
||||
LastPublishToken string `json:"lastPublishToken"`
|
||||
}{gsync.Project.LastPublishToken},
|
||||
},
|
||||
})
|
||||
|
||||
queueExt := model.PublishTraceQueueExt{
|
||||
Script: gsync.Project.Script,
|
||||
}
|
||||
ext, _ := json.Marshal(queueExt)
|
||||
gsync.PublishTrace = model.PublishTrace{
|
||||
Token: gsync.Project.LastPublishToken,
|
||||
ProjectID: gsync.Project.ID,
|
||||
ProjectName: gsync.Project.Name,
|
||||
PublisherID: gsync.UserInfo.ID,
|
||||
PublisherName: gsync.UserInfo.Name,
|
||||
Ext: string(ext),
|
||||
InsertTime: time.Now().Format("20060102150405"),
|
||||
Type: model.Queue,
|
||||
State: model.Success,
|
||||
}
|
||||
if _, err := gsync.PublishTrace.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, gsync.Project.ID, "insert trace error, "+err.Error())
|
||||
}
|
||||
deployList.PushBack(&gsync)
|
||||
}
|
||||
|
||||
func (gsync *Gsync) Exec() {
|
||||
log.Tracef(projectLogFormat, gsync.Project.ID, "deploy start")
|
||||
var err error
|
||||
defer func() {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
ws.Send(ws.Data{
|
||||
Type: ws.TypeProject,
|
||||
Message: deployMessage{ProjectID: gsync.Project.ID, ProjectName: gsync.Project.Name, State: Fail, Message: err.Error()},
|
||||
})
|
||||
log.Errorf(projectLogFormat, gsync.Project.ID, err)
|
||||
if err := gsync.Project.DeployFail(); err != nil {
|
||||
log.Errorf(projectLogFormat, gsync.Project.ID, err)
|
||||
}
|
||||
gsync.notify(model.ProjectFail, err.Error())
|
||||
|
||||
}()
|
||||
|
||||
err = gsync.repoStage()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = gsync.copyLocalFileStage()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = gsync.afterPullScriptStage()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = gsync.serverStage()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = gsync.deployFinishScriptStage()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := gsync.Project.DeploySuccess(); err != nil {
|
||||
log.Errorf(projectLogFormat, gsync.Project.ID, err)
|
||||
}
|
||||
|
||||
gsync.PublishTrace.Type = model.PublishFinish
|
||||
gsync.PublishTrace.State = model.Success
|
||||
gsync.PublishTrace.InsertTime = time.Now().Format("20060102150405")
|
||||
if _, err := gsync.PublishTrace.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, gsync.Project.ID, err)
|
||||
}
|
||||
|
||||
log.Tracef(projectLogFormat, gsync.Project.ID, "deploy success")
|
||||
ws.Send(ws.Data{
|
||||
Type: ws.TypeProject,
|
||||
Message: deployMessage{ProjectID: gsync.Project.ID, ProjectName: gsync.Project.Name, State: Success, Message: "Success", Ext: gsync.CommitInfo},
|
||||
})
|
||||
gsync.notify(model.ProjectSuccess, "")
|
||||
|
||||
gsync.removeExpiredBackup()
|
||||
}
|
||||
|
||||
func (gsync *Gsync) repoStage() error {
|
||||
ws.Send(ws.Data{
|
||||
Type: ws.TypeProject,
|
||||
Message: deployMessage{ProjectID: gsync.Project.ID, ProjectName: gsync.Project.Name, State: Deploying, Message: "Repo follow"},
|
||||
})
|
||||
gsync.PublishTrace.Type = model.Pull
|
||||
gsync.PublishTrace.InsertTime = time.Now().Format("20060102150405")
|
||||
var err error
|
||||
r, _ := repo.GetRepo(gsync.Project.RepoType)
|
||||
if len(gsync.CommitID) == 0 {
|
||||
err = r.Follow(gsync.Project.ID, "origin/"+gsync.Project.Branch, gsync.Project.URL, gsync.Project.Branch)
|
||||
} else {
|
||||
err = r.Follow(gsync.Project.ID, gsync.CommitID, gsync.Project.URL, gsync.Project.Branch)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
gsync.PublishTrace.Detail = err.Error()
|
||||
gsync.PublishTrace.State = model.Fail
|
||||
if _, err := gsync.PublishTrace.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, gsync.Project.ID, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
commitList, err := r.CommitLog(gsync.Project.ID, 1)
|
||||
if err != nil {
|
||||
gsync.PublishTrace.Detail = err.Error()
|
||||
gsync.PublishTrace.State = model.Fail
|
||||
if _, err := gsync.PublishTrace.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, gsync.Project.ID, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
gsync.CommitInfo = commitList[0]
|
||||
if gsync.Branch != "" {
|
||||
gsync.CommitInfo.Branch = gsync.Branch
|
||||
} else {
|
||||
gsync.CommitInfo.Branch = "origin/" + gsync.Project.Branch
|
||||
}
|
||||
|
||||
ext, _ := json.Marshal(gsync.CommitInfo)
|
||||
gsync.PublishTrace.Ext = string(ext)
|
||||
gsync.PublishTrace.State = model.Success
|
||||
if _, err := gsync.PublishTrace.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, gsync.Project.ID, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gsync *Gsync) copyLocalFileStage() error {
|
||||
if totalFileNumber, err := (model.ProjectFile{ProjectID: gsync.Project.ID}).GetTotalByProjectID(); err != nil {
|
||||
return err
|
||||
} else if totalFileNumber > 0 {
|
||||
if err := pkg.CopyDir(config.GetProjectFilePath(gsync.Project.ID), config.GetProjectPath(gsync.Project.ID)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gsync *Gsync) afterPullScriptStage() error {
|
||||
if gsync.Project.Script.AfterPull.Content == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
gsync.PublishTrace.Type = model.AfterPull
|
||||
gsync.PublishTrace.InsertTime = time.Now().Format("20060102150405")
|
||||
return gsync.runLocalScript()
|
||||
}
|
||||
|
||||
func (gsync *Gsync) serverStage() error {
|
||||
ws.Send(ws.Data{
|
||||
Type: ws.TypeProject,
|
||||
Message: deployMessage{ProjectID: gsync.Project.ID, ProjectName: gsync.Project.Name, State: Deploying, Message: "Sync"},
|
||||
})
|
||||
ch := make(chan syncMessage, len(gsync.ProjectServers))
|
||||
gsync.PublishTrace.Type = model.Deploy
|
||||
var serverSync = func(projectServer model.ProjectServer, index int) {
|
||||
project := gsync.Project
|
||||
publishTraceModel := gsync.PublishTrace
|
||||
publishTraceModel.InsertTime = time.Now().Format("20060102150405")
|
||||
// write after deploy script for rsync
|
||||
var dockerScript docker.Script
|
||||
scriptName := fmt.Sprintf("goploy-after-deploy-p%d-s%d.%s", project.ID, projectServer.ServerID, pkg.GetScriptExt(project.Script.AfterDeploy.Mode))
|
||||
scriptContent := ""
|
||||
if project.Script.AfterDeploy.Content != "" {
|
||||
scriptContent = project.ReplaceVars(project.Script.AfterDeploy.Content)
|
||||
scriptContent = project.ReplaceCustomVars(scriptContent)
|
||||
scriptContent = projectServer.ReplaceVars(scriptContent)
|
||||
scriptContent = strings.Replace(scriptContent, "${SERVER_TOTAL_NUMBER}", strconv.Itoa(len(gsync.ProjectServers)), -1)
|
||||
scriptContent = strings.Replace(scriptContent, "${SERVER_SERIAL_NUMBER}", strconv.Itoa(index), -1)
|
||||
|
||||
if project.Script.AfterDeploy.Mode == "yaml" {
|
||||
_ = yaml.Unmarshal([]byte(scriptContent), &dockerScript)
|
||||
|
||||
for stepIndex, step := range dockerScript.Steps {
|
||||
scriptName = fmt.Sprintf("goploy-after-deploy-p%d-s%d-y%d", project.ID, projectServer.ServerID, stepIndex)
|
||||
// delete the script
|
||||
step.Commands = append(step.Commands, fmt.Sprintf("rm -f %s", docker.GetDockerProjectScriptPath(project.ID, scriptName)))
|
||||
scriptContent = strings.Join(step.Commands, "\n")
|
||||
project.Script.AfterDeploy.ScriptNames = append(project.Script.AfterDeploy.ScriptNames, scriptName)
|
||||
_ = os.WriteFile(path.Join(config.GetProjectPath(project.ID), scriptName), []byte(scriptContent), 0755)
|
||||
}
|
||||
} else {
|
||||
project.Script.AfterDeploy.ScriptNames = append(project.Script.AfterDeploy.ScriptNames, scriptName)
|
||||
_ = os.WriteFile(path.Join(config.GetProjectPath(project.ID), scriptName), []byte(scriptContent), 0755)
|
||||
}
|
||||
}
|
||||
|
||||
transmitterEntity := transmitter.New(project, projectServer)
|
||||
logCmd := transmitterEntity.String()
|
||||
log.Trace("projectID: " + strconv.FormatInt(project.ID, 10) + " " + logCmd)
|
||||
ext, _ := json.Marshal(struct {
|
||||
ServerID int64 `json:"serverId"`
|
||||
ServerName string `json:"serverName"`
|
||||
Command string `json:"command"`
|
||||
}{projectServer.ServerID, projectServer.Server.Name, logCmd})
|
||||
publishTraceModel.Ext = string(ext)
|
||||
|
||||
if transmitterOutput, err := transmitterEntity.Exec(); err != nil {
|
||||
log.Error(fmt.Sprintf("projectID: %d transmit exec err: %s, output: %s", project.ID, err, transmitterOutput))
|
||||
publishTraceModel.Detail = fmt.Sprintf("err: %s\noutput: %s", err, transmitterOutput)
|
||||
publishTraceModel.State = model.Fail
|
||||
if _, err := publishTraceModel.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, project.ID, err)
|
||||
}
|
||||
|
||||
ch <- syncMessage{
|
||||
serverName: projectServer.Server.Name,
|
||||
projectID: project.ID,
|
||||
detail: err.Error(),
|
||||
state: model.ProjectFail,
|
||||
}
|
||||
return
|
||||
} else {
|
||||
publishTraceModel.Detail = transmitterOutput
|
||||
publishTraceModel.State = model.Success
|
||||
if _, err := publishTraceModel.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, project.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
if project.Script.AfterDeploy.Mode == "yaml" && len(dockerScript.Steps) > 0 {
|
||||
dockerConfig := docker.Config{
|
||||
ProjectID: project.ID,
|
||||
ProjectPath: project.Path,
|
||||
Server: projectServer.Server,
|
||||
}
|
||||
|
||||
if err := dockerConfig.Setup(); err != nil {
|
||||
log.Error(fmt.Sprintf("projectID: %d err: %s", project.ID, err))
|
||||
publishTraceModel.Detail = err.Error()
|
||||
publishTraceModel.State = model.Fail
|
||||
if _, err := publishTraceModel.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, project.ID, err)
|
||||
}
|
||||
ch <- syncMessage{
|
||||
serverName: projectServer.Server.Name,
|
||||
projectID: project.ID,
|
||||
detail: err.Error(),
|
||||
state: model.ProjectFail,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for stepIndex, step := range dockerScript.Steps {
|
||||
publishTraceModel.Type = model.AfterDeploy
|
||||
publishTraceModel.InsertTime = time.Now().Format("20060102150405")
|
||||
|
||||
step.ScriptName = fmt.Sprintf("goploy-after-deploy-p%d-s%d-y%d", project.ID, projectServer.ServerID, stepIndex)
|
||||
dockerOutput, dockerErr := dockerConfig.Run(step)
|
||||
|
||||
scriptContent = strings.Join(step.Commands, "\n")
|
||||
ext, _ = json.Marshal(struct {
|
||||
ServerID int64 `json:"serverId"`
|
||||
ServerName string `json:"serverName"`
|
||||
Script string `json:"script"`
|
||||
Step string `json:"step"`
|
||||
}{projectServer.ServerID, projectServer.Server.Name, scriptContent, step.Name})
|
||||
publishTraceModel.Ext = string(ext)
|
||||
|
||||
scriptFullName := path.Join(config.GetProjectPath(project.ID), step.ScriptName)
|
||||
_ = os.Remove(scriptFullName)
|
||||
|
||||
if dockerErr != nil {
|
||||
log.Error(fmt.Sprintf("projectID: %d run docker script err: %s", project.ID, dockerErr))
|
||||
publishTraceModel.Detail = fmt.Sprintf("err: %s\noutput: %s", dockerErr, dockerOutput)
|
||||
publishTraceModel.State = model.Fail
|
||||
if _, err := publishTraceModel.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, project.ID, err)
|
||||
}
|
||||
ch <- syncMessage{
|
||||
serverName: projectServer.Server.Name,
|
||||
projectID: project.ID,
|
||||
detail: fmt.Sprintf("err: %s\noutput: %s", dockerErr, dockerOutput),
|
||||
state: model.ProjectFail,
|
||||
}
|
||||
return
|
||||
} else {
|
||||
publishTraceModel.Detail = dockerOutput
|
||||
publishTraceModel.State = model.Success
|
||||
if _, err := publishTraceModel.AddRow(); err != nil {
|
||||
log.Error("projectID: " + strconv.FormatInt(project.ID, 10) + " " + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var afterDeployCommands []string
|
||||
cmdEntity := cmd.New(projectServer.Server.OS)
|
||||
if len(project.SymlinkPath) != 0 {
|
||||
destDir := cmd.Join(project.SymlinkPath, project.LastPublishToken)
|
||||
afterDeployCommands = append(afterDeployCommands, cmdEntity.Symlink(destDir, project.Path))
|
||||
}
|
||||
|
||||
if project.Script.AfterDeploy.Content != "" {
|
||||
afterDeployScriptPath := cmd.Join(project.Path, scriptName)
|
||||
afterDeployCommands = append(afterDeployCommands, cmdEntity.Script(project.Script.AfterDeploy.Mode, afterDeployScriptPath))
|
||||
afterDeployCommands = append(afterDeployCommands, cmdEntity.Remove(afterDeployScriptPath))
|
||||
}
|
||||
|
||||
// no symlink and deploy script
|
||||
if len(afterDeployCommands) == 0 {
|
||||
ch <- syncMessage{
|
||||
serverName: projectServer.Server.Name,
|
||||
projectID: project.ID,
|
||||
state: model.ProjectSuccess,
|
||||
}
|
||||
return
|
||||
}
|
||||
completeAfterDeployCmd := strings.Join(afterDeployCommands, "&&")
|
||||
publishTraceModel.Type = model.AfterDeploy
|
||||
publishTraceModel.InsertTime = time.Now().Format("20060102150405")
|
||||
ext, _ = json.Marshal(struct {
|
||||
ServerID int64 `json:"serverId"`
|
||||
ServerName string `json:"serverName"`
|
||||
Script string `json:"script"`
|
||||
}{projectServer.ServerID, projectServer.Server.Name, scriptContent})
|
||||
publishTraceModel.Ext = string(ext)
|
||||
|
||||
client, err := projectServer.ToSSHConfig().Dial()
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
publishTraceModel.Detail = err.Error()
|
||||
publishTraceModel.State = model.Fail
|
||||
if _, err := publishTraceModel.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, project.ID, err)
|
||||
}
|
||||
ch <- syncMessage{
|
||||
serverName: projectServer.Server.Name,
|
||||
projectID: project.ID,
|
||||
detail: err.Error(),
|
||||
state: model.ProjectFail,
|
||||
}
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
session, sessionErr := client.NewSession()
|
||||
if sessionErr != nil {
|
||||
log.Error(sessionErr.Error())
|
||||
publishTraceModel.Detail = sessionErr.Error()
|
||||
publishTraceModel.State = model.Fail
|
||||
if _, err := publishTraceModel.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, project.ID, err)
|
||||
}
|
||||
ch <- syncMessage{
|
||||
serverName: projectServer.Server.Name,
|
||||
projectID: project.ID,
|
||||
detail: sessionErr.Error(),
|
||||
state: model.ProjectFail,
|
||||
}
|
||||
return
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
log.Trace(fmt.Sprintf("projectID: %d ssh exec: %s", project.ID, completeAfterDeployCmd))
|
||||
|
||||
output, err := session.CombinedOutput(completeAfterDeployCmd)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("projectID: %d ssh exec err: %s, output: %s", project.ID, err, output))
|
||||
publishTraceModel.Detail = fmt.Sprintf("err: %s\noutput: %s", err, output)
|
||||
publishTraceModel.State = model.Fail
|
||||
if _, err := publishTraceModel.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, project.ID, err)
|
||||
}
|
||||
ch <- syncMessage{
|
||||
serverName: projectServer.Server.Name,
|
||||
projectID: project.ID,
|
||||
detail: fmt.Sprintf("%s\noutput: %s", err.Error(), output),
|
||||
state: model.ProjectFail,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
publishTraceModel.Detail = string(output)
|
||||
publishTraceModel.State = model.Success
|
||||
if _, err := publishTraceModel.AddRow(); err != nil {
|
||||
log.Error("projectID: " + strconv.FormatInt(project.ID, 10) + " " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
ch <- syncMessage{
|
||||
serverName: projectServer.Server.Name,
|
||||
projectID: project.ID,
|
||||
state: model.ProjectSuccess,
|
||||
}
|
||||
}
|
||||
|
||||
for index, projectServer := range gsync.ProjectServers {
|
||||
if gsync.Project.DeployServerMode == "serial" {
|
||||
serverSync(projectServer, index+1)
|
||||
} else {
|
||||
go serverSync(projectServer, 0)
|
||||
}
|
||||
}
|
||||
|
||||
message := ""
|
||||
for i := 0; i < len(gsync.ProjectServers); i++ {
|
||||
msg := <-ch
|
||||
if msg.state == model.ProjectFail {
|
||||
message += msg.serverName + " error message: " + msg.detail
|
||||
}
|
||||
}
|
||||
close(ch)
|
||||
|
||||
if message != "" {
|
||||
return errors.New(message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gsync *Gsync) deployFinishScriptStage() error {
|
||||
if gsync.Project.Script.DeployFinish.Content == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
gsync.PublishTrace.Type = model.DeployFinish
|
||||
gsync.PublishTrace.InsertTime = time.Now().Format("20060102150405")
|
||||
return gsync.runLocalScript()
|
||||
}
|
||||
|
||||
func (gsync *Gsync) runLocalScript() error {
|
||||
var mode = ""
|
||||
var content = ""
|
||||
var scriptName = ""
|
||||
project := gsync.Project
|
||||
switch gsync.PublishTrace.Type {
|
||||
case model.AfterPull:
|
||||
ws.Send(ws.Data{
|
||||
Type: ws.TypeProject,
|
||||
Message: deployMessage{ProjectID: gsync.Project.ID, ProjectName: gsync.Project.Name, State: Deploying, Message: "Run pull script"},
|
||||
})
|
||||
mode = project.Script.AfterPull.Mode
|
||||
content = project.Script.AfterPull.Content
|
||||
scriptName = "goploy-after-pull"
|
||||
|
||||
case model.DeployFinish:
|
||||
ws.Send(ws.Data{
|
||||
Type: ws.TypeProject,
|
||||
Message: deployMessage{ProjectID: gsync.Project.ID, ProjectName: gsync.Project.Name, State: Deploying, Message: "Run finish script"},
|
||||
})
|
||||
mode = project.Script.DeployFinish.Mode
|
||||
content = project.Script.DeployFinish.Content
|
||||
scriptName = "goploy-deploy-finish"
|
||||
|
||||
default:
|
||||
return errors.New("not support stage")
|
||||
}
|
||||
|
||||
log.Tracef(projectLogFormat, gsync.Project.ID, content)
|
||||
|
||||
commitInfo := gsync.CommitInfo
|
||||
srcPath := config.GetProjectPath(project.ID)
|
||||
scriptMode := "bash"
|
||||
if mode != "" {
|
||||
scriptMode = mode
|
||||
}
|
||||
scriptText := commitInfo.ReplaceVars(content)
|
||||
scriptText = project.ReplaceVars(scriptText)
|
||||
scriptText = project.ReplaceCustomVars(scriptText)
|
||||
|
||||
// run yaml script by docker
|
||||
if mode == "yaml" {
|
||||
var dockerScript docker.Script
|
||||
_ = yaml.Unmarshal([]byte(scriptText), &dockerScript)
|
||||
|
||||
projectPath, err := filepath.Abs(config.GetProjectPath(project.ID))
|
||||
if err != nil {
|
||||
return fmt.Errorf("get repository abs path err: %s", err)
|
||||
}
|
||||
|
||||
if len(dockerScript.Steps) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
dockerConfig := docker.Config{
|
||||
ProjectID: project.ID,
|
||||
ProjectPath: projectPath,
|
||||
}
|
||||
|
||||
if err = dockerConfig.Setup(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for stepIndex, step := range dockerScript.Steps {
|
||||
gsync.PublishTrace.InsertTime = time.Now().Format("20060102150405")
|
||||
scriptText = strings.Join(step.Commands, "\n")
|
||||
tmpScriptName := scriptName + fmt.Sprintf("-y%d", stepIndex)
|
||||
scriptFullName := path.Join(srcPath, tmpScriptName)
|
||||
step.ScriptName = tmpScriptName
|
||||
|
||||
_ = os.WriteFile(scriptFullName, []byte(scriptText), 0755)
|
||||
|
||||
dockerOutput, dockerErr := dockerConfig.Run(step)
|
||||
|
||||
ext, _ := json.Marshal(struct {
|
||||
Script string `json:"script"`
|
||||
Step string `json:"step"`
|
||||
}{scriptText, step.Name})
|
||||
gsync.PublishTrace.Ext = string(ext)
|
||||
|
||||
_ = os.Remove(scriptFullName)
|
||||
|
||||
if dockerErr != nil {
|
||||
gsync.PublishTrace.Detail = dockerErr.Error()
|
||||
gsync.PublishTrace.State = model.Fail
|
||||
if _, err := gsync.PublishTrace.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, gsync.Project.ID, err)
|
||||
}
|
||||
return fmt.Errorf("run docker script err: %s", dockerErr)
|
||||
} else {
|
||||
gsync.PublishTrace.Detail = dockerOutput
|
||||
gsync.PublishTrace.State = model.Success
|
||||
if _, err := gsync.PublishTrace.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, gsync.Project.ID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
scriptName += fmt.Sprintf(".%s", pkg.GetScriptExt(mode))
|
||||
scriptFullName := path.Join(srcPath, scriptName)
|
||||
_ = os.WriteFile(scriptFullName, []byte(scriptText), 0755)
|
||||
|
||||
var commandOptions []string
|
||||
if scriptMode == "cmd" {
|
||||
commandOptions = append(commandOptions, "/C")
|
||||
scriptFullName, _ = filepath.Abs(scriptFullName)
|
||||
}
|
||||
commandOptions = append(commandOptions, scriptFullName)
|
||||
|
||||
ext, _ := json.Marshal(struct {
|
||||
Script string `json:"script"`
|
||||
}{Script: scriptText})
|
||||
gsync.PublishTrace.Ext = string(ext)
|
||||
|
||||
handler := exec.Command(scriptMode, commandOptions...)
|
||||
handler.Dir = srcPath
|
||||
|
||||
if output, err := handler.CombinedOutput(); err != nil {
|
||||
gsync.PublishTrace.Detail = fmt.Sprintf("err: %s\noutput: %s", err, string(output))
|
||||
gsync.PublishTrace.State = model.Fail
|
||||
if _, err := gsync.PublishTrace.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, gsync.Project.ID, err)
|
||||
}
|
||||
return fmt.Errorf("err: %s, output: %s", err, string(output))
|
||||
} else {
|
||||
_ = os.Remove(scriptFullName)
|
||||
gsync.PublishTrace.Detail = string(output)
|
||||
gsync.PublishTrace.State = model.Success
|
||||
if _, err := gsync.PublishTrace.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, gsync.Project.ID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// commit id
|
||||
// commit message
|
||||
// server ip & name
|
||||
// deploy user name
|
||||
// deploy time
|
||||
func (gsync *Gsync) notify(deployState int, detail string) {
|
||||
if gsync.Project.NotifyType == 0 {
|
||||
return
|
||||
}
|
||||
serverList := ""
|
||||
for _, projectServer := range gsync.ProjectServers {
|
||||
if projectServer.Server.Name != projectServer.Server.IP {
|
||||
serverList += projectServer.Server.Name + "(" + projectServer.Server.IP + ")"
|
||||
} else {
|
||||
serverList += projectServer.Server.IP
|
||||
}
|
||||
serverList += ", "
|
||||
}
|
||||
serverList = strings.TrimRight(serverList, ", ")
|
||||
project := gsync.Project
|
||||
commitInfo := gsync.CommitInfo
|
||||
var err error
|
||||
var resp *http.Response
|
||||
if project.NotifyType == model.NotifyWeiXin {
|
||||
type markdown struct {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
type message struct {
|
||||
Msgtype string `json:"msgtype"`
|
||||
Markdown markdown `json:"markdown"`
|
||||
}
|
||||
content := "Deploy: <font color=\"warning\">" + project.Name + "</font>\n"
|
||||
content += "Publisher: <font color=\"comment\">" + project.PublisherName + "</font>\n"
|
||||
content += "Author: <font color=\"comment\">" + commitInfo.Author + "</font>\n"
|
||||
if commitInfo.Tag != "" {
|
||||
content += "Tag: <font color=\"comment\">" + commitInfo.Tag + "</font>\n"
|
||||
}
|
||||
content += "Branch: <font color=\"comment\">" + commitInfo.Branch + "</font>\n"
|
||||
content += "CommitSHA: <font color=\"comment\">" + commitInfo.Commit + "</font>\n"
|
||||
content += "CommitMessage: <font color=\"comment\">" + commitInfo.Message + "</font>\n"
|
||||
content += "ServerList: <font color=\"comment\">" + serverList + "</font>\n"
|
||||
if deployState == model.ProjectFail {
|
||||
content += "State: <font color=\"red\">fail</font> \n"
|
||||
content += "> Detail: <font color=\"comment\">" + detail + "</font>"
|
||||
} else {
|
||||
content += "State: <font color=\"green\">success</font>"
|
||||
}
|
||||
|
||||
msg := message{
|
||||
Msgtype: "markdown",
|
||||
Markdown: markdown{
|
||||
Content: content,
|
||||
},
|
||||
}
|
||||
b, _ := json.Marshal(msg)
|
||||
resp, err = http.Post(project.NotifyTarget, "application/json", bytes.NewBuffer(b))
|
||||
} else if project.NotifyType == model.NotifyDingTalk {
|
||||
type markdown struct {
|
||||
Title string `json:"title"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
type message struct {
|
||||
Msgtype string `json:"msgtype"`
|
||||
Markdown markdown `json:"markdown"`
|
||||
}
|
||||
text := "#### Deploy:" + project.Name + " \n "
|
||||
text += "#### Publisher:" + project.PublisherName + " \n "
|
||||
text += "#### Author:" + commitInfo.Author + " \n "
|
||||
if commitInfo.Tag != "" {
|
||||
text += "#### Tag:" + commitInfo.Tag + " \n "
|
||||
}
|
||||
text += "#### Branch:" + commitInfo.Branch + " \n "
|
||||
text += "#### CommitSHA:" + commitInfo.Commit + " \n "
|
||||
text += "#### CommitMessage:" + commitInfo.Message + " \n "
|
||||
text += "#### ServerList:" + serverList + " \n "
|
||||
if deployState == model.ProjectFail {
|
||||
text += "#### State: <font color=\"red\">fail</font> \n "
|
||||
text += "> Detail: <font color=\"comment\">" + detail + "</font>"
|
||||
} else {
|
||||
text += "#### State: <font color=\"green\">success</font>"
|
||||
}
|
||||
|
||||
msg := message{
|
||||
Msgtype: "markdown",
|
||||
Markdown: markdown{
|
||||
Title: project.Name,
|
||||
Text: text,
|
||||
},
|
||||
}
|
||||
b, _ := json.Marshal(msg)
|
||||
resp, err = http.Post(project.NotifyTarget, "application/json", bytes.NewBuffer(b))
|
||||
} else if project.NotifyType == model.NotifyFeiShu {
|
||||
type content struct {
|
||||
Text string `json:"text"`
|
||||
}
|
||||
type message struct {
|
||||
MsgType string `json:"msg_type"`
|
||||
Content content `json:"content"`
|
||||
}
|
||||
text := ""
|
||||
text += "Deploy:" + project.Name + "\n"
|
||||
text += "Publisher: " + project.PublisherName + "\n"
|
||||
text += "Author: " + commitInfo.Author + "\n"
|
||||
if commitInfo.Tag != "" {
|
||||
text += "Tag: " + commitInfo.Tag + "\n"
|
||||
}
|
||||
text += "Branch: " + commitInfo.Branch + "\n"
|
||||
text += "CommitSHA: " + commitInfo.Commit + "\n"
|
||||
text += "CommitMessage: " + commitInfo.Message + "\n"
|
||||
text += "ServerList: " + serverList + "\n"
|
||||
if deployState == model.ProjectFail {
|
||||
text += "State: fail\n "
|
||||
text += "Detail: " + detail
|
||||
} else {
|
||||
text += "State: success"
|
||||
}
|
||||
|
||||
msg := message{
|
||||
MsgType: "text",
|
||||
Content: content{
|
||||
Text: text,
|
||||
},
|
||||
}
|
||||
b, _ := json.Marshal(msg)
|
||||
resp, err = http.Post(project.NotifyTarget, "application/json", bytes.NewBuffer(b))
|
||||
} else if project.NotifyType == model.NotifyCustom {
|
||||
type message struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data struct {
|
||||
ProjectID int64 `json:"projectId"`
|
||||
ProjectName string `json:"projectName"`
|
||||
Publisher string `json:"publisher"`
|
||||
Author string `json:"author"`
|
||||
Branch string `json:"branch"`
|
||||
Tag string `json:"tag"`
|
||||
CommitSHA string `json:"commitSHA"`
|
||||
CommitMessage string `json:"commitMessage"`
|
||||
ServerList string `json:"serverList"`
|
||||
} `json:"data"`
|
||||
}
|
||||
code := 0
|
||||
if deployState == model.ProjectFail {
|
||||
code = 1
|
||||
}
|
||||
msg := message{
|
||||
Code: code,
|
||||
Message: detail,
|
||||
}
|
||||
msg.Data.ProjectID = project.ID
|
||||
msg.Data.ProjectName = project.Name
|
||||
msg.Data.Publisher = project.PublisherName
|
||||
msg.Data.Author = commitInfo.Author
|
||||
msg.Data.Branch = commitInfo.Branch
|
||||
msg.Data.Tag = commitInfo.Tag
|
||||
msg.Data.CommitSHA = commitInfo.Commit
|
||||
msg.Data.CommitMessage = commitInfo.Message
|
||||
msg.Data.ServerList = serverList
|
||||
b, _ := json.Marshal(msg)
|
||||
resp, err = http.Post(project.NotifyTarget, "application/json", bytes.NewBuffer(b))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("projectID: %d notify exec err: %s", project.ID, err))
|
||||
} else {
|
||||
responseData, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("projectID: %d notify read body err: %s", project.ID, err))
|
||||
} else {
|
||||
log.Trace(fmt.Sprintf("projectID: %d notify success: %s", project.ID, string(responseData)))
|
||||
}
|
||||
_ = resp.Body.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// keep the latest 10 project
|
||||
func (gsync *Gsync) removeExpiredBackup() {
|
||||
if gsync.Project.SymlinkPath == "" {
|
||||
return
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
for _, projectServer := range gsync.ProjectServers {
|
||||
wg.Add(1)
|
||||
go func(projectServer model.ProjectServer) {
|
||||
defer wg.Done()
|
||||
client, err := projectServer.ToSSHConfig().Dial()
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err := client.Close(); err != nil {
|
||||
log.Error(err.Error())
|
||||
}
|
||||
}()
|
||||
session, err := client.NewSession()
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err := session.Close(); err != nil {
|
||||
log.Error(err.Error())
|
||||
}
|
||||
}()
|
||||
var sshOutbuf, sshErrbuf bytes.Buffer
|
||||
session.Stdout = &sshOutbuf
|
||||
session.Stderr = &sshErrbuf
|
||||
if err = session.Run("cd " + gsync.Project.SymlinkPath + ";ls -t | awk 'NR>" + strconv.Itoa(int(gsync.Project.SymlinkBackupNumber)) + "' | xargs rm -rf"); err != nil {
|
||||
log.Error(err.Error())
|
||||
}
|
||||
}(projectServer)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
18
cmd/server/task/deploy/copy_local_file_stage.go
Normal file
18
cmd/server/task/deploy/copy_local_file_stage.go
Normal file
@ -0,0 +1,18 @@
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"github.com/zhenorzz/goploy/config"
|
||||
"github.com/zhenorzz/goploy/internal/model"
|
||||
"github.com/zhenorzz/goploy/internal/pkg"
|
||||
)
|
||||
|
||||
func (gsync *Gsync) copyLocalFileStage() error {
|
||||
if totalFileNumber, err := (model.ProjectFile{ProjectID: gsync.Project.ID}).GetTotalByProjectID(); err != nil {
|
||||
return err
|
||||
} else if totalFileNumber > 0 {
|
||||
if err := pkg.CopyDir(config.GetProjectFilePath(gsync.Project.ID), config.GetProjectPath(gsync.Project.ID)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
247
cmd/server/task/deploy/deploy.go
Normal file
247
cmd/server/task/deploy/deploy.go
Normal file
@ -0,0 +1,247 @@
|
||||
// Copyright 2022 The Goploy Authors. All rights reserved.
|
||||
// Use of this source code is governed by a GPLv3-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/google/uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/zhenorzz/goploy/cmd/server/ws"
|
||||
"github.com/zhenorzz/goploy/config"
|
||||
"github.com/zhenorzz/goploy/internal/model"
|
||||
"github.com/zhenorzz/goploy/internal/repo"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
var deployList = list.New()
|
||||
var deploySecondTick = time.Tick(time.Millisecond)
|
||||
var cronMinuteTick = time.Tick(time.Minute)
|
||||
|
||||
type Gsync struct {
|
||||
UserInfo model.User
|
||||
Project model.Project
|
||||
ProjectServers model.ProjectServers
|
||||
PublishTrace model.PublishTrace
|
||||
CommitInfo repo.CommitInfo
|
||||
CommitID string
|
||||
Branch string
|
||||
}
|
||||
|
||||
type syncMessage struct {
|
||||
serverName string
|
||||
projectID int64
|
||||
detail string
|
||||
state int
|
||||
}
|
||||
|
||||
type deployMessage struct {
|
||||
ProjectID int64 `json:"projectId"`
|
||||
ProjectName string `json:"projectName"`
|
||||
State uint8 `json:"state"`
|
||||
Message string `json:"message"`
|
||||
Ext interface{} `json:"ext"`
|
||||
}
|
||||
|
||||
const (
|
||||
Queue = iota
|
||||
Deploying
|
||||
Success
|
||||
Fail
|
||||
)
|
||||
|
||||
func (deployMessage) CanSendTo(*ws.Client) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var projectLogFormat = "projectID: %d %s"
|
||||
|
||||
func Run(counter *int32, stop <-chan struct{}) {
|
||||
atomic.AddInt32(counter, 1)
|
||||
var deployingNumber int32
|
||||
var wg sync.WaitGroup
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-deploySecondTick:
|
||||
if atomic.LoadInt32(&deployingNumber) < config.Toml.APP.DeployLimit {
|
||||
atomic.AddInt32(&deployingNumber, 1)
|
||||
if deployElem := deployList.Front(); deployElem != nil {
|
||||
wg.Add(1)
|
||||
go func(gsync *Gsync) {
|
||||
gsync.exec()
|
||||
atomic.AddInt32(&deployingNumber, -1)
|
||||
wg.Done()
|
||||
}(deployList.Remove(deployElem).(*Gsync))
|
||||
} else {
|
||||
atomic.AddInt32(&deployingNumber, -1)
|
||||
}
|
||||
}
|
||||
case <-cronMinuteTick:
|
||||
cronTask()
|
||||
case <-stop:
|
||||
wg.Wait()
|
||||
atomic.AddInt32(counter, -1)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func cronTask() {
|
||||
date := time.Now().Format("2006-01-02 15:04:05")
|
||||
projectTasks, err := model.ProjectTask{}.GetNotRunListLTDate(date)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
log.Error("get project task list error, detail:" + err.Error())
|
||||
}
|
||||
for _, projectTask := range projectTasks {
|
||||
project, err := model.Project{ID: projectTask.ProjectID}.GetData()
|
||||
|
||||
if err != nil {
|
||||
log.Error("publish task has no project, detail:" + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if err := projectTask.SetRun(); err != nil {
|
||||
log.Error("publish task set run fail, detail:" + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
projectServers, err := model.ProjectServer{ProjectID: projectTask.ProjectID}.GetBindServerListByProjectID()
|
||||
|
||||
if err != nil {
|
||||
log.Error("publish task has no server, detail:" + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
userInfo, err := model.User{ID: 1}.GetData()
|
||||
if err != nil {
|
||||
log.Error("publish task has no user, detail:" + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
project.PublisherID = userInfo.ID
|
||||
project.PublisherName = userInfo.Name
|
||||
project.DeployState = model.ProjectDeploying
|
||||
project.LastPublishToken = uuid.New().String()
|
||||
err = project.Publish()
|
||||
if err != nil {
|
||||
log.Error("publish task change state error, detail:" + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
AddTask(Gsync{
|
||||
UserInfo: userInfo,
|
||||
Project: project,
|
||||
ProjectServers: projectServers,
|
||||
CommitID: projectTask.CommitID,
|
||||
Branch: projectTask.Branch,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func AddTask(gsync Gsync) {
|
||||
ws.Send(ws.Data{
|
||||
Type: ws.TypeProject,
|
||||
Message: deployMessage{
|
||||
ProjectID: gsync.Project.ID,
|
||||
ProjectName: gsync.Project.Name,
|
||||
State: Queue,
|
||||
Message: "Task waiting",
|
||||
Ext: struct {
|
||||
LastPublishToken string `json:"lastPublishToken"`
|
||||
}{gsync.Project.LastPublishToken},
|
||||
},
|
||||
})
|
||||
|
||||
queueExt := model.PublishTraceQueueExt{
|
||||
Script: gsync.Project.Script,
|
||||
}
|
||||
ext, _ := json.Marshal(queueExt)
|
||||
gsync.PublishTrace = model.PublishTrace{
|
||||
Token: gsync.Project.LastPublishToken,
|
||||
ProjectID: gsync.Project.ID,
|
||||
ProjectName: gsync.Project.Name,
|
||||
PublisherID: gsync.UserInfo.ID,
|
||||
PublisherName: gsync.UserInfo.Name,
|
||||
Ext: string(ext),
|
||||
InsertTime: time.Now().Format("20060102150405"),
|
||||
Type: model.Queue,
|
||||
State: model.Success,
|
||||
}
|
||||
if _, err := gsync.PublishTrace.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, gsync.Project.ID, "insert trace error, "+err.Error())
|
||||
}
|
||||
deployList.PushBack(&gsync)
|
||||
}
|
||||
|
||||
func (gsync *Gsync) exec() {
|
||||
log.Tracef(projectLogFormat, gsync.Project.ID, "deploy start")
|
||||
var err error
|
||||
defer func() {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
ws.Send(ws.Data{
|
||||
Type: ws.TypeProject,
|
||||
Message: deployMessage{ProjectID: gsync.Project.ID, ProjectName: gsync.Project.Name, State: Fail, Message: err.Error()},
|
||||
})
|
||||
log.Errorf(projectLogFormat, gsync.Project.ID, err)
|
||||
if err := gsync.Project.DeployFail(); err != nil {
|
||||
log.Errorf(projectLogFormat, gsync.Project.ID, err)
|
||||
}
|
||||
gsync.notify(model.ProjectFail, err.Error())
|
||||
|
||||
}()
|
||||
|
||||
err = gsync.repoStage()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = gsync.copyLocalFileStage()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = gsync.afterPullScriptStage()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = gsync.serverStage()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = gsync.deployFinishScriptStage()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := gsync.Project.DeploySuccess(); err != nil {
|
||||
log.Errorf(projectLogFormat, gsync.Project.ID, err)
|
||||
}
|
||||
|
||||
gsync.PublishTrace.Type = model.PublishFinish
|
||||
gsync.PublishTrace.State = model.Success
|
||||
gsync.PublishTrace.InsertTime = time.Now().Format("20060102150405")
|
||||
if _, err := gsync.PublishTrace.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, gsync.Project.ID, err)
|
||||
}
|
||||
|
||||
log.Tracef(projectLogFormat, gsync.Project.ID, "deploy success")
|
||||
ws.Send(ws.Data{
|
||||
Type: ws.TypeProject,
|
||||
Message: deployMessage{ProjectID: gsync.Project.ID, ProjectName: gsync.Project.Name, State: Success, Message: "Success", Ext: gsync.CommitInfo},
|
||||
})
|
||||
gsync.notify(model.ProjectSuccess, "")
|
||||
|
||||
gsync.removeExpiredBackup()
|
||||
}
|
177
cmd/server/task/deploy/local_script_stage.go
Normal file
177
cmd/server/task/deploy/local_script_stage.go
Normal file
@ -0,0 +1,177 @@
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/zhenorzz/goploy/cmd/server/ws"
|
||||
"github.com/zhenorzz/goploy/config"
|
||||
"github.com/zhenorzz/goploy/internal/model"
|
||||
"github.com/zhenorzz/goploy/internal/pipeline/docker"
|
||||
"github.com/zhenorzz/goploy/internal/pkg"
|
||||
"gopkg.in/yaml.v3"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (gsync *Gsync) afterPullScriptStage() error {
|
||||
if gsync.Project.Script.AfterPull.Content == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
gsync.PublishTrace.Type = model.AfterPull
|
||||
gsync.PublishTrace.InsertTime = time.Now().Format("20060102150405")
|
||||
return gsync.runLocalScript()
|
||||
}
|
||||
|
||||
func (gsync *Gsync) deployFinishScriptStage() error {
|
||||
if gsync.Project.Script.DeployFinish.Content == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
gsync.PublishTrace.Type = model.DeployFinish
|
||||
gsync.PublishTrace.InsertTime = time.Now().Format("20060102150405")
|
||||
return gsync.runLocalScript()
|
||||
}
|
||||
|
||||
func (gsync *Gsync) runLocalScript() error {
|
||||
var mode = ""
|
||||
var content = ""
|
||||
var scriptName = ""
|
||||
project := gsync.Project
|
||||
switch gsync.PublishTrace.Type {
|
||||
case model.AfterPull:
|
||||
ws.Send(ws.Data{
|
||||
Type: ws.TypeProject,
|
||||
Message: deployMessage{ProjectID: gsync.Project.ID, ProjectName: gsync.Project.Name, State: Deploying, Message: "Run pull script"},
|
||||
})
|
||||
mode = project.Script.AfterPull.Mode
|
||||
content = project.Script.AfterPull.Content
|
||||
scriptName = "goploy-after-pull"
|
||||
|
||||
case model.DeployFinish:
|
||||
ws.Send(ws.Data{
|
||||
Type: ws.TypeProject,
|
||||
Message: deployMessage{ProjectID: gsync.Project.ID, ProjectName: gsync.Project.Name, State: Deploying, Message: "Run finish script"},
|
||||
})
|
||||
mode = project.Script.DeployFinish.Mode
|
||||
content = project.Script.DeployFinish.Content
|
||||
scriptName = "goploy-deploy-finish"
|
||||
|
||||
default:
|
||||
return errors.New("not support stage")
|
||||
}
|
||||
|
||||
log.Tracef(projectLogFormat, gsync.Project.ID, content)
|
||||
|
||||
commitInfo := gsync.CommitInfo
|
||||
srcPath := config.GetProjectPath(project.ID)
|
||||
scriptMode := "bash"
|
||||
if mode != "" {
|
||||
scriptMode = mode
|
||||
}
|
||||
scriptText := commitInfo.ReplaceVars(content)
|
||||
scriptText = project.ReplaceVars(scriptText)
|
||||
scriptText = project.ReplaceCustomVars(scriptText)
|
||||
|
||||
// run yaml script by docker
|
||||
if mode == "yaml" {
|
||||
var dockerScript docker.Script
|
||||
_ = yaml.Unmarshal([]byte(scriptText), &dockerScript)
|
||||
|
||||
projectPath, err := filepath.Abs(config.GetProjectPath(project.ID))
|
||||
if err != nil {
|
||||
return fmt.Errorf("get repository abs path err: %s", err)
|
||||
}
|
||||
|
||||
if len(dockerScript.Steps) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
dockerConfig := docker.Config{
|
||||
ProjectID: project.ID,
|
||||
ProjectPath: projectPath,
|
||||
}
|
||||
|
||||
if err = dockerConfig.Setup(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for stepIndex, step := range dockerScript.Steps {
|
||||
gsync.PublishTrace.InsertTime = time.Now().Format("20060102150405")
|
||||
scriptText = strings.Join(step.Commands, "\n")
|
||||
tmpScriptName := scriptName + fmt.Sprintf("-y%d", stepIndex)
|
||||
scriptFullName := path.Join(srcPath, tmpScriptName)
|
||||
step.ScriptName = tmpScriptName
|
||||
|
||||
_ = os.WriteFile(scriptFullName, []byte(scriptText), 0755)
|
||||
|
||||
dockerOutput, dockerErr := dockerConfig.Run(step)
|
||||
|
||||
ext, _ := json.Marshal(struct {
|
||||
Script string `json:"script"`
|
||||
Step string `json:"step"`
|
||||
}{scriptText, step.Name})
|
||||
gsync.PublishTrace.Ext = string(ext)
|
||||
|
||||
_ = os.Remove(scriptFullName)
|
||||
|
||||
if dockerErr != nil {
|
||||
gsync.PublishTrace.Detail = dockerErr.Error()
|
||||
gsync.PublishTrace.State = model.Fail
|
||||
if _, err := gsync.PublishTrace.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, gsync.Project.ID, err)
|
||||
}
|
||||
return fmt.Errorf("run docker script err: %s", dockerErr)
|
||||
} else {
|
||||
gsync.PublishTrace.Detail = dockerOutput
|
||||
gsync.PublishTrace.State = model.Success
|
||||
if _, err := gsync.PublishTrace.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, gsync.Project.ID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
scriptName += fmt.Sprintf(".%s", pkg.GetScriptExt(mode))
|
||||
scriptFullName := path.Join(srcPath, scriptName)
|
||||
_ = os.WriteFile(scriptFullName, []byte(scriptText), 0755)
|
||||
|
||||
var commandOptions []string
|
||||
if scriptMode == "cmd" {
|
||||
commandOptions = append(commandOptions, "/C")
|
||||
scriptFullName, _ = filepath.Abs(scriptFullName)
|
||||
}
|
||||
commandOptions = append(commandOptions, scriptFullName)
|
||||
|
||||
ext, _ := json.Marshal(struct {
|
||||
Script string `json:"script"`
|
||||
}{Script: scriptText})
|
||||
gsync.PublishTrace.Ext = string(ext)
|
||||
|
||||
handler := exec.Command(scriptMode, commandOptions...)
|
||||
handler.Dir = srcPath
|
||||
|
||||
if output, err := handler.CombinedOutput(); err != nil {
|
||||
gsync.PublishTrace.Detail = fmt.Sprintf("err: %s\noutput: %s", err, string(output))
|
||||
gsync.PublishTrace.State = model.Fail
|
||||
if _, err := gsync.PublishTrace.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, gsync.Project.ID, err)
|
||||
}
|
||||
return fmt.Errorf("err: %s, output: %s", err, string(output))
|
||||
} else {
|
||||
_ = os.Remove(scriptFullName)
|
||||
gsync.PublishTrace.Detail = string(output)
|
||||
gsync.PublishTrace.State = model.Success
|
||||
if _, err := gsync.PublishTrace.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, gsync.Project.ID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
187
cmd/server/task/deploy/notify_stage.go
Normal file
187
cmd/server/task/deploy/notify_stage.go
Normal file
@ -0,0 +1,187 @@
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/zhenorzz/goploy/internal/model"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// commit id
|
||||
// commit message
|
||||
// server ip & name
|
||||
// deploy user name
|
||||
// deploy time
|
||||
func (gsync *Gsync) notify(deployState int, detail string) {
|
||||
if gsync.Project.NotifyType == 0 {
|
||||
return
|
||||
}
|
||||
serverList := ""
|
||||
for _, projectServer := range gsync.ProjectServers {
|
||||
if projectServer.Server.Name != projectServer.Server.IP {
|
||||
serverList += projectServer.Server.Name + "(" + projectServer.Server.IP + ")"
|
||||
} else {
|
||||
serverList += projectServer.Server.IP
|
||||
}
|
||||
serverList += ", "
|
||||
}
|
||||
serverList = strings.TrimRight(serverList, ", ")
|
||||
project := gsync.Project
|
||||
commitInfo := gsync.CommitInfo
|
||||
var err error
|
||||
var resp *http.Response
|
||||
if project.NotifyType == model.NotifyWeiXin {
|
||||
type markdown struct {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
type message struct {
|
||||
Msgtype string `json:"msgtype"`
|
||||
Markdown markdown `json:"markdown"`
|
||||
}
|
||||
content := "Deploy: <font color=\"warning\">" + project.Name + "</font>\n"
|
||||
content += "Publisher: <font color=\"comment\">" + project.PublisherName + "</font>\n"
|
||||
content += "Author: <font color=\"comment\">" + commitInfo.Author + "</font>\n"
|
||||
if commitInfo.Tag != "" {
|
||||
content += "Tag: <font color=\"comment\">" + commitInfo.Tag + "</font>\n"
|
||||
}
|
||||
content += "Branch: <font color=\"comment\">" + commitInfo.Branch + "</font>\n"
|
||||
content += "CommitSHA: <font color=\"comment\">" + commitInfo.Commit + "</font>\n"
|
||||
content += "CommitMessage: <font color=\"comment\">" + commitInfo.Message + "</font>\n"
|
||||
content += "ServerList: <font color=\"comment\">" + serverList + "</font>\n"
|
||||
if deployState == model.ProjectFail {
|
||||
content += "State: <font color=\"red\">fail</font> \n"
|
||||
content += "> Detail: <font color=\"comment\">" + detail + "</font>"
|
||||
} else {
|
||||
content += "State: <font color=\"green\">success</font>"
|
||||
}
|
||||
|
||||
msg := message{
|
||||
Msgtype: "markdown",
|
||||
Markdown: markdown{
|
||||
Content: content,
|
||||
},
|
||||
}
|
||||
b, _ := json.Marshal(msg)
|
||||
resp, err = http.Post(project.NotifyTarget, "application/json", bytes.NewBuffer(b))
|
||||
} else if project.NotifyType == model.NotifyDingTalk {
|
||||
type markdown struct {
|
||||
Title string `json:"title"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
type message struct {
|
||||
Msgtype string `json:"msgtype"`
|
||||
Markdown markdown `json:"markdown"`
|
||||
}
|
||||
text := "#### Deploy:" + project.Name + " \n "
|
||||
text += "#### Publisher:" + project.PublisherName + " \n "
|
||||
text += "#### Author:" + commitInfo.Author + " \n "
|
||||
if commitInfo.Tag != "" {
|
||||
text += "#### Tag:" + commitInfo.Tag + " \n "
|
||||
}
|
||||
text += "#### Branch:" + commitInfo.Branch + " \n "
|
||||
text += "#### CommitSHA:" + commitInfo.Commit + " \n "
|
||||
text += "#### CommitMessage:" + commitInfo.Message + " \n "
|
||||
text += "#### ServerList:" + serverList + " \n "
|
||||
if deployState == model.ProjectFail {
|
||||
text += "#### State: <font color=\"red\">fail</font> \n "
|
||||
text += "> Detail: <font color=\"comment\">" + detail + "</font>"
|
||||
} else {
|
||||
text += "#### State: <font color=\"green\">success</font>"
|
||||
}
|
||||
|
||||
msg := message{
|
||||
Msgtype: "markdown",
|
||||
Markdown: markdown{
|
||||
Title: project.Name,
|
||||
Text: text,
|
||||
},
|
||||
}
|
||||
b, _ := json.Marshal(msg)
|
||||
resp, err = http.Post(project.NotifyTarget, "application/json", bytes.NewBuffer(b))
|
||||
} else if project.NotifyType == model.NotifyFeiShu {
|
||||
type content struct {
|
||||
Text string `json:"text"`
|
||||
}
|
||||
type message struct {
|
||||
MsgType string `json:"msg_type"`
|
||||
Content content `json:"content"`
|
||||
}
|
||||
text := ""
|
||||
text += "Deploy:" + project.Name + "\n"
|
||||
text += "Publisher: " + project.PublisherName + "\n"
|
||||
text += "Author: " + commitInfo.Author + "\n"
|
||||
if commitInfo.Tag != "" {
|
||||
text += "Tag: " + commitInfo.Tag + "\n"
|
||||
}
|
||||
text += "Branch: " + commitInfo.Branch + "\n"
|
||||
text += "CommitSHA: " + commitInfo.Commit + "\n"
|
||||
text += "CommitMessage: " + commitInfo.Message + "\n"
|
||||
text += "ServerList: " + serverList + "\n"
|
||||
if deployState == model.ProjectFail {
|
||||
text += "State: fail\n "
|
||||
text += "Detail: " + detail
|
||||
} else {
|
||||
text += "State: success"
|
||||
}
|
||||
|
||||
msg := message{
|
||||
MsgType: "text",
|
||||
Content: content{
|
||||
Text: text,
|
||||
},
|
||||
}
|
||||
b, _ := json.Marshal(msg)
|
||||
resp, err = http.Post(project.NotifyTarget, "application/json", bytes.NewBuffer(b))
|
||||
} else if project.NotifyType == model.NotifyCustom {
|
||||
type message struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data struct {
|
||||
ProjectID int64 `json:"projectId"`
|
||||
ProjectName string `json:"projectName"`
|
||||
Publisher string `json:"publisher"`
|
||||
Author string `json:"author"`
|
||||
Branch string `json:"branch"`
|
||||
Tag string `json:"tag"`
|
||||
CommitSHA string `json:"commitSHA"`
|
||||
CommitMessage string `json:"commitMessage"`
|
||||
ServerList string `json:"serverList"`
|
||||
} `json:"data"`
|
||||
}
|
||||
code := 0
|
||||
if deployState == model.ProjectFail {
|
||||
code = 1
|
||||
}
|
||||
msg := message{
|
||||
Code: code,
|
||||
Message: detail,
|
||||
}
|
||||
msg.Data.ProjectID = project.ID
|
||||
msg.Data.ProjectName = project.Name
|
||||
msg.Data.Publisher = project.PublisherName
|
||||
msg.Data.Author = commitInfo.Author
|
||||
msg.Data.Branch = commitInfo.Branch
|
||||
msg.Data.Tag = commitInfo.Tag
|
||||
msg.Data.CommitSHA = commitInfo.Commit
|
||||
msg.Data.CommitMessage = commitInfo.Message
|
||||
msg.Data.ServerList = serverList
|
||||
b, _ := json.Marshal(msg)
|
||||
resp, err = http.Post(project.NotifyTarget, "application/json", bytes.NewBuffer(b))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("projectID: %d notify exec err: %s", project.ID, err))
|
||||
} else {
|
||||
responseData, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("projectID: %d notify read body err: %s", project.ID, err))
|
||||
} else {
|
||||
log.Trace(fmt.Sprintf("projectID: %d notify success: %s", project.ID, string(responseData)))
|
||||
}
|
||||
_ = resp.Body.Close()
|
||||
}
|
||||
}
|
50
cmd/server/task/deploy/remove_expired_backup_stage.go
Normal file
50
cmd/server/task/deploy/remove_expired_backup_stage.go
Normal file
@ -0,0 +1,50 @@
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/zhenorzz/goploy/internal/model"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// keep the latest 10 project
|
||||
func (gsync *Gsync) removeExpiredBackup() {
|
||||
if gsync.Project.SymlinkPath == "" {
|
||||
return
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
for _, projectServer := range gsync.ProjectServers {
|
||||
wg.Add(1)
|
||||
go func(projectServer model.ProjectServer) {
|
||||
defer wg.Done()
|
||||
client, err := projectServer.ToSSHConfig().Dial()
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err := client.Close(); err != nil {
|
||||
log.Error(err.Error())
|
||||
}
|
||||
}()
|
||||
session, err := client.NewSession()
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err := session.Close(); err != nil {
|
||||
log.Error(err.Error())
|
||||
}
|
||||
}()
|
||||
var sshOutbuf, sshErrbuf bytes.Buffer
|
||||
session.Stdout = &sshOutbuf
|
||||
session.Stderr = &sshErrbuf
|
||||
if err = session.Run("cd " + gsync.Project.SymlinkPath + ";ls -t | awk 'NR>" + strconv.Itoa(int(gsync.Project.SymlinkBackupNumber)) + "' | xargs rm -rf"); err != nil {
|
||||
log.Error(err.Error())
|
||||
}
|
||||
}(projectServer)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
60
cmd/server/task/deploy/repo_stage.go
Normal file
60
cmd/server/task/deploy/repo_stage.go
Normal file
@ -0,0 +1,60 @@
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/zhenorzz/goploy/cmd/server/ws"
|
||||
"github.com/zhenorzz/goploy/internal/model"
|
||||
"github.com/zhenorzz/goploy/internal/repo"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (gsync *Gsync) repoStage() error {
|
||||
ws.Send(ws.Data{
|
||||
Type: ws.TypeProject,
|
||||
Message: deployMessage{ProjectID: gsync.Project.ID, ProjectName: gsync.Project.Name, State: Deploying, Message: "Repo follow"},
|
||||
})
|
||||
gsync.PublishTrace.Type = model.Pull
|
||||
gsync.PublishTrace.InsertTime = time.Now().Format("20060102150405")
|
||||
var err error
|
||||
r, _ := repo.GetRepo(gsync.Project.RepoType)
|
||||
if len(gsync.CommitID) == 0 {
|
||||
err = r.Follow(gsync.Project.ID, "origin/"+gsync.Project.Branch, gsync.Project.URL, gsync.Project.Branch)
|
||||
} else {
|
||||
err = r.Follow(gsync.Project.ID, gsync.CommitID, gsync.Project.URL, gsync.Project.Branch)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
gsync.PublishTrace.Detail = err.Error()
|
||||
gsync.PublishTrace.State = model.Fail
|
||||
if _, err := gsync.PublishTrace.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, gsync.Project.ID, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
commitList, err := r.CommitLog(gsync.Project.ID, 1)
|
||||
if err != nil {
|
||||
gsync.PublishTrace.Detail = err.Error()
|
||||
gsync.PublishTrace.State = model.Fail
|
||||
if _, err := gsync.PublishTrace.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, gsync.Project.ID, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
gsync.CommitInfo = commitList[0]
|
||||
if gsync.Branch != "" {
|
||||
gsync.CommitInfo.Branch = gsync.Branch
|
||||
} else {
|
||||
gsync.CommitInfo.Branch = "origin/" + gsync.Project.Branch
|
||||
}
|
||||
|
||||
ext, _ := json.Marshal(gsync.CommitInfo)
|
||||
gsync.PublishTrace.Ext = string(ext)
|
||||
gsync.PublishTrace.State = model.Success
|
||||
if _, err := gsync.PublishTrace.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, gsync.Project.ID, err)
|
||||
}
|
||||
return nil
|
||||
}
|
282
cmd/server/task/deploy/server_stage.go
Normal file
282
cmd/server/task/deploy/server_stage.go
Normal file
@ -0,0 +1,282 @@
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/zhenorzz/goploy/cmd/server/ws"
|
||||
"github.com/zhenorzz/goploy/config"
|
||||
"github.com/zhenorzz/goploy/internal/model"
|
||||
"github.com/zhenorzz/goploy/internal/pipeline/docker"
|
||||
"github.com/zhenorzz/goploy/internal/pkg"
|
||||
"github.com/zhenorzz/goploy/internal/pkg/cmd"
|
||||
"github.com/zhenorzz/goploy/internal/transmitter"
|
||||
"gopkg.in/yaml.v3"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (gsync *Gsync) serverStage() error {
|
||||
ws.Send(ws.Data{
|
||||
Type: ws.TypeProject,
|
||||
Message: deployMessage{ProjectID: gsync.Project.ID, ProjectName: gsync.Project.Name, State: Deploying, Message: "Sync"},
|
||||
})
|
||||
ch := make(chan syncMessage, len(gsync.ProjectServers))
|
||||
gsync.PublishTrace.Type = model.Deploy
|
||||
var serverSync = func(projectServer model.ProjectServer, index int) {
|
||||
project := gsync.Project
|
||||
publishTraceModel := gsync.PublishTrace
|
||||
publishTraceModel.InsertTime = time.Now().Format("20060102150405")
|
||||
// write after deploy script for rsync
|
||||
var dockerScript docker.Script
|
||||
scriptName := fmt.Sprintf("goploy-after-deploy-p%d-s%d.%s", project.ID, projectServer.ServerID, pkg.GetScriptExt(project.Script.AfterDeploy.Mode))
|
||||
scriptContent := ""
|
||||
if project.Script.AfterDeploy.Content != "" {
|
||||
scriptContent = project.ReplaceVars(project.Script.AfterDeploy.Content)
|
||||
scriptContent = project.ReplaceCustomVars(scriptContent)
|
||||
scriptContent = projectServer.ReplaceVars(scriptContent)
|
||||
scriptContent = strings.Replace(scriptContent, "${SERVER_TOTAL_NUMBER}", strconv.Itoa(len(gsync.ProjectServers)), -1)
|
||||
scriptContent = strings.Replace(scriptContent, "${SERVER_SERIAL_NUMBER}", strconv.Itoa(index), -1)
|
||||
|
||||
if project.Script.AfterDeploy.Mode == "yaml" {
|
||||
_ = yaml.Unmarshal([]byte(scriptContent), &dockerScript)
|
||||
|
||||
for stepIndex, step := range dockerScript.Steps {
|
||||
scriptName = fmt.Sprintf("goploy-after-deploy-p%d-s%d-y%d", project.ID, projectServer.ServerID, stepIndex)
|
||||
// delete the script
|
||||
step.Commands = append(step.Commands, fmt.Sprintf("rm -f %s", docker.GetDockerProjectScriptPath(project.ID, scriptName)))
|
||||
scriptContent = strings.Join(step.Commands, "\n")
|
||||
project.Script.AfterDeploy.ScriptNames = append(project.Script.AfterDeploy.ScriptNames, scriptName)
|
||||
_ = os.WriteFile(path.Join(config.GetProjectPath(project.ID), scriptName), []byte(scriptContent), 0755)
|
||||
}
|
||||
} else {
|
||||
project.Script.AfterDeploy.ScriptNames = append(project.Script.AfterDeploy.ScriptNames, scriptName)
|
||||
_ = os.WriteFile(path.Join(config.GetProjectPath(project.ID), scriptName), []byte(scriptContent), 0755)
|
||||
}
|
||||
}
|
||||
|
||||
transmitterEntity := transmitter.New(project, projectServer)
|
||||
logCmd := transmitterEntity.String()
|
||||
log.Trace("projectID: " + strconv.FormatInt(project.ID, 10) + " " + logCmd)
|
||||
ext, _ := json.Marshal(struct {
|
||||
ServerID int64 `json:"serverId"`
|
||||
ServerName string `json:"serverName"`
|
||||
Command string `json:"command"`
|
||||
}{projectServer.ServerID, projectServer.Server.Name, logCmd})
|
||||
publishTraceModel.Ext = string(ext)
|
||||
|
||||
if transmitterOutput, err := transmitterEntity.Exec(); err != nil {
|
||||
log.Error(fmt.Sprintf("projectID: %d transmit exec err: %s, output: %s", project.ID, err, transmitterOutput))
|
||||
publishTraceModel.Detail = fmt.Sprintf("err: %s\noutput: %s", err, transmitterOutput)
|
||||
publishTraceModel.State = model.Fail
|
||||
if _, err := publishTraceModel.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, project.ID, err)
|
||||
}
|
||||
|
||||
ch <- syncMessage{
|
||||
serverName: projectServer.Server.Name,
|
||||
projectID: project.ID,
|
||||
detail: err.Error(),
|
||||
state: model.ProjectFail,
|
||||
}
|
||||
return
|
||||
} else {
|
||||
publishTraceModel.Detail = transmitterOutput
|
||||
publishTraceModel.State = model.Success
|
||||
if _, err := publishTraceModel.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, project.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
if project.Script.AfterDeploy.Mode == "yaml" && len(dockerScript.Steps) > 0 {
|
||||
dockerConfig := docker.Config{
|
||||
ProjectID: project.ID,
|
||||
ProjectPath: project.Path,
|
||||
Server: projectServer.Server,
|
||||
}
|
||||
|
||||
if err := dockerConfig.Setup(); err != nil {
|
||||
log.Error(fmt.Sprintf("projectID: %d err: %s", project.ID, err))
|
||||
publishTraceModel.Detail = err.Error()
|
||||
publishTraceModel.State = model.Fail
|
||||
if _, err := publishTraceModel.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, project.ID, err)
|
||||
}
|
||||
ch <- syncMessage{
|
||||
serverName: projectServer.Server.Name,
|
||||
projectID: project.ID,
|
||||
detail: err.Error(),
|
||||
state: model.ProjectFail,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for stepIndex, step := range dockerScript.Steps {
|
||||
publishTraceModel.Type = model.AfterDeploy
|
||||
publishTraceModel.InsertTime = time.Now().Format("20060102150405")
|
||||
|
||||
step.ScriptName = fmt.Sprintf("goploy-after-deploy-p%d-s%d-y%d", project.ID, projectServer.ServerID, stepIndex)
|
||||
dockerOutput, dockerErr := dockerConfig.Run(step)
|
||||
|
||||
scriptContent = strings.Join(step.Commands, "\n")
|
||||
ext, _ = json.Marshal(struct {
|
||||
ServerID int64 `json:"serverId"`
|
||||
ServerName string `json:"serverName"`
|
||||
Script string `json:"script"`
|
||||
Step string `json:"step"`
|
||||
}{projectServer.ServerID, projectServer.Server.Name, scriptContent, step.Name})
|
||||
publishTraceModel.Ext = string(ext)
|
||||
|
||||
scriptFullName := path.Join(config.GetProjectPath(project.ID), step.ScriptName)
|
||||
_ = os.Remove(scriptFullName)
|
||||
|
||||
if dockerErr != nil {
|
||||
log.Error(fmt.Sprintf("projectID: %d run docker script err: %s", project.ID, dockerErr))
|
||||
publishTraceModel.Detail = fmt.Sprintf("err: %s\noutput: %s", dockerErr, dockerOutput)
|
||||
publishTraceModel.State = model.Fail
|
||||
if _, err := publishTraceModel.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, project.ID, err)
|
||||
}
|
||||
ch <- syncMessage{
|
||||
serverName: projectServer.Server.Name,
|
||||
projectID: project.ID,
|
||||
detail: fmt.Sprintf("err: %s\noutput: %s", dockerErr, dockerOutput),
|
||||
state: model.ProjectFail,
|
||||
}
|
||||
return
|
||||
} else {
|
||||
publishTraceModel.Detail = dockerOutput
|
||||
publishTraceModel.State = model.Success
|
||||
if _, err := publishTraceModel.AddRow(); err != nil {
|
||||
log.Error("projectID: " + strconv.FormatInt(project.ID, 10) + " " + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var afterDeployCommands []string
|
||||
cmdEntity := cmd.New(projectServer.Server.OS)
|
||||
if len(project.SymlinkPath) != 0 {
|
||||
destDir := cmd.Join(project.SymlinkPath, project.LastPublishToken)
|
||||
afterDeployCommands = append(afterDeployCommands, cmdEntity.Symlink(destDir, project.Path))
|
||||
}
|
||||
|
||||
if project.Script.AfterDeploy.Content != "" {
|
||||
afterDeployScriptPath := cmd.Join(project.Path, scriptName)
|
||||
afterDeployCommands = append(afterDeployCommands, cmdEntity.Script(project.Script.AfterDeploy.Mode, afterDeployScriptPath))
|
||||
afterDeployCommands = append(afterDeployCommands, cmdEntity.Remove(afterDeployScriptPath))
|
||||
}
|
||||
|
||||
// no symlink and deploy script
|
||||
if len(afterDeployCommands) == 0 {
|
||||
ch <- syncMessage{
|
||||
serverName: projectServer.Server.Name,
|
||||
projectID: project.ID,
|
||||
state: model.ProjectSuccess,
|
||||
}
|
||||
return
|
||||
}
|
||||
completeAfterDeployCmd := strings.Join(afterDeployCommands, "&&")
|
||||
publishTraceModel.Type = model.AfterDeploy
|
||||
publishTraceModel.InsertTime = time.Now().Format("20060102150405")
|
||||
ext, _ = json.Marshal(struct {
|
||||
ServerID int64 `json:"serverId"`
|
||||
ServerName string `json:"serverName"`
|
||||
Script string `json:"script"`
|
||||
}{projectServer.ServerID, projectServer.Server.Name, scriptContent})
|
||||
publishTraceModel.Ext = string(ext)
|
||||
|
||||
client, err := projectServer.ToSSHConfig().Dial()
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
publishTraceModel.Detail = err.Error()
|
||||
publishTraceModel.State = model.Fail
|
||||
if _, err := publishTraceModel.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, project.ID, err)
|
||||
}
|
||||
ch <- syncMessage{
|
||||
serverName: projectServer.Server.Name,
|
||||
projectID: project.ID,
|
||||
detail: err.Error(),
|
||||
state: model.ProjectFail,
|
||||
}
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
session, sessionErr := client.NewSession()
|
||||
if sessionErr != nil {
|
||||
log.Error(sessionErr.Error())
|
||||
publishTraceModel.Detail = sessionErr.Error()
|
||||
publishTraceModel.State = model.Fail
|
||||
if _, err := publishTraceModel.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, project.ID, err)
|
||||
}
|
||||
ch <- syncMessage{
|
||||
serverName: projectServer.Server.Name,
|
||||
projectID: project.ID,
|
||||
detail: sessionErr.Error(),
|
||||
state: model.ProjectFail,
|
||||
}
|
||||
return
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
log.Trace(fmt.Sprintf("projectID: %d ssh exec: %s", project.ID, completeAfterDeployCmd))
|
||||
|
||||
output, err := session.CombinedOutput(completeAfterDeployCmd)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("projectID: %d ssh exec err: %s, output: %s", project.ID, err, output))
|
||||
publishTraceModel.Detail = fmt.Sprintf("err: %s\noutput: %s", err, output)
|
||||
publishTraceModel.State = model.Fail
|
||||
if _, err := publishTraceModel.AddRow(); err != nil {
|
||||
log.Errorf(projectLogFormat, project.ID, err)
|
||||
}
|
||||
ch <- syncMessage{
|
||||
serverName: projectServer.Server.Name,
|
||||
projectID: project.ID,
|
||||
detail: fmt.Sprintf("%s\noutput: %s", err.Error(), output),
|
||||
state: model.ProjectFail,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
publishTraceModel.Detail = string(output)
|
||||
publishTraceModel.State = model.Success
|
||||
if _, err := publishTraceModel.AddRow(); err != nil {
|
||||
log.Error("projectID: " + strconv.FormatInt(project.ID, 10) + " " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
ch <- syncMessage{
|
||||
serverName: projectServer.Server.Name,
|
||||
projectID: project.ID,
|
||||
state: model.ProjectSuccess,
|
||||
}
|
||||
}
|
||||
|
||||
for index, projectServer := range gsync.ProjectServers {
|
||||
if gsync.Project.DeployServerMode == "serial" {
|
||||
serverSync(projectServer, index+1)
|
||||
} else {
|
||||
go serverSync(projectServer, 0)
|
||||
}
|
||||
}
|
||||
|
||||
message := ""
|
||||
for i := 0; i < len(gsync.ProjectServers); i++ {
|
||||
msg := <-ch
|
||||
if msg.state == model.ProjectFail {
|
||||
message += msg.serverName + " error message: " + msg.detail
|
||||
}
|
||||
}
|
||||
close(ch)
|
||||
|
||||
if message != "" {
|
||||
return errors.New(message)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
// Copyright 2022 The Goploy Authors. All rights reserved.
|
||||
// Use of this source code is governed by a GPLv3-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package task
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"github.com/google/uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/zhenorzz/goploy/internal/model"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
var projectTick = time.Tick(time.Minute)
|
||||
|
||||
func startProjectTask() {
|
||||
atomic.AddInt32(&counter, 1)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-projectTick:
|
||||
projectTask()
|
||||
case <-stop:
|
||||
atomic.AddInt32(&counter, -1)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func projectTask() {
|
||||
date := time.Now().Format("2006-01-02 15:04:05")
|
||||
projectTasks, err := model.ProjectTask{}.GetNotRunListLTDate(date)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
log.Error("get project task list error, detail:" + err.Error())
|
||||
}
|
||||
for _, projectTask := range projectTasks {
|
||||
project, err := model.Project{ID: projectTask.ProjectID}.GetData()
|
||||
|
||||
if err != nil {
|
||||
log.Error("publish task has no project, detail:" + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if err := projectTask.SetRun(); err != nil {
|
||||
log.Error("publish task set run fail, detail:" + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
projectServers, err := model.ProjectServer{ProjectID: projectTask.ProjectID}.GetBindServerListByProjectID()
|
||||
|
||||
if err != nil {
|
||||
log.Error("publish task has no server, detail:" + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
userInfo, err := model.User{ID: 1}.GetData()
|
||||
if err != nil {
|
||||
log.Error("publish task has no user, detail:" + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
project.PublisherID = userInfo.ID
|
||||
project.PublisherName = userInfo.Name
|
||||
project.DeployState = model.ProjectDeploying
|
||||
project.LastPublishToken = uuid.New().String()
|
||||
err = project.Publish()
|
||||
if err != nil {
|
||||
log.Error("publish task change state error, detail:" + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
AddDeployTask(Gsync{
|
||||
UserInfo: userInfo,
|
||||
Project: project,
|
||||
ProjectServers: projectServers,
|
||||
CommitID: projectTask.CommitID,
|
||||
Branch: projectTask.Branch,
|
||||
})
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/zhenorzz/goploy/cmd/server/task/deploy"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
@ -16,9 +17,8 @@ var stop = make(chan struct{})
|
||||
|
||||
func Init() {
|
||||
startMonitorTask()
|
||||
startProjectTask()
|
||||
startServerMonitorTask()
|
||||
startDeployTask()
|
||||
deploy.Run(&counter, stop)
|
||||
}
|
||||
|
||||
func Shutdown(ctx context.Context) error {
|
||||
|
Loading…
Reference in New Issue
Block a user