Scert/backend/internal/app/certificate_workflow.go

121 lines
4.3 KiB
Go

package app
import (
"context"
"errors"
"fmt"
"time"
"git.pcux.cn/xuans/Scert/backend/internal/certificate"
"git.pcux.cn/xuans/Scert/backend/internal/deployment"
"git.pcux.cn/xuans/Scert/backend/internal/domain"
)
type CertificateWorkflow struct {
Certificates certificateService
Domains domainDeploymentRepository
Servers serverLister
Deployment savedDeployer
}
type certificateService interface {
Issue(context.Context, string) (certificate.IssueResult, error)
Import(context.Context, certificate.ImportRequest) (certificate.IssueResult, error)
}
type serverLister interface {
ListServersByDomainID(context.Context, string) ([]deployment.Server, error)
}
type savedDeployer interface {
DeploySaved(context.Context, []deployment.Target, deployment.SavedFiles) (deployment.Report, error)
}
type domainDeploymentRepository interface {
GetByID(context.Context, string) (domain.Domain, error)
UpdateCertificateState(context.Context, string, string, *time.Time) error
}
type CertificateWorkflowResult struct {
Certificate certificate.IssueResult `json:"certificate"`
Deployment deployment.Report `json:"deployment"`
}
func (w *CertificateWorkflow) Issue(ctx context.Context, domainID string) (certificate.IssueResult, error) {
result, err := w.IssueWithReport(ctx, domainID)
return result.Certificate, err
}
func (w *CertificateWorkflow) IssueWithReport(ctx context.Context, domainID string) (CertificateWorkflowResult, error) {
if w == nil || w.Certificates == nil || w.Domains == nil || w.Servers == nil || w.Deployment == nil {
return CertificateWorkflowResult{}, errors.New("证书部署流程未初始化")
}
certificateResult, err := w.Certificates.Issue(ctx, domainID)
if err != nil {
return CertificateWorkflowResult{}, err
}
report, err := w.deploy(ctx, certificateResult)
if err != nil {
w.markDeploymentError(certificateResult)
}
return CertificateWorkflowResult{Certificate: certificateResult, Deployment: report}, err
}
func (w *CertificateWorkflow) IssueForHTTP(ctx context.Context, domainID string) (certificate.IssueResult, deployment.Report, error) {
result, err := w.IssueWithReport(ctx, domainID)
return result.Certificate, result.Deployment, err
}
func (w *CertificateWorkflow) Import(ctx context.Context, request certificate.ImportRequest) (CertificateWorkflowResult, error) {
if w == nil || w.Certificates == nil || w.Domains == nil || w.Servers == nil || w.Deployment == nil {
return CertificateWorkflowResult{}, errors.New("证书部署流程未初始化")
}
certificateResult, err := w.Certificates.Import(ctx, request)
if err != nil {
return CertificateWorkflowResult{}, err
}
report, err := w.deploy(ctx, certificateResult)
if err != nil {
w.markDeploymentError(certificateResult)
}
return CertificateWorkflowResult{Certificate: certificateResult, Deployment: report}, err
}
func (w *CertificateWorkflow) deploy(ctx context.Context, result certificate.IssueResult) (deployment.Report, error) {
domainItem, err := w.Domains.GetByID(ctx, result.DomainID)
if err != nil {
return deployment.Report{}, fmt.Errorf("查询域名配置失败:%w", err)
}
servers, err := w.Servers.ListServersByDomainID(ctx, result.DomainID)
if err != nil {
return deployment.Report{}, fmt.Errorf("查询域名部署服务器失败:%w", err)
}
targets := make([]deployment.Target, 0, len(servers))
for _, server := range servers {
resolved, resolveErr := deployment.ResolveServerPaths(server, domainItem.Name, string(domainItem.CertificateType))
if resolveErr != nil {
return deployment.Report{}, fmt.Errorf("解析服务器 %s 的证书路径失败:%w", server.Name, resolveErr)
}
targets = append(targets, deployment.Target{Server: resolved})
}
report, err := w.Deployment.DeploySaved(ctx, targets, deployment.SavedFiles{
CertificatePath: result.Saved.CertificatePath,
FullChainPath: result.Saved.FullChainPath,
PrivateKeyPath: result.Saved.PrivateKeyPath,
})
if err != nil {
return report, err
}
if report.Status != "success" {
return report, fmt.Errorf("证书部署未全部成功:成功 %d 台,失败 %d 台", report.Success, report.Failed)
}
return report, nil
}
func (w *CertificateWorkflow) markDeploymentError(result certificate.IssueResult) {
if w == nil || w.Domains == nil {
return
}
_ = w.Domains.UpdateCertificateState(context.Background(), result.DomainID, "error", &result.Saved.NotAfter)
}