200 lines
4.8 KiB
Go
200 lines
4.8 KiB
Go
package orchestrator
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os/exec"
|
|
"strings"
|
|
|
|
"app-deploy-platform/backend/internal/model"
|
|
)
|
|
|
|
type Target struct {
|
|
Host model.Host
|
|
Instance model.ServiceInstance
|
|
}
|
|
|
|
type Context struct {
|
|
Deployment model.Deployment
|
|
Release *model.Release
|
|
}
|
|
|
|
type BuildContext struct {
|
|
ServiceName string
|
|
ReleaseID string
|
|
Branch string
|
|
OnOutput func(string)
|
|
}
|
|
|
|
type BuildResult struct {
|
|
ServiceName string `json:"service_name"`
|
|
ReleaseID string `json:"release_id"`
|
|
Branch string `json:"branch"`
|
|
RepoURL string `json:"repo_url"`
|
|
RepoPath string `json:"repo_path"`
|
|
CommitSHA string `json:"commit_sha"`
|
|
BuildHost string `json:"build_host"`
|
|
COSKey string `json:"cos_key"`
|
|
ArtifactURL string `json:"artifact_url"`
|
|
SHA256 string `json:"sha256"`
|
|
}
|
|
|
|
type Executor interface {
|
|
Deploy(context.Context, Target, Context) (string, error)
|
|
Restart(context.Context, Target, Context) (string, error)
|
|
Rollback(context.Context, Target, Context) (string, error)
|
|
}
|
|
|
|
type BuildExecutor interface {
|
|
Build(context.Context, BuildContext) (BuildResult, string, error)
|
|
}
|
|
|
|
type DeployCommandExecutor struct {
|
|
operatorScript string
|
|
configPath string
|
|
workDir string
|
|
}
|
|
|
|
type ScriptBuildExecutor struct {
|
|
operatorScript string
|
|
configPath string
|
|
workDir string
|
|
}
|
|
|
|
func NewDeployCommandExecutor(operatorScript, configPath, workDir string) *DeployCommandExecutor {
|
|
return &DeployCommandExecutor{
|
|
operatorScript: operatorScript,
|
|
configPath: configPath,
|
|
workDir: workDir,
|
|
}
|
|
}
|
|
|
|
func NewBuildCommandExecutor(operatorScript, configPath, workDir string) *ScriptBuildExecutor {
|
|
return &ScriptBuildExecutor{
|
|
operatorScript: operatorScript,
|
|
configPath: configPath,
|
|
workDir: workDir,
|
|
}
|
|
}
|
|
|
|
func (e *DeployCommandExecutor) Deploy(ctx context.Context, target Target, deployment Context) (string, error) {
|
|
return e.run(ctx, model.OperationDeploy, target, deployment)
|
|
}
|
|
|
|
func (e *DeployCommandExecutor) Restart(ctx context.Context, target Target, deployment Context) (string, error) {
|
|
return e.run(ctx, model.OperationRestart, target, deployment)
|
|
}
|
|
|
|
func (e *DeployCommandExecutor) Rollback(ctx context.Context, target Target, deployment Context) (string, error) {
|
|
return e.run(ctx, model.OperationRollback, target, deployment)
|
|
}
|
|
|
|
func (e *DeployCommandExecutor) run(ctx context.Context, action string, target Target, deployment Context) (string, error) {
|
|
if e.operatorScript == "" {
|
|
return fmt.Sprintf("no %s script configured, simulated success for %s@%s", action, target.Instance.ServiceName, target.Host.PrivateIP), nil
|
|
}
|
|
|
|
args := []string{
|
|
e.operatorScript,
|
|
"--config", e.configPath,
|
|
"--action", action,
|
|
"--service", target.Instance.ServiceName,
|
|
"--instance-id", target.Host.InstanceID,
|
|
}
|
|
if deployment.Deployment.ReleaseID != "" {
|
|
args = append(args, "--release-id", deployment.Deployment.ReleaseID)
|
|
}
|
|
|
|
command := exec.CommandContext(ctx, "python3", args...)
|
|
if e.workDir != "" {
|
|
command.Dir = e.workDir
|
|
}
|
|
|
|
var output bytes.Buffer
|
|
command.Stdout = &output
|
|
command.Stderr = &output
|
|
|
|
if err := command.Run(); err != nil {
|
|
return output.String(), err
|
|
}
|
|
return output.String(), nil
|
|
}
|
|
|
|
func (e *ScriptBuildExecutor) Build(ctx context.Context, buildCtx BuildContext) (BuildResult, string, error) {
|
|
var result BuildResult
|
|
if e.operatorScript == "" {
|
|
return result, "no build script configured", fmt.Errorf("no build script configured")
|
|
}
|
|
|
|
args := []string{
|
|
e.operatorScript,
|
|
"--config", e.configPath,
|
|
"--service", buildCtx.ServiceName,
|
|
"--release-id", buildCtx.ReleaseID,
|
|
}
|
|
if buildCtx.Branch != "" {
|
|
args = append(args, "--branch", buildCtx.Branch)
|
|
}
|
|
|
|
command := exec.CommandContext(ctx, "python3", args...)
|
|
if e.workDir != "" {
|
|
command.Dir = e.workDir
|
|
}
|
|
|
|
stdout, err := command.StdoutPipe()
|
|
if err != nil {
|
|
return result, "", err
|
|
}
|
|
command.Stderr = command.Stdout
|
|
|
|
var output bytes.Buffer
|
|
if err := command.Start(); err != nil {
|
|
return result, "", err
|
|
}
|
|
|
|
reader := bufio.NewReader(stdout)
|
|
for {
|
|
chunk, readErr := reader.ReadString('\n')
|
|
if chunk != "" {
|
|
output.WriteString(chunk)
|
|
if buildCtx.OnOutput != nil {
|
|
buildCtx.OnOutput(chunk)
|
|
}
|
|
}
|
|
if readErr == nil {
|
|
continue
|
|
}
|
|
if readErr == io.EOF {
|
|
break
|
|
}
|
|
return result, output.String(), readErr
|
|
}
|
|
|
|
err = command.Wait()
|
|
raw := output.String()
|
|
|
|
for _, line := range strings.Split(raw, "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if !strings.HasPrefix(line, "BUILD_RESULT_JSON=") {
|
|
continue
|
|
}
|
|
payload := strings.TrimPrefix(line, "BUILD_RESULT_JSON=")
|
|
if unmarshalErr := json.Unmarshal([]byte(payload), &result); unmarshalErr != nil {
|
|
return result, raw, unmarshalErr
|
|
}
|
|
break
|
|
}
|
|
|
|
if err != nil {
|
|
return result, raw, err
|
|
}
|
|
if result.ReleaseID == "" {
|
|
return result, raw, fmt.Errorf("build script did not return build result")
|
|
}
|
|
return result, raw, nil
|
|
}
|