管理端全部功能跑通
This commit is contained in:
@@ -343,6 +343,8 @@ func mapNodeRole(role nodeconfigs.NodeRole) (string, error) {
|
||||
return fluentBitRoleNode, nil
|
||||
case nodeconfigs.NodeRoleDNS:
|
||||
return fluentBitRoleDNS, nil
|
||||
case nodeconfigs.NodeRoleHTTPDNS:
|
||||
return fluentBitRoleDNS, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported fluent-bit role '%s'", role)
|
||||
}
|
||||
|
||||
230
EdgeAPI/internal/installers/installer_httpdns_node.go
Normal file
230
EdgeAPI/internal/installers/installer_httpdns_node.go
Normal file
@@ -0,0 +1,230 @@
|
||||
package installers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
)
|
||||
|
||||
type HTTPDNSNodeInstaller struct {
|
||||
BaseInstaller
|
||||
}
|
||||
|
||||
func (i *HTTPDNSNodeInstaller) Install(dir string, params interface{}, installStatus *models.NodeInstallStatus) error {
|
||||
if params == nil {
|
||||
return errors.New("'params' required for node installation")
|
||||
}
|
||||
nodeParams, ok := params.(*NodeParams)
|
||||
if !ok {
|
||||
return errors.New("'params' should be *NodeParams")
|
||||
}
|
||||
err := nodeParams.Validate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("params validation: %w", err)
|
||||
}
|
||||
|
||||
installRootDir, appDir := resolveHTTPDNSInstallPaths(dir)
|
||||
|
||||
_, err = i.client.Stat(installRootDir)
|
||||
if err != nil {
|
||||
err = i.client.MkdirAll(installRootDir)
|
||||
if err != nil {
|
||||
installStatus.ErrorCode = "CREATE_ROOT_DIRECTORY_FAILED"
|
||||
return fmt.Errorf("create directory '%s' failed: %w", installRootDir, err)
|
||||
}
|
||||
}
|
||||
|
||||
env, err := i.InstallHelper(installRootDir, nodeconfigs.NodeRoleHTTPDNS)
|
||||
if err != nil {
|
||||
installStatus.ErrorCode = "INSTALL_HELPER_FAILED"
|
||||
return err
|
||||
}
|
||||
|
||||
filePrefix := "edge-httpdns-" + env.OS + "-" + env.Arch
|
||||
zipFile, err := i.LookupLatestInstallerForTarget(filePrefix, env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(zipFile) == 0 {
|
||||
return errors.New("can not find installer file for " + env.OS + "/" + env.Arch + ", expected '" + filePrefix + "-v*.zip' or distro-specific '" + filePrefix + "-{ubuntu22.04|amzn2023}-v*.zip'")
|
||||
}
|
||||
|
||||
targetZip, err := i.copyZipToRemote(installRootDir, zipFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !nodeParams.IsUpgrading {
|
||||
_, stderr, testErr := i.client.Exec(env.HelperPath + " -cmd=test")
|
||||
if testErr != nil {
|
||||
return fmt.Errorf("test failed: %w", testErr)
|
||||
}
|
||||
if len(stderr) > 0 {
|
||||
return errors.New("test failed: " + stderr)
|
||||
}
|
||||
}
|
||||
|
||||
exePath := appDir + "/bin/edge-httpdns"
|
||||
if nodeParams.IsUpgrading {
|
||||
_, err = i.client.Stat(exePath)
|
||||
if err == nil {
|
||||
_, _, _ = i.client.Exec(exePath + " stop")
|
||||
removeErr := i.client.Remove(exePath)
|
||||
if removeErr != nil && removeErr != os.ErrNotExist {
|
||||
return fmt.Errorf("remove old file failed: %w", removeErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, stderr, err := i.client.Exec(env.HelperPath + " -cmd=unzip -zip=\"" + targetZip + "\" -target=\"" + installRootDir + "\"")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(stderr) > 0 {
|
||||
return errors.New("unzip installer failed: " + stderr)
|
||||
}
|
||||
|
||||
certFile := appDir + "/configs/tls/server.crt"
|
||||
keyFile := appDir + "/configs/tls/server.key"
|
||||
err = i.writeTLSCertificate(certFile, keyFile, nodeParams.TLSCertData, nodeParams.TLSKeyData)
|
||||
if err != nil {
|
||||
installStatus.ErrorCode = "WRITE_TLS_CERT_FAILED"
|
||||
return err
|
||||
}
|
||||
|
||||
configFile := appDir + "/configs/api_httpdns.yaml"
|
||||
if i.client.sudo {
|
||||
_, _, _ = i.client.Exec("chown " + i.client.User() + " " + filepath.Dir(configFile))
|
||||
}
|
||||
|
||||
configData := []byte(`rpc.endpoints: [ ${endpoints} ]
|
||||
nodeId: "${nodeId}"
|
||||
secret: "${nodeSecret}"
|
||||
|
||||
https.listenAddr: ":443"
|
||||
https.cert: "${certFile}"
|
||||
https.key: "${keyFile}"`)
|
||||
certFileClean := strings.ReplaceAll(certFile, "\\", "/")
|
||||
keyFileClean := strings.ReplaceAll(keyFile, "\\", "/")
|
||||
|
||||
configData = bytes.ReplaceAll(configData, []byte("${endpoints}"), []byte(nodeParams.QuoteEndpoints()))
|
||||
configData = bytes.ReplaceAll(configData, []byte("${nodeId}"), []byte(nodeParams.NodeId))
|
||||
configData = bytes.ReplaceAll(configData, []byte("${nodeSecret}"), []byte(nodeParams.Secret))
|
||||
configData = bytes.ReplaceAll(configData, []byte("${certFile}"), []byte(certFileClean))
|
||||
configData = bytes.ReplaceAll(configData, []byte("${keyFile}"), []byte(keyFileClean))
|
||||
|
||||
_, err = i.client.WriteFile(configFile, configData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write '%s': %w", configFile, err)
|
||||
}
|
||||
|
||||
err = i.SetupFluentBit(nodeconfigs.NodeRoleHTTPDNS)
|
||||
if err != nil {
|
||||
installStatus.ErrorCode = "SETUP_FLUENT_BIT_FAILED"
|
||||
return fmt.Errorf("setup fluent-bit failed: %w", err)
|
||||
}
|
||||
|
||||
startCmdPrefix := "cd " + shQuote(appDir+"/configs") + " && ../bin/edge-httpdns "
|
||||
|
||||
stdout, stderr, err := i.client.Exec(startCmdPrefix + "test")
|
||||
if err != nil {
|
||||
installStatus.ErrorCode = "TEST_FAILED"
|
||||
return fmt.Errorf("test edge-httpdns failed: %w, stdout: %s, stderr: %s", err, stdout, stderr)
|
||||
}
|
||||
if len(stderr) > 0 {
|
||||
if regexp.MustCompile(`(?i)rpc`).MatchString(stderr) || regexp.MustCompile(`(?i)rpc`).MatchString(stdout) {
|
||||
installStatus.ErrorCode = "RPC_TEST_FAILED"
|
||||
}
|
||||
return errors.New("test edge-httpdns failed, stdout: " + stdout + ", stderr: " + stderr)
|
||||
}
|
||||
|
||||
stdout, stderr, err = i.client.Exec(startCmdPrefix + "start")
|
||||
if err != nil {
|
||||
return fmt.Errorf("start edge-httpdns failed: %w, stdout: %s, stderr: %s", err, stdout, stderr)
|
||||
}
|
||||
if len(stderr) > 0 {
|
||||
return errors.New("start edge-httpdns failed, stdout: " + stdout + ", stderr: " + stderr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolveHTTPDNSInstallPaths(rawDir string) (installRootDir string, appDir string) {
|
||||
dir := strings.TrimSpace(rawDir)
|
||||
dir = strings.TrimRight(dir, "/")
|
||||
if len(dir) == 0 {
|
||||
return rawDir, rawDir + "/edge-httpdns"
|
||||
}
|
||||
|
||||
if strings.HasSuffix(dir, "/edge-httpdns") {
|
||||
root := strings.TrimSuffix(dir, "/edge-httpdns")
|
||||
if len(root) == 0 {
|
||||
root = "/"
|
||||
}
|
||||
return root, dir
|
||||
}
|
||||
|
||||
return dir, dir + "/edge-httpdns"
|
||||
}
|
||||
|
||||
func (i *HTTPDNSNodeInstaller) copyZipToRemote(dir string, zipFile string) (string, error) {
|
||||
targetZip := ""
|
||||
var firstCopyErr error
|
||||
zipName := filepath.Base(zipFile)
|
||||
for _, candidate := range []string{
|
||||
dir + "/" + zipName,
|
||||
i.client.UserHome() + "/" + zipName,
|
||||
"/tmp/" + zipName,
|
||||
} {
|
||||
err := i.client.Copy(zipFile, candidate, 0777)
|
||||
if err != nil {
|
||||
if firstCopyErr == nil {
|
||||
firstCopyErr = err
|
||||
}
|
||||
continue
|
||||
}
|
||||
targetZip = candidate
|
||||
firstCopyErr = nil
|
||||
break
|
||||
}
|
||||
if firstCopyErr != nil {
|
||||
return "", fmt.Errorf("upload httpdns file failed: %w", firstCopyErr)
|
||||
}
|
||||
return targetZip, nil
|
||||
}
|
||||
|
||||
func (i *HTTPDNSNodeInstaller) writeTLSCertificate(certFile string, keyFile string, certData []byte, keyData []byte) error {
|
||||
if len(certData) == 0 || len(keyData) == 0 {
|
||||
return errors.New("cluster tls certificate is empty")
|
||||
}
|
||||
certDir := filepath.Dir(certFile)
|
||||
_, stderr, err := i.client.Exec("mkdir -p " + shQuote(certDir))
|
||||
if err != nil {
|
||||
return fmt.Errorf("create tls directory failed: %w, stderr: %s", err, stderr)
|
||||
}
|
||||
if i.client.sudo {
|
||||
_, _, _ = i.client.Exec("chown " + i.client.User() + " " + shQuote(certDir))
|
||||
}
|
||||
|
||||
_, err = i.client.WriteFile(certFile, certData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write cert file failed: %w", err)
|
||||
}
|
||||
_, err = i.client.WriteFile(keyFile, keyData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write key file failed: %w", err)
|
||||
}
|
||||
|
||||
_, stderr, err = i.client.Exec("chmod 0644 " + shQuote(certFile) + " && chmod 0600 " + shQuote(keyFile))
|
||||
if err != nil {
|
||||
return fmt.Errorf("chmod tls files failed: %w, stderr: %s", err, stderr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -9,6 +9,8 @@ type NodeParams struct {
|
||||
Endpoints []string
|
||||
NodeId string
|
||||
Secret string
|
||||
TLSCertData []byte
|
||||
TLSKeyData []byte
|
||||
IsUpgrading bool // 是否为升级
|
||||
}
|
||||
|
||||
|
||||
280
EdgeAPI/internal/installers/queue_httpdns_node.go
Normal file
280
EdgeAPI/internal/installers/queue_httpdns_node.go
Normal file
@@ -0,0 +1,280 @@
|
||||
package installers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/utils/numberutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
var sharedHTTPDNSNodeQueue = NewHTTPDNSNodeQueue()
|
||||
|
||||
type HTTPDNSNodeQueue struct{}
|
||||
|
||||
func NewHTTPDNSNodeQueue() *HTTPDNSNodeQueue {
|
||||
return &HTTPDNSNodeQueue{}
|
||||
}
|
||||
|
||||
func SharedHTTPDNSNodeQueue() *HTTPDNSNodeQueue {
|
||||
return sharedHTTPDNSNodeQueue
|
||||
}
|
||||
|
||||
// InstallNodeProcess 鍦ㄧ嚎瀹夎 HTTPDNS 鑺傜偣娴佺▼鎺у埗
|
||||
func (q *HTTPDNSNodeQueue) InstallNodeProcess(nodeId int64, isUpgrading bool) error {
|
||||
installStatus := models.NewNodeInstallStatus()
|
||||
installStatus.IsRunning = true
|
||||
installStatus.IsFinished = false
|
||||
installStatus.IsOk = false
|
||||
installStatus.Error = ""
|
||||
installStatus.ErrorCode = ""
|
||||
installStatus.UpdatedAt = time.Now().Unix()
|
||||
|
||||
err := models.SharedHTTPDNSNodeDAO.UpdateNodeInstallStatus(nil, nodeId, installStatus)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ticker := utils.NewTicker(3 * time.Second)
|
||||
goman.New(func() {
|
||||
for ticker.Wait() {
|
||||
installStatus.UpdatedAt = time.Now().Unix()
|
||||
updateErr := models.SharedHTTPDNSNodeDAO.UpdateNodeInstallStatus(nil, nodeId, installStatus)
|
||||
if updateErr != nil {
|
||||
logs.Println("[HTTPDNS_INSTALL]" + updateErr.Error())
|
||||
}
|
||||
}
|
||||
})
|
||||
defer ticker.Stop()
|
||||
|
||||
err = q.InstallNode(nodeId, installStatus, isUpgrading)
|
||||
|
||||
installStatus.IsRunning = false
|
||||
installStatus.IsFinished = true
|
||||
if err != nil {
|
||||
installStatus.IsOk = false
|
||||
installStatus.Error = err.Error()
|
||||
} else {
|
||||
installStatus.IsOk = true
|
||||
}
|
||||
installStatus.UpdatedAt = time.Now().Unix()
|
||||
|
||||
updateErr := models.SharedHTTPDNSNodeDAO.UpdateNodeInstallStatus(nil, nodeId, installStatus)
|
||||
if updateErr != nil {
|
||||
return updateErr
|
||||
}
|
||||
|
||||
if installStatus.IsOk {
|
||||
return models.SharedHTTPDNSNodeDAO.UpdateNodeIsInstalled(nil, nodeId, true)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InstallNode 鍦ㄧ嚎瀹夎 HTTPDNS 鑺傜偣
|
||||
func (q *HTTPDNSNodeQueue) InstallNode(nodeId int64, installStatus *models.NodeInstallStatus, isUpgrading bool) error {
|
||||
node, err := models.SharedHTTPDNSNodeDAO.FindEnabledNode(nil, nodeId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if node == nil {
|
||||
return errors.New("can not find node, ID '" + numberutils.FormatInt64(nodeId) + "'")
|
||||
}
|
||||
cluster, err := models.SharedHTTPDNSClusterDAO.FindEnabledCluster(nil, int64(node.ClusterId))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cluster == nil {
|
||||
return errors.New("can not find cluster")
|
||||
}
|
||||
|
||||
sshHost, sshPort, grantId, err := q.parseSSHInfo(node)
|
||||
if err != nil {
|
||||
installStatus.ErrorCode = "EMPTY_SSH"
|
||||
return err
|
||||
}
|
||||
|
||||
grant, err := models.SharedNodeGrantDAO.FindEnabledNodeGrant(nil, grantId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if grant == nil {
|
||||
installStatus.ErrorCode = "EMPTY_GRANT"
|
||||
return errors.New("can not find user grant with id '" + numberutils.FormatInt64(grantId) + "'")
|
||||
}
|
||||
|
||||
apiNodes, err := models.SharedAPINodeDAO.FindAllEnabledAndOnAPINodes(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(apiNodes) == 0 {
|
||||
return errors.New("no available api nodes")
|
||||
}
|
||||
|
||||
apiEndpoints := make([]string, 0, 8)
|
||||
for _, apiNode := range apiNodes {
|
||||
addrConfigs, decodeErr := apiNode.DecodeAccessAddrs()
|
||||
if decodeErr != nil {
|
||||
return fmt.Errorf("decode api node access addresses failed: %w", decodeErr)
|
||||
}
|
||||
for _, addrConfig := range addrConfigs {
|
||||
apiEndpoints = append(apiEndpoints, addrConfig.FullAddresses()...)
|
||||
}
|
||||
}
|
||||
if len(apiEndpoints) == 0 {
|
||||
return errors.New("no available api endpoints")
|
||||
}
|
||||
|
||||
tlsCertData, tlsKeyData, err := q.resolveClusterTLSCertPair(cluster)
|
||||
if err != nil {
|
||||
installStatus.ErrorCode = "EMPTY_TLS_CERT"
|
||||
return err
|
||||
}
|
||||
|
||||
params := &NodeParams{
|
||||
Endpoints: apiEndpoints,
|
||||
NodeId: node.UniqueId,
|
||||
Secret: node.Secret,
|
||||
TLSCertData: tlsCertData,
|
||||
TLSKeyData: tlsKeyData,
|
||||
IsUpgrading: isUpgrading,
|
||||
}
|
||||
|
||||
installer := &HTTPDNSNodeInstaller{}
|
||||
err = installer.Login(&Credentials{
|
||||
Host: sshHost,
|
||||
Port: sshPort,
|
||||
Username: grant.Username,
|
||||
Password: grant.Password,
|
||||
PrivateKey: grant.PrivateKey,
|
||||
Passphrase: grant.Passphrase,
|
||||
Method: grant.Method,
|
||||
Sudo: grant.Su == 1,
|
||||
})
|
||||
if err != nil {
|
||||
installStatus.ErrorCode = "SSH_LOGIN_FAILED"
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = installer.Close()
|
||||
}()
|
||||
|
||||
installDir := node.InstallDir
|
||||
if len(installDir) == 0 {
|
||||
if cluster != nil && len(cluster.InstallDir) > 0 {
|
||||
installDir = cluster.InstallDir
|
||||
}
|
||||
if len(installDir) == 0 {
|
||||
installDir = installer.client.UserHome() + "/edge-httpdns"
|
||||
}
|
||||
}
|
||||
|
||||
return installer.Install(installDir, params, installStatus)
|
||||
}
|
||||
|
||||
func (q *HTTPDNSNodeQueue) resolveClusterTLSCertPair(cluster *models.HTTPDNSCluster) ([]byte, []byte, error) {
|
||||
if cluster == nil {
|
||||
return nil, nil, errors.New("cluster not found")
|
||||
}
|
||||
if len(cluster.TLSPolicy) == 0 {
|
||||
return nil, nil, errors.New("cluster tls policy is empty")
|
||||
}
|
||||
|
||||
tlsConfig := map[string]json.RawMessage{}
|
||||
if err := json.Unmarshal(cluster.TLSPolicy, &tlsConfig); err != nil {
|
||||
return nil, nil, fmt.Errorf("decode cluster tls policy failed: %w", err)
|
||||
}
|
||||
|
||||
sslPolicyData := tlsConfig["sslPolicy"]
|
||||
if len(sslPolicyData) == 0 {
|
||||
// Compatible with old data where TLSPolicy stores SSLPolicy directly.
|
||||
sslPolicyData = json.RawMessage(cluster.TLSPolicy)
|
||||
}
|
||||
sslPolicy := &sslconfigs.SSLPolicy{}
|
||||
if err := json.Unmarshal(sslPolicyData, sslPolicy); err != nil {
|
||||
return nil, nil, fmt.Errorf("decode ssl policy failed: %w", err)
|
||||
}
|
||||
|
||||
for _, cert := range sslPolicy.Certs {
|
||||
if cert == nil {
|
||||
continue
|
||||
}
|
||||
if len(cert.CertData) > 0 && len(cert.KeyData) > 0 {
|
||||
return cert.CertData, cert.KeyData, nil
|
||||
}
|
||||
}
|
||||
|
||||
for _, certRef := range sslPolicy.CertRefs {
|
||||
if certRef == nil || certRef.CertId <= 0 {
|
||||
continue
|
||||
}
|
||||
certConfig, err := models.SharedSSLCertDAO.ComposeCertConfig(nil, certRef.CertId, false, nil, nil)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("load ssl cert %d failed: %w", certRef.CertId, err)
|
||||
}
|
||||
if certConfig == nil {
|
||||
continue
|
||||
}
|
||||
if len(certConfig.CertData) > 0 && len(certConfig.KeyData) > 0 {
|
||||
return certConfig.CertData, certConfig.KeyData, nil
|
||||
}
|
||||
}
|
||||
|
||||
if sslPolicy.Id > 0 {
|
||||
policyConfig, err := models.SharedSSLPolicyDAO.ComposePolicyConfig(nil, sslPolicy.Id, false, nil, nil)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("load ssl policy %d failed: %w", sslPolicy.Id, err)
|
||||
}
|
||||
if policyConfig != nil {
|
||||
for _, cert := range policyConfig.Certs {
|
||||
if cert == nil {
|
||||
continue
|
||||
}
|
||||
if len(cert.CertData) > 0 && len(cert.KeyData) > 0 {
|
||||
return cert.CertData, cert.KeyData, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil, errors.New("cluster tls certificate is not configured")
|
||||
}
|
||||
|
||||
func (q *HTTPDNSNodeQueue) parseSSHInfo(node *models.HTTPDNSNode) (string, int, int64, error) {
|
||||
if node == nil {
|
||||
return "", 0, 0, errors.New("node should not be nil")
|
||||
}
|
||||
if len(node.InstallStatus) == 0 {
|
||||
return "", 0, 0, errors.New("ssh config should not be empty")
|
||||
}
|
||||
|
||||
statusMap := maps.Map{}
|
||||
err := json.Unmarshal(node.InstallStatus, &statusMap)
|
||||
if err != nil {
|
||||
return "", 0, 0, errors.New("invalid install status data")
|
||||
}
|
||||
sshMap := statusMap.GetMap("ssh")
|
||||
if sshMap == nil {
|
||||
return "", 0, 0, errors.New("ssh config should not be empty")
|
||||
}
|
||||
|
||||
host := sshMap.GetString("host")
|
||||
port := sshMap.GetInt("port")
|
||||
grantId := sshMap.GetInt64("grantId")
|
||||
if len(host) == 0 {
|
||||
return "", 0, 0, errors.New("ssh host should not be empty")
|
||||
}
|
||||
if port <= 0 {
|
||||
port = 22
|
||||
}
|
||||
if grantId <= 0 {
|
||||
return "", 0, 0, errors.New("grant id should not be empty")
|
||||
}
|
||||
return host, port, grantId, nil
|
||||
}
|
||||
Reference in New Issue
Block a user