support ssh deployemnt

This commit is contained in:
yoan
2024-08-22 20:24:00 +08:00
parent 1bfd477587
commit bcd8f8dea1
15 changed files with 1060 additions and 23 deletions

View File

@@ -16,6 +16,7 @@ const (
const (
targetAliyunOss = "aliyun-oss"
targetAliyunCdn = "aliyun-cdn"
targetSSH = "ssh"
)
type DeployerOption struct {
@@ -47,6 +48,8 @@ func Get(record *models.Record) (Deployer, error) {
return NewAliyun(option)
case targetAliyunCdn:
return NewAliyunCdn(option)
case targetSSH:
return NewSSH(option)
}
return nil, errors.New("not implemented")
}
@@ -54,5 +57,8 @@ func Get(record *models.Record) (Deployer, error) {
func getProduct(record *models.Record) string {
targetType := record.GetString("targetType")
rs := strings.Split(targetType, "-")
if len(rs) < 2 {
return ""
}
return rs[1]
}

125
internal/deployer/ssh.go Normal file
View File

@@ -0,0 +1,125 @@
package deployer
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
xpath "path"
"github.com/pkg/sftp"
sshPkg "golang.org/x/crypto/ssh"
)
type ssh struct {
option *DeployerOption
}
type sshAccess struct {
Host string `json:"host"`
Username string `json:"username"`
Password string `json:"password"`
Key string `json:"key"`
Port string `json:"port"`
Command string `json:"command"`
CertPath string `json:"certPath"`
KeyPath string `json:"keyPath"`
}
func NewSSH(option *DeployerOption) (Deployer, error) {
return &ssh{
option: option,
}, nil
}
func (s *ssh) Deploy(ctx context.Context) error {
access := &sshAccess{}
if err := json.Unmarshal([]byte(s.option.Access), access); err != nil {
return err
}
// 连接
client, err := s.getClient(access)
if err != nil {
return err
}
defer client.Close()
// 上传
session, err := client.NewSession()
if err != nil {
return fmt.Errorf("failed to create session: %w", err)
}
defer session.Close()
// 上传证书
if err := s.upload(client, s.option.Certificate.Certificate, access.CertPath); err != nil {
return fmt.Errorf("failed to upload certificate: %w", err)
}
// 上传私钥
if err := s.upload(client, s.option.Certificate.PrivateKey, access.KeyPath); err != nil {
return fmt.Errorf("failed to upload private key: %w", err)
}
// 执行命令
var stdoutBuf bytes.Buffer
session.Stdout = &stdoutBuf
var stderrBuf bytes.Buffer
session.Stderr = &stderrBuf
if err := session.Run(access.Command); err != nil {
return fmt.Errorf("failed to run command: %w, stdout: %s, stderr: %s", err, stdoutBuf.String(), stderrBuf.String())
}
return nil
}
func (s *ssh) upload(client *sshPkg.Client, content, path string) error {
sftpCli, err := sftp.NewClient(client)
if err != nil {
return fmt.Errorf("failed to create sftp client: %w", err)
}
defer sftpCli.Close()
if err := sftpCli.MkdirAll(xpath.Base(path)); err != nil {
return fmt.Errorf("failed to create remote directory: %w", err)
}
file, err := sftpCli.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
if err != nil {
return fmt.Errorf("failed to open remote file: %w", err)
}
defer file.Close()
_, err = file.Write([]byte(content))
if err != nil {
return fmt.Errorf("failed to write to remote file: %w", err)
}
return nil
}
func (s *ssh) getClient(access *sshAccess) (*sshPkg.Client, error) {
var authMethod sshPkg.AuthMethod
if access.Key != "" {
signer, err := sshPkg.ParsePrivateKey([]byte(access.Key))
if err != nil {
return nil, err
}
authMethod = sshPkg.PublicKeys(signer)
} else {
authMethod = sshPkg.Password(access.Password)
}
return sshPkg.Dial("tcp", fmt.Sprintf("%s:%s", access.Host, access.Port), &sshPkg.ClientConfig{
User: access.Username,
Auth: []sshPkg.AuthMethod{
authMethod,
},
HostKeyCallback: sshPkg.InsecureIgnoreHostKey(),
})
}

View File

@@ -21,7 +21,11 @@ const (
)
func deploy(ctx context.Context, record *models.Record) error {
defer func() {
if r := recover(); r != nil {
app.GetApp().Logger().Error("部署失败", "err", r)
}
}()
currRecord, err := app.GetApp().Dao().FindRecordById("domains", record.Id)
history := NewHistory(record)
defer history.commit()
@@ -86,7 +90,7 @@ func deploy(ctx context.Context, record *models.Record) error {
history.record(applyPhase, "保存证书成功", nil, true)
// ############3.部署证书
history.record(deployPhase, "开始部署", nil)
history.record(deployPhase, "开始部署", nil, false)
deployer, err := deployer.Get(currRecord)
if err != nil {
history.record(deployPhase, "获取deployer失败", err)

View File

@@ -34,8 +34,8 @@ func NewHistory(record *models.Record) *history {
func (a *history) record(phase Phase, msg string, err error, pass ...bool) {
a.Phase = phase
if len(pass) > 0 && pass[0] {
a.PhaseSuccess = true
if len(pass) > 0 {
a.PhaseSuccess = pass[0]
}
errMsg := ""