mirror of
https://github.com/usual2970/certimate.git
synced 2025-06-08 21:49:52 +00:00
feat: enhance certificate model
This commit is contained in:
parent
5f5c835533
commit
a41ee9c3ca
@ -1,6 +1,7 @@
|
|||||||
package applicant
|
package applicant
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
@ -110,14 +111,11 @@ func registerAcmeUser(client *lego.Client, sslProviderConfig *acmeSSLProviderCon
|
|||||||
Kid: sslProviderConfig.Config.GoogleTrustServices.EabKid,
|
Kid: sslProviderConfig.Config.GoogleTrustServices.EabKid,
|
||||||
HmacEncoded: sslProviderConfig.Config.GoogleTrustServices.EabHmacKey,
|
HmacEncoded: sslProviderConfig.Config.GoogleTrustServices.EabHmacKey,
|
||||||
})
|
})
|
||||||
|
|
||||||
case sslProviderLetsEncrypt, sslProviderLetsEncryptStaging:
|
case sslProviderLetsEncrypt, sslProviderLetsEncryptStaging:
|
||||||
reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||||
|
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("unsupported ssl provider: %s", sslProviderConfig.Provider)
|
err = fmt.Errorf("unsupported ssl provider: %s", sslProviderConfig.Provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -129,7 +127,12 @@ func registerAcmeUser(client *lego.Client, sslProviderConfig *acmeSSLProviderCon
|
|||||||
return resp.Resource, nil
|
return resp.Resource, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := repo.Save(sslProviderConfig.Provider, user.GetEmail(), user.getPrivateKeyPEM(), reg); err != nil {
|
if _, err := repo.Save(context.Background(), &domain.AcmeAccount{
|
||||||
|
CA: sslProviderConfig.Provider,
|
||||||
|
Email: user.GetEmail(),
|
||||||
|
Key: user.getPrivateKeyPEM(),
|
||||||
|
Resource: reg,
|
||||||
|
}); err != nil {
|
||||||
return nil, fmt.Errorf("failed to save registration: %w", err)
|
return nil, fmt.Errorf("failed to save registration: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ type ApplyCertResult struct {
|
|||||||
CertificateFullChain string
|
CertificateFullChain string
|
||||||
IssuerCertificate string
|
IssuerCertificate string
|
||||||
PrivateKey string
|
PrivateKey string
|
||||||
|
ACMEAccountUrl string
|
||||||
ACMECertUrl string
|
ACMECertUrl string
|
||||||
ACMECertStableUrl string
|
ACMECertStableUrl string
|
||||||
CSR string
|
CSR string
|
||||||
@ -46,8 +47,7 @@ type applicantOptions struct {
|
|||||||
DnsPropagationTimeout int32
|
DnsPropagationTimeout int32
|
||||||
DnsTTL int32
|
DnsTTL int32
|
||||||
DisableFollowCNAME bool
|
DisableFollowCNAME bool
|
||||||
DisableARI bool
|
ReplacedARIAccount string
|
||||||
SkipBeforeExpiryDays int32
|
|
||||||
ReplacedARICertId string
|
ReplacedARICertId string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,8 +67,6 @@ func NewWithApplyNode(node *domain.WorkflowNode) (Applicant, error) {
|
|||||||
DnsPropagationTimeout: nodeConfig.DnsPropagationTimeout,
|
DnsPropagationTimeout: nodeConfig.DnsPropagationTimeout,
|
||||||
DnsTTL: nodeConfig.DnsTTL,
|
DnsTTL: nodeConfig.DnsTTL,
|
||||||
DisableFollowCNAME: nodeConfig.DisableFollowCNAME,
|
DisableFollowCNAME: nodeConfig.DisableFollowCNAME,
|
||||||
DisableARI: nodeConfig.DisableARI,
|
|
||||||
SkipBeforeExpiryDays: nodeConfig.SkipBeforeExpiryDays,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
accessRepo := repository.NewAccessRepository()
|
accessRepo := repository.NewAccessRepository()
|
||||||
@ -95,6 +93,7 @@ func NewWithApplyNode(node *domain.WorkflowNode) (Applicant, error) {
|
|||||||
lastCertX509, _ := certcrypto.ParsePEMCertificate([]byte(lastCertificate.Certificate))
|
lastCertX509, _ := certcrypto.ParsePEMCertificate([]byte(lastCertificate.Certificate))
|
||||||
if lastCertX509 != nil {
|
if lastCertX509 != nil {
|
||||||
replacedARICertId, _ := certificate.MakeARICertID(lastCertX509)
|
replacedARICertId, _ := certificate.MakeARICertID(lastCertX509)
|
||||||
|
options.ReplacedARIAccount = lastCertificate.ACMEAccountUrl
|
||||||
options.ReplacedARICertId = replacedARICertId
|
options.ReplacedARICertId = replacedARICertId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,7 +140,7 @@ func apply(challengeProvider challenge.Provider, options *applicantOptions) (*Ap
|
|||||||
// Create an ACME client config
|
// Create an ACME client config
|
||||||
config := lego.NewConfig(acmeUser)
|
config := lego.NewConfig(acmeUser)
|
||||||
config.CADirURL = sslProviderUrls[sslProviderConfig.Provider]
|
config.CADirURL = sslProviderUrls[sslProviderConfig.Provider]
|
||||||
config.Certificate.KeyType = parseKeyAlgorithm(options.KeyAlgorithm)
|
config.Certificate.KeyType = parseKeyAlgorithm(domain.CertificateKeyAlgorithmType(options.KeyAlgorithm))
|
||||||
|
|
||||||
// Create an ACME client
|
// Create an ACME client
|
||||||
client, err := lego.NewClient(config)
|
client, err := lego.NewClient(config)
|
||||||
@ -171,7 +170,7 @@ func apply(challengeProvider challenge.Provider, options *applicantOptions) (*Ap
|
|||||||
Domains: options.Domains,
|
Domains: options.Domains,
|
||||||
Bundle: true,
|
Bundle: true,
|
||||||
}
|
}
|
||||||
if !options.DisableARI {
|
if options.ReplacedARICertId != "" && options.ReplacedARIAccount != acmeUser.Registration.URI {
|
||||||
certRequest.ReplacesCertID = options.ReplacedARICertId
|
certRequest.ReplacesCertID = options.ReplacedARICertId
|
||||||
}
|
}
|
||||||
certResource, err := client.Certificate.Obtain(certRequest)
|
certResource, err := client.Certificate.Obtain(certRequest)
|
||||||
@ -183,29 +182,30 @@ func apply(challengeProvider challenge.Provider, options *applicantOptions) (*Ap
|
|||||||
CertificateFullChain: strings.TrimSpace(string(certResource.Certificate)),
|
CertificateFullChain: strings.TrimSpace(string(certResource.Certificate)),
|
||||||
IssuerCertificate: strings.TrimSpace(string(certResource.IssuerCertificate)),
|
IssuerCertificate: strings.TrimSpace(string(certResource.IssuerCertificate)),
|
||||||
PrivateKey: strings.TrimSpace(string(certResource.PrivateKey)),
|
PrivateKey: strings.TrimSpace(string(certResource.PrivateKey)),
|
||||||
|
ACMEAccountUrl: acmeUser.Registration.URI,
|
||||||
ACMECertUrl: certResource.CertURL,
|
ACMECertUrl: certResource.CertURL,
|
||||||
ACMECertStableUrl: certResource.CertStableURL,
|
ACMECertStableUrl: certResource.CertStableURL,
|
||||||
CSR: strings.TrimSpace(string(certResource.CSR)),
|
CSR: strings.TrimSpace(string(certResource.CSR)),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseKeyAlgorithm(algo string) certcrypto.KeyType {
|
func parseKeyAlgorithm(algo domain.CertificateKeyAlgorithmType) certcrypto.KeyType {
|
||||||
switch algo {
|
switch algo {
|
||||||
case "RSA2048":
|
case domain.CertificateKeyAlgorithmTypeRSA2048:
|
||||||
return certcrypto.RSA2048
|
return certcrypto.RSA2048
|
||||||
case "RSA3072":
|
case domain.CertificateKeyAlgorithmTypeRSA3072:
|
||||||
return certcrypto.RSA3072
|
return certcrypto.RSA3072
|
||||||
case "RSA4096":
|
case domain.CertificateKeyAlgorithmTypeRSA4096:
|
||||||
return certcrypto.RSA4096
|
return certcrypto.RSA4096
|
||||||
case "RSA8192":
|
case domain.CertificateKeyAlgorithmTypeRSA8192:
|
||||||
return certcrypto.RSA8192
|
return certcrypto.RSA8192
|
||||||
case "EC256":
|
case domain.CertificateKeyAlgorithmTypeEC256:
|
||||||
return certcrypto.EC256
|
return certcrypto.EC256
|
||||||
case "EC384":
|
case domain.CertificateKeyAlgorithmTypeEC384:
|
||||||
return certcrypto.EC384
|
return certcrypto.EC384
|
||||||
default:
|
|
||||||
return certcrypto.RSA2048
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return certcrypto.RSA2048
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 暂时使用代理模式以兼容之前版本代码,后续重新实现此处逻辑
|
// TODO: 暂时使用代理模式以兼容之前版本代码,后续重新实现此处逻辑
|
||||||
|
@ -1,24 +1,76 @@
|
|||||||
package domain
|
package domain
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/certs"
|
||||||
|
)
|
||||||
|
|
||||||
const CollectionNameCertificate = "certificate"
|
const CollectionNameCertificate = "certificate"
|
||||||
|
|
||||||
type Certificate struct {
|
type Certificate struct {
|
||||||
Meta
|
Meta
|
||||||
Source CertificateSourceType `json:"source" db:"source"`
|
Source CertificateSourceType `json:"source" db:"source"`
|
||||||
SubjectAltNames string `json:"subjectAltNames" db:"subjectAltNames"`
|
SubjectAltNames string `json:"subjectAltNames" db:"subjectAltNames"`
|
||||||
Certificate string `json:"certificate" db:"certificate"`
|
SerialNumber string `json:"serialNumber" db:"serialNumber"`
|
||||||
PrivateKey string `json:"privateKey" db:"privateKey"`
|
Certificate string `json:"certificate" db:"certificate"`
|
||||||
IssuerCertificate string `json:"issuerCertificate" db:"issuerCertificate"`
|
PrivateKey string `json:"privateKey" db:"privateKey"`
|
||||||
EffectAt time.Time `json:"effectAt" db:"effectAt"`
|
Issuer string `json:"issuer" db:"issuer"`
|
||||||
ExpireAt time.Time `json:"expireAt" db:"expireAt"`
|
IssuerCertificate string `json:"issuerCertificate" db:"issuerCertificate"`
|
||||||
ACMECertUrl string `json:"acmeCertUrl" db:"acmeCertUrl"`
|
KeyAlgorithm CertificateKeyAlgorithmType `json:"keyAlgorithm" db:"keyAlgorithm"`
|
||||||
ACMECertStableUrl string `json:"acmeCertStableUrl" db:"acmeCertStableUrl"`
|
EffectAt time.Time `json:"effectAt" db:"effectAt"`
|
||||||
WorkflowId string `json:"workflowId" db:"workflowId"`
|
ExpireAt time.Time `json:"expireAt" db:"expireAt"`
|
||||||
WorkflowNodeId string `json:"workflowNodeId" db:"workflowNodeId"`
|
ACMEAccountUrl string `json:"acmeAccountUrl" db:"acmeAccountUrl"`
|
||||||
WorkflowOutputId string `json:"workflowOutputId" db:"workflowOutputId"`
|
ACMECertUrl string `json:"acmeCertUrl" db:"acmeCertUrl"`
|
||||||
DeletedAt *time.Time `json:"deleted" db:"deleted"`
|
ACMECertStableUrl string `json:"acmeCertStableUrl" db:"acmeCertStableUrl"`
|
||||||
|
WorkflowId string `json:"workflowId" db:"workflowId"`
|
||||||
|
WorkflowNodeId string `json:"workflowNodeId" db:"workflowNodeId"`
|
||||||
|
WorkflowOutputId string `json:"workflowOutputId" db:"workflowOutputId"`
|
||||||
|
DeletedAt *time.Time `json:"deleted" db:"deleted"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Certificate) PopulateFromX509(certX509 *x509.Certificate) *Certificate {
|
||||||
|
c.SubjectAltNames = strings.Join(certX509.DNSNames, ";")
|
||||||
|
c.SerialNumber = strings.ToUpper(certX509.SerialNumber.Text(16))
|
||||||
|
c.Issuer = strings.Join(certX509.Issuer.Organization, ";")
|
||||||
|
c.EffectAt = certX509.NotBefore
|
||||||
|
c.ExpireAt = certX509.NotAfter
|
||||||
|
|
||||||
|
switch certX509.SignatureAlgorithm {
|
||||||
|
case x509.SHA256WithRSA, x509.SHA256WithRSAPSS:
|
||||||
|
c.KeyAlgorithm = CertificateKeyAlgorithmTypeRSA2048
|
||||||
|
case x509.SHA384WithRSA, x509.SHA384WithRSAPSS:
|
||||||
|
c.KeyAlgorithm = CertificateKeyAlgorithmTypeRSA3072
|
||||||
|
case x509.SHA512WithRSA, x509.SHA512WithRSAPSS:
|
||||||
|
c.KeyAlgorithm = CertificateKeyAlgorithmTypeRSA4096
|
||||||
|
case x509.ECDSAWithSHA256:
|
||||||
|
c.KeyAlgorithm = CertificateKeyAlgorithmTypeEC256
|
||||||
|
case x509.ECDSAWithSHA384:
|
||||||
|
c.KeyAlgorithm = CertificateKeyAlgorithmTypeEC384
|
||||||
|
case x509.ECDSAWithSHA512:
|
||||||
|
c.KeyAlgorithm = CertificateKeyAlgorithmTypeEC512
|
||||||
|
default:
|
||||||
|
c.KeyAlgorithm = CertificateKeyAlgorithmType("")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Certificate) PopulateFromPEM(certPEM, privkeyPEM string) *Certificate {
|
||||||
|
c.Certificate = certPEM
|
||||||
|
c.PrivateKey = privkeyPEM
|
||||||
|
|
||||||
|
_, issuerCertPEM, _ := certs.ExtractCertificatesFromPEM(certPEM)
|
||||||
|
c.IssuerCertificate = issuerCertPEM
|
||||||
|
|
||||||
|
certX509, _ := certs.ParseCertificateFromPEM(certPEM)
|
||||||
|
if certX509 != nil {
|
||||||
|
c.PopulateFromX509(certX509)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
type CertificateSourceType string
|
type CertificateSourceType string
|
||||||
@ -27,3 +79,15 @@ const (
|
|||||||
CertificateSourceTypeWorkflow = CertificateSourceType("workflow")
|
CertificateSourceTypeWorkflow = CertificateSourceType("workflow")
|
||||||
CertificateSourceTypeUpload = CertificateSourceType("upload")
|
CertificateSourceTypeUpload = CertificateSourceType("upload")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type CertificateKeyAlgorithmType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
CertificateKeyAlgorithmTypeRSA2048 = CertificateKeyAlgorithmType("RSA2048")
|
||||||
|
CertificateKeyAlgorithmTypeRSA3072 = CertificateKeyAlgorithmType("RSA3072")
|
||||||
|
CertificateKeyAlgorithmTypeRSA4096 = CertificateKeyAlgorithmType("RSA4096")
|
||||||
|
CertificateKeyAlgorithmTypeRSA8192 = CertificateKeyAlgorithmType("RSA8192")
|
||||||
|
CertificateKeyAlgorithmTypeEC256 = CertificateKeyAlgorithmType("EC256")
|
||||||
|
CertificateKeyAlgorithmTypeEC384 = CertificateKeyAlgorithmType("EC384")
|
||||||
|
CertificateKeyAlgorithmTypeEC512 = CertificateKeyAlgorithmType("EC512")
|
||||||
|
)
|
||||||
|
@ -78,7 +78,7 @@ func (d *QiniuCDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPe
|
|||||||
|
|
||||||
// 获取域名信息
|
// 获取域名信息
|
||||||
// REF: https://developer.qiniu.com/fusion/4246/the-domain-name
|
// REF: https://developer.qiniu.com/fusion/4246/the-domain-name
|
||||||
getDomainInfoResp, err := d.sdkClient.GetDomainInfo(domain)
|
getDomainInfoResp, err := d.sdkClient.GetDomainInfo(context.TODO(), domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.GetDomainInfo'")
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.GetDomainInfo'")
|
||||||
}
|
}
|
||||||
@ -88,14 +88,14 @@ func (d *QiniuCDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPe
|
|||||||
// 判断域名是否已启用 HTTPS。如果已启用,修改域名证书;否则,启用 HTTPS
|
// 判断域名是否已启用 HTTPS。如果已启用,修改域名证书;否则,启用 HTTPS
|
||||||
// REF: https://developer.qiniu.com/fusion/4246/the-domain-name
|
// REF: https://developer.qiniu.com/fusion/4246/the-domain-name
|
||||||
if getDomainInfoResp.Https != nil && getDomainInfoResp.Https.CertID != "" {
|
if getDomainInfoResp.Https != nil && getDomainInfoResp.Https.CertID != "" {
|
||||||
modifyDomainHttpsConfResp, err := d.sdkClient.ModifyDomainHttpsConf(domain, upres.CertId, getDomainInfoResp.Https.ForceHttps, getDomainInfoResp.Https.Http2Enable)
|
modifyDomainHttpsConfResp, err := d.sdkClient.ModifyDomainHttpsConf(context.TODO(), domain, upres.CertId, getDomainInfoResp.Https.ForceHttps, getDomainInfoResp.Https.Http2Enable)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.ModifyDomainHttpsConf'")
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.ModifyDomainHttpsConf'")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.logger.Logt("已修改域名证书", modifyDomainHttpsConfResp)
|
d.logger.Logt("已修改域名证书", modifyDomainHttpsConfResp)
|
||||||
} else {
|
} else {
|
||||||
enableDomainHttpsResp, err := d.sdkClient.EnableDomainHttps(domain, upres.CertId, true, true)
|
enableDomainHttpsResp, err := d.sdkClient.EnableDomainHttps(context.TODO(), domain, upres.CertId, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.EnableDomainHttps'")
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.EnableDomainHttps'")
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ func (u *QiniuSSLCertUploader) Upload(ctx context.Context, certPem string, privk
|
|||||||
|
|
||||||
// 上传新证书
|
// 上传新证书
|
||||||
// REF: https://developer.qiniu.com/fusion/8593/interface-related-certificate
|
// REF: https://developer.qiniu.com/fusion/8593/interface-related-certificate
|
||||||
uploadSslCertResp, err := u.sdkClient.UploadSslCert(certName, certX509.Subject.CommonName, certPem, privkeyPem)
|
uploadSslCertResp, err := u.sdkClient.UploadSslCert(context.TODO(), certName, certX509.Subject.CommonName, certPem, privkeyPem)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.UploadSslCert'")
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.UploadSslCert'")
|
||||||
}
|
}
|
||||||
|
16
internal/pkg/vendors/qiniu-sdk/client.go
vendored
16
internal/pkg/vendors/qiniu-sdk/client.go
vendored
@ -26,15 +26,15 @@ func NewClient(mac *auth.Credentials) *Client {
|
|||||||
return &Client{client: &client}
|
return &Client{client: &client}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetDomainInfo(domain string) (*GetDomainInfoResponse, error) {
|
func (c *Client) GetDomainInfo(ctx context.Context, domain string) (*GetDomainInfoResponse, error) {
|
||||||
resp := new(GetDomainInfoResponse)
|
resp := new(GetDomainInfoResponse)
|
||||||
if err := c.client.Call(context.Background(), resp, http.MethodGet, c.urlf("domain/%s", domain), nil); err != nil {
|
if err := c.client.Call(ctx, resp, http.MethodGet, c.urlf("domain/%s", domain), nil); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) ModifyDomainHttpsConf(domain, certId string, forceHttps, http2Enable bool) (*ModifyDomainHttpsConfResponse, error) {
|
func (c *Client) ModifyDomainHttpsConf(ctx context.Context, domain string, certId string, forceHttps bool, http2Enable bool) (*ModifyDomainHttpsConfResponse, error) {
|
||||||
req := &ModifyDomainHttpsConfRequest{
|
req := &ModifyDomainHttpsConfRequest{
|
||||||
DomainInfoHttpsData: DomainInfoHttpsData{
|
DomainInfoHttpsData: DomainInfoHttpsData{
|
||||||
CertID: certId,
|
CertID: certId,
|
||||||
@ -43,13 +43,13 @@ func (c *Client) ModifyDomainHttpsConf(domain, certId string, forceHttps, http2E
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
resp := new(ModifyDomainHttpsConfResponse)
|
resp := new(ModifyDomainHttpsConfResponse)
|
||||||
if err := c.client.CallWithJson(context.Background(), resp, http.MethodPut, c.urlf("domain/%s/httpsconf", domain), nil, req); err != nil {
|
if err := c.client.CallWithJson(ctx, resp, http.MethodPut, c.urlf("domain/%s/httpsconf", domain), nil, req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) EnableDomainHttps(domain, certId string, forceHttps, http2Enable bool) (*EnableDomainHttpsResponse, error) {
|
func (c *Client) EnableDomainHttps(ctx context.Context, domain string, certId string, forceHttps bool, http2Enable bool) (*EnableDomainHttpsResponse, error) {
|
||||||
req := &EnableDomainHttpsRequest{
|
req := &EnableDomainHttpsRequest{
|
||||||
DomainInfoHttpsData: DomainInfoHttpsData{
|
DomainInfoHttpsData: DomainInfoHttpsData{
|
||||||
CertID: certId,
|
CertID: certId,
|
||||||
@ -58,13 +58,13 @@ func (c *Client) EnableDomainHttps(domain, certId string, forceHttps, http2Enabl
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
resp := new(EnableDomainHttpsResponse)
|
resp := new(EnableDomainHttpsResponse)
|
||||||
if err := c.client.CallWithJson(context.Background(), resp, http.MethodPut, c.urlf("domain/%s/sslize", domain), nil, req); err != nil {
|
if err := c.client.CallWithJson(ctx, resp, http.MethodPut, c.urlf("domain/%s/sslize", domain), nil, req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) UploadSslCert(name, commonName, certificate, privateKey string) (*UploadSslCertResponse, error) {
|
func (c *Client) UploadSslCert(ctx context.Context, name string, commonName string, certificate string, privateKey string) (*UploadSslCertResponse, error) {
|
||||||
req := &UploadSslCertRequest{
|
req := &UploadSslCertRequest{
|
||||||
Name: name,
|
Name: name,
|
||||||
CommonName: commonName,
|
CommonName: commonName,
|
||||||
@ -72,7 +72,7 @@ func (c *Client) UploadSslCert(name, commonName, certificate, privateKey string)
|
|||||||
PrivateKey: privateKey,
|
PrivateKey: privateKey,
|
||||||
}
|
}
|
||||||
resp := new(UploadSslCertResponse)
|
resp := new(UploadSslCertResponse)
|
||||||
if err := c.client.CallWithJson(context.Background(), resp, http.MethodPost, c.urlf("sslcert"), nil, req); err != nil {
|
if err := c.client.CallWithJson(ctx, resp, http.MethodPost, c.urlf("sslcert"), nil, req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return resp, nil
|
return resp, nil
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/registration"
|
"github.com/go-acme/lego/v4/registration"
|
||||||
@ -48,18 +51,37 @@ func (r *AcmeAccountRepository) GetByCAAndEmail(ca, email string) (*domain.AcmeA
|
|||||||
return r.castRecordToModel(record)
|
return r.castRecordToModel(record)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *AcmeAccountRepository) Save(ca, email, key string, resource *registration.Resource) error {
|
func (r *AcmeAccountRepository) Save(ctx context.Context, acmeAccount *domain.AcmeAccount) (*domain.AcmeAccount, error) {
|
||||||
collection, err := app.GetApp().FindCollectionByNameOrId(domain.CollectionNameAcmeAccount)
|
collection, err := app.GetApp().FindCollectionByNameOrId(domain.CollectionNameAcmeAccount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return acmeAccount, err
|
||||||
}
|
}
|
||||||
|
|
||||||
record := core.NewRecord(collection)
|
var record *core.Record
|
||||||
record.Set("ca", ca)
|
if acmeAccount.Id == "" {
|
||||||
record.Set("email", email)
|
record = core.NewRecord(collection)
|
||||||
record.Set("key", key)
|
} else {
|
||||||
record.Set("resource", resource)
|
record, err = app.GetApp().FindRecordById(collection, acmeAccount.Id)
|
||||||
return app.GetApp().Save(record)
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return acmeAccount, domain.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
return acmeAccount, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record.Set("ca", acmeAccount.CA)
|
||||||
|
record.Set("email", acmeAccount.Email)
|
||||||
|
record.Set("key", acmeAccount.Key)
|
||||||
|
record.Set("resource", acmeAccount.Resource)
|
||||||
|
if err := app.GetApp().Save(record); err != nil {
|
||||||
|
return acmeAccount, err
|
||||||
|
}
|
||||||
|
|
||||||
|
acmeAccount.Id = record.Id
|
||||||
|
acmeAccount.CreatedAt = record.GetDateTime("created").Time()
|
||||||
|
acmeAccount.UpdatedAt = record.GetDateTime("updated").Time()
|
||||||
|
return acmeAccount, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *AcmeAccountRepository) castRecordToModel(record *core.Record) (*domain.AcmeAccount, error) {
|
func (r *AcmeAccountRepository) castRecordToModel(record *core.Record) (*domain.AcmeAccount, error) {
|
||||||
|
@ -79,6 +79,51 @@ func (r *CertificateRepository) GetByWorkflowNodeId(ctx context.Context, workflo
|
|||||||
return r.castRecordToModel(records[0])
|
return r.castRecordToModel(records[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *CertificateRepository) Save(ctx context.Context, certificate *domain.Certificate) (*domain.Certificate, error) {
|
||||||
|
collection, err := app.GetApp().FindCollectionByNameOrId(domain.CollectionNameCertificate)
|
||||||
|
if err != nil {
|
||||||
|
return certificate, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var record *core.Record
|
||||||
|
if certificate.Id == "" {
|
||||||
|
record = core.NewRecord(collection)
|
||||||
|
} else {
|
||||||
|
record, err = app.GetApp().FindRecordById(collection, certificate.Id)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return certificate, domain.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
return certificate, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record.Set("source", string(certificate.Source))
|
||||||
|
record.Set("subjectAltNames", certificate.SubjectAltNames)
|
||||||
|
record.Set("serialNumber", certificate.SerialNumber)
|
||||||
|
record.Set("certificate", certificate.Certificate)
|
||||||
|
record.Set("privateKey", certificate.PrivateKey)
|
||||||
|
record.Set("issuer", certificate.Issuer)
|
||||||
|
record.Set("issuerCertificate", certificate.IssuerCertificate)
|
||||||
|
record.Set("keyAlgorithm", string(certificate.KeyAlgorithm))
|
||||||
|
record.Set("effectAt", certificate.EffectAt)
|
||||||
|
record.Set("expireAt", certificate.ExpireAt)
|
||||||
|
record.Set("acmeAccountUrl", certificate.ACMEAccountUrl)
|
||||||
|
record.Set("acmeCertUrl", certificate.ACMECertUrl)
|
||||||
|
record.Set("acmeCertStableUrl", certificate.ACMECertStableUrl)
|
||||||
|
record.Set("workflowId", certificate.WorkflowId)
|
||||||
|
record.Set("workflowNodeId", certificate.WorkflowNodeId)
|
||||||
|
record.Set("workflowOutputId", certificate.WorkflowOutputId)
|
||||||
|
if err := app.GetApp().Save(record); err != nil {
|
||||||
|
return certificate, err
|
||||||
|
}
|
||||||
|
|
||||||
|
certificate.Id = record.Id
|
||||||
|
certificate.CreatedAt = record.GetDateTime("created").Time()
|
||||||
|
certificate.UpdatedAt = record.GetDateTime("updated").Time()
|
||||||
|
return certificate, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *CertificateRepository) castRecordToModel(record *core.Record) (*domain.Certificate, error) {
|
func (r *CertificateRepository) castRecordToModel(record *core.Record) (*domain.Certificate, error) {
|
||||||
if record == nil {
|
if record == nil {
|
||||||
return nil, fmt.Errorf("record is nil")
|
return nil, fmt.Errorf("record is nil")
|
||||||
@ -92,11 +137,15 @@ func (r *CertificateRepository) castRecordToModel(record *core.Record) (*domain.
|
|||||||
},
|
},
|
||||||
Source: domain.CertificateSourceType(record.GetString("source")),
|
Source: domain.CertificateSourceType(record.GetString("source")),
|
||||||
SubjectAltNames: record.GetString("subjectAltNames"),
|
SubjectAltNames: record.GetString("subjectAltNames"),
|
||||||
|
SerialNumber: record.GetString("serialNumber"),
|
||||||
Certificate: record.GetString("certificate"),
|
Certificate: record.GetString("certificate"),
|
||||||
PrivateKey: record.GetString("privateKey"),
|
PrivateKey: record.GetString("privateKey"),
|
||||||
|
Issuer: record.GetString("issuer"),
|
||||||
IssuerCertificate: record.GetString("issuerCertificate"),
|
IssuerCertificate: record.GetString("issuerCertificate"),
|
||||||
|
KeyAlgorithm: domain.CertificateKeyAlgorithmType(record.GetString("keyAlgorithm")),
|
||||||
EffectAt: record.GetDateTime("effectAt").Time(),
|
EffectAt: record.GetDateTime("effectAt").Time(),
|
||||||
ExpireAt: record.GetDateTime("expireAt").Time(),
|
ExpireAt: record.GetDateTime("expireAt").Time(),
|
||||||
|
ACMEAccountUrl: record.GetString("acmeAccountUrl"),
|
||||||
ACMECertUrl: record.GetString("acmeCertUrl"),
|
ACMECertUrl: record.GetString("acmeCertUrl"),
|
||||||
ACMECertStableUrl: record.GetString("acmeCertStableUrl"),
|
ACMECertStableUrl: record.GetString("acmeCertStableUrl"),
|
||||||
WorkflowId: record.GetString("workflowId"),
|
WorkflowId: record.GetString("workflowId"),
|
||||||
|
@ -65,7 +65,7 @@ func (r *WorkflowRepository) Save(ctx context.Context, workflow *domain.Workflow
|
|||||||
if workflow.Id == "" {
|
if workflow.Id == "" {
|
||||||
record = core.NewRecord(collection)
|
record = core.NewRecord(collection)
|
||||||
} else {
|
} else {
|
||||||
record, err = app.GetApp().FindRecordById(domain.CollectionNameWorkflow, workflow.Id)
|
record, err = app.GetApp().FindRecordById(collection, workflow.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
return workflow, domain.ErrRecordNotFound
|
return workflow, domain.ErrRecordNotFound
|
||||||
@ -85,7 +85,6 @@ func (r *WorkflowRepository) Save(ctx context.Context, workflow *domain.Workflow
|
|||||||
record.Set("lastRunId", workflow.LastRunId)
|
record.Set("lastRunId", workflow.LastRunId)
|
||||||
record.Set("lastRunStatus", string(workflow.LastRunStatus))
|
record.Set("lastRunStatus", string(workflow.LastRunStatus))
|
||||||
record.Set("lastRunTime", workflow.LastRunTime)
|
record.Set("lastRunTime", workflow.LastRunTime)
|
||||||
|
|
||||||
if err := app.GetApp().Save(record); err != nil {
|
if err := app.GetApp().Save(record); err != nil {
|
||||||
return workflow, err
|
return workflow, err
|
||||||
}
|
}
|
||||||
@ -96,63 +95,63 @@ func (r *WorkflowRepository) Save(ctx context.Context, workflow *domain.Workflow
|
|||||||
return workflow, nil
|
return workflow, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *WorkflowRepository) SaveRun(ctx context.Context, workflowRun *domain.WorkflowRun) (*domain.WorkflowRun, error) {
|
func (r *WorkflowRepository) SaveRun(ctx context.Context, run *domain.WorkflowRun) (*domain.WorkflowRun, error) {
|
||||||
collection, err := app.GetApp().FindCollectionByNameOrId(domain.CollectionNameWorkflowRun)
|
collection, err := app.GetApp().FindCollectionByNameOrId(domain.CollectionNameWorkflowRun)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return workflowRun, err
|
return run, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var workflowRunRecord *core.Record
|
var runRecord *core.Record
|
||||||
if workflowRun.Id == "" {
|
if run.Id == "" {
|
||||||
workflowRunRecord = core.NewRecord(collection)
|
runRecord = core.NewRecord(collection)
|
||||||
} else {
|
} else {
|
||||||
workflowRunRecord, err = app.GetApp().FindRecordById(domain.CollectionNameWorkflowRun, workflowRun.Id)
|
runRecord, err = app.GetApp().FindRecordById(collection, run.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
return workflowRun, err
|
return run, err
|
||||||
}
|
}
|
||||||
workflowRunRecord = core.NewRecord(collection)
|
runRecord = core.NewRecord(collection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = app.GetApp().RunInTransaction(func(txApp core.App) error {
|
err = app.GetApp().RunInTransaction(func(txApp core.App) error {
|
||||||
workflowRunRecord.Set("workflowId", workflowRun.WorkflowId)
|
runRecord.Set("workflowId", run.WorkflowId)
|
||||||
workflowRunRecord.Set("trigger", string(workflowRun.Trigger))
|
runRecord.Set("trigger", string(run.Trigger))
|
||||||
workflowRunRecord.Set("status", string(workflowRun.Status))
|
runRecord.Set("status", string(run.Status))
|
||||||
workflowRunRecord.Set("startedAt", workflowRun.StartedAt)
|
runRecord.Set("startedAt", run.StartedAt)
|
||||||
workflowRunRecord.Set("endedAt", workflowRun.EndedAt)
|
runRecord.Set("endedAt", run.EndedAt)
|
||||||
workflowRunRecord.Set("logs", workflowRun.Logs)
|
runRecord.Set("logs", run.Logs)
|
||||||
workflowRunRecord.Set("error", workflowRun.Error)
|
runRecord.Set("error", run.Error)
|
||||||
err = txApp.Save(workflowRunRecord)
|
err = txApp.Save(runRecord)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
workflowRecord, err := txApp.FindRecordById(domain.CollectionNameWorkflow, workflowRun.WorkflowId)
|
workflowRecord, err := txApp.FindRecordById(domain.CollectionNameWorkflow, run.WorkflowId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
workflowRecord.IgnoreUnchangedFields(true)
|
workflowRecord.IgnoreUnchangedFields(true)
|
||||||
workflowRecord.Set("lastRunId", workflowRunRecord.Id)
|
workflowRecord.Set("lastRunId", runRecord.Id)
|
||||||
workflowRecord.Set("lastRunStatus", workflowRunRecord.GetString("status"))
|
workflowRecord.Set("lastRunStatus", runRecord.GetString("status"))
|
||||||
workflowRecord.Set("lastRunTime", workflowRunRecord.GetString("startedAt"))
|
workflowRecord.Set("lastRunTime", runRecord.GetString("startedAt"))
|
||||||
err = txApp.Save(workflowRecord)
|
err = txApp.Save(workflowRecord)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
workflowRun.Id = workflowRunRecord.Id
|
run.Id = runRecord.Id
|
||||||
workflowRun.CreatedAt = workflowRunRecord.GetDateTime("created").Time()
|
run.CreatedAt = runRecord.GetDateTime("created").Time()
|
||||||
workflowRun.UpdatedAt = workflowRunRecord.GetDateTime("updated").Time()
|
run.UpdatedAt = runRecord.GetDateTime("updated").Time()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return workflowRun, err
|
return run, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return workflowRun, nil
|
return run, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *WorkflowRepository) castRecordToModel(record *core.Record) (*domain.Workflow, error) {
|
func (r *WorkflowRepository) castRecordToModel(record *core.Record) (*domain.Workflow, error) {
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/pocketbase/dbx"
|
"github.com/pocketbase/dbx"
|
||||||
"github.com/pocketbase/pocketbase/core"
|
"github.com/pocketbase/pocketbase/core"
|
||||||
@ -17,13 +18,13 @@ func NewWorkflowOutputRepository() *WorkflowOutputRepository {
|
|||||||
return &WorkflowOutputRepository{}
|
return &WorkflowOutputRepository{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *WorkflowOutputRepository) GetByNodeId(ctx context.Context, nodeId string) (*domain.WorkflowOutput, error) {
|
func (r *WorkflowOutputRepository) GetByNodeId(ctx context.Context, workflowNodeId string) (*domain.WorkflowOutput, error) {
|
||||||
records, err := app.GetApp().FindRecordsByFilter(
|
records, err := app.GetApp().FindRecordsByFilter(
|
||||||
domain.CollectionNameWorkflowOutput,
|
domain.CollectionNameWorkflowOutput,
|
||||||
"nodeId={:nodeId}",
|
"nodeId={:nodeId}",
|
||||||
"-created",
|
"-created",
|
||||||
1, 0,
|
1, 0,
|
||||||
dbx.Params{"nodeId": nodeId},
|
dbx.Params{"nodeId": workflowNodeId},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
@ -34,7 +35,61 @@ func (r *WorkflowOutputRepository) GetByNodeId(ctx context.Context, nodeId strin
|
|||||||
if len(records) == 0 {
|
if len(records) == 0 {
|
||||||
return nil, domain.ErrRecordNotFound
|
return nil, domain.ErrRecordNotFound
|
||||||
}
|
}
|
||||||
record := records[0]
|
|
||||||
|
return r.castRecordToModel(records[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *WorkflowOutputRepository) Save(ctx context.Context, workflowOutput *domain.WorkflowOutput) (*domain.WorkflowOutput, error) {
|
||||||
|
record, err := r.saveRecord(ctx, workflowOutput)
|
||||||
|
if err != nil {
|
||||||
|
return workflowOutput, err
|
||||||
|
}
|
||||||
|
|
||||||
|
workflowOutput.Id = record.Id
|
||||||
|
workflowOutput.CreatedAt = record.GetDateTime("created").Time()
|
||||||
|
workflowOutput.UpdatedAt = record.GetDateTime("updated").Time()
|
||||||
|
return workflowOutput, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *WorkflowOutputRepository) SaveWithCertificate(ctx context.Context, workflowOutput *domain.WorkflowOutput, certificate *domain.Certificate) (*domain.WorkflowOutput, error) {
|
||||||
|
record, err := r.saveRecord(ctx, workflowOutput)
|
||||||
|
if err != nil {
|
||||||
|
return workflowOutput, err
|
||||||
|
} else {
|
||||||
|
workflowOutput.Id = record.Id
|
||||||
|
workflowOutput.CreatedAt = record.GetDateTime("created").Time()
|
||||||
|
workflowOutput.UpdatedAt = record.GetDateTime("updated").Time()
|
||||||
|
}
|
||||||
|
|
||||||
|
if certificate != nil {
|
||||||
|
certificate.WorkflowId = workflowOutput.WorkflowId
|
||||||
|
certificate.WorkflowNodeId = workflowOutput.NodeId
|
||||||
|
certificate.WorkflowOutputId = workflowOutput.Id
|
||||||
|
certificate, err := NewCertificateRepository().Save(ctx, certificate)
|
||||||
|
if err != nil {
|
||||||
|
return workflowOutput, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入证书 ID 到工作流输出结果中
|
||||||
|
for i, item := range workflowOutput.Outputs {
|
||||||
|
if item.Name == string(domain.WorkflowNodeIONameCertificate) {
|
||||||
|
workflowOutput.Outputs[i].Value = certificate.Id
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
record.Set("outputs", workflowOutput.Outputs)
|
||||||
|
if err := app.GetApp().Save(record); err != nil {
|
||||||
|
return workflowOutput, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return workflowOutput, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *WorkflowOutputRepository) castRecordToModel(record *core.Record) (*domain.WorkflowOutput, error) {
|
||||||
|
if record == nil {
|
||||||
|
return nil, fmt.Errorf("record is nil")
|
||||||
|
}
|
||||||
|
|
||||||
node := &domain.WorkflowNode{}
|
node := &domain.WorkflowNode{}
|
||||||
if err := record.UnmarshalJSONField("node", node); err != nil {
|
if err := record.UnmarshalJSONField("node", node); err != nil {
|
||||||
@ -46,7 +101,7 @@ func (r *WorkflowOutputRepository) GetByNodeId(ctx context.Context, nodeId strin
|
|||||||
return nil, errors.New("failed to unmarshal output")
|
return nil, errors.New("failed to unmarshal output")
|
||||||
}
|
}
|
||||||
|
|
||||||
rs := &domain.WorkflowOutput{
|
workflowOutput := &domain.WorkflowOutput{
|
||||||
Meta: domain.Meta{
|
Meta: domain.Meta{
|
||||||
Id: record.Id,
|
Id: record.Id,
|
||||||
CreatedAt: record.GetDateTime("created").Time(),
|
CreatedAt: record.GetDateTime("created").Time(),
|
||||||
@ -58,25 +113,22 @@ func (r *WorkflowOutputRepository) GetByNodeId(ctx context.Context, nodeId strin
|
|||||||
Outputs: outputs,
|
Outputs: outputs,
|
||||||
Succeeded: record.GetBool("succeeded"),
|
Succeeded: record.GetBool("succeeded"),
|
||||||
}
|
}
|
||||||
|
return workflowOutput, nil
|
||||||
return rs, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存节点输出
|
func (r *WorkflowOutputRepository) saveRecord(ctx context.Context, output *domain.WorkflowOutput) (*core.Record, error) {
|
||||||
func (r *WorkflowOutputRepository) Save(ctx context.Context, output *domain.WorkflowOutput, certificate *domain.Certificate, cb func(id string) error) error {
|
collection, err := app.GetApp().FindCollectionByNameOrId(domain.CollectionNameWorkflowOutput)
|
||||||
var record *core.Record
|
if err != nil {
|
||||||
var err error
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var record *core.Record
|
||||||
if output.Id == "" {
|
if output.Id == "" {
|
||||||
collection, err := app.GetApp().FindCollectionByNameOrId(domain.CollectionNameWorkflowOutput)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
record = core.NewRecord(collection)
|
record = core.NewRecord(collection)
|
||||||
} else {
|
} else {
|
||||||
record, err = app.GetApp().FindRecordById(domain.CollectionNameWorkflowOutput, output.Id)
|
record, err = app.GetApp().FindRecordById(collection, output.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return record, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
record.Set("workflowId", output.WorkflowId)
|
record.Set("workflowId", output.WorkflowId)
|
||||||
@ -84,53 +136,9 @@ func (r *WorkflowOutputRepository) Save(ctx context.Context, output *domain.Work
|
|||||||
record.Set("node", output.Node)
|
record.Set("node", output.Node)
|
||||||
record.Set("outputs", output.Outputs)
|
record.Set("outputs", output.Outputs)
|
||||||
record.Set("succeeded", output.Succeeded)
|
record.Set("succeeded", output.Succeeded)
|
||||||
|
|
||||||
if err := app.GetApp().Save(record); err != nil {
|
if err := app.GetApp().Save(record); err != nil {
|
||||||
return err
|
return record, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if cb != nil && certificate != nil {
|
return record, err
|
||||||
if err := cb(record.Id); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
certCollection, err := app.GetApp().FindCollectionByNameOrId(domain.CollectionNameCertificate)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
certRecord := core.NewRecord(certCollection)
|
|
||||||
certRecord.Set("source", string(certificate.Source))
|
|
||||||
certRecord.Set("subjectAltNames", certificate.SubjectAltNames)
|
|
||||||
certRecord.Set("certificate", certificate.Certificate)
|
|
||||||
certRecord.Set("privateKey", certificate.PrivateKey)
|
|
||||||
certRecord.Set("issuerCertificate", certificate.IssuerCertificate)
|
|
||||||
certRecord.Set("effectAt", certificate.EffectAt)
|
|
||||||
certRecord.Set("expireAt", certificate.ExpireAt)
|
|
||||||
certRecord.Set("acmeCertUrl", certificate.ACMECertUrl)
|
|
||||||
certRecord.Set("acmeCertStableUrl", certificate.ACMECertStableUrl)
|
|
||||||
certRecord.Set("workflowId", certificate.WorkflowId)
|
|
||||||
certRecord.Set("workflowNodeId", certificate.WorkflowNodeId)
|
|
||||||
certRecord.Set("workflowOutputId", certificate.WorkflowOutputId)
|
|
||||||
|
|
||||||
if err := app.GetApp().Save(certRecord); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新 certificate
|
|
||||||
for i, item := range output.Outputs {
|
|
||||||
if item.Name == string(domain.WorkflowNodeIONameCertificate) {
|
|
||||||
output.Outputs[i].Value = certRecord.Id
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
record.Set("outputs", output.Outputs)
|
|
||||||
|
|
||||||
if err := app.GetApp().Save(record); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ package nodeprocessor
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/exp/maps"
|
"golang.org/x/exp/maps"
|
||||||
@ -30,89 +29,79 @@ func NewApplyNode(node *domain.WorkflowNode) *applyNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 申请节点根据申请类型执行不同的操作
|
func (n *applyNode) Run(ctx context.Context) error {
|
||||||
func (a *applyNode) Run(ctx context.Context) error {
|
n.AddOutput(ctx, n.node.Name, "开始执行")
|
||||||
a.AddOutput(ctx, a.node.Name, "开始执行")
|
|
||||||
|
|
||||||
// 查询上次执行结果
|
// 查询上次执行结果
|
||||||
lastOutput, err := a.outputRepo.GetByNodeId(ctx, a.node.Id)
|
lastOutput, err := n.outputRepo.GetByNodeId(ctx, n.node.Id)
|
||||||
if err != nil && !domain.IsRecordNotFoundError(err) {
|
if err != nil && !domain.IsRecordNotFoundError(err) {
|
||||||
a.AddOutput(ctx, a.node.Name, "查询申请记录失败", err.Error())
|
n.AddOutput(ctx, n.node.Name, "查询申请记录失败", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检测是否可以跳过本次执行
|
// 检测是否可以跳过本次执行
|
||||||
if skippable, skipReason := a.checkCanSkip(ctx, lastOutput); skippable {
|
if skippable, skipReason := n.checkCanSkip(ctx, lastOutput); skippable {
|
||||||
a.AddOutput(ctx, a.node.Name, skipReason)
|
n.AddOutput(ctx, n.node.Name, skipReason)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化申请器
|
// 初始化申请器
|
||||||
applicant, err := applicant.NewWithApplyNode(a.node)
|
applicant, err := applicant.NewWithApplyNode(n.node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.AddOutput(ctx, a.node.Name, "获取申请对象失败", err.Error())
|
n.AddOutput(ctx, n.node.Name, "获取申请对象失败", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 申请证书
|
// 申请证书
|
||||||
applyResult, err := applicant.Apply()
|
applyResult, err := applicant.Apply()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.AddOutput(ctx, a.node.Name, "申请失败", err.Error())
|
n.AddOutput(ctx, n.node.Name, "申请失败", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
a.AddOutput(ctx, a.node.Name, "申请成功")
|
n.AddOutput(ctx, n.node.Name, "申请成功")
|
||||||
|
|
||||||
// 解析证书并生成实体
|
// 解析证书并生成实体
|
||||||
certX509, err := certs.ParseCertificateFromPEM(applyResult.CertificateFullChain)
|
certX509, err := certs.ParseCertificateFromPEM(applyResult.CertificateFullChain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.AddOutput(ctx, a.node.Name, "解析证书失败", err.Error())
|
n.AddOutput(ctx, n.node.Name, "解析证书失败", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
certificate := &domain.Certificate{
|
certificate := &domain.Certificate{
|
||||||
Source: domain.CertificateSourceTypeWorkflow,
|
Source: domain.CertificateSourceTypeWorkflow,
|
||||||
SubjectAltNames: strings.Join(certX509.DNSNames, ";"),
|
|
||||||
Certificate: applyResult.CertificateFullChain,
|
Certificate: applyResult.CertificateFullChain,
|
||||||
PrivateKey: applyResult.PrivateKey,
|
PrivateKey: applyResult.PrivateKey,
|
||||||
IssuerCertificate: applyResult.IssuerCertificate,
|
IssuerCertificate: applyResult.IssuerCertificate,
|
||||||
|
ACMEAccountUrl: applyResult.ACMEAccountUrl,
|
||||||
ACMECertUrl: applyResult.ACMECertUrl,
|
ACMECertUrl: applyResult.ACMECertUrl,
|
||||||
ACMECertStableUrl: applyResult.ACMECertStableUrl,
|
ACMECertStableUrl: applyResult.ACMECertStableUrl,
|
||||||
EffectAt: certX509.NotBefore,
|
|
||||||
ExpireAt: certX509.NotAfter,
|
|
||||||
WorkflowId: getContextWorkflowId(ctx),
|
|
||||||
WorkflowNodeId: a.node.Id,
|
|
||||||
}
|
}
|
||||||
|
certificate.PopulateFromX509(certX509)
|
||||||
|
|
||||||
// 保存执行结果
|
// 保存执行结果
|
||||||
// TODO: 先保持一个节点始终只有一个输出,后续增加版本控制
|
// TODO: 先保持一个节点始终只有一个输出,后续增加版本控制
|
||||||
currentOutput := &domain.WorkflowOutput{
|
currentOutput := &domain.WorkflowOutput{
|
||||||
WorkflowId: getContextWorkflowId(ctx),
|
WorkflowId: getContextWorkflowId(ctx),
|
||||||
NodeId: a.node.Id,
|
NodeId: n.node.Id,
|
||||||
Node: a.node,
|
Node: n.node,
|
||||||
Succeeded: true,
|
Succeeded: true,
|
||||||
Outputs: a.node.Outputs,
|
Outputs: n.node.Outputs,
|
||||||
}
|
}
|
||||||
if lastOutput != nil {
|
if lastOutput != nil {
|
||||||
currentOutput.Id = lastOutput.Id
|
currentOutput.Id = lastOutput.Id
|
||||||
}
|
}
|
||||||
if err := a.outputRepo.Save(ctx, currentOutput, certificate, func(id string) error {
|
if _, err := n.outputRepo.SaveWithCertificate(ctx, currentOutput, certificate); err != nil {
|
||||||
if certificate != nil {
|
n.AddOutput(ctx, n.node.Name, "保存申请记录失败", err.Error())
|
||||||
certificate.WorkflowOutputId = id
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}); err != nil {
|
|
||||||
a.AddOutput(ctx, a.node.Name, "保存申请记录失败", err.Error())
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
a.AddOutput(ctx, a.node.Name, "保存申请记录成功")
|
n.AddOutput(ctx, n.node.Name, "保存申请记录成功")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *applyNode) checkCanSkip(ctx context.Context, lastOutput *domain.WorkflowOutput) (skip bool, reason string) {
|
func (n *applyNode) checkCanSkip(ctx context.Context, lastOutput *domain.WorkflowOutput) (skip bool, reason string) {
|
||||||
if lastOutput != nil && lastOutput.Succeeded {
|
if lastOutput != nil && lastOutput.Succeeded {
|
||||||
// 比较和上次申请时的关键配置(即影响证书签发的)参数是否一致
|
// 比较和上次申请时的关键配置(即影响证书签发的)参数是否一致
|
||||||
currentNodeConfig := a.node.GetConfigForApply()
|
currentNodeConfig := n.node.GetConfigForApply()
|
||||||
lastNodeConfig := lastOutput.Node.GetConfigForApply()
|
lastNodeConfig := lastOutput.Node.GetConfigForApply()
|
||||||
if currentNodeConfig.Domains != lastNodeConfig.Domains {
|
if currentNodeConfig.Domains != lastNodeConfig.Domains {
|
||||||
return false, "配置项变化:域名"
|
return false, "配置项变化:域名"
|
||||||
@ -130,7 +119,7 @@ func (a *applyNode) checkCanSkip(ctx context.Context, lastOutput *domain.Workflo
|
|||||||
return false, "配置项变化:数字签名算法"
|
return false, "配置项变化:数字签名算法"
|
||||||
}
|
}
|
||||||
|
|
||||||
lastCertificate, _ := a.certRepo.GetByWorkflowNodeId(ctx, a.node.Id)
|
lastCertificate, _ := n.certRepo.GetByWorkflowNodeId(ctx, n.node.Id)
|
||||||
renewalInterval := time.Duration(currentNodeConfig.SkipBeforeExpiryDays) * time.Hour * 24
|
renewalInterval := time.Duration(currentNodeConfig.SkipBeforeExpiryDays) * time.Hour * 24
|
||||||
expirationTime := time.Until(lastCertificate.ExpireAt)
|
expirationTime := time.Until(lastCertificate.ExpireAt)
|
||||||
if lastCertificate != nil && expirationTime > renewalInterval {
|
if lastCertificate != nil && expirationTime > renewalInterval {
|
||||||
|
@ -18,11 +18,9 @@ func NewConditionNode(node *domain.WorkflowNode) *conditionNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 条件节点没有任何操作
|
func (n *conditionNode) Run(ctx context.Context) error {
|
||||||
func (c *conditionNode) Run(ctx context.Context) error {
|
// 此类型节点不需要执行任何操作,直接返回
|
||||||
c.AddOutput(ctx,
|
n.AddOutput(ctx, n.node.Name, "完成")
|
||||||
c.node.Name,
|
|
||||||
"完成",
|
|
||||||
)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -27,81 +27,81 @@ func NewDeployNode(node *domain.WorkflowNode) *deployNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *deployNode) Run(ctx context.Context) error {
|
func (n *deployNode) Run(ctx context.Context) error {
|
||||||
d.AddOutput(ctx, d.node.Name, "开始执行")
|
n.AddOutput(ctx, n.node.Name, "开始执行")
|
||||||
|
|
||||||
// 查询上次执行结果
|
// 查询上次执行结果
|
||||||
lastOutput, err := d.outputRepo.GetByNodeId(ctx, d.node.Id)
|
lastOutput, err := n.outputRepo.GetByNodeId(ctx, n.node.Id)
|
||||||
if err != nil && !domain.IsRecordNotFoundError(err) {
|
if err != nil && !domain.IsRecordNotFoundError(err) {
|
||||||
d.AddOutput(ctx, d.node.Name, "查询部署记录失败", err.Error())
|
n.AddOutput(ctx, n.node.Name, "查询部署记录失败", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取前序节点输出证书
|
// 获取前序节点输出证书
|
||||||
previousNodeOutputCertificateSource := d.node.GetConfigForDeploy().Certificate
|
previousNodeOutputCertificateSource := n.node.GetConfigForDeploy().Certificate
|
||||||
previousNodeOutputCertificateSourceSlice := strings.Split(previousNodeOutputCertificateSource, "#")
|
previousNodeOutputCertificateSourceSlice := strings.Split(previousNodeOutputCertificateSource, "#")
|
||||||
if len(previousNodeOutputCertificateSourceSlice) != 2 {
|
if len(previousNodeOutputCertificateSourceSlice) != 2 {
|
||||||
d.AddOutput(ctx, d.node.Name, "证书来源配置错误", previousNodeOutputCertificateSource)
|
n.AddOutput(ctx, n.node.Name, "证书来源配置错误", previousNodeOutputCertificateSource)
|
||||||
return fmt.Errorf("证书来源配置错误: %s", previousNodeOutputCertificateSource)
|
return fmt.Errorf("证书来源配置错误: %s", previousNodeOutputCertificateSource)
|
||||||
}
|
}
|
||||||
certificate, err := d.certRepo.GetByWorkflowNodeId(ctx, previousNodeOutputCertificateSourceSlice[0])
|
certificate, err := n.certRepo.GetByWorkflowNodeId(ctx, previousNodeOutputCertificateSourceSlice[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.AddOutput(ctx, d.node.Name, "获取证书失败", err.Error())
|
n.AddOutput(ctx, n.node.Name, "获取证书失败", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检测是否可以跳过本次执行
|
// 检测是否可以跳过本次执行
|
||||||
if skippable, skipReason := d.checkCanSkip(ctx, lastOutput); skippable {
|
if skippable, skipReason := n.checkCanSkip(ctx, lastOutput); skippable {
|
||||||
if certificate.CreatedAt.Before(lastOutput.UpdatedAt) {
|
if certificate.CreatedAt.Before(lastOutput.UpdatedAt) {
|
||||||
d.AddOutput(ctx, d.node.Name, "已部署过且证书未更新")
|
n.AddOutput(ctx, n.node.Name, "已部署过且证书未更新")
|
||||||
} else {
|
} else {
|
||||||
d.AddOutput(ctx, d.node.Name, skipReason)
|
n.AddOutput(ctx, n.node.Name, skipReason)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化部署器
|
// 初始化部署器
|
||||||
deploy, err := deployer.NewWithDeployNode(d.node, struct {
|
deploy, err := deployer.NewWithDeployNode(n.node, struct {
|
||||||
Certificate string
|
Certificate string
|
||||||
PrivateKey string
|
PrivateKey string
|
||||||
}{Certificate: certificate.Certificate, PrivateKey: certificate.PrivateKey})
|
}{Certificate: certificate.Certificate, PrivateKey: certificate.PrivateKey})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.AddOutput(ctx, d.node.Name, "获取部署对象失败", err.Error())
|
n.AddOutput(ctx, n.node.Name, "获取部署对象失败", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 部署证书
|
// 部署证书
|
||||||
if err := deploy.Deploy(ctx); err != nil {
|
if err := deploy.Deploy(ctx); err != nil {
|
||||||
d.AddOutput(ctx, d.node.Name, "部署失败", err.Error())
|
n.AddOutput(ctx, n.node.Name, "部署失败", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
d.AddOutput(ctx, d.node.Name, "部署成功")
|
n.AddOutput(ctx, n.node.Name, "部署成功")
|
||||||
|
|
||||||
// 保存执行结果
|
// 保存执行结果
|
||||||
// TODO: 先保持一个节点始终只有一个输出,后续增加版本控制
|
// TODO: 先保持一个节点始终只有一个输出,后续增加版本控制
|
||||||
currentOutput := &domain.WorkflowOutput{
|
currentOutput := &domain.WorkflowOutput{
|
||||||
Meta: domain.Meta{},
|
Meta: domain.Meta{},
|
||||||
WorkflowId: getContextWorkflowId(ctx),
|
WorkflowId: getContextWorkflowId(ctx),
|
||||||
NodeId: d.node.Id,
|
NodeId: n.node.Id,
|
||||||
Node: d.node,
|
Node: n.node,
|
||||||
Succeeded: true,
|
Succeeded: true,
|
||||||
}
|
}
|
||||||
if lastOutput != nil {
|
if lastOutput != nil {
|
||||||
currentOutput.Id = lastOutput.Id
|
currentOutput.Id = lastOutput.Id
|
||||||
}
|
}
|
||||||
if err := d.outputRepo.Save(ctx, currentOutput, nil, nil); err != nil {
|
if _, err := n.outputRepo.Save(ctx, currentOutput); err != nil {
|
||||||
d.AddOutput(ctx, d.node.Name, "保存部署记录失败", err.Error())
|
n.AddOutput(ctx, n.node.Name, "保存部署记录失败", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
d.AddOutput(ctx, d.node.Name, "保存部署记录成功")
|
n.AddOutput(ctx, n.node.Name, "保存部署记录成功")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *deployNode) checkCanSkip(ctx context.Context, lastOutput *domain.WorkflowOutput) (skip bool, reason string) {
|
func (n *deployNode) checkCanSkip(ctx context.Context, lastOutput *domain.WorkflowOutput) (skip bool, reason string) {
|
||||||
if lastOutput != nil && lastOutput.Succeeded {
|
if lastOutput != nil && lastOutput.Succeeded {
|
||||||
// 比较和上次部署时的关键配置(即影响证书部署的)参数是否一致
|
// 比较和上次部署时的关键配置(即影响证书部署的)参数是否一致
|
||||||
currentNodeConfig := d.node.GetConfigForDeploy()
|
currentNodeConfig := n.node.GetConfigForDeploy()
|
||||||
lastNodeConfig := lastOutput.Node.GetConfigForDeploy()
|
lastNodeConfig := lastOutput.Node.GetConfigForDeploy()
|
||||||
if currentNodeConfig.ProviderAccessId != lastNodeConfig.ProviderAccessId {
|
if currentNodeConfig.ProviderAccessId != lastNodeConfig.ProviderAccessId {
|
||||||
return false, "配置项变化:主机提供商授权"
|
return false, "配置项变化:主机提供商授权"
|
||||||
|
@ -18,10 +18,9 @@ func NewExecuteFailureNode(node *domain.WorkflowNode) *executeFailureNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *executeFailureNode) Run(ctx context.Context) error {
|
func (n *executeFailureNode) Run(ctx context.Context) error {
|
||||||
e.AddOutput(ctx,
|
// 此类型节点不需要执行任何操作,直接返回
|
||||||
e.node.Name,
|
n.AddOutput(ctx, n.node.Name, "进入执行失败分支")
|
||||||
"进入执行失败分支",
|
|
||||||
)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -18,10 +18,9 @@ func NewExecuteSuccessNode(node *domain.WorkflowNode) *executeSuccessNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *executeSuccessNode) Run(ctx context.Context) error {
|
func (n *executeSuccessNode) Run(ctx context.Context) error {
|
||||||
e.AddOutput(ctx,
|
// 此类型节点不需要执行任何操作,直接返回
|
||||||
e.node.Name,
|
n.AddOutput(ctx, n.node.Name, "进入执行成功分支")
|
||||||
"进入执行成功分支",
|
|
||||||
)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,9 @@ type certificateRepository interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type workflowOutputRepository interface {
|
type workflowOutputRepository interface {
|
||||||
GetByNodeId(ctx context.Context, nodeId string) (*domain.WorkflowOutput, error)
|
GetByNodeId(ctx context.Context, workflowNodeId string) (*domain.WorkflowOutput, error)
|
||||||
Save(ctx context.Context, output *domain.WorkflowOutput, certificate *domain.Certificate, cb func(id string) error) error
|
Save(ctx context.Context, workflowOutput *domain.WorkflowOutput) (*domain.WorkflowOutput, error)
|
||||||
|
SaveWithCertificate(ctx context.Context, workflowOutput *domain.WorkflowOutput, certificate *domain.Certificate) (*domain.WorkflowOutput, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type settingsRepository interface {
|
type settingsRepository interface {
|
||||||
|
@ -18,9 +18,9 @@ func NewStartNode(node *domain.WorkflowNode) *startNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *startNode) Run(ctx context.Context) error {
|
func (n *startNode) Run(ctx context.Context) error {
|
||||||
// 开始节点没有任何操作
|
// 此类型节点不需要执行任何操作,直接返回
|
||||||
s.AddOutput(ctx, s.node.Name, "完成")
|
n.AddOutput(ctx, n.node.Name, "完成")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ package nodeprocessor
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
@ -28,43 +27,34 @@ func NewUploadNode(node *domain.WorkflowNode) *uploadNode {
|
|||||||
// Run 上传证书节点执行
|
// Run 上传证书节点执行
|
||||||
// 包含上传证书的工作流,理论上应该手动执行,如果每天定时执行,也只是重新保存一下
|
// 包含上传证书的工作流,理论上应该手动执行,如果每天定时执行,也只是重新保存一下
|
||||||
func (n *uploadNode) Run(ctx context.Context) error {
|
func (n *uploadNode) Run(ctx context.Context) error {
|
||||||
n.AddOutput(ctx,
|
n.AddOutput(ctx, n.node.Name, "进入上传证书节点")
|
||||||
n.node.Name,
|
|
||||||
"进入上传证书节点",
|
|
||||||
)
|
|
||||||
|
|
||||||
config := n.node.GetConfigForUpload()
|
nodeConfig := n.node.GetConfigForUpload()
|
||||||
|
|
||||||
// 检查证书是否过期
|
// 查询上次执行结果
|
||||||
// 如果证书过期,则直接返回错误
|
lastOutput, err := n.outputRepo.GetByNodeId(ctx, n.node.Id)
|
||||||
certX509, err := certs.ParseCertificateFromPEM(config.Certificate)
|
if err != nil && !domain.IsRecordNotFoundError(err) {
|
||||||
if err != nil {
|
n.AddOutput(ctx, n.node.Name, "查询申请记录失败", err.Error())
|
||||||
n.AddOutput(ctx,
|
|
||||||
n.node.Name,
|
|
||||||
"解析证书失败",
|
|
||||||
)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查证书是否过期
|
||||||
|
// 如果证书过期,则直接返回错误
|
||||||
|
certX509, err := certs.ParseCertificateFromPEM(nodeConfig.Certificate)
|
||||||
|
if err != nil {
|
||||||
|
n.AddOutput(ctx, n.node.Name, "解析证书失败")
|
||||||
|
return err
|
||||||
|
}
|
||||||
if time.Now().After(certX509.NotAfter) {
|
if time.Now().After(certX509.NotAfter) {
|
||||||
n.AddOutput(ctx,
|
n.AddOutput(ctx, n.node.Name, "证书已过期")
|
||||||
n.node.Name,
|
|
||||||
"证书已过期",
|
|
||||||
)
|
|
||||||
return errors.New("certificate is expired")
|
return errors.New("certificate is expired")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 生成实体
|
||||||
certificate := &domain.Certificate{
|
certificate := &domain.Certificate{
|
||||||
Source: domain.CertificateSourceTypeUpload,
|
Source: domain.CertificateSourceTypeUpload,
|
||||||
SubjectAltNames: strings.Join(certX509.DNSNames, ";"),
|
|
||||||
Certificate: config.Certificate,
|
|
||||||
PrivateKey: config.PrivateKey,
|
|
||||||
|
|
||||||
EffectAt: certX509.NotBefore,
|
|
||||||
ExpireAt: certX509.NotAfter,
|
|
||||||
WorkflowId: getContextWorkflowId(ctx),
|
|
||||||
WorkflowNodeId: n.node.Id,
|
|
||||||
}
|
}
|
||||||
|
certificate.PopulateFromPEM(nodeConfig.Certificate, nodeConfig.PrivateKey)
|
||||||
|
|
||||||
// 保存执行结果
|
// 保存执行结果
|
||||||
// TODO: 先保持一个节点始终只有一个输出,后续增加版本控制
|
// TODO: 先保持一个节点始终只有一个输出,后续增加版本控制
|
||||||
@ -75,23 +65,10 @@ func (n *uploadNode) Run(ctx context.Context) error {
|
|||||||
Succeeded: true,
|
Succeeded: true,
|
||||||
Outputs: n.node.Outputs,
|
Outputs: n.node.Outputs,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询上次执行结果
|
|
||||||
lastOutput, err := n.outputRepo.GetByNodeId(ctx, n.node.Id)
|
|
||||||
if err != nil && !domain.IsRecordNotFoundError(err) {
|
|
||||||
n.AddOutput(ctx, n.node.Name, "查询上传记录失败", err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if lastOutput != nil {
|
if lastOutput != nil {
|
||||||
currentOutput.Id = lastOutput.Id
|
currentOutput.Id = lastOutput.Id
|
||||||
}
|
}
|
||||||
if err := n.outputRepo.Save(ctx, currentOutput, certificate, func(id string) error {
|
if _, err := n.outputRepo.SaveWithCertificate(ctx, currentOutput, certificate); err != nil {
|
||||||
if certificate != nil {
|
|
||||||
certificate.WorkflowOutputId = id
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}); err != nil {
|
|
||||||
n.AddOutput(ctx, n.node.Name, "保存上传记录失败", err.Error())
|
n.AddOutput(ctx, n.node.Name, "保存上传记录失败", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
127
migrations/1738767422_updated_certificate.go
Normal file
127
migrations/1738767422_updated_certificate.go
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/pocketbase/pocketbase/core"
|
||||||
|
m "github.com/pocketbase/pocketbase/migrations"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
m.Register(func(app core.App) error {
|
||||||
|
collection, err := app.FindCollectionByNameOrId("4szxr9x43tpj6np")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update collection data
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"indexes": [
|
||||||
|
"CREATE INDEX ` + "`" + `idx_Jx8TXzDCmw` + "`" + ` ON ` + "`" + `certificate` + "`" + ` (` + "`" + `workflowId` + "`" + `)",
|
||||||
|
"CREATE INDEX ` + "`" + `idx_kcKpgAZapk` + "`" + ` ON ` + "`" + `certificate` + "`" + ` (` + "`" + `workflowNodeId` + "`" + `)"
|
||||||
|
]
|
||||||
|
}`), &collection); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// add field
|
||||||
|
if err := collection.Fields.AddMarshaledJSONAt(3, []byte(`{
|
||||||
|
"autogeneratePattern": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "text2069360702",
|
||||||
|
"max": 0,
|
||||||
|
"min": 0,
|
||||||
|
"name": "serialNumber",
|
||||||
|
"pattern": "",
|
||||||
|
"presentable": false,
|
||||||
|
"primaryKey": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "text"
|
||||||
|
}`)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// add field
|
||||||
|
if err := collection.Fields.AddMarshaledJSONAt(6, []byte(`{
|
||||||
|
"autogeneratePattern": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "text2910474005",
|
||||||
|
"max": 0,
|
||||||
|
"min": 0,
|
||||||
|
"name": "issuer",
|
||||||
|
"pattern": "",
|
||||||
|
"presentable": false,
|
||||||
|
"primaryKey": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "text"
|
||||||
|
}`)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// add field
|
||||||
|
if err := collection.Fields.AddMarshaledJSONAt(8, []byte(`{
|
||||||
|
"autogeneratePattern": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "text4164403445",
|
||||||
|
"max": 0,
|
||||||
|
"min": 0,
|
||||||
|
"name": "keyAlgorithm",
|
||||||
|
"pattern": "",
|
||||||
|
"presentable": false,
|
||||||
|
"primaryKey": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "text"
|
||||||
|
}`)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// add field
|
||||||
|
if err := collection.Fields.AddMarshaledJSONAt(11, []byte(`{
|
||||||
|
"autogeneratePattern": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "text2045248758",
|
||||||
|
"max": 0,
|
||||||
|
"min": 0,
|
||||||
|
"name": "acmeAccountUrl",
|
||||||
|
"pattern": "",
|
||||||
|
"presentable": false,
|
||||||
|
"primaryKey": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "text"
|
||||||
|
}`)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.Save(collection)
|
||||||
|
}, func(app core.App) error {
|
||||||
|
collection, err := app.FindCollectionByNameOrId("4szxr9x43tpj6np")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update collection data
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"indexes": []
|
||||||
|
}`), &collection); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove field
|
||||||
|
collection.Fields.RemoveById("text2069360702")
|
||||||
|
|
||||||
|
// remove field
|
||||||
|
collection.Fields.RemoveById("text2910474005")
|
||||||
|
|
||||||
|
// remove field
|
||||||
|
collection.Fields.RemoveById("text4164403445")
|
||||||
|
|
||||||
|
// remove field
|
||||||
|
collection.Fields.RemoveById("text2045248758")
|
||||||
|
|
||||||
|
return app.Save(collection)
|
||||||
|
})
|
||||||
|
}
|
@ -38,11 +38,27 @@ const CertificateDetail = ({ data, ...props }: CertificateDetailProps) => {
|
|||||||
|
|
||||||
<Form layout="vertical">
|
<Form layout="vertical">
|
||||||
<Form.Item label={t("certificate.props.subject_alt_names")}>
|
<Form.Item label={t("certificate.props.subject_alt_names")}>
|
||||||
<Input value={data.subjectAltNames} placeholder="" />
|
<Input value={data.subjectAltNames} variant="filled" placeholder="" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label={t("certificate.props.issuer")}>
|
||||||
|
<Input value={data.issuer} variant="filled" placeholder="" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item label={t("certificate.props.validity")}>
|
<Form.Item label={t("certificate.props.validity")}>
|
||||||
<Input value={`${dayjs(data.effectAt).format("YYYY-MM-DD HH:mm:ss")} ~ ${dayjs(data.expireAt).format("YYYY-MM-DD HH:mm:ss")}`} placeholder="" />
|
<Input
|
||||||
|
value={`${dayjs(data.effectAt).format("YYYY-MM-DD HH:mm:ss")} ~ ${dayjs(data.expireAt).format("YYYY-MM-DD HH:mm:ss")}`}
|
||||||
|
variant="filled"
|
||||||
|
placeholder=""
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label={t("certificate.props.serial_number")}>
|
||||||
|
<Input value={data.serialNumber} variant="filled" placeholder="" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label={t("certificate.props.key_algorithm")}>
|
||||||
|
<Input value={data.keyAlgorithm} variant="filled" placeholder="" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
@ -59,7 +75,7 @@ const CertificateDetail = ({ data, ...props }: CertificateDetailProps) => {
|
|||||||
</CopyToClipboard>
|
</CopyToClipboard>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<Input.TextArea value={data.certificate} rows={10} autoSize={{ maxRows: 10 }} readOnly />
|
<Input.TextArea value={data.certificate} variant="filled" rows={5} autoSize={{ maxRows: 5 }} readOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
@ -76,7 +92,7 @@ const CertificateDetail = ({ data, ...props }: CertificateDetailProps) => {
|
|||||||
</CopyToClipboard>
|
</CopyToClipboard>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<Input.TextArea value={data.privateKey} rows={10} autoSize={{ maxRows: 10 }} readOnly />
|
<Input.TextArea value={data.privateKey} variant="filled" rows={5} autoSize={{ maxRows: 5 }} readOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
|
@ -137,7 +137,7 @@ const UploadNodeConfigForm = forwardRef<UploadNodeConfigFormInstance, UploadNode
|
|||||||
return (
|
return (
|
||||||
<Form className={className} style={style} {...formProps} disabled={disabled} layout="vertical" scrollToFirstError onValuesChange={handleFormChange}>
|
<Form className={className} style={style} {...formProps} disabled={disabled} layout="vertical" scrollToFirstError onValuesChange={handleFormChange}>
|
||||||
<Form.Item name="domains" label={t("workflow_node.upload.form.domains.label")} rules={[formRule]}>
|
<Form.Item name="domains" label={t("workflow_node.upload.form.domains.label")} rules={[formRule]}>
|
||||||
<Input placeholder={t("workflow_node.upload.form.domains.placeholder")} readOnly />
|
<Input variant="filled" placeholder={t("workflow_node.upload.form.domains.placeholder")} readOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item name="certificate" label={t("workflow_node.upload.form.certificate.label")} rules={[formRule]}>
|
<Form.Item name="certificate" label={t("workflow_node.upload.form.certificate.label")} rules={[formRule]}>
|
||||||
|
@ -3,8 +3,11 @@ import { type WorkflowModel } from "./workflow";
|
|||||||
export interface CertificateModel extends BaseModel {
|
export interface CertificateModel extends BaseModel {
|
||||||
source: string;
|
source: string;
|
||||||
subjectAltNames: string;
|
subjectAltNames: string;
|
||||||
|
serialNumber: string;
|
||||||
certificate: string;
|
certificate: string;
|
||||||
privateKey: string;
|
privateKey: string;
|
||||||
|
issuer: string;
|
||||||
|
keyAlgorithm: string;
|
||||||
effectAt: ISO8601String;
|
effectAt: ISO8601String;
|
||||||
expireAt: ISO8601String;
|
expireAt: ISO8601String;
|
||||||
workflowId: string;
|
workflowId: string;
|
||||||
|
@ -15,11 +15,15 @@
|
|||||||
"certificate.props.validity.expiration": "Expire on {{date}}",
|
"certificate.props.validity.expiration": "Expire on {{date}}",
|
||||||
"certificate.props.validity.filter.expire_soon": "Expire soon",
|
"certificate.props.validity.filter.expire_soon": "Expire soon",
|
||||||
"certificate.props.validity.filter.expired": "Expired",
|
"certificate.props.validity.filter.expired": "Expired",
|
||||||
|
"certificate.props.brand": "Brand",
|
||||||
"certificate.props.source": "Source",
|
"certificate.props.source": "Source",
|
||||||
"certificate.props.source.workflow": "Workflow",
|
"certificate.props.source.workflow": "Workflow",
|
||||||
"certificate.props.source.upload": "Upload",
|
"certificate.props.source.upload": "Upload",
|
||||||
"certificate.props.certificate": "Certificate chain",
|
"certificate.props.certificate": "Certificate chain",
|
||||||
"certificate.props.private_key": "Private key",
|
"certificate.props.private_key": "Private key",
|
||||||
|
"certificate.props.serial_number": "Serial number",
|
||||||
|
"certificate.props.key_algorithm": "Key algorithm",
|
||||||
|
"certificate.props.issuer": "Issuer",
|
||||||
"certificate.props.created_at": "Created at",
|
"certificate.props.created_at": "Created at",
|
||||||
"certificate.props.updated_at": "Updated at"
|
"certificate.props.updated_at": "Updated at"
|
||||||
}
|
}
|
||||||
|
@ -15,11 +15,15 @@
|
|||||||
"certificate.props.validity.expiration": "{{date}} 到期",
|
"certificate.props.validity.expiration": "{{date}} 到期",
|
||||||
"certificate.props.validity.filter.expire_soon": "即将到期",
|
"certificate.props.validity.filter.expire_soon": "即将到期",
|
||||||
"certificate.props.validity.filter.expired": "已到期",
|
"certificate.props.validity.filter.expired": "已到期",
|
||||||
|
"certificate.props.brand": "证书品牌",
|
||||||
"certificate.props.source": "来源",
|
"certificate.props.source": "来源",
|
||||||
"certificate.props.source.workflow": "工作流",
|
"certificate.props.source.workflow": "工作流",
|
||||||
"certificate.props.source.upload": "用户上传",
|
"certificate.props.source.upload": "用户上传",
|
||||||
"certificate.props.certificate": "证书内容",
|
"certificate.props.certificate": "证书内容",
|
||||||
"certificate.props.private_key": "私钥内容",
|
"certificate.props.private_key": "私钥内容",
|
||||||
|
"certificate.props.serial_number": "证书序列号",
|
||||||
|
"certificate.props.key_algorithm": "证书算法",
|
||||||
|
"certificate.props.issuer": "颁发者",
|
||||||
"certificate.props.created_at": "创建时间",
|
"certificate.props.created_at": "创建时间",
|
||||||
"certificate.props.updated_at": "更新时间"
|
"certificate.props.updated_at": "更新时间"
|
||||||
}
|
}
|
||||||
|
@ -107,6 +107,16 @@ const CertificateList = () => {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "issuer",
|
||||||
|
title: t("certificate.props.brand"),
|
||||||
|
render: (_, record) => (
|
||||||
|
<Space className="max-w-full" direction="vertical" size={4}>
|
||||||
|
<Typography.Text>{record.issuer}</Typography.Text>
|
||||||
|
<Typography.Text>{record.keyAlgorithm}</Typography.Text>
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "source",
|
key: "source",
|
||||||
title: t("certificate.props.source"),
|
title: t("certificate.props.source"),
|
||||||
@ -250,7 +260,7 @@ const CertificateList = () => {
|
|||||||
dataSource={tableData}
|
dataSource={tableData}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
locale={{
|
locale={{
|
||||||
emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={loadedError ? getErrMsg(loadedError) : t("certificate.nodata")} />,
|
emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={getErrMsg(loadedError ?? t("certificate.nodata"))} />,
|
||||||
}}
|
}}
|
||||||
pagination={{
|
pagination={{
|
||||||
current: page,
|
current: page,
|
||||||
|
@ -60,13 +60,13 @@ const WorkflowDetail = () => {
|
|||||||
const [allowRun, setAllowRun] = useState(false);
|
const [allowRun, setAllowRun] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsRunning(lastRunStatus == WORKFLOW_RUN_STATUSES.RUNNING);
|
setIsRunning(lastRunStatus == WORKFLOW_RUN_STATUSES.PENDING || lastRunStatus == WORKFLOW_RUN_STATUSES.RUNNING);
|
||||||
}, [lastRunStatus]);
|
}, [lastRunStatus]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!!workflowId && isRunning) {
|
if (!!workflowId && isRunning) {
|
||||||
subscribeWorkflow(workflowId, (e) => {
|
subscribeWorkflow(workflowId, (e) => {
|
||||||
if (e.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.RUNNING) {
|
if (e.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.PENDING && e.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.RUNNING) {
|
||||||
setIsRunning(false);
|
setIsRunning(false);
|
||||||
unsubscribeWorkflow(workflowId);
|
unsubscribeWorkflow(workflowId);
|
||||||
}
|
}
|
||||||
@ -178,7 +178,7 @@ const WorkflowDetail = () => {
|
|||||||
|
|
||||||
// subscribe before running workflow
|
// subscribe before running workflow
|
||||||
unsubscribeFn = await subscribeWorkflow(workflowId!, (e) => {
|
unsubscribeFn = await subscribeWorkflow(workflowId!, (e) => {
|
||||||
if (e.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.RUNNING) {
|
if (e.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.PENDING && e.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.RUNNING) {
|
||||||
setIsRunning(false);
|
setIsRunning(false);
|
||||||
unsubscribeFn?.();
|
unsubscribeFn?.();
|
||||||
}
|
}
|
||||||
|
@ -350,7 +350,7 @@ const WorkflowList = () => {
|
|||||||
dataSource={tableData}
|
dataSource={tableData}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
locale={{
|
locale={{
|
||||||
emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={loadedError ? getErrMsg(loadedError) : t("workflow.nodata")} />,
|
emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={getErrMsg(loadedError ?? t("workflow.nodata"))} />,
|
||||||
}}
|
}}
|
||||||
pagination={{
|
pagination={{
|
||||||
current: page,
|
current: page,
|
||||||
|
@ -7,13 +7,13 @@ export const getErrMsg = (error: unknown): string => {
|
|||||||
return error.message;
|
return error.message;
|
||||||
} else if (typeof error === "object" && error != null) {
|
} else if (typeof error === "object" && error != null) {
|
||||||
if ("message" in error) {
|
if ("message" in error) {
|
||||||
return String(error.message);
|
return getErrMsg(error.message);
|
||||||
} else if ("msg" in error) {
|
} else if ("msg" in error) {
|
||||||
return String(error.msg);
|
return getErrMsg(error.msg);
|
||||||
}
|
}
|
||||||
} else if (typeof error === "string") {
|
} else if (typeof error === "string") {
|
||||||
return error;
|
return error || "Unknown error";
|
||||||
}
|
}
|
||||||
|
|
||||||
return String(error ?? "Unknown error");
|
return "Unknown error";
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user