diff --git a/cmd/server/api/deploy/handler.go b/cmd/server/api/deploy/handler.go index ac8f04c..fa44d22 100644 --- a/cmd/server/api/deploy/handler.go +++ b/cmd/server/api/deploy/handler.go @@ -20,11 +20,14 @@ import ( "github.com/zhenorzz/goploy/cmd/server/task" "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/server" "github.com/zhenorzz/goploy/internal/server/response" + "github.com/zhenorzz/goploy/internal/transmitter" + "gopkg.in/yaml.v3" "io" "net/http" "net/url" @@ -628,23 +631,51 @@ func (Deploy) Rebuild(gp *server.Goploy) server.Response { ch := make(chan bool, len(projectServers)) for _, projectServer := range projectServers { go func(projectServer model.ProjectServer) { + var dockerScript docker.Script destDir := path.Join(project.SymlinkPath, project.LastPublishToken) cmdEntity := cmd.New(projectServer.Server.OS) - afterDeployCommands := []string{cmdEntity.Symlink(destDir, project.Path), cmdEntity.ChangeDirTime(destDir)} + scriptName := fmt.Sprintf("goploy-after-deploy-p%d-s%d.%s", project.ID, projectServer.ServerID, pkg.GetScriptExt(project.Script.AfterDeploy.Mode)) + if project.Script.AfterDeploy.Content != "" { - scriptName := fmt.Sprintf("goploy-after-deploy-p%d-s%d.%s", project.ID, projectServer.ServerID, pkg.GetScriptExt(project.Script.AfterDeploy.Mode)) scriptContent := project.ReplaceVars(project.Script.AfterDeploy.Content) scriptContent = projectServer.ReplaceVars(scriptContent) - err = os.WriteFile(path.Join(config.GetProjectPath(project.ID), scriptName), []byte(project.ReplaceVars(project.Script.AfterDeploy.Content)), 0755) - if err != nil { - log.Error("projectID:" + strconv.FormatInt(project.ID, 10) + " write file err: " + err.Error()) - ch <- false - return + + if project.Script.AfterDeploy.Mode == "yaml" { + if err := yaml.Unmarshal([]byte(scriptContent), &dockerScript); err != nil { + log.Error("projectID:" + strconv.FormatInt(project.ID, 10) + " unmarshal yaml script fail err: " + err.Error()) + ch <- false + return + } + + 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") + err = os.WriteFile(path.Join(config.GetProjectPath(project.ID), scriptName), []byte(scriptContent), 0755) + if err != nil { + log.Error("projectID:" + strconv.FormatInt(project.ID, 10) + " write file err: " + err.Error()) + ch <- false + return + } + } + } else { + err = os.WriteFile(path.Join(config.GetProjectPath(project.ID), scriptName), []byte(scriptContent), 0755) + if err != nil { + log.Error("projectID:" + strconv.FormatInt(project.ID, 10) + " write file err: " + err.Error()) + ch <- false + return + } } - afterDeployScriptPath := path.Join(project.Path, scriptName) - afterDeployCommands = append(afterDeployCommands, cmdEntity.Script(project.Script.AfterDeploy.Mode, afterDeployScriptPath)) - afterDeployCommands = append(afterDeployCommands, cmdEntity.Remove(afterDeployScriptPath)) } + + transmitterEntity := transmitter.New(project, projectServer) + if transmitterOutput, err := transmitterEntity.Exec(); err != nil { + log.Error("projectID:" + strconv.FormatInt(project.ID, 10) + " transmit exec err: " + err.Error() + ", output: " + transmitterOutput) + ch <- false + return + } + client, err := projectServer.ToSSHConfig().Dial() if err != nil { log.Error("projectID:" + strconv.FormatInt(project.ID, 10) + " dial err: " + err.Error()) @@ -652,12 +683,14 @@ func (Deploy) Rebuild(gp *server.Goploy) server.Response { return } defer client.Close() + session, err := client.NewSession() if err != nil { log.Error("projectID:" + strconv.FormatInt(project.ID, 10) + " new session err: " + err.Error()) ch <- false return } + defer session.Close() // check if the path is existed or not if output, err := session.CombinedOutput("cd " + destDir); err != nil { @@ -665,19 +698,55 @@ func (Deploy) Rebuild(gp *server.Goploy) server.Response { ch <- false return } - session, err = client.NewSession() - if err != nil { - log.Error("projectID:" + strconv.FormatInt(project.ID, 10) + " new session err: " + err.Error()) - ch <- false - return + + 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("projectID:" + strconv.FormatInt(project.ID, 10) + " setup docker err: " + err.Error()) + ch <- false + return + } + + for stepIndex, step := range dockerScript.Steps { + step.ScriptName = fmt.Sprintf("goploy-after-deploy-p%d-s%d-y%d", project.ID, projectServer.ServerID, stepIndex) + dockerOutput, dockerErr := dockerConfig.Run(step) + + scriptFullName := path.Join(config.GetProjectPath(project.ID), step.ScriptName) + _ = os.Remove(scriptFullName) + + if dockerErr != "" { + log.Error("projectID:" + strconv.FormatInt(project.ID, 10) + " run docker script err: " + err.Error() + ", detail: " + string(dockerOutput)) + ch <- false + return + } + } + } else { + afterDeployCommands := []string{cmdEntity.Symlink(destDir, project.Path), cmdEntity.ChangeDirTime(destDir)} + afterDeployScriptPath := path.Join(project.Path, scriptName) + afterDeployCommands = append(afterDeployCommands, cmdEntity.Script(project.Script.AfterDeploy.Mode, afterDeployScriptPath)) + afterDeployCommands = append(afterDeployCommands, cmdEntity.Remove(afterDeployScriptPath)) + + session, err = client.NewSession() + defer session.Close() + if err != nil { + log.Error("projectID:" + strconv.FormatInt(project.ID, 10) + " new session err: " + err.Error()) + ch <- false + return + } + + // redirect to project path + if output, err := session.CombinedOutput(strings.Join(afterDeployCommands, "&&")); err != nil { + log.Error("projectID:" + strconv.FormatInt(project.ID, 10) + " symlink err: " + err.Error() + ", detail: " + string(output)) + ch <- false + return + } } - // redirect to project path - if output, err := session.CombinedOutput(strings.Join(afterDeployCommands, "&&")); err != nil { - log.Error("projectID:" + strconv.FormatInt(project.ID, 10) + " symlink err: " + err.Error() + ", detail: " + string(output)) - ch <- false - return - } ch <- true }(projectServer) } diff --git a/cmd/server/api/project/handler.go b/cmd/server/api/project/handler.go index 4cdb18d..a12d794 100644 --- a/cmd/server/api/project/handler.go +++ b/cmd/server/api/project/handler.go @@ -597,7 +597,7 @@ func (Project) Edit(gp *server.Goploy) server.Response { func (Project) SetAutoDeploy(gp *server.Goploy) server.Response { type ReqData struct { ID int64 `json:"id" validate:"required,gt=0"` - AutoDeploy uint8 `json:"autoDeploy" validate:"required,gte=0"` + AutoDeploy uint8 `json:"autoDeploy" validate:"oneof=0 1"` } var reqData ReqData if err := gp.Decode(&reqData); err != nil { diff --git a/cmd/server/task/deploy.go b/cmd/server/task/deploy.go index bdb7d6f..a197a30 100644 --- a/cmd/server/task/deploy.go +++ b/cmd/server/task/deploy.go @@ -14,10 +14,12 @@ import ( "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" @@ -261,10 +263,6 @@ func (gsync *Gsync) afterPullScriptStage() error { } gsync.PublishTrace.Type = model.AfterPull - ext, _ := json.Marshal(struct { - Script string `json:"script"` - }{gsync.Project.Script.AfterPull.Content}) - gsync.PublishTrace.Ext = string(ext) return gsync.runLocalScript() } @@ -279,13 +277,45 @@ func (gsync *Gsync) serverStage() error { project := gsync.Project publishTraceModel := gsync.PublishTrace // 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.ReplaceVars(project.Script.AfterDeploy.Content) 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) - _ = os.WriteFile(path.Join(config.GetProjectPath(project.ID), scriptName), []byte(scriptContent), 0755) + + if project.Script.AfterDeploy.Mode == "yaml" { + if err := yaml.Unmarshal([]byte(scriptContent), &dockerScript); err != nil { + log.Error(fmt.Sprintf("projectID: %d unmarshal yaml script fail err: %s", project.ID, err)) + publishTraceModel.Detail = fmt.Sprintf("err: %s", err) + 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 { + 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) @@ -321,95 +351,162 @@ func (gsync *Gsync) serverStage() error { } } - var afterDeployCommands []string - cmdEntity := cmd.New(projectServer.Server.OS) - if len(project.SymlinkPath) != 0 { - destDir := path.Join(project.SymlinkPath, project.LastPublishToken) - afterDeployCommands = append(afterDeployCommands, cmdEntity.Symlink(destDir, project.Path)) - } - - if project.Script.AfterDeploy.Content != "" { - afterDeployScriptPath := path.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, + if project.Script.AfterDeploy.Mode == "yaml" && len(dockerScript.Steps) > 0 { + dockerConfig := docker.Config{ + ProjectID: project.ID, + ProjectPath: project.Path, + Server: projectServer.Server, } - return - } - completeAfterDeployCmd := strings.Join(afterDeployCommands, "&&") - publishTraceModel.Type = model.AfterDeploy - ext, _ = json.Marshal(struct { - ServerID int64 `json:"serverId"` - ServerName string `json:"serverName"` - Script string `json:"script"` - }{projectServer.ServerID, projectServer.Server.Name, completeAfterDeployCmd}) - 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 := 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 { + 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") + publishTraceModel.Type = model.AfterDeploy + 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 != "" { + 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 := path.Join(project.SymlinkPath, project.LastPublishToken) + afterDeployCommands = append(afterDeployCommands, cmdEntity.Symlink(destDir, project.Path)) + } + + if project.Script.AfterDeploy.Content != "" { + afterDeployScriptPath := path.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 + 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.Errorf(projectLogFormat, project.ID, err) + log.Error("projectID: " + strconv.FormatInt(project.ID, 10) + " " + err.Error()) } - 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, @@ -446,11 +543,6 @@ func (gsync *Gsync) deployFinishScriptStage() error { } gsync.PublishTrace.Type = model.DeployFinish - ext, _ := json.Marshal(struct { - Script string `json:"script"` - }{gsync.Project.Script.DeployFinish.Content}) - gsync.PublishTrace.Ext = string(ext) - return gsync.runLocalScript() } @@ -467,7 +559,7 @@ func (gsync *Gsync) runLocalScript() error { }) mode = project.Script.AfterPull.Mode content = project.Script.AfterPull.Content - scriptName = "goploy-after-pull." + pkg.GetScriptExt(project.Script.AfterPull.Mode) + scriptName = "goploy-after-pull" case model.DeployFinish: ws.Send(ws.Data{ @@ -476,7 +568,7 @@ func (gsync *Gsync) runLocalScript() error { }) mode = project.Script.DeployFinish.Mode content = project.Script.DeployFinish.Content - scriptName = "goploy-deploy-finish." + pkg.GetScriptExt(project.Script.DeployFinish.Mode) + scriptName = "goploy-deploy-finish" default: return errors.New("not support stage") @@ -486,39 +578,109 @@ func (gsync *Gsync) runLocalScript() error { commitInfo := gsync.CommitInfo srcPath := config.GetProjectPath(project.ID) - scriptFullName := path.Join(srcPath, scriptName) scriptMode := "bash" if mode != "" { scriptMode = mode } scriptText := project.ReplaceVars(commitInfo.ReplaceVars(content)) - _ = os.WriteFile(scriptFullName, []byte(scriptText), 0755) - var commandOptions []string - if scriptMode == "cmd" { - commandOptions = append(commandOptions, "/C") - scriptFullName, _ = filepath.Abs(scriptFullName) - } - commandOptions = append(commandOptions, scriptFullName) - 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) + // run yaml script by docker + if mode == "yaml" { + var dockerScript docker.Script + err := yaml.Unmarshal([]byte(scriptText), &dockerScript) + if err != nil { + return errors.New("unmarshal yaml script fail") + } + + 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 { + 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 != "" { + gsync.PublishTrace.Detail = dockerErr + 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) + } + } } - 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) + 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 } + + return nil } // commit id diff --git a/go.mod b/go.mod index ec76fe3..115f4be 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,13 @@ go 1.18 require ( github.com/Masterminds/squirrel v1.4.0 + github.com/docker/docker v24.0.7+incompatible github.com/go-ldap/ldap/v3 v3.4.1 github.com/go-playground/locales v0.13.0 github.com/go-playground/universal-translator v0.17.0 github.com/go-sql-driver/mysql v1.5.0 github.com/golang-jwt/jwt v3.2.2+incompatible - github.com/google/uuid v1.1.1 + github.com/google/uuid v1.3.0 github.com/gorilla/schema v1.2.0 github.com/gorilla/websocket v1.4.2 github.com/hashicorp/go-version v1.3.0 @@ -19,22 +20,43 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/vearutop/statigz v1.1.8 github.com/wenlng/go-captcha v1.2.5 - golang.org/x/crypto v0.1.0 + golang.org/x/crypto v0.14.0 gopkg.in/go-playground/validator.v9 v9.31.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/containerd/containerd v1.7.9 // 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/go-asn1-ber/asn1-ber v1.5.3 // indirect + github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/klauspost/compress v1.17.3 // indirect github.com/kr/fs v0.1.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/leodido/go-urn v1.2.0 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/sequential v0.5.0 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect + github.com/opencontainers/runc v1.1.10 // indirect github.com/pkg/errors v0.9.1 // indirect golang.org/x/image v0.10.0 // indirect - golang.org/x/sys v0.5.0 // indirect + golang.org/x/mod v0.11.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/time v0.4.0 // indirect + golang.org/x/tools v0.10.0 // indirect gopkg.in/go-playground/assert.v1 v1.2.1 // indirect + gotest.tools/v3 v3.5.1 // indirect ) diff --git a/go.sum b/go.sum index 3048f30..0cc65c6 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,30 @@ +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e h1:ZU22z/2YRFLyf/P4ZwUYSdNCWsMEI0VeyrFoI2rAhJQ= github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/Masterminds/squirrel v1.4.0 h1:he5i/EXixZxrBUWcxzDYMiju9WZ3ld/l7QBNuo/eN3w= github.com/Masterminds/squirrel v1.4.0/go.mod h1:yaPeOnPG5ZRwL9oKdTsO/prlkPbXWZlRVMQ/gGlzIuA= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/bool64/dev v0.2.9 h1:efyGf5pgx4CYWQpCzPEX8a1PgewaCGaEexXa+IYHT/8= +github.com/containerd/containerd v1.7.9 h1:KOhK01szQbM80YfW1H6RZKh85PHGqY/9OcEZ35Je8sc= +github.com/containerd/containerd v1.7.9/go.mod h1:0/W44LWEYfSHoxBtsHIiNU/duEkgpMokemafHVCpq9Y= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/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.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= +github.com/docker/docker v24.0.7+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/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-asn1-ber/asn1-ber v1.5.3 h1:u7utq56RUFiynqUzgVMFDymapcOtQ/MZkh3H4QYkxag= github.com/go-asn1-ber/asn1-ber v1.5.3/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= @@ -19,12 +36,15 @@ github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD87 github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc= github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= @@ -38,6 +58,10 @@ github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04 github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/jlaffaye/ftp v0.0.0-20220301181425-a81b090061fa h1:Cv45cONl1gg0QBv5rvDBrZycQtJtEJlEloN4IIeVhxg= github.com/jlaffaye/ftp v0.0.0-20220301181425-a81b090061fa/go.mod h1:oZaomI+9/et52UBjvNU9LCIqmgt816+7ljXCx0EIPzo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.3 h1:qkRjuerhUU1EmXLYGkSH6EZL+vPSxIrYjLNAK4slzwA= +github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= @@ -46,6 +70,20 @@ github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhR github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8= +github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= +github.com/opencontainers/runc v1.1.10 h1:EaL5WeO9lv9wmS6SASjszOeQdSctvpbu0DdBQBizE40= +github.com/opencontainers/runc v1.1.10/go.mod h1:+/R6+KmDlh+hOO8NkjmgkG9Qzvypzk0yXxAPYYR65+M= github.com/pelletier/go-toml/v2 v2.0.0-beta.4 h1:GCs8ebsDtEH3RiO78+BvhHqj65d/I6tjESitJZc07Rc= github.com/pelletier/go-toml/v2 v2.0.0-beta.4/go.mod h1:ke6xncR3W76Ba8xnVxkrZG0js6Rd2BsQEAYrfgJ6eQA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -60,47 +98,64 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1-0.20210427113832-6241f9ab9942 h1:t0lM6y/M5IiUZyvbBTcngso8SZEZICH7is9B6g/obVU= github.com/stretchr/testify v1.7.1-0.20210427113832-6241f9ab9942/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/vearutop/statigz v1.1.8 h1:IJgQHx6EomuYOYd2TzFt3haP+BIzV471zn7aepRiLHA= github.com/vearutop/statigz v1.1.8/go.mod h1:pfzrpvgLRnFeSVZd9iUYrpYDLqbV+RgeCfizr3ZFf44= github.com/wenlng/go-captcha v1.2.5 h1:zA0/fovEl9oAhSg+KwHBwmq99GeeAXknWx6wYKjhjTg= github.com/wenlng/go-captcha v1.2.5/go.mod h1:QgPgpEURSa37gF3GtojNoNRwbMwuatSBx5NXrzASOb0= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/image v0.10.0 h1:gXjUUtwtx5yOE0VKWq1CH4IJAClq4UGgUA3i+rpON9M= golang.org/x/image v0.10.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -108,16 +163,29 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.4.0 h1:Z81tqI5ddIoXDPvVQ7/7CC9TnLM7ubaFG2qXYd5BbYY= +golang.org/x/time v0.4.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= +golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v9 v9.31.0 h1:bmXmP2RSNtFES+bn4uYuHT7iJFJv7Vj+an+ZQdDaD1M= gopkg.in/go-playground/validator.v9 v9.31.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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= diff --git a/internal/model/project.go b/internal/model/project.go index 5237643..6c67c62 100644 --- a/internal/model/project.go +++ b/internal/model/project.go @@ -22,8 +22,9 @@ type ProjectScript struct { Content string `json:"content"` } `json:"afterPull"` AfterDeploy struct { - Mode string `json:"mode"` - Content string `json:"content"` + Mode string `json:"mode"` + Content string `json:"content"` + ScriptNames []string `json:"scriptNames"` } `json:"afterDeploy"` DeployFinish struct { Mode string `json:"mode"` @@ -407,7 +408,8 @@ func (p Project) GetDeployList() (Projects, error) { project.environment, project.branch, project.symlink_path, - project.review, + project.review, + project.script, project.last_publish_token, project.auto_deploy, project.deploy_state, @@ -436,7 +438,7 @@ func (p Project) GetDeployList() (Projects, error) { projects := Projects{} for rows.Next() { var project Project - + var script []byte if err := rows.Scan( &project.ID, &project.Name, @@ -451,12 +453,18 @@ func (p Project) GetDeployList() (Projects, error) { &project.Branch, &project.SymlinkPath, &project.Review, + &script, &project.LastPublishToken, &project.AutoDeploy, &project.DeployState, &project.UpdateTime); err != nil { return projects, err } + + if err = json.Unmarshal(script, &project.Script); err != nil { + return nil, err + } + projects = append(projects, project) } diff --git a/internal/pipeline/docker/docker.go b/internal/pipeline/docker/docker.go new file mode 100644 index 0000000..1aaa9df --- /dev/null +++ b/internal/pipeline/docker/docker.go @@ -0,0 +1,212 @@ +package docker + +import ( + "bytes" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/mount" + "github.com/docker/docker/api/types/registry" + "github.com/docker/docker/client" + "github.com/docker/docker/pkg/archive" + "github.com/docker/docker/pkg/stdcopy" + "github.com/google/uuid" + "github.com/zhenorzz/goploy/config" + "github.com/zhenorzz/goploy/internal/model" + "path" + "path/filepath" +) + +type Script struct { + Steps []Step `yaml:"steps"` +} + +type Step struct { + Name string `yaml:"name"` + Commands []string `yaml:"commands"` + Image string `yaml:"image"` + ImageOptions struct { + Registry string `yaml:"registry"` + Username string `yaml:"username"` + Password string `yaml:"password"` + Dockerfile string `yaml:"dockerfile"` + } `yaml:"imageOptions"` + ScriptName string + ContainerName string +} + +type Config struct { + ProjectID int64 + ProjectPath string + Server model.Server + Client client.APIClient +} + +func GetDockerProjectPath(projectID int64) string { + return fmt.Sprintf("/data/www/repository/project_%d", projectID) +} + +func GetDockerProjectScriptPath(projectID int64, scriptName string) string { + return path.Join(GetDockerProjectPath(projectID), scriptName) +} + +func (c *Config) Setup() (err error) { + if c.Server.IP != "" { + c.Client, err = client.NewClientWithOpts(client.WithHost(fmt.Sprintf("tcp://%s:2375", c.Server.IP)), client.WithAPIVersionNegotiation()) + } else { + c.Client, err = client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + } + if err != nil { + return fmt.Errorf("connect docker err: %s", err) + } + + return nil +} + +func (c *Config) Run(step Step) (outStr string, errStr string) { + defer func() { + c.Destroy(step) + }() + + step.ContainerName = uuid.New().String() + ctx := context.Background() + pullOptions := types.ImagePullOptions{} + + if step.ImageOptions.Registry != "" { + step.Image = fmt.Sprintf("%s%s", step.ImageOptions.Registry, step.Image) + } + + // build image by dockerfile + if step.ImageOptions.Dockerfile != "" { + localProjectPath, err := filepath.Abs(config.GetProjectPath(c.ProjectID)) + if err != nil { + errStr = fmt.Sprintf("get local repository abs path err: %s", err) + return + } + + tar, err := archive.TarWithOptions(filepath.Join(localProjectPath, step.ImageOptions.Dockerfile), &archive.TarOptions{}) + + _, err = c.Client.ImageBuild(ctx, tar, types.ImageBuildOptions{ + Tags: []string{step.Image}, + Dockerfile: "Dockerfile", + Remove: true, + ForceRemove: true, + }) + if err != nil { + errStr = fmt.Sprintf("build image err: %s", err) + return + } + } else { + // pull image from private registry + if step.ImageOptions.Username != "" && step.ImageOptions.Password != "" { + authConfig := registry.AuthConfig{ + Username: step.ImageOptions.Username, + Password: step.ImageOptions.Password, + } + authConfigBytes, _ := json.Marshal(authConfig) + authConfigEncoded := base64.URLEncoding.EncodeToString(authConfigBytes) + pullOptions.RegistryAuth = authConfigEncoded + } + + _, err := c.Client.ImagePull(ctx, step.Image, pullOptions) + if err != nil { + errStr = fmt.Sprintf("pull docker image err: %s", err) + return + } + } + + dockerProjectPath := GetDockerProjectPath(c.ProjectID) + + hostConfig := &container.HostConfig{ + Mounts: []mount.Mount{ + { + Type: mount.TypeBind, + Source: c.ProjectPath, + Target: dockerProjectPath, + }, + }, + } + + _, err := c.Client.ContainerCreate(ctx, &container.Config{ + Image: step.Image, + Cmd: []string{GetDockerProjectScriptPath(c.ProjectID, step.ScriptName)}, + Entrypoint: []string{"/bin/sh"}, + WorkingDir: dockerProjectPath, + AttachStdin: false, + AttachStdout: true, + AttachStderr: true, + }, hostConfig, nil, nil, step.ContainerName) + + // if err is image does not exist, re-pull image and re-create container + if client.IsErrNotFound(err) && step.ImageOptions.Dockerfile == "" { + _, err = c.Client.ImagePull(ctx, step.Image, pullOptions) + if err != nil { + errStr = fmt.Sprintf("pull docker image twice err: %s", err) + return + } + + _, err = c.Client.ContainerCreate(ctx, &container.Config{ + Image: step.Image, + Cmd: []string{GetDockerProjectScriptPath(c.ProjectID, step.ScriptName)}, + Entrypoint: []string{"/bin/sh"}, + WorkingDir: dockerProjectPath, + AttachStdin: false, + AttachStdout: true, + AttachStderr: true, + }, hostConfig, nil, nil, step.ContainerName) + } + + if err != nil { + errStr = fmt.Sprintf("create docker container err: %s", err) + return + } + + if err := c.Client.ContainerStart(ctx, step.ContainerName, types.ContainerStartOptions{}); err != nil { + errStr = fmt.Sprintf("start docker container err: %s", err) + return + } + + statusCh, errCh := c.Client.ContainerWait(ctx, step.ContainerName, container.WaitConditionNotRunning) + select { + case err := <-errCh: + if err != nil { + errStr = fmt.Sprintf("wait docker container err: %s", err) + break + } + case <-statusCh: + } + + if errStr != "" { + return + } + + out, err := c.Client.ContainerLogs(ctx, step.ContainerName, types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true}) + if err != nil { + errStr = fmt.Sprintf("logs docker container err: %s", err) + return + } + + var dockerOutbuf, dockerErrbuf bytes.Buffer + stdcopy.StdCopy(&dockerOutbuf, &dockerErrbuf, out) + defer out.Close() + + if dockerErrbuf.Len() > 0 { + errStr = fmt.Sprintf("run docker script err: %s", dockerErrbuf.String()) + return + } + + return dockerOutbuf.String(), errStr +} + +func (c *Config) Destroy(step Step) { + ctx := context.Background() + _ = c.Client.ContainerKill(ctx, step.ContainerName, "9") + _ = c.Client.ContainerRemove(ctx, step.ContainerName, types.ContainerRemoveOptions{ + RemoveVolumes: true, + RemoveLinks: false, + Force: true, + }) +} diff --git a/internal/transmitter/custom.go b/internal/transmitter/custom.go index 45a42cf..5e98bf2 100644 --- a/internal/transmitter/custom.go +++ b/internal/transmitter/custom.go @@ -24,6 +24,9 @@ func (ct customTransmitter) String() string { scriptVars := map[string]string{ "${AFTER_DEPLOY_FILENAME}": fmt.Sprintf("goploy-after-deploy-p%d-s%d.%s", project.ID, server.ServerID, pkg.GetScriptExt(project.Script.AfterDeploy.Mode)), } + for index, scriptName := range project.Script.AfterDeploy.ScriptNames { + scriptVars[fmt.Sprintf("${AFTER_DEPLOY_FILENAME_YAML_%d}", index)] = scriptName + } for key, value := range scriptVars { script = strings.Replace(script, key, value, -1) } diff --git a/internal/transmitter/rsync.go b/internal/transmitter/rsync.go index b4d7e44..c4ba86f 100644 --- a/internal/transmitter/rsync.go +++ b/internal/transmitter/rsync.go @@ -5,7 +5,6 @@ package transmitter import ( - "fmt" "github.com/zhenorzz/goploy/config" "github.com/zhenorzz/goploy/internal/model" "github.com/zhenorzz/goploy/internal/pkg" @@ -36,10 +35,12 @@ func (rt rsyncTransmitter) args() []string { } rsyncOption, _ := pkg.ParseCommandLine(projectServer.ReplaceVars(project.ReplaceVars(project.TransferOption))) - rsyncOption = append([]string{ - "--include", - fmt.Sprintf("goploy-after-deploy-p%d-s%d.%s", project.ID, projectServer.ServerID, pkg.GetScriptExt(project.Script.AfterDeploy.Mode)), - }, rsyncOption...) + var includes []string + for _, scriptName := range project.Script.AfterDeploy.ScriptNames { + includes = append(includes, "--include", scriptName) + } + + rsyncOption = append(includes, rsyncOption...) rsyncOption = append(rsyncOption, "-e", projectServer.ToSSHOption()) if projectServer.Server.OS == model.ServerOSLinux { diff --git a/internal/transmitter/sftp.go b/internal/transmitter/sftp.go index 3a602b8..36a71f1 100644 --- a/internal/transmitter/sftp.go +++ b/internal/transmitter/sftp.go @@ -5,7 +5,6 @@ package transmitter import ( - "fmt" "github.com/pkg/sftp" "github.com/zhenorzz/goploy/config" "github.com/zhenorzz/goploy/internal/model" @@ -97,7 +96,7 @@ func (st sftpTransmitter) Exec() (string, error) { } nextItem = "" } - includes = append(includes, fmt.Sprintf("goploy-after-deploy-p%d-s%d.%s", project.ID, st.ProjectServer.ServerID, pkg.GetScriptExt(project.Script.AfterDeploy.Mode))) + includes = append(includes, project.Script.AfterDeploy.ScriptNames...) srcPath := config.GetProjectPath(project.ID) + "/" destPath := project.Path diff --git a/web/src/api/deploy.ts b/web/src/api/deploy.ts index a53bdac..99ccf09 100644 --- a/web/src/api/deploy.ts +++ b/web/src/api/deploy.ts @@ -45,6 +45,7 @@ export interface PublishTraceExt { diff: string script: string command: string + step: string } export class DeployList extends Request { diff --git a/web/src/const/const.ts b/web/src/const/const.ts index f71eb15..5777a6e 100644 --- a/web/src/const/const.ts +++ b/web/src/const/const.ts @@ -6,6 +6,7 @@ const scriptLang = { { label: 'python', value: 'python', lang: 'python' }, { label: 'php', value: 'php', lang: 'php' }, { label: 'bat', value: 'cmd', lang: 'batchfile' }, + { label: 'yaml', value: 'yaml', lang: 'yaml' }, ], getScriptLang: function (mode = '') { if (mode !== '') { diff --git a/web/src/views/deploy/TheDetailDialog.vue b/web/src/views/deploy/TheDetailDialog.vue index 87d4f0a..0813082 100644 --- a/web/src/views/deploy/TheDetailDialog.vue +++ b/web/src/views/deploy/TheDetailDialog.vue @@ -245,7 +245,7 @@ {{ projectRow.repoType }} @@ -253,51 +253,51 @@ Time: - {{ localTraceList[PublishTraceType.Pull].updateTime }} + {{ localTraceList[PublishTraceType.Pull][0].updateTime }} -