fix conflict

This commit is contained in:
yoan
2025-01-19 19:02:58 +08:00
142 changed files with 3458 additions and 8971 deletions

View File

@@ -4,14 +4,16 @@ import (
"log/slog"
"sync"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
var instance *pocketbase.PocketBase
var instance core.App
var intanceOnce sync.Once
func GetApp() *pocketbase.PocketBase {
func GetApp() core.App {
intanceOnce.Do(func() {
instance = pocketbase.NewWithConfig(pocketbase.Config{
HideStartBanner: true,
@@ -21,6 +23,10 @@ func GetApp() *pocketbase.PocketBase {
return instance
}
func GetDB() dbx.Builder {
return GetApp().DB()
}
func GetLogger() *slog.Logger {
return GetApp().Logger()
}

View File

@@ -12,11 +12,13 @@ var scheduler *cron.Cron
var schedulerOnce sync.Once
func GetScheduler() *cron.Cron {
scheduler = GetApp().Cron()
schedulerOnce.Do(func() {
scheduler = cron.New()
location, err := time.LoadLocation("Local")
if err == nil {
scheduler.Stop()
scheduler.SetTimezone(location)
scheduler.Start()
}
})

View File

@@ -35,15 +35,17 @@ type Applicant interface {
}
type applicantOptions struct {
Domains []string
ContactEmail string
Provider domain.ApplyDNSProviderType
ProviderAccessConfig map[string]any
ProviderApplyConfig map[string]any
KeyAlgorithm string
Nameservers []string
PropagationTimeout int32
DisableFollowCNAME bool
Domains []string
ContactEmail string
Provider domain.ApplyDNSProviderType
ProviderAccessConfig map[string]any
ProviderApplyConfig map[string]any
KeyAlgorithm string
Nameservers []string
DnsPropagationTimeout int32
DnsTTL int32
DisableFollowCNAME bool
SkipBeforeExpiryDays int32
}
func NewWithApplyNode(node *domain.WorkflowNode) (Applicant, error) {
@@ -51,11 +53,12 @@ func NewWithApplyNode(node *domain.WorkflowNode) (Applicant, error) {
return nil, fmt.Errorf("node type is not apply")
}
nodeConfig := node.GetConfigForApply()
accessRepo := repository.NewAccessRepository()
accessId := node.GetConfigString("providerAccessId")
access, err := accessRepo.GetById(context.Background(), accessId)
access, err := accessRepo.GetById(context.Background(), nodeConfig.ProviderAccessId)
if err != nil {
return nil, fmt.Errorf("failed to get access #%s record: %w", accessId, err)
return nil, fmt.Errorf("failed to get access #%s record: %w", nodeConfig.ProviderAccessId, err)
}
accessConfig, err := access.UnmarshalConfigToMap()
@@ -64,15 +67,17 @@ func NewWithApplyNode(node *domain.WorkflowNode) (Applicant, error) {
}
options := &applicantOptions{
Domains: slices.Filter(strings.Split(node.GetConfigString("domains"), ";"), func(s string) bool { return s != "" }),
ContactEmail: node.GetConfigString("contactEmail"),
Provider: domain.ApplyDNSProviderType(node.GetConfigString("provider")),
ProviderAccessConfig: accessConfig,
ProviderApplyConfig: node.GetConfigMap("providerConfig"),
KeyAlgorithm: node.GetConfigString("keyAlgorithm"),
Nameservers: slices.Filter(strings.Split(node.GetConfigString("nameservers"), ";"), func(s string) bool { return s != "" }),
PropagationTimeout: node.GetConfigInt32("propagationTimeout"),
DisableFollowCNAME: node.GetConfigBool("disableFollowCNAME"),
Domains: slices.Filter(strings.Split(nodeConfig.Domains, ";"), func(s string) bool { return s != "" }),
ContactEmail: nodeConfig.ContactEmail,
Provider: domain.ApplyDNSProviderType(nodeConfig.Provider),
ProviderAccessConfig: accessConfig,
ProviderApplyConfig: nodeConfig.ProviderConfig,
KeyAlgorithm: nodeConfig.KeyAlgorithm,
Nameservers: slices.Filter(strings.Split(nodeConfig.Nameservers, ";"), func(s string) bool { return s != "" }),
DnsPropagationTimeout: nodeConfig.DnsPropagationTimeout,
DnsTTL: nodeConfig.DnsTTL,
DisableFollowCNAME: nodeConfig.DisableFollowCNAME,
SkipBeforeExpiryDays: nodeConfig.SkipBeforeExpiryDays,
}
applicant, err := createApplicant(options)

View File

@@ -1,4 +1,4 @@
package applicant
package applicant_test
import (
"testing"

View File

@@ -36,11 +36,11 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
}
applicant, err := providerACMEHttpReq.NewChallengeProvider(&providerACMEHttpReq.ACMEHttpReqApplicantConfig{
Endpoint: access.Endpoint,
Mode: access.Mode,
Username: access.Username,
Password: access.Password,
PropagationTimeout: options.PropagationTimeout,
Endpoint: access.Endpoint,
Mode: access.Mode,
Username: access.Username,
Password: access.Password,
DnsPropagationTimeout: options.DnsPropagationTimeout,
})
return applicant, err
}
@@ -53,9 +53,10 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
}
applicant, err := providerAliyun.NewChallengeProvider(&providerAliyun.AliyunApplicantConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
PropagationTimeout: options.PropagationTimeout,
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
@@ -68,11 +69,12 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
}
applicant, err := providerAWSRoute53.NewChallengeProvider(&providerAWSRoute53.AWSRoute53ApplicantConfig{
AccessKeyId: access.AccessKeyId,
SecretAccessKey: access.SecretAccessKey,
Region: maps.GetValueAsString(options.ProviderApplyConfig, "region"),
HostedZoneId: maps.GetValueAsString(options.ProviderApplyConfig, "hostedZoneId"),
PropagationTimeout: options.PropagationTimeout,
AccessKeyId: access.AccessKeyId,
SecretAccessKey: access.SecretAccessKey,
Region: maps.GetValueAsString(options.ProviderApplyConfig, "region"),
HostedZoneId: maps.GetValueAsString(options.ProviderApplyConfig, "hostedZoneId"),
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
@@ -85,11 +87,12 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
}
applicant, err := providerAzureDNS.NewChallengeProvider(&providerAzureDNS.AzureDNSApplicantConfig{
TenantId: access.TenantId,
ClientId: access.ClientId,
ClientSecret: access.ClientSecret,
CloudName: access.CloudName,
PropagationTimeout: options.PropagationTimeout,
TenantId: access.TenantId,
ClientId: access.ClientId,
ClientSecret: access.ClientSecret,
CloudName: access.CloudName,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
@@ -102,8 +105,9 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
}
applicant, err := providerCloudflare.NewChallengeProvider(&providerCloudflare.CloudflareApplicantConfig{
DnsApiToken: access.DnsApiToken,
PropagationTimeout: options.PropagationTimeout,
DnsApiToken: access.DnsApiToken,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
@@ -116,9 +120,10 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
}
applicant, err := providerGoDaddy.NewChallengeProvider(&providerGoDaddy.GoDaddyApplicantConfig{
ApiKey: access.ApiKey,
ApiSecret: access.ApiSecret,
PropagationTimeout: options.PropagationTimeout,
ApiKey: access.ApiKey,
ApiSecret: access.ApiSecret,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
@@ -131,10 +136,11 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
}
applicant, err := providerHuaweiCloud.NewChallengeProvider(&providerHuaweiCloud.HuaweiCloudApplicantConfig{
AccessKeyId: access.AccessKeyId,
SecretAccessKey: access.SecretAccessKey,
Region: maps.GetValueAsString(options.ProviderApplyConfig, "region"),
PropagationTimeout: options.PropagationTimeout,
AccessKeyId: access.AccessKeyId,
SecretAccessKey: access.SecretAccessKey,
Region: maps.GetValueAsString(options.ProviderApplyConfig, "region"),
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
@@ -147,9 +153,10 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
}
applicant, err := providerNameDotCom.NewChallengeProvider(&providerNameDotCom.NameDotComApplicantConfig{
Username: access.Username,
ApiToken: access.ApiToken,
PropagationTimeout: options.PropagationTimeout,
Username: access.Username,
ApiToken: access.ApiToken,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
@@ -162,8 +169,9 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
}
applicant, err := providerNameSilo.NewChallengeProvider(&providerNameSilo.NameSiloApplicantConfig{
ApiKey: access.ApiKey,
PropagationTimeout: options.PropagationTimeout,
ApiKey: access.ApiKey,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
@@ -176,8 +184,9 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
}
applicant, err := providerNS1.NewChallengeProvider(&providerNS1.NS1ApplicantConfig{
ApiKey: access.ApiKey,
PropagationTimeout: options.PropagationTimeout,
ApiKey: access.ApiKey,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
@@ -190,9 +199,10 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
}
applicant, err := providerPowerDNS.NewChallengeProvider(&providerPowerDNS.PowerDNSApplicantConfig{
ApiUrl: access.ApiUrl,
ApiKey: access.ApiKey,
PropagationTimeout: options.PropagationTimeout,
ApiUrl: access.ApiUrl,
ApiKey: access.ApiKey,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
@@ -205,9 +215,10 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
}
applicant, err := providerTencentCloud.NewChallengeProvider(&providerTencentCloud.TencentCloudApplicantConfig{
SecretId: access.SecretId,
SecretKey: access.SecretKey,
PropagationTimeout: options.PropagationTimeout,
SecretId: access.SecretId,
SecretKey: access.SecretKey,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
@@ -220,9 +231,10 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
}
applicant, err := providerVolcEngine.NewChallengeProvider(&providerVolcEngine.VolcEngineApplicantConfig{
AccessKeyId: access.AccessKeyId,
SecretAccessKey: access.SecretAccessKey,
PropagationTimeout: options.PropagationTimeout,
AccessKeyId: access.AccessKeyId,
SecretAccessKey: access.SecretAccessKey,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}

View File

@@ -1,6 +1,8 @@
package certificate
import (
"archive/zip"
"bytes"
"context"
"encoding/json"
"strconv"
@@ -8,7 +10,9 @@ import (
"github.com/usual2970/certimate/internal/app"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/domain/dtos"
"github.com/usual2970/certimate/internal/notify"
"github.com/usual2970/certimate/internal/pkg/utils/certs"
"github.com/usual2970/certimate/internal/repository"
)
@@ -18,6 +22,7 @@ const (
)
type certificateRepository interface {
GetById(ctx context.Context, id string) (*domain.Certificate, error)
ListExpireSoon(ctx context.Context) ([]*domain.Certificate, error)
}
@@ -32,8 +37,7 @@ func NewCertificateService(repo certificateRepository) *CertificateService {
}
func (s *CertificateService) InitSchedule(ctx context.Context) error {
scheduler := app.GetScheduler()
err := scheduler.Add("certificate", "0 0 * * *", func() {
app.GetScheduler().MustAdd("certificateExpireSoonNotify", "0 0 * * *", func() {
certs, err := s.repo.ListExpireSoon(context.Background())
if err != nil {
app.GetLogger().Error("failed to get certificates which expire soon", "err", err)
@@ -49,15 +53,129 @@ func (s *CertificateService) InitSchedule(ctx context.Context) error {
app.GetLogger().Error("failed to send notification", "err", err)
}
})
if err != nil {
app.GetLogger().Error("failed to add schedule", "err", err)
return err
}
scheduler.Start()
app.GetLogger().Info("certificate schedule started")
return nil
}
func (s *CertificateService) ArchiveFile(ctx context.Context, req *dtos.CertificateArchiveFileReq) ([]byte, error) {
certificate, err := s.repo.GetById(ctx, req.CertificateId)
if err != nil {
return nil, err
}
var buf bytes.Buffer
zipWriter := zip.NewWriter(&buf)
defer zipWriter.Close()
switch strings.ToUpper(req.Format) {
case "", "PEM":
{
certWriter, err := zipWriter.Create("certbundle.pem")
if err != nil {
return nil, err
}
_, err = certWriter.Write([]byte(certificate.Certificate))
if err != nil {
return nil, err
}
keyWriter, err := zipWriter.Create("privkey.pem")
if err != nil {
return nil, err
}
_, err = keyWriter.Write([]byte(certificate.PrivateKey))
if err != nil {
return nil, err
}
err = zipWriter.Close()
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
case "PFX":
{
const pfxPassword = "certimate"
certPFX, err := certs.TransformCertificateFromPEMToPFX(certificate.Certificate, certificate.PrivateKey, pfxPassword)
if err != nil {
return nil, err
}
certWriter, err := zipWriter.Create("cert.pfx")
if err != nil {
return nil, err
}
_, err = certWriter.Write(certPFX)
if err != nil {
return nil, err
}
keyWriter, err := zipWriter.Create("pfx-password.txt")
if err != nil {
return nil, err
}
_, err = keyWriter.Write([]byte(pfxPassword))
if err != nil {
return nil, err
}
err = zipWriter.Close()
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
case "JKS":
{
const jksPassword = "certimate"
certJKS, err := certs.TransformCertificateFromPEMToJKS(certificate.Certificate, certificate.PrivateKey, jksPassword, jksPassword, jksPassword)
if err != nil {
return nil, err
}
certWriter, err := zipWriter.Create("cert.jks")
if err != nil {
return nil, err
}
_, err = certWriter.Write(certJKS)
if err != nil {
return nil, err
}
keyWriter, err := zipWriter.Create("jks-password.txt")
if err != nil {
return nil, err
}
_, err = keyWriter.Write([]byte(jksPassword))
if err != nil {
return nil, err
}
err = zipWriter.Close()
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
default:
return nil, domain.ErrInvalidParams
}
}
func buildExpireSoonNotification(certificates []*domain.Certificate) *struct {
Subject string
Message string
@@ -70,11 +188,11 @@ func buildExpireSoonNotification(certificates []*domain.Certificate) *struct {
message := defaultExpireMessage
// 查询模板信息
settingRepo := repository.NewSettingsRepository()
setting, err := settingRepo.GetByName(context.Background(), "notifyTemplates")
settingsRepo := repository.NewSettingsRepository()
settings, err := settingsRepo.GetByName(context.Background(), "notifyTemplates")
if err == nil {
var templates *domain.NotifyTemplatesSettingsContent
json.Unmarshal([]byte(setting.Content), &templates)
json.Unmarshal([]byte(settings.Content), &templates)
if templates != nil && len(templates.NotifyTemplates) > 0 {
subject = templates.NotifyTemplates[0].Subject

View File

@@ -29,11 +29,12 @@ func NewWithDeployNode(node *domain.WorkflowNode, certdata struct {
return nil, fmt.Errorf("node type is not deploy")
}
nodeConfig := node.GetConfigForDeploy()
accessRepo := repository.NewAccessRepository()
accessId := node.GetConfigString("providerAccessId")
access, err := accessRepo.GetById(context.Background(), accessId)
access, err := accessRepo.GetById(context.Background(), nodeConfig.ProviderAccessId)
if err != nil {
return nil, fmt.Errorf("failed to get access #%s record: %w", accessId, err)
return nil, fmt.Errorf("failed to get access #%s record: %w", nodeConfig.ProviderAccessId, err)
}
accessConfig, err := access.UnmarshalConfigToMap()
@@ -42,9 +43,9 @@ func NewWithDeployNode(node *domain.WorkflowNode, certdata struct {
}
deployer, logger, err := createDeployer(&deployerOptions{
Provider: domain.DeployProviderType(node.GetConfigString("provider")),
Provider: domain.DeployProviderType(nodeConfig.Provider),
ProviderAccessConfig: accessConfig,
ProviderDeployConfig: node.GetConfigMap("providerConfig"),
ProviderDeployConfig: nodeConfig.ProviderConfig,
})
if err != nil {
return nil, err

View File

@@ -5,6 +5,8 @@ import (
"time"
)
const CollectionNameAccess = "access"
type Access struct {
Meta
Name string `json:"name" db:"name"`

View File

@@ -4,6 +4,8 @@ import (
"github.com/go-acme/lego/v4/registration"
)
const CollectionNameAcmeAccount = "acme_accounts"
type AcmeAccount struct {
Meta
CA string `json:"ca" db:"ca"`

View File

@@ -2,12 +2,7 @@ package domain
import "time"
type CertificateSourceType string
const (
CertificateSourceTypeWorkflow = CertificateSourceType("workflow")
CertificateSourceTypeUpload = CertificateSourceType("upload")
)
const CollectionNameCertificate = "certificate"
type Certificate struct {
Meta
@@ -25,3 +20,10 @@ type Certificate struct {
WorkflowOutputId string `json:"workflowOutputId" db:"workflowOutputId"`
DeletedAt *time.Time `json:"deleted" db:"deleted"`
}
type CertificateSourceType string
const (
CertificateSourceTypeWorkflow = CertificateSourceType("workflow")
CertificateSourceTypeUpload = CertificateSourceType("upload")
)

View File

@@ -0,0 +1,6 @@
package dtos
type CertificateArchiveFileReq struct {
CertificateId string `json:"-"`
Format string `json:"format"`
}

View File

@@ -0,0 +1,7 @@
package dtos
import "github.com/usual2970/certimate/internal/domain"
type NotifyTestPushReq struct {
Channel domain.NotifyChannelType `json:"channel"`
}

View File

@@ -0,0 +1,8 @@
package dtos
import "github.com/usual2970/certimate/internal/domain"
type WorkflowRunReq struct {
WorkflowId string `json:"-"`
Trigger domain.WorkflowTriggerType `json:"trigger"`
}

View File

@@ -18,7 +18,3 @@ const (
NotifyChannelTypeWebhook = NotifyChannelType("webhook")
NotifyChannelTypeWeCom = NotifyChannelType("wecom")
)
type NotifyTestPushReq struct {
Channel string `json:"channel"`
}

View File

@@ -5,6 +5,8 @@ import (
"fmt"
)
const CollectionNameSettings = "settings"
type Settings struct {
Meta
Name string `json:"name" db:"name"`

View File

@@ -6,6 +6,23 @@ import (
"github.com/usual2970/certimate/internal/pkg/utils/maps"
)
const CollectionNameWorkflow = "workflow"
type Workflow struct {
Meta
Name string `json:"name" db:"name"`
Description string `json:"description" db:"description"`
Trigger WorkflowTriggerType `json:"trigger" db:"trigger"`
TriggerCron string `json:"triggerCron" db:"triggerCron"`
Enabled bool `json:"enabled" db:"enabled"`
Content *WorkflowNode `json:"content" db:"content"`
Draft *WorkflowNode `json:"draft" db:"draft"`
HasDraft bool `json:"hasDraft" db:"hasDraft"`
LastRunId string `json:"lastRunId" db:"lastRunId"`
LastRunStatus WorkflowRunStatusType `json:"lastRunStatus" db:"lastRunStatus"`
LastRunTime time.Time `json:"lastRunTime" db:"lastRunTime"`
}
type WorkflowNodeType string
const (
@@ -28,25 +45,6 @@ const (
WorkflowTriggerTypeManual = WorkflowTriggerType("manual")
)
type Workflow struct {
Meta
Name string `json:"name" db:"name"`
Description string `json:"description" db:"description"`
Trigger WorkflowTriggerType `json:"trigger" db:"trigger"`
TriggerCron string `json:"triggerCron" db:"triggerCron"`
Enabled bool `json:"enabled" db:"enabled"`
Content *WorkflowNode `json:"content" db:"content"`
Draft *WorkflowNode `json:"draft" db:"draft"`
HasDraft bool `json:"hasDraft" db:"hasDraft"`
LastRunId string `json:"lastRunId" db:"lastRunId"`
LastRunStatus WorkflowRunStatusType `json:"lastRunStatus" db:"lastRunStatus"`
LastRunTime time.Time `json:"lastRunTime" db:"lastRunTime"`
}
func (w *Workflow) Table() string {
return "workflow"
}
type WorkflowNode struct {
Id string `json:"id"`
Type WorkflowNodeType `json:"type"`
@@ -62,23 +60,47 @@ type WorkflowNode struct {
Validated bool `json:"validated"`
}
func (n *WorkflowNode) GetConfigString(key string) string {
type WorkflowNodeConfigForApply struct {
Domains string `json:"domains"` // 域名列表,以半角逗号分隔
ContactEmail string `json:"contactEmail"` // 联系邮箱
Provider string `json:"provider"` // DNS 提供商
ProviderAccessId string `json:"providerAccessId"` // DNS 提供商授权记录 ID
ProviderConfig map[string]any `json:"providerConfig"` // DNS 提供商额外配置
KeyAlgorithm string `json:"keyAlgorithm"` // 密钥算法
Nameservers string `json:"nameservers"` // DNS 服务器列表,以半角逗号分隔
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout"` // DNS 传播超时时间(默认取决于提供商)
DnsTTL int32 `json:"dnsTTL"` // DNS TTL默认取决于提供商
DisableFollowCNAME bool `json:"disableFollowCNAME"` // 是否禁用 CNAME 跟随
SkipBeforeExpiryDays int32 `json:"skipBeforeExpiryDays"` // 证书到期前多少天前跳过续期默认值30
}
type WorkflowNodeConfigForDeploy struct {
Certificate string `json:"certificate"` // 前序节点输出的证书,形如“${NodeId}#certificate”
Provider string `json:"provider"` // 主机提供商
ProviderAccessId string `json:"providerAccessId"` // 主机提供商授权记录 ID
ProviderConfig map[string]any `json:"providerConfig"` // 主机提供商额外配置
SkipOnLastSucceeded bool `json:"skipOnLastSucceeded"` // 上次部署成功时是否跳过
}
type WorkflowNodeConfigForNotify struct {
Channel string `json:"channel"` // 通知渠道
Subject string `json:"subject"` // 通知主题
Message string `json:"message"` // 通知内容
}
func (n *WorkflowNode) getConfigValueAsString(key string) string {
return maps.GetValueAsString(n.Config, key)
}
func (n *WorkflowNode) GetConfigBool(key string) bool {
func (n *WorkflowNode) getConfigValueAsBool(key string) bool {
return maps.GetValueAsBool(n.Config, key)
}
func (n *WorkflowNode) GetConfigInt32(key string) int32 {
func (n *WorkflowNode) getConfigValueAsInt32(key string) int32 {
return maps.GetValueAsInt32(n.Config, key)
}
func (n *WorkflowNode) GetConfigInt64(key string) int64 {
return maps.GetValueAsInt64(n.Config, key)
}
func (n *WorkflowNode) GetConfigMap(key string) map[string]any {
func (n *WorkflowNode) getConfigValueAsMap(key string) map[string]any {
if val, ok := n.Config[key]; ok {
if result, ok := val.(map[string]any); ok {
return result
@@ -88,6 +110,45 @@ func (n *WorkflowNode) GetConfigMap(key string) map[string]any {
return make(map[string]any)
}
func (n *WorkflowNode) GetConfigForApply() WorkflowNodeConfigForApply {
skipBeforeExpiryDays := n.getConfigValueAsInt32("skipBeforeExpiryDays")
if skipBeforeExpiryDays == 0 {
skipBeforeExpiryDays = 30
}
return WorkflowNodeConfigForApply{
Domains: n.getConfigValueAsString("domains"),
ContactEmail: n.getConfigValueAsString("contactEmail"),
Provider: n.getConfigValueAsString("provider"),
ProviderAccessId: n.getConfigValueAsString("providerAccessId"),
ProviderConfig: n.getConfigValueAsMap("providerConfig"),
KeyAlgorithm: n.getConfigValueAsString("keyAlgorithm"),
Nameservers: n.getConfigValueAsString("nameservers"),
DnsPropagationTimeout: n.getConfigValueAsInt32("dnsPropagationTimeout"),
DnsTTL: n.getConfigValueAsInt32("dnsTTL"),
DisableFollowCNAME: n.getConfigValueAsBool("disableFollowCNAME"),
SkipBeforeExpiryDays: skipBeforeExpiryDays,
}
}
func (n *WorkflowNode) GetConfigForDeploy() WorkflowNodeConfigForDeploy {
return WorkflowNodeConfigForDeploy{
Certificate: n.getConfigValueAsString("certificate"),
Provider: n.getConfigValueAsString("provider"),
ProviderAccessId: n.getConfigValueAsString("providerAccessId"),
ProviderConfig: n.getConfigValueAsMap("providerConfig"),
SkipOnLastSucceeded: n.getConfigValueAsBool("skipOnLastSucceeded"),
}
}
func (n *WorkflowNode) GetConfigForNotify() WorkflowNodeConfigForNotify {
return WorkflowNodeConfigForNotify{
Channel: n.getConfigValueAsString("channel"),
Subject: n.getConfigValueAsString("subject"),
Message: n.getConfigValueAsString("message"),
}
}
type WorkflowNodeIO struct {
Label string `json:"label"`
Name string `json:"name"`
@@ -102,7 +163,4 @@ type WorkflowNodeIOValueSelector struct {
Name string `json:"name"`
}
type WorkflowRunReq struct {
WorkflowId string `json:"workflowId"`
Trigger WorkflowTriggerType `json:"trigger"`
}
const WorkflowNodeIONameCertificate string = "certificate"

View File

@@ -1,5 +1,7 @@
package domain
const CollectionNameWorkflowOutput = "workflow_output"
type WorkflowOutput struct {
Meta
WorkflowId string `json:"workflowId" db:"workflow"`
@@ -8,5 +10,3 @@ type WorkflowOutput struct {
Outputs []WorkflowNodeIO `json:"outputs" db:"outputs"`
Succeeded bool `json:"succeeded" db:"succeeded"`
}
const WORKFLOW_OUTPUT_CERTIFICATE = "certificate"

View File

@@ -2,14 +2,7 @@ package domain
import "time"
type WorkflowRunStatusType string
const (
WorkflowRunStatusTypePending WorkflowRunStatusType = "pending"
WorkflowRunStatusTypeRunning WorkflowRunStatusType = "running"
WorkflowRunStatusTypeSucceeded WorkflowRunStatusType = "succeeded"
WorkflowRunStatusTypeFailed WorkflowRunStatusType = "failed"
)
const CollectionNameWorkflowRun = "workflow_run"
type WorkflowRun struct {
Meta
@@ -22,6 +15,15 @@ type WorkflowRun struct {
Error string `json:"error" db:"error"`
}
type WorkflowRunStatusType string
const (
WorkflowRunStatusTypePending WorkflowRunStatusType = "pending"
WorkflowRunStatusTypeRunning WorkflowRunStatusType = "running"
WorkflowRunStatusTypeSucceeded WorkflowRunStatusType = "succeeded"
WorkflowRunStatusTypeFailed WorkflowRunStatusType = "failed"
)
type WorkflowRunLog struct {
NodeId string `json:"nodeId"`
NodeName string `json:"nodeName"`

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/domain/dtos"
)
const (
@@ -17,25 +18,25 @@ type settingsRepository interface {
}
type NotifyService struct {
settingRepo settingsRepository
settingsRepo settingsRepository
}
func NewNotifyService(settingRepo settingsRepository) *NotifyService {
func NewNotifyService(settingsRepo settingsRepository) *NotifyService {
return &NotifyService{
settingRepo: settingRepo,
settingsRepo: settingsRepo,
}
}
func (n *NotifyService) Test(ctx context.Context, req *domain.NotifyTestPushReq) error {
setting, err := n.settingRepo.GetByName(ctx, "notifyChannels")
func (n *NotifyService) Test(ctx context.Context, req *dtos.NotifyTestPushReq) error {
settings, err := n.settingsRepo.GetByName(ctx, "notifyChannels")
if err != nil {
return fmt.Errorf("failed to get notify channels settings: %w", err)
}
channelConfig, err := setting.GetNotifyChannelConfig(req.Channel)
channelConfig, err := settings.GetNotifyChannelConfig(string(req.Channel))
if err != nil {
return fmt.Errorf("failed to get notify channel \"%s\" config: %w", req.Channel, err)
}
return SendToChannel(notifyTestTitle, notifyTestBody, req.Channel, channelConfig)
return SendToChannel(notifyTestTitle, notifyTestBody, string(req.Channel), channelConfig)
}

View File

@@ -10,11 +10,11 @@ import (
)
type ACMEHttpReqApplicantConfig struct {
Endpoint string `json:"endpoint"`
Mode string `json:"mode"`
Username string `json:"username"`
Password string `json:"password"`
PropagationTimeout int32 `json:"propagationTimeout,omitempty"`
Endpoint string `json:"endpoint"`
Mode string `json:"mode"`
Username string `json:"username"`
Password string `json:"password"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
}
func NewChallengeProvider(config *ACMEHttpReqApplicantConfig) (challenge.Provider, error) {
@@ -28,8 +28,8 @@ func NewChallengeProvider(config *ACMEHttpReqApplicantConfig) (challenge.Provide
providerConfig.Mode = config.Mode
providerConfig.Username = config.Username
providerConfig.Password = config.Password
if config.PropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.PropagationTimeout) * time.Second
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
provider, err := httpreq.NewDNSProviderConfig(providerConfig)

View File

@@ -9,9 +9,10 @@ import (
)
type AliyunApplicantConfig struct {
AccessKeyId string `json:"accessKeyId"`
AccessKeySecret string `json:"accessKeySecret"`
PropagationTimeout int32 `json:"propagationTimeout,omitempty"`
AccessKeyId string `json:"accessKeyId"`
AccessKeySecret string `json:"accessKeySecret"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *AliyunApplicantConfig) (challenge.Provider, error) {
@@ -22,8 +23,11 @@ func NewChallengeProvider(config *AliyunApplicantConfig) (challenge.Provider, er
providerConfig := alidns.NewDefaultConfig()
providerConfig.APIKey = config.AccessKeyId
providerConfig.SecretKey = config.AccessKeySecret
if config.PropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.PropagationTimeout) * time.Second
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := alidns.NewDNSProviderConfig(providerConfig)

View File

@@ -9,11 +9,12 @@ import (
)
type AWSRoute53ApplicantConfig struct {
AccessKeyId string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
Region string `json:"region"`
HostedZoneId string `json:"hostedZoneId"`
PropagationTimeout int32 `json:"propagationTimeout,omitempty"`
AccessKeyId string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
Region string `json:"region"`
HostedZoneId string `json:"hostedZoneId"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *AWSRoute53ApplicantConfig) (challenge.Provider, error) {
@@ -26,8 +27,11 @@ func NewChallengeProvider(config *AWSRoute53ApplicantConfig) (challenge.Provider
providerConfig.SecretAccessKey = config.SecretAccessKey
providerConfig.Region = config.Region
providerConfig.HostedZoneID = config.HostedZoneId
if config.PropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.PropagationTimeout) * time.Second
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := route53.NewDNSProviderConfig(providerConfig)

View File

@@ -12,11 +12,12 @@ import (
)
type AzureDNSApplicantConfig struct {
TenantId string `json:"tenantId"`
ClientId string `json:"clientId"`
ClientSecret string `json:"clientSecret"`
CloudName string `json:"cloudName,omitempty"`
PropagationTimeout int32 `json:"propagationTimeout,omitempty"`
TenantId string `json:"tenantId"`
ClientId string `json:"clientId"`
ClientSecret string `json:"clientSecret"`
CloudName string `json:"cloudName,omitempty"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *AzureDNSApplicantConfig) (challenge.Provider, error) {
@@ -40,8 +41,11 @@ func NewChallengeProvider(config *AzureDNSApplicantConfig) (challenge.Provider,
return nil, fmt.Errorf("azuredns: unknown environment %s", config.CloudName)
}
}
if config.PropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.PropagationTimeout) * time.Second
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := azuredns.NewDNSProviderConfig(providerConfig)

View File

@@ -9,8 +9,9 @@ import (
)
type CloudflareApplicantConfig struct {
DnsApiToken string `json:"dnsApiToken"`
PropagationTimeout int32 `json:"propagationTimeout,omitempty"`
DnsApiToken string `json:"dnsApiToken"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *CloudflareApplicantConfig) (challenge.Provider, error) {
@@ -20,8 +21,11 @@ func NewChallengeProvider(config *CloudflareApplicantConfig) (challenge.Provider
providerConfig := cloudflare.NewDefaultConfig()
providerConfig.AuthToken = config.DnsApiToken
if config.PropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.PropagationTimeout) * time.Second
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := cloudflare.NewDNSProviderConfig(providerConfig)

View File

@@ -9,9 +9,10 @@ import (
)
type GoDaddyApplicantConfig struct {
ApiKey string `json:"apiKey"`
ApiSecret string `json:"apiSecret"`
PropagationTimeout int32 `json:"propagationTimeout,omitempty"`
ApiKey string `json:"apiKey"`
ApiSecret string `json:"apiSecret"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *GoDaddyApplicantConfig) (challenge.Provider, error) {
@@ -22,8 +23,11 @@ func NewChallengeProvider(config *GoDaddyApplicantConfig) (challenge.Provider, e
providerConfig := godaddy.NewDefaultConfig()
providerConfig.APIKey = config.ApiKey
providerConfig.APISecret = config.ApiSecret
if config.PropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.PropagationTimeout) * time.Second
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := godaddy.NewDNSProviderConfig(providerConfig)

View File

@@ -9,10 +9,11 @@ import (
)
type HuaweiCloudApplicantConfig struct {
AccessKeyId string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
Region string `json:"region"`
PropagationTimeout int32 `json:"propagationTimeout,omitempty"`
AccessKeyId string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
Region string `json:"region"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *HuaweiCloudApplicantConfig) (challenge.Provider, error) {
@@ -30,8 +31,11 @@ func NewChallengeProvider(config *HuaweiCloudApplicantConfig) (challenge.Provide
providerConfig.AccessKeyID = config.AccessKeyId
providerConfig.SecretAccessKey = config.SecretAccessKey
providerConfig.Region = region
if config.PropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.PropagationTimeout) * time.Second
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = config.DnsTTL
}
provider, err := hwc.NewDNSProviderConfig(providerConfig)

View File

@@ -9,9 +9,10 @@ import (
)
type NameDotComApplicantConfig struct {
Username string `json:"username"`
ApiToken string `json:"apiToken"`
PropagationTimeout int32 `json:"propagationTimeout,omitempty"`
Username string `json:"username"`
ApiToken string `json:"apiToken"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *NameDotComApplicantConfig) (challenge.Provider, error) {
@@ -22,8 +23,11 @@ func NewChallengeProvider(config *NameDotComApplicantConfig) (challenge.Provider
providerConfig := namedotcom.NewDefaultConfig()
providerConfig.Username = config.Username
providerConfig.APIToken = config.ApiToken
if config.PropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.PropagationTimeout) * time.Second
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := namedotcom.NewDNSProviderConfig(providerConfig)

View File

@@ -9,8 +9,9 @@ import (
)
type NameSiloApplicantConfig struct {
ApiKey string `json:"apiKey"`
PropagationTimeout int32 `json:"propagationTimeout,omitempty"`
ApiKey string `json:"apiKey"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *NameSiloApplicantConfig) (challenge.Provider, error) {
@@ -20,8 +21,11 @@ func NewChallengeProvider(config *NameSiloApplicantConfig) (challenge.Provider,
providerConfig := namesilo.NewDefaultConfig()
providerConfig.APIKey = config.ApiKey
if config.PropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.PropagationTimeout) * time.Second
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := namesilo.NewDNSProviderConfig(providerConfig)

View File

@@ -9,8 +9,9 @@ import (
)
type NS1ApplicantConfig struct {
ApiKey string `json:"apiKey"`
PropagationTimeout int32 `json:"propagationTimeout,omitempty"`
ApiKey string `json:"apiKey"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *NS1ApplicantConfig) (challenge.Provider, error) {
@@ -20,8 +21,11 @@ func NewChallengeProvider(config *NS1ApplicantConfig) (challenge.Provider, error
providerConfig := ns1.NewDefaultConfig()
providerConfig.APIKey = config.ApiKey
if config.PropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.PropagationTimeout) * time.Second
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := ns1.NewDNSProviderConfig(providerConfig)

View File

@@ -10,9 +10,10 @@ import (
)
type PowerDNSApplicantConfig struct {
ApiUrl string `json:"apiUrl"`
ApiKey string `json:"apiKey"`
PropagationTimeout int32 `json:"propagationTimeout,omitempty"`
ApiUrl string `json:"apiUrl"`
ApiKey string `json:"apiKey"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *PowerDNSApplicantConfig) (challenge.Provider, error) {
@@ -24,8 +25,11 @@ func NewChallengeProvider(config *PowerDNSApplicantConfig) (challenge.Provider,
providerConfig := pdns.NewDefaultConfig()
providerConfig.Host = host
providerConfig.APIKey = config.ApiKey
if config.PropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.PropagationTimeout) * time.Second
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := pdns.NewDNSProviderConfig(providerConfig)

View File

@@ -9,9 +9,10 @@ import (
)
type TencentCloudApplicantConfig struct {
SecretId string `json:"secretId"`
SecretKey string `json:"secretKey"`
PropagationTimeout int32 `json:"propagationTimeout,omitempty"`
SecretId string `json:"secretId"`
SecretKey string `json:"secretKey"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *TencentCloudApplicantConfig) (challenge.Provider, error) {
@@ -22,8 +23,11 @@ func NewChallengeProvider(config *TencentCloudApplicantConfig) (challenge.Provid
providerConfig := tencentcloud.NewDefaultConfig()
providerConfig.SecretID = config.SecretId
providerConfig.SecretKey = config.SecretKey
if config.PropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.PropagationTimeout) * time.Second
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := tencentcloud.NewDNSProviderConfig(providerConfig)

View File

@@ -9,9 +9,10 @@ import (
)
type VolcEngineApplicantConfig struct {
AccessKeyId string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
PropagationTimeout int32 `json:"propagationTimeout,omitempty"`
AccessKeyId string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *VolcEngineApplicantConfig) (challenge.Provider, error) {
@@ -22,8 +23,11 @@ func NewChallengeProvider(config *VolcEngineApplicantConfig) (challenge.Provider
providerConfig := volcengine.NewDefaultConfig()
providerConfig.AccessKey = config.AccessKeyId
providerConfig.SecretKey = config.SecretAccessKey
if config.PropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.PropagationTimeout) * time.Second
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := volcengine.NewDNSProviderConfig(providerConfig)

View File

@@ -15,7 +15,6 @@ import (
"github.com/usual2970/certimate/internal/pkg/core/uploader"
providerScm "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/huaweicloud-scm"
hwsdk "github.com/usual2970/certimate/internal/pkg/vendors/huaweicloud-sdk"
hwsdkCdn "github.com/usual2970/certimate/internal/pkg/vendors/huaweicloud-sdk/cdn"
)
type HuaweiCloudCDNDeployerConfig struct {
@@ -32,7 +31,7 @@ type HuaweiCloudCDNDeployerConfig struct {
type HuaweiCloudCDNDeployer struct {
config *HuaweiCloudCDNDeployerConfig
logger logger.Logger
sdkClient *hwsdkCdn.Client
sdkClient *hcCdn.CdnClient
sslUploader uploader.Uploader
}
@@ -100,21 +99,21 @@ func (d *HuaweiCloudCDNDeployer) Deploy(ctx context.Context, certPem string, pri
// 更新加速域名配置
// REF: https://support.huaweicloud.com/api-cdn/UpdateDomainMultiCertificates.html
// REF: https://support.huaweicloud.com/usermanual-cdn/cdn_01_0306.html
updateDomainMultiCertificatesReqBodyContent := &hwsdkCdn.UpdateDomainMultiCertificatesExRequestBodyContent{}
updateDomainMultiCertificatesReqBodyContent := &hcCdnModel.UpdateDomainMultiCertificatesRequestBodyContent{}
updateDomainMultiCertificatesReqBodyContent.DomainName = d.config.Domain
updateDomainMultiCertificatesReqBodyContent.HttpsSwitch = 1
updateDomainMultiCertificatesReqBodyContent.CertificateType = hwsdk.Int32Ptr(2)
updateDomainMultiCertificatesReqBodyContent.SCMCertificateId = hwsdk.StringPtr(upres.CertId)
updateDomainMultiCertificatesReqBodyContent.ScmCertificateId = hwsdk.StringPtr(upres.CertId)
updateDomainMultiCertificatesReqBodyContent.CertName = hwsdk.StringPtr(upres.CertName)
updateDomainMultiCertificatesReqBodyContent = updateDomainMultiCertificatesReqBodyContent.MergeConfig(showDomainFullConfigResp.Configs)
updateDomainMultiCertificatesReq := &hwsdkCdn.UpdateDomainMultiCertificatesExRequest{
Body: &hwsdkCdn.UpdateDomainMultiCertificatesExRequestBody{
updateDomainMultiCertificatesReqBodyContent = assign(updateDomainMultiCertificatesReqBodyContent, showDomainFullConfigResp.Configs)
updateDomainMultiCertificatesReq := &hcCdnModel.UpdateDomainMultiCertificatesRequest{
Body: &hcCdnModel.UpdateDomainMultiCertificatesRequestBody{
Https: updateDomainMultiCertificatesReqBodyContent,
},
}
updateDomainMultiCertificatesResp, err := d.sdkClient.UploadDomainMultiCertificatesEx(updateDomainMultiCertificatesReq)
updateDomainMultiCertificatesResp, err := d.sdkClient.UpdateDomainMultiCertificates(updateDomainMultiCertificatesReq)
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.UploadDomainMultiCertificatesEx'")
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.UploadDomainMultiCertificates'")
}
d.logger.Logt("已更新加速域名配置", updateDomainMultiCertificatesResp)
@@ -122,7 +121,7 @@ func (d *HuaweiCloudCDNDeployer) Deploy(ctx context.Context, certPem string, pri
return &deployer.DeployResult{}, nil
}
func createSdkClient(accessKeyId, secretAccessKey, region string) (*hwsdkCdn.Client, error) {
func createSdkClient(accessKeyId, secretAccessKey, region string) (*hcCdn.CdnClient, error) {
if region == "" {
region = "cn-north-1" // CDN 服务默认区域:华北一北京
}
@@ -148,6 +147,42 @@ func createSdkClient(accessKeyId, secretAccessKey, region string) (*hwsdkCdn.Cli
return nil, err
}
client := hwsdkCdn.NewClient(hcClient)
client := hcCdn.NewCdnClient(hcClient)
return client, nil
}
func assign(reqContent *hcCdnModel.UpdateDomainMultiCertificatesRequestBodyContent, target *hcCdnModel.ConfigsGetBody) *hcCdnModel.UpdateDomainMultiCertificatesRequestBodyContent {
if target == nil {
return reqContent
}
// 华为云 API 中不传的字段表示使用默认值、而非保留原值,因此这里需要把原配置中的参数重新赋值回去。
// 而且蛋疼的是查询接口返回的数据结构和更新接口传入的参数结构不一致,需要做很多转化。
if *target.OriginProtocol == "follow" {
reqContent.AccessOriginWay = hwsdk.Int32Ptr(1)
} else if *target.OriginProtocol == "http" {
reqContent.AccessOriginWay = hwsdk.Int32Ptr(2)
} else if *target.OriginProtocol == "https" {
reqContent.AccessOriginWay = hwsdk.Int32Ptr(3)
}
if target.ForceRedirect != nil {
reqContent.ForceRedirectConfig = &hcCdnModel.ForceRedirect{}
if target.ForceRedirect.Status == "on" {
reqContent.ForceRedirectConfig.Switch = 1
reqContent.ForceRedirectConfig.RedirectType = target.ForceRedirect.Type
} else {
reqContent.ForceRedirectConfig.Switch = 0
}
}
if target.Https != nil {
if *target.Https.Http2Status == "on" {
reqContent.Http2 = hwsdk.Int32Ptr(1)
}
}
return reqContent
}

View File

@@ -2,6 +2,8 @@
import (
"bytes"
"crypto/ecdsa"
"crypto/rsa"
"encoding/pem"
"errors"
"time"
@@ -26,9 +28,23 @@ func TransformCertificateFromPEMToPFX(certPem string, privkeyPem string, pfxPass
return nil, err
}
privkey, err := ParsePKCS1PrivateKeyFromPEM(privkeyPem)
if err != nil {
return nil, err
var privkey interface{}
switch cert.PublicKey.(type) {
case *rsa.PublicKey:
{
privkey, err = ParsePKCS1PrivateKeyFromPEM(privkeyPem)
if err != nil {
return nil, err
}
}
case *ecdsa.PublicKey:
{
privkey, err = ParseECPrivateKeyFromPEM(privkeyPem)
if err != nil {
return nil, err
}
}
}
pfxData, err := pkcs12.LegacyRC2.Encode(privkey, cert, nil, pfxPassword)

View File

@@ -1,28 +0,0 @@
package cdn
import (
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core"
hcCdn "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2"
hcCdnModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2/model"
)
type Client struct {
hcCdn.CdnClient
}
func NewClient(hcClient *core.HcHttpClient) *Client {
return &Client{
CdnClient: *hcCdn.NewCdnClient(hcClient),
}
}
func (c *Client) UploadDomainMultiCertificatesEx(request *UpdateDomainMultiCertificatesExRequest) (*UpdateDomainMultiCertificatesExResponse, error) {
requestDef := hcCdn.GenReqDefForUpdateDomainMultiCertificates()
if resp, err := c.HcClient.Sync(request, requestDef); err != nil {
return nil, err
} else {
temp := resp.(*hcCdnModel.UpdateDomainMultiCertificatesResponse)
return &UpdateDomainMultiCertificatesExResponse{UpdateDomainMultiCertificatesResponse: *temp}, nil
}
}

View File

@@ -1,62 +0,0 @@
package cdn
import (
hcCdnModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2/model"
hwsdk "github.com/usual2970/certimate/internal/pkg/vendors/huaweicloud-sdk"
)
type UpdateDomainMultiCertificatesExRequestBodyContent struct {
hcCdnModel.UpdateDomainMultiCertificatesRequestBodyContent `json:",inline"`
// 华为云官方 SDK 中目前提供的字段缺失,这里暂时先需自定义请求,可能需要等之后 SDK 更新。
SCMCertificateId *string `json:"scm_certificate_id,omitempty"`
}
type UpdateDomainMultiCertificatesExRequestBody struct {
Https *UpdateDomainMultiCertificatesExRequestBodyContent `json:"https,omitempty"`
}
type UpdateDomainMultiCertificatesExRequest struct {
Body *UpdateDomainMultiCertificatesExRequestBody `json:"body,omitempty"`
}
type UpdateDomainMultiCertificatesExResponse struct {
hcCdnModel.UpdateDomainMultiCertificatesResponse
}
func (m *UpdateDomainMultiCertificatesExRequestBodyContent) MergeConfig(src *hcCdnModel.ConfigsGetBody) *UpdateDomainMultiCertificatesExRequestBodyContent {
if src == nil {
return m
}
// 华为云 API 中不传的字段表示使用默认值、而非保留原值,因此这里需要把原配置中的参数重新赋值回去。
// 而且蛋疼的是查询接口返回的数据结构和更新接口传入的参数结构不一致,需要做很多转化。
if *src.OriginProtocol == "follow" {
m.AccessOriginWay = hwsdk.Int32Ptr(1)
} else if *src.OriginProtocol == "http" {
m.AccessOriginWay = hwsdk.Int32Ptr(2)
} else if *src.OriginProtocol == "https" {
m.AccessOriginWay = hwsdk.Int32Ptr(3)
}
if src.ForceRedirect != nil {
m.ForceRedirectConfig = &hcCdnModel.ForceRedirect{}
if src.ForceRedirect.Status == "on" {
m.ForceRedirectConfig.Switch = 1
m.ForceRedirectConfig.RedirectType = src.ForceRedirect.Type
} else {
m.ForceRedirectConfig.Switch = 0
}
}
if src.Https != nil {
if *src.Https.Http2Status == "on" {
m.Http2 = hwsdk.Int32Ptr(1)
}
}
return m
}

View File

@@ -6,7 +6,8 @@ import (
"errors"
"fmt"
"github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/core"
"github.com/usual2970/certimate/internal/app"
"github.com/usual2970/certimate/internal/domain"
)
@@ -18,7 +19,7 @@ func NewAccessRepository() *AccessRepository {
}
func (r *AccessRepository) GetById(ctx context.Context, id string) (*domain.Access, error) {
record, err := app.GetApp().Dao().FindRecordById("access", id)
record, err := app.GetApp().FindRecordById(domain.CollectionNameAccess, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, domain.ErrRecordNotFound
@@ -33,16 +34,16 @@ func (r *AccessRepository) GetById(ctx context.Context, id string) (*domain.Acce
return r.castRecordToModel(record)
}
func (r *AccessRepository) castRecordToModel(record *models.Record) (*domain.Access, error) {
func (r *AccessRepository) castRecordToModel(record *core.Record) (*domain.Access, error) {
if record == nil {
return nil, fmt.Errorf("record is nil")
}
access := &domain.Access{
Meta: domain.Meta{
Id: record.GetId(),
CreatedAt: record.GetCreated().Time(),
UpdatedAt: record.GetUpdated().Time(),
Id: record.Id,
CreatedAt: record.GetDateTime("created").Time(),
UpdatedAt: record.GetDateTime("updated").Time(),
},
Name: record.GetString("name"),
Provider: record.GetString("provider"),

View File

@@ -5,7 +5,7 @@ import (
"github.com/go-acme/lego/v4/registration"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/core"
"golang.org/x/sync/singleflight"
"github.com/usual2970/certimate/internal/app"
@@ -22,8 +22,8 @@ var g singleflight.Group
func (r *AcmeAccountRepository) GetByCAAndEmail(ca, email string) (*domain.AcmeAccount, error) {
resp, err, _ := g.Do(fmt.Sprintf("acme_account_%s_%s", ca, email), func() (interface{}, error) {
resp, err := app.GetApp().Dao().FindFirstRecordByFilter(
"acme_accounts",
resp, err := app.GetApp().FindFirstRecordByFilter(
domain.CollectionNameAcmeAccount,
"ca={:ca} && email={:email}",
dbx.Params{"ca": ca, "email": email},
)
@@ -40,7 +40,7 @@ func (r *AcmeAccountRepository) GetByCAAndEmail(ca, email string) (*domain.AcmeA
return nil, domain.ErrRecordNotFound
}
record, ok := resp.(*models.Record)
record, ok := resp.(*core.Record)
if !ok {
return nil, domain.ErrRecordNotFound
}
@@ -49,20 +49,20 @@ func (r *AcmeAccountRepository) GetByCAAndEmail(ca, email string) (*domain.AcmeA
}
func (r *AcmeAccountRepository) Save(ca, email, key string, resource *registration.Resource) error {
collection, err := app.GetApp().Dao().FindCollectionByNameOrId("acme_accounts")
collection, err := app.GetApp().FindCollectionByNameOrId(domain.CollectionNameAcmeAccount)
if err != nil {
return err
}
record := models.NewRecord(collection)
record := core.NewRecord(collection)
record.Set("ca", ca)
record.Set("email", email)
record.Set("key", key)
record.Set("resource", resource)
return app.GetApp().Dao().Save(record)
return app.GetApp().Save(record)
}
func (r *AcmeAccountRepository) castRecordToModel(record *models.Record) (*domain.AcmeAccount, error) {
func (r *AcmeAccountRepository) castRecordToModel(record *core.Record) (*domain.AcmeAccount, error) {
if record == nil {
return nil, fmt.Errorf("record is nil")
}
@@ -74,9 +74,9 @@ func (r *AcmeAccountRepository) castRecordToModel(record *models.Record) (*domai
acmeAccount := &domain.AcmeAccount{
Meta: domain.Meta{
Id: record.GetId(),
CreatedAt: record.GetCreated().Time(),
UpdatedAt: record.GetUpdated().Time(),
Id: record.Id,
CreatedAt: record.GetDateTime("created").Time(),
UpdatedAt: record.GetDateTime("updated").Time(),
},
CA: record.GetString("ca"),
Email: record.GetString("email"),

View File

@@ -7,7 +7,7 @@ import (
"fmt"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/core"
"github.com/usual2970/certimate/internal/app"
"github.com/usual2970/certimate/internal/domain"
)
@@ -19,8 +19,8 @@ func NewCertificateRepository() *CertificateRepository {
}
func (r *CertificateRepository) ListExpireSoon(ctx context.Context) ([]*domain.Certificate, error) {
records, err := app.GetApp().Dao().FindRecordsByFilter(
"certificate",
records, err := app.GetApp().FindRecordsByFilter(
domain.CollectionNameCertificate,
"expireAt>DATETIME('now') && expireAt<DATETIME('now', '+20 days') && deleted=null",
"-created",
0, 0,
@@ -43,7 +43,7 @@ func (r *CertificateRepository) ListExpireSoon(ctx context.Context) ([]*domain.C
}
func (r *CertificateRepository) GetById(ctx context.Context, id string) (*domain.Certificate, error) {
record, err := app.GetApp().Dao().FindRecordById("certificate", id)
record, err := app.GetApp().FindRecordById(domain.CollectionNameCertificate, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, domain.ErrRecordNotFound
@@ -59,8 +59,8 @@ func (r *CertificateRepository) GetById(ctx context.Context, id string) (*domain
}
func (r *CertificateRepository) GetByWorkflowNodeId(ctx context.Context, workflowNodeId string) (*domain.Certificate, error) {
records, err := app.GetApp().Dao().FindRecordsByFilter(
"certificate",
records, err := app.GetApp().FindRecordsByFilter(
domain.CollectionNameCertificate,
"workflowNodeId={:workflowNodeId} && deleted=null",
"-created", 1, 0,
dbx.Params{"workflowNodeId": workflowNodeId},
@@ -78,16 +78,16 @@ func (r *CertificateRepository) GetByWorkflowNodeId(ctx context.Context, workflo
return r.castRecordToModel(records[0])
}
func (r *CertificateRepository) castRecordToModel(record *models.Record) (*domain.Certificate, error) {
func (r *CertificateRepository) castRecordToModel(record *core.Record) (*domain.Certificate, error) {
if record == nil {
return nil, fmt.Errorf("record is nil")
}
certificate := &domain.Certificate{
Meta: domain.Meta{
Id: record.GetId(),
CreatedAt: record.GetCreated().Time(),
UpdatedAt: record.GetUpdated().Time(),
Id: record.Id,
CreatedAt: record.GetDateTime("created").Time(),
UpdatedAt: record.GetDateTime("updated").Time(),
},
Source: domain.CertificateSourceType(record.GetString("source")),
SubjectAltNames: record.GetString("subjectAltNames"),

View File

@@ -17,8 +17,8 @@ func NewSettingsRepository() *SettingsRepository {
}
func (r *SettingsRepository) GetByName(ctx context.Context, name string) (*domain.Settings, error) {
record, err := app.GetApp().Dao().FindFirstRecordByFilter(
"settings",
record, err := app.GetApp().FindFirstRecordByFilter(
domain.CollectionNameSettings,
"name={:name}",
dbx.Params{"name": name},
)
@@ -31,9 +31,9 @@ func (r *SettingsRepository) GetByName(ctx context.Context, name string) (*domai
settings := &domain.Settings{
Meta: domain.Meta{
Id: record.GetId(),
CreatedAt: record.GetCreated().Time(),
UpdatedAt: record.GetUpdated().Time(),
Id: record.Id,
CreatedAt: record.GetDateTime("created").Time(),
UpdatedAt: record.GetDateTime("updated").Time(),
},
Name: record.GetString("name"),
Content: record.GetString("content"),

View File

@@ -20,7 +20,9 @@ func (r *StatisticsRepository) Get(ctx context.Context) (*domain.Statistics, err
certTotal := struct {
Total int `db:"total"`
}{}
if err := app.GetApp().Dao().DB().NewQuery("SELECT COUNT(*) AS total FROM certificate").One(&certTotal); err != nil {
if err := app.GetDB().
NewQuery("SELECT COUNT(*) AS total FROM certificate").
One(&certTotal); err != nil {
return nil, err
}
rs.CertificateTotal = certTotal.Total
@@ -29,7 +31,7 @@ func (r *StatisticsRepository) Get(ctx context.Context) (*domain.Statistics, err
certExpireSoonTotal := struct {
Total int `db:"total"`
}{}
if err := app.GetApp().Dao().DB().
if err := app.GetDB().
NewQuery("SELECT COUNT(*) AS total FROM certificate WHERE expireAt > DATETIME('now') and expireAt < DATETIME('now', '+20 days')").
One(&certExpireSoonTotal); err != nil {
return nil, err
@@ -40,7 +42,7 @@ func (r *StatisticsRepository) Get(ctx context.Context) (*domain.Statistics, err
certExpiredTotal := struct {
Total int `db:"total"`
}{}
if err := app.GetApp().Dao().DB().
if err := app.GetDB().
NewQuery("SELECT COUNT(*) AS total FROM certificate WHERE expireAt < DATETIME('now')").
One(&certExpiredTotal); err != nil {
return nil, err
@@ -51,7 +53,9 @@ func (r *StatisticsRepository) Get(ctx context.Context) (*domain.Statistics, err
workflowTotal := struct {
Total int `db:"total"`
}{}
if err := app.GetApp().Dao().DB().NewQuery("SELECT COUNT(*) AS total FROM workflow").One(&workflowTotal); err != nil {
if err := app.GetDB().
NewQuery("SELECT COUNT(*) AS total FROM workflow").
One(&workflowTotal); err != nil {
return nil, err
}
rs.WorkflowTotal = workflowTotal.Total
@@ -60,7 +64,9 @@ func (r *StatisticsRepository) Get(ctx context.Context) (*domain.Statistics, err
workflowEnabledTotal := struct {
Total int `db:"total"`
}{}
if err := app.GetApp().Dao().DB().NewQuery("SELECT COUNT(*) AS total FROM workflow WHERE enabled IS TRUE").One(&workflowEnabledTotal); err != nil {
if err := app.GetDB().
NewQuery("SELECT COUNT(*) AS total FROM workflow WHERE enabled IS TRUE").
One(&workflowEnabledTotal); err != nil {
return nil, err
}
rs.WorkflowEnabled = workflowEnabledTotal.Total

View File

@@ -7,8 +7,7 @@ import (
"fmt"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/daos"
"github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/core"
"github.com/usual2970/certimate/internal/app"
"github.com/usual2970/certimate/internal/domain"
)
@@ -20,8 +19,8 @@ func NewWorkflowRepository() *WorkflowRepository {
}
func (r *WorkflowRepository) ListEnabledAuto(ctx context.Context) ([]*domain.Workflow, error) {
records, err := app.GetApp().Dao().FindRecordsByFilter(
"workflow",
records, err := app.GetApp().FindRecordsByFilter(
domain.CollectionNameWorkflow,
"enabled={:enabled} && trigger={:trigger}",
"-created",
0, 0,
@@ -45,7 +44,7 @@ func (r *WorkflowRepository) ListEnabledAuto(ctx context.Context) ([]*domain.Wor
}
func (r *WorkflowRepository) GetById(ctx context.Context, id string) (*domain.Workflow, error) {
record, err := app.GetApp().Dao().FindRecordById("workflow", id)
record, err := app.GetApp().FindRecordById(domain.CollectionNameWorkflow, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, domain.ErrRecordNotFound
@@ -57,15 +56,16 @@ func (r *WorkflowRepository) GetById(ctx context.Context, id string) (*domain.Wo
}
func (r *WorkflowRepository) Save(ctx context.Context, workflow *domain.Workflow) error {
collection, err := app.GetApp().Dao().FindCollectionByNameOrId(workflow.Table())
collection, err := app.GetApp().FindCollectionByNameOrId(domain.CollectionNameWorkflow)
if err != nil {
return err
}
var record *models.Record
var record *core.Record
if workflow.Id == "" {
record = models.NewRecord(collection)
record = core.NewRecord(collection)
} else {
record, err = app.GetApp().Dao().FindRecordById(workflow.Table(), workflow.Id)
record, err = app.GetApp().FindRecordById(domain.CollectionNameWorkflow, workflow.Id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return domain.ErrRecordNotFound
@@ -86,40 +86,44 @@ func (r *WorkflowRepository) Save(ctx context.Context, workflow *domain.Workflow
record.Set("lastRunStatus", string(workflow.LastRunStatus))
record.Set("lastRunTime", workflow.LastRunTime)
return app.GetApp().Dao().SaveRecord(record)
return app.GetApp().Save(record)
}
func (r *WorkflowRepository) SaveRun(ctx context.Context, workflowRun *domain.WorkflowRun) error {
collection, err := app.GetApp().Dao().FindCollectionByNameOrId("workflow_run")
collection, err := app.GetApp().FindCollectionByNameOrId(domain.CollectionNameWorkflowRun)
if err != nil {
return err
}
err = app.GetApp().Dao().RunInTransaction(func(txDao *daos.Dao) error {
record := models.NewRecord(collection)
record.Set("workflowId", workflowRun.WorkflowId)
record.Set("trigger", string(workflowRun.Trigger))
record.Set("status", string(workflowRun.Status))
record.Set("startedAt", workflowRun.StartedAt)
record.Set("endedAt", workflowRun.EndedAt)
record.Set("logs", workflowRun.Logs)
record.Set("error", workflowRun.Error)
err = txDao.SaveRecord(record)
err = app.GetApp().RunInTransaction(func(txApp core.App) error {
workflowRunRecord := core.NewRecord(collection)
workflowRunRecord.Id = workflowRun.Id
workflowRunRecord.Set("workflowId", workflowRun.WorkflowId)
workflowRunRecord.Set("trigger", string(workflowRun.Trigger))
workflowRunRecord.Set("status", string(workflowRun.Status))
workflowRunRecord.Set("startedAt", workflowRun.StartedAt)
workflowRunRecord.Set("endedAt", workflowRun.EndedAt)
workflowRunRecord.Set("logs", workflowRun.Logs)
workflowRunRecord.Set("error", workflowRun.Error)
err = txApp.Save(workflowRunRecord)
if err != nil {
return err
}
// unable trigger sse using DB()
workflowRecord, err := txDao.FindRecordById("workflow", workflowRun.WorkflowId)
workflowRecord, err := txApp.FindRecordById(domain.CollectionNameWorkflow, workflowRun.WorkflowId)
if err != nil {
return err
}
workflowRecord.Set("lastRunId", record.GetId())
workflowRecord.Set("lastRunStatus", record.GetString("status"))
workflowRecord.Set("lastRunTime", record.GetString("startedAt"))
workflowRecord.Set("lastRunId", workflowRunRecord.Id)
workflowRecord.Set("lastRunStatus", workflowRunRecord.GetString("status"))
workflowRecord.Set("lastRunTime", workflowRunRecord.GetString("startedAt"))
err = txApp.Save(workflowRecord)
if err != nil {
return err
}
return txDao.SaveRecord(workflowRecord)
return nil
})
if err != nil {
return err
@@ -128,7 +132,7 @@ func (r *WorkflowRepository) SaveRun(ctx context.Context, workflowRun *domain.Wo
return nil
}
func (r *WorkflowRepository) castRecordToModel(record *models.Record) (*domain.Workflow, error) {
func (r *WorkflowRepository) castRecordToModel(record *core.Record) (*domain.Workflow, error) {
if record == nil {
return nil, fmt.Errorf("record is nil")
}
@@ -145,9 +149,9 @@ func (r *WorkflowRepository) castRecordToModel(record *models.Record) (*domain.W
workflow := &domain.Workflow{
Meta: domain.Meta{
Id: record.GetId(),
CreatedAt: record.GetCreated().Time(),
UpdatedAt: record.GetUpdated().Time(),
Id: record.Id,
CreatedAt: record.GetDateTime("created").Time(),
UpdatedAt: record.GetDateTime("updated").Time(),
},
Name: record.GetString("name"),
Description: record.GetString("description"),

View File

@@ -6,7 +6,7 @@ import (
"errors"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/core"
"github.com/usual2970/certimate/internal/app"
"github.com/usual2970/certimate/internal/domain"
)
@@ -18,8 +18,8 @@ func NewWorkflowOutputRepository() *WorkflowOutputRepository {
}
func (r *WorkflowOutputRepository) GetByNodeId(ctx context.Context, nodeId string) (*domain.WorkflowOutput, error) {
records, err := app.GetApp().Dao().FindRecordsByFilter(
"workflow_output",
records, err := app.GetApp().FindRecordsByFilter(
domain.CollectionNameWorkflowOutput,
"nodeId={:nodeId}",
"-created",
1, 0,
@@ -48,9 +48,9 @@ func (r *WorkflowOutputRepository) GetByNodeId(ctx context.Context, nodeId strin
rs := &domain.WorkflowOutput{
Meta: domain.Meta{
Id: record.GetId(),
CreatedAt: record.GetCreated().Time(),
UpdatedAt: record.GetUpdated().Time(),
Id: record.Id,
CreatedAt: record.GetDateTime("created").Time(),
UpdatedAt: record.GetDateTime("updated").Time(),
},
WorkflowId: record.GetString("workflowId"),
NodeId: record.GetString("nodeId"),
@@ -64,17 +64,17 @@ func (r *WorkflowOutputRepository) GetByNodeId(ctx context.Context, nodeId strin
// 保存节点输出
func (r *WorkflowOutputRepository) Save(ctx context.Context, output *domain.WorkflowOutput, certificate *domain.Certificate, cb func(id string) error) error {
var record *models.Record
var record *core.Record
var err error
if output.Id == "" {
collection, err := app.GetApp().Dao().FindCollectionByNameOrId("workflow_output")
collection, err := app.GetApp().FindCollectionByNameOrId(domain.CollectionNameWorkflowOutput)
if err != nil {
return err
}
record = models.NewRecord(collection)
record = core.NewRecord(collection)
} else {
record, err = app.GetApp().Dao().FindRecordById("workflow_output", output.Id)
record, err = app.GetApp().FindRecordById(domain.CollectionNameWorkflowOutput, output.Id)
if err != nil {
return err
}
@@ -85,21 +85,21 @@ func (r *WorkflowOutputRepository) Save(ctx context.Context, output *domain.Work
record.Set("outputs", output.Outputs)
record.Set("succeeded", output.Succeeded)
if err := app.GetApp().Dao().SaveRecord(record); err != nil {
if err := app.GetApp().Save(record); err != nil {
return err
}
if cb != nil && certificate != nil {
if err := cb(record.GetId()); err != nil {
if err := cb(record.Id); err != nil {
return err
}
certCollection, err := app.GetApp().Dao().FindCollectionByNameOrId("certificate")
certCollection, err := app.GetApp().FindCollectionByNameOrId(domain.CollectionNameCertificate)
if err != nil {
return err
}
certRecord := models.NewRecord(certCollection)
certRecord := core.NewRecord(certCollection)
certRecord.Set("source", string(certificate.Source))
certRecord.Set("subjectAltNames", certificate.SubjectAltNames)
certRecord.Set("certificate", certificate.Certificate)
@@ -113,21 +113,21 @@ func (r *WorkflowOutputRepository) Save(ctx context.Context, output *domain.Work
certRecord.Set("workflowNodeId", certificate.WorkflowNodeId)
certRecord.Set("workflowOutputId", certificate.WorkflowOutputId)
if err := app.GetApp().Dao().SaveRecord(certRecord); err != nil {
if err := app.GetApp().Save(certRecord); err != nil {
return err
}
// 更新 certificate
for i, item := range output.Outputs {
if item.Name == domain.WORKFLOW_OUTPUT_CERTIFICATE {
output.Outputs[i].Value = certRecord.GetId()
if item.Name == string(domain.WorkflowNodeIONameCertificate) {
output.Outputs[i].Value = certRecord.Id
break
}
}
record.Set("outputs", output.Outputs)
if err := app.GetApp().Dao().SaveRecord(record); err != nil {
if err := app.GetApp().Save(record); err != nil {
return err
}

View File

@@ -0,0 +1,42 @@
package handlers
import (
"context"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/router"
"github.com/usual2970/certimate/internal/domain/dtos"
"github.com/usual2970/certimate/internal/rest/resp"
)
type certificateService interface {
ArchiveFile(ctx context.Context, req *dtos.CertificateArchiveFileReq) ([]byte, error)
}
type CertificateHandler struct {
service certificateService
}
func NewCertificateHandler(router *router.RouterGroup[*core.RequestEvent], service certificateService) {
handler := &CertificateHandler{
service: service,
}
group := router.Group("/certificates")
group.POST("/{id}/archive", handler.run)
}
func (handler *CertificateHandler) run(e *core.RequestEvent) error {
req := &dtos.CertificateArchiveFileReq{}
req.CertificateId = e.Request.PathValue("id")
if err := e.BindBody(req); err != nil {
return resp.Err(e, err)
}
if bt, err := handler.service.ArchiveFile(e.Request.Context(), req); err != nil {
return resp.Err(e, err)
} else {
return resp.Ok(e, bt)
}
}

View File

@@ -0,0 +1,41 @@
package handlers
import (
"context"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/router"
"github.com/usual2970/certimate/internal/domain/dtos"
"github.com/usual2970/certimate/internal/rest/resp"
)
type notifyService interface {
Test(ctx context.Context, req *dtos.NotifyTestPushReq) error
}
type NotifyHandler struct {
service notifyService
}
func NewNotifyHandler(router *router.RouterGroup[*core.RequestEvent], service notifyService) {
handler := &NotifyHandler{
service: service,
}
group := router.Group("/notify")
group.POST("/test", handler.test)
}
func (handler *NotifyHandler) test(e *core.RequestEvent) error {
req := &dtos.NotifyTestPushReq{}
if err := e.BindBody(req); err != nil {
return resp.Err(e, err)
}
if err := handler.service.Test(e.Request.Context(), req); err != nil {
return resp.Err(e, err)
}
return resp.Ok(e, nil)
}

View File

@@ -0,0 +1,36 @@
package handlers
import (
"context"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/router"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/rest/resp"
)
type statisticsService interface {
Get(ctx context.Context) (*domain.Statistics, error)
}
type StatisticsHandler struct {
service statisticsService
}
func NewStatisticsHandler(router *router.RouterGroup[*core.RequestEvent], service statisticsService) {
handler := &StatisticsHandler{
service: service,
}
group := router.Group("/statistics")
group.GET("/get", handler.get)
}
func (handler *StatisticsHandler) get(e *core.RequestEvent) error {
if statistics, err := handler.service.Get(e.Request.Context()); err != nil {
return resp.Err(e, err)
} else {
return resp.Ok(e, statistics)
}
}

View File

@@ -0,0 +1,43 @@
package handlers
import (
"context"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/router"
"github.com/usual2970/certimate/internal/domain/dtos"
"github.com/usual2970/certimate/internal/rest/resp"
)
type workflowService interface {
Run(ctx context.Context, req *dtos.WorkflowRunReq) error
Stop(ctx context.Context)
}
type WorkflowHandler struct {
service workflowService
}
func NewWorkflowHandler(router *router.RouterGroup[*core.RequestEvent], service workflowService) {
handler := &WorkflowHandler{
service: service,
}
group := router.Group("/workflows")
group.POST("/{id}/run", handler.run)
}
func (handler *WorkflowHandler) run(e *core.RequestEvent) error {
req := &dtos.WorkflowRunReq{}
req.WorkflowId = e.Request.PathValue("id")
if err := e.BindBody(req); err != nil {
return resp.Err(e, err)
}
if err := handler.service.Run(e.Request.Context(), req); err != nil {
return resp.Err(e, err)
}
return resp.Ok(e, nil)
}

View File

@@ -1,41 +0,0 @@
package rest
import (
"context"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/rest/resp"
"github.com/labstack/echo/v5"
)
type NotifyService interface {
Test(ctx context.Context, req *domain.NotifyTestPushReq) error
}
type notifyHandler struct {
service NotifyService
}
func NewNotifyHandler(route *echo.Group, service NotifyService) {
handler := &notifyHandler{
service: service,
}
group := route.Group("/notify")
group.POST("/test", handler.test)
}
func (handler *notifyHandler) test(c echo.Context) error {
req := &domain.NotifyTestPushReq{}
if err := c.Bind(req); err != nil {
return resp.Err(c, err)
}
if err := handler.service.Test(c.Request().Context(), req); err != nil {
return resp.Err(c, err)
}
return resp.Ok(c, nil)
}

View File

@@ -3,7 +3,7 @@ package resp
import (
"net/http"
"github.com/labstack/echo/v5"
"github.com/pocketbase/pocketbase/core"
"github.com/usual2970/certimate/internal/domain"
)
@@ -14,7 +14,7 @@ type Response struct {
Data interface{} `json:"data"`
}
func Ok(e echo.Context, data interface{}) error {
func Ok(e *core.RequestEvent, data interface{}) error {
rs := &Response{
Code: 0,
Msg: "success",
@@ -23,7 +23,7 @@ func Ok(e echo.Context, data interface{}) error {
return e.JSON(http.StatusOK, rs)
}
func Err(e echo.Context, err error) error {
func Err(e *core.RequestEvent, err error) error {
code := 500
xerr, ok := err.(*domain.Error)

View File

@@ -0,0 +1,50 @@
package routes
import (
"context"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/router"
"github.com/usual2970/certimate/internal/certificate"
"github.com/usual2970/certimate/internal/notify"
"github.com/usual2970/certimate/internal/repository"
"github.com/usual2970/certimate/internal/rest/handlers"
"github.com/usual2970/certimate/internal/statistics"
"github.com/usual2970/certimate/internal/workflow"
)
var (
certificateSvc *certificate.CertificateService
workflowSvc *workflow.WorkflowService
statisticsSvc *statistics.StatisticsService
notifySvc *notify.NotifyService
)
func Register(router *router.Router[*core.RequestEvent]) {
certificateRepo := repository.NewCertificateRepository()
certificateSvc = certificate.NewCertificateService(certificateRepo)
workflowRepo := repository.NewWorkflowRepository()
workflowSvc = workflow.NewWorkflowService(workflowRepo)
statisticsRepo := repository.NewStatisticsRepository()
statisticsSvc = statistics.NewStatisticsService(statisticsRepo)
notifyRepo := repository.NewSettingsRepository()
notifySvc = notify.NewNotifyService(notifyRepo)
group := router.Group("/api")
group.Bind(apis.RequireSuperuserAuth())
handlers.NewCertificateHandler(group, certificateSvc)
handlers.NewWorkflowHandler(group, workflowSvc)
handlers.NewStatisticsHandler(group, statisticsSvc)
handlers.NewNotifyHandler(group, notifySvc)
}
func Unregister() {
if workflowSvc != nil {
workflowSvc.Stop(context.Background())
}
}

View File

@@ -1,35 +0,0 @@
package rest
import (
"context"
"github.com/labstack/echo/v5"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/rest/resp"
)
type StatisticsService interface {
Get(ctx context.Context) (*domain.Statistics, error)
}
type statisticsHandler struct {
service StatisticsService
}
func NewStatisticsHandler(route *echo.Group, service StatisticsService) {
handler := &statisticsHandler{
service: service,
}
group := route.Group("/statistics")
group.GET("/get", handler.get)
}
func (handler *statisticsHandler) get(c echo.Context) error {
if statistics, err := handler.service.Get(c.Request().Context()); err != nil {
return resp.Err(c, err)
} else {
return resp.Ok(c, statistics)
}
}

View File

@@ -1,40 +0,0 @@
package rest
import (
"context"
"github.com/labstack/echo/v5"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/rest/resp"
)
type WorkflowService interface {
Run(ctx context.Context, req *domain.WorkflowRunReq) error
Stop()
}
type workflowHandler struct {
service WorkflowService
}
func NewWorkflowHandler(route *echo.Group, service WorkflowService) {
handler := &workflowHandler{
service: service,
}
group := route.Group("/workflow")
group.POST("/run", handler.run)
}
func (handler *workflowHandler) run(c echo.Context) error {
req := &domain.WorkflowRunReq{}
if err := c.Bind(req); err != nil {
return resp.Err(c, err)
}
if err := handler.service.Run(c.Request().Context(), req); err != nil {
return resp.Err(c, err)
}
return resp.Ok(c, nil)
}

View File

@@ -1,49 +0,0 @@
package routes
import (
"sync"
"github.com/usual2970/certimate/internal/notify"
"github.com/usual2970/certimate/internal/repository"
"github.com/usual2970/certimate/internal/rest"
"github.com/usual2970/certimate/internal/statistics"
"github.com/usual2970/certimate/internal/workflow"
"github.com/labstack/echo/v5"
"github.com/pocketbase/pocketbase/apis"
)
var (
workflowSvc rest.WorkflowService
workflowSvcOnce sync.Once
)
func getWorkflowService() rest.WorkflowService {
workflowSvcOnce.Do(func() {
workflowRepo := repository.NewWorkflowRepository()
workflowSvc = workflow.NewWorkflowService(workflowRepo)
})
return workflowSvc
}
func Register(e *echo.Echo) {
group := e.Group("/api", apis.RequireAdminAuth())
notifyRepo := repository.NewSettingsRepository()
notifySvc := notify.NewNotifyService(notifyRepo)
workflowSvc := getWorkflowService()
statisticsRepo := repository.NewStatisticsRepository()
statisticsSvc := statistics.NewStatisticsService(statisticsRepo)
rest.NewWorkflowHandler(group, workflowSvc)
rest.NewNotifyHandler(group, notifySvc)
rest.NewStatisticsHandler(group, statisticsSvc)
}
func Unregister() {
getWorkflowService().Stop()
}

View File

@@ -2,10 +2,10 @@ package scheduler
import "context"
type CertificateService interface {
type certificateService interface {
InitSchedule(ctx context.Context) error
}
func NewCertificateScheduler(service CertificateService) error {
func NewCertificateScheduler(service certificateService) error {
return service.InitSchedule(context.Background())
}

View File

@@ -2,10 +2,10 @@ package scheduler
import "context"
type WorkflowService interface {
type workflowService interface {
InitSchedule(ctx context.Context) error
}
func NewWorkflowScheduler(service WorkflowService) error {
func NewWorkflowScheduler(service workflowService) error {
return service.InitSchedule(context.Background())
}

View File

@@ -5,49 +5,67 @@ import (
"fmt"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/models"
"github.com/usual2970/certimate/internal/app"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/domain/dtos"
"github.com/usual2970/certimate/internal/repository"
)
const tableName = "workflow"
func Register() {
app := app.GetApp()
app.OnRecordCreateRequest(domain.CollectionNameWorkflow).BindFunc(func(e *core.RecordRequestEvent) error {
if err := e.Next(); err != nil {
return err
}
app.OnRecordAfterCreateRequest(tableName).Add(func(e *core.RecordCreateEvent) error {
return update(e.HttpContext.Request().Context(), e.Record)
if err := onWorkflowRecordCreateOrUpdate(e.Request.Context(), e.Record); err != nil {
return err
}
return nil
})
app.OnRecordUpdateRequest(domain.CollectionNameWorkflow).BindFunc(func(e *core.RecordRequestEvent) error {
if err := e.Next(); err != nil {
return err
}
app.OnRecordAfterUpdateRequest(tableName).Add(func(e *core.RecordUpdateEvent) error {
return update(e.HttpContext.Request().Context(), e.Record)
if err := onWorkflowRecordCreateOrUpdate(e.Request.Context(), e.Record); err != nil {
return err
}
return nil
})
app.OnRecordDeleteRequest(domain.CollectionNameWorkflow).BindFunc(func(e *core.RecordRequestEvent) error {
if err := e.Next(); err != nil {
return err
}
app.OnRecordAfterDeleteRequest(tableName).Add(func(e *core.RecordDeleteEvent) error {
return delete(e.HttpContext.Request().Context(), e.Record)
if err := onWorkflowRecordDelete(e.Request.Context(), e.Record); err != nil {
return err
}
return nil
})
}
func update(ctx context.Context, record *models.Record) error {
func onWorkflowRecordCreateOrUpdate(ctx context.Context, record *core.Record) error {
scheduler := app.GetScheduler()
// 向数据库插入/更新时,同时更新定时任务
workflowId := record.GetId()
workflowId := record.Id
enabled := record.GetBool("enabled")
trigger := record.GetString("trigger")
// 如果是手动触发或未启用,移除定时任务
if !enabled || trigger == string(domain.WorkflowTriggerTypeManual) {
scheduler.Remove(fmt.Sprintf("workflow#%s", workflowId))
scheduler.Start()
return nil
}
// 反之,重新添加定时任务
err := scheduler.Add(fmt.Sprintf("workflow#%s", workflowId), record.GetString("triggerCron"), func() {
NewWorkflowService(repository.NewWorkflowRepository()).Run(ctx, &domain.WorkflowRunReq{
NewWorkflowService(repository.NewWorkflowRepository()).Run(ctx, &dtos.WorkflowRunReq{
WorkflowId: workflowId,
Trigger: domain.WorkflowTriggerTypeAuto,
})
@@ -57,18 +75,15 @@ func update(ctx context.Context, record *models.Record) error {
return fmt.Errorf("add cron job failed: %w", err)
}
scheduler.Start()
return nil
}
func delete(_ context.Context, record *models.Record) error {
func onWorkflowRecordDelete(_ context.Context, record *core.Record) error {
scheduler := app.GetScheduler()
// 从数据库删除时,同时移除定时任务
workflowId := record.GetId()
workflowId := record.Id
scheduler.Remove(fmt.Sprintf("workflow#%s", workflowId))
scheduler.Start()
return nil
}

View File

@@ -77,14 +77,14 @@ func (a *applyNode) Run(ctx context.Context) error {
ACMECertStableUrl: applyResult.ACMECertStableUrl,
EffectAt: certX509.NotBefore,
ExpireAt: certX509.NotAfter,
WorkflowId: GetWorkflowId(ctx),
WorkflowId: getContextWorkflowId(ctx),
WorkflowNodeId: a.node.Id,
}
// 保存执行结果
// TODO: 先保持一个节点始终只有一个输出,后续增加版本控制
currentOutput := &domain.WorkflowOutput{
WorkflowId: GetWorkflowId(ctx),
WorkflowId: getContextWorkflowId(ctx),
NodeId: a.node.Id,
Node: a.node,
Succeeded: true,
@@ -109,32 +109,32 @@ func (a *applyNode) Run(ctx context.Context) error {
}
func (a *applyNode) checkCanSkip(ctx context.Context, lastOutput *domain.WorkflowOutput) (skip bool, reason string) {
const validityDuration = time.Hour * 24 * 10
// TODO: 可控制是否强制申请
if lastOutput != nil && lastOutput.Succeeded {
// 比较和上次申请时的关键配置(即影响证书签发的)参数是否一致
if lastOutput.Node.GetConfigString("domains") != a.node.GetConfigString("domains") {
currentNodeConfig := a.node.GetConfigForApply()
lastNodeConfig := lastOutput.Node.GetConfigForApply()
if currentNodeConfig.Domains != lastNodeConfig.Domains {
return false, "配置项变化:域名"
}
if lastOutput.Node.GetConfigString("contactEmail") != a.node.GetConfigString("contactEmail") {
if currentNodeConfig.ContactEmail != lastNodeConfig.ContactEmail {
return false, "配置项变化:联系邮箱"
}
if lastOutput.Node.GetConfigString("provider") != a.node.GetConfigString("provider") {
if currentNodeConfig.ProviderAccessId != lastNodeConfig.ProviderAccessId {
return false, "配置项变化DNS 提供商授权"
}
if !maps.Equal(lastOutput.Node.GetConfigMap("providerConfig"), a.node.GetConfigMap("providerConfig")) {
if !maps.Equal(currentNodeConfig.ProviderConfig, lastNodeConfig.ProviderConfig) {
return false, "配置项变化DNS 提供商参数"
}
if lastOutput.Node.GetConfigString("keyAlgorithm") != a.node.GetConfigString("keyAlgorithm") {
if currentNodeConfig.KeyAlgorithm != lastNodeConfig.KeyAlgorithm {
return false, "配置项变化:数字签名算法"
}
lastCertificate, _ := a.certRepo.GetByWorkflowNodeId(ctx, a.node.Id)
if lastCertificate != nil && time.Until(lastCertificate.ExpireAt) > validityDuration {
renewalInterval := time.Duration(currentNodeConfig.SkipBeforeExpiryDays) * time.Hour * 24
if lastCertificate != nil && time.Until(lastCertificate.ExpireAt) > renewalInterval {
return true, "已申请过证书,且证书尚未临近过期"
}
}
return false, "无历史申请记录"
return false, ""
}

View File

@@ -38,13 +38,13 @@ func (d *deployNode) Run(ctx context.Context) error {
}
// 获取前序节点输出证书
certSource := d.node.GetConfigString("certificate")
certSourceSlice := strings.Split(certSource, "#")
if len(certSourceSlice) != 2 {
d.AddOutput(ctx, d.node.Name, "证书来源配置错误", certSource)
return fmt.Errorf("证书来源配置错误: %s", certSource)
previousNodeOutputCertificateSource := d.node.GetConfigForDeploy().Certificate
previousNodeOutputCertificateSourceSlice := strings.Split(previousNodeOutputCertificateSource, "#")
if len(previousNodeOutputCertificateSourceSlice) != 2 {
d.AddOutput(ctx, d.node.Name, "证书来源配置错误", previousNodeOutputCertificateSource)
return fmt.Errorf("证书来源配置错误: %s", previousNodeOutputCertificateSource)
}
certificate, err := d.certRepo.GetByWorkflowNodeId(ctx, certSourceSlice[0])
certificate, err := d.certRepo.GetByWorkflowNodeId(ctx, previousNodeOutputCertificateSourceSlice[0])
if err != nil {
d.AddOutput(ctx, d.node.Name, "获取证书失败", err.Error())
return err
@@ -81,7 +81,7 @@ func (d *deployNode) Run(ctx context.Context) error {
// TODO: 先保持一个节点始终只有一个输出,后续增加版本控制
currentOutput := &domain.WorkflowOutput{
Meta: domain.Meta{},
WorkflowId: GetWorkflowId(ctx),
WorkflowId: getContextWorkflowId(ctx),
NodeId: d.node.Id,
Node: d.node,
Succeeded: true,
@@ -99,18 +99,21 @@ func (d *deployNode) Run(ctx context.Context) error {
}
func (d *deployNode) checkCanSkip(ctx context.Context, lastOutput *domain.WorkflowOutput) (skip bool, reason string) {
// TODO: 可控制是否强制部署
if lastOutput != nil && lastOutput.Succeeded {
// 比较和上次部署时的关键配置(即影响证书部署的)参数是否一致
if lastOutput.Node.GetConfigString("provider") != d.node.GetConfigString("provider") {
currentNodeConfig := d.node.GetConfigForDeploy()
lastNodeConfig := lastOutput.Node.GetConfigForDeploy()
if currentNodeConfig.ProviderAccessId != lastNodeConfig.ProviderAccessId {
return false, "配置项变化:主机提供商授权"
}
if !maps.Equal(lastOutput.Node.GetConfigMap("providerConfig"), d.node.GetConfigMap("providerConfig")) {
if !maps.Equal(currentNodeConfig.ProviderConfig, lastNodeConfig.ProviderConfig) {
return false, "配置项变化:主机提供商参数"
}
return true, "已部署过证书"
if currentNodeConfig.SkipOnLastSucceeded {
return true, "已部署过证书"
}
}
return false, "无历史部署记录"
return false, ""
}

View File

@@ -10,7 +10,7 @@ import (
type notifyNode struct {
node *domain.WorkflowNode
settingsRepo settingRepository
settingsRepo settingsRepository
*nodeLogger
}
@@ -25,6 +25,8 @@ func NewNotifyNode(node *domain.WorkflowNode) *notifyNode {
func (n *notifyNode) Run(ctx context.Context) error {
n.AddOutput(ctx, n.node.Name, "开始执行")
nodeConfig := n.node.GetConfigForNotify()
// 获取通知配置
settings, err := n.settingsRepo.GetByName(ctx, "notifyChannels")
if err != nil {
@@ -33,18 +35,14 @@ func (n *notifyNode) Run(ctx context.Context) error {
}
// 获取通知渠道
channelConfig, err := settings.GetNotifyChannelConfig(n.node.GetConfigString("channel"))
channelConfig, err := settings.GetNotifyChannelConfig(nodeConfig.Channel)
if err != nil {
n.AddOutput(ctx, n.node.Name, "获取通知渠道配置失败", err.Error())
return err
}
// 发送通知
if err := notify.SendToChannel(n.node.GetConfigString("subject"),
n.node.GetConfigString("message"),
n.node.GetConfigString("channel"),
channelConfig,
); err != nil {
if err := notify.SendToChannel(nodeConfig.Subject, nodeConfig.Message, nodeConfig.Channel, channelConfig); err != nil {
n.AddOutput(ctx, n.node.Name, "发送通知失败", err.Error())
return err
}

View File

@@ -8,7 +8,7 @@ import (
"github.com/usual2970/certimate/internal/domain"
)
type nodeProcessor interface {
type NodeProcessor interface {
Run(ctx context.Context) error
Log(ctx context.Context) *domain.WorkflowRunLog
AddOutput(ctx context.Context, title, content string, err ...string)
@@ -18,6 +18,19 @@ type nodeLogger struct {
log *domain.WorkflowRunLog
}
type certificateRepository interface {
GetByWorkflowNodeId(ctx context.Context, workflowNodeId string) (*domain.Certificate, error)
}
type workflowOutputRepository interface {
GetByNodeId(ctx context.Context, nodeId string) (*domain.WorkflowOutput, error)
Save(ctx context.Context, output *domain.WorkflowOutput, certificate *domain.Certificate, cb func(id string) error) error
}
type settingsRepository interface {
GetByName(ctx context.Context, name string) (*domain.Settings, error)
}
func NewNodeLogger(node *domain.WorkflowNode) *nodeLogger {
return &nodeLogger{
log: &domain.WorkflowRunLog{
@@ -45,7 +58,7 @@ func (l *nodeLogger) AddOutput(ctx context.Context, title, content string, err .
l.log.Outputs = append(l.log.Outputs, output)
}
func GetProcessor(node *domain.WorkflowNode) (nodeProcessor, error) {
func GetProcessor(node *domain.WorkflowNode) (NodeProcessor, error) {
switch node.Type {
case domain.WorkflowNodeTypeStart:
return NewStartNode(node), nil
@@ -65,15 +78,6 @@ func GetProcessor(node *domain.WorkflowNode) (nodeProcessor, error) {
return nil, errors.New("not implemented")
}
type certificateRepository interface {
GetByWorkflowNodeId(ctx context.Context, workflowNodeId string) (*domain.Certificate, error)
}
type workflowOutputRepository interface {
GetByNodeId(ctx context.Context, nodeId string) (*domain.WorkflowOutput, error)
Save(ctx context.Context, output *domain.WorkflowOutput, certificate *domain.Certificate, cb func(id string) error) error
}
type settingRepository interface {
GetByName(ctx context.Context, name string) (*domain.Settings, error)
func getContextWorkflowId(ctx context.Context) string {
return ctx.Value("workflow_id").(string)
}

View File

@@ -1,9 +1,10 @@
package nodeprocessor
package processor
import (
"context"
"github.com/usual2970/certimate/internal/domain"
nodes "github.com/usual2970/certimate/internal/workflow/node-processor"
)
type workflowProcessor struct {
@@ -23,26 +24,26 @@ func (w *workflowProcessor) Log(ctx context.Context) []domain.WorkflowRunLog {
}
func (w *workflowProcessor) Run(ctx context.Context) error {
ctx = WithWorkflowId(ctx, w.workflow.Id)
return w.runNode(ctx, w.workflow.Content)
ctx = setContextWorkflowId(ctx, w.workflow.Id)
return w.processNode(ctx, w.workflow.Content)
}
func (w *workflowProcessor) runNode(ctx context.Context, node *domain.WorkflowNode) error {
func (w *workflowProcessor) processNode(ctx context.Context, node *domain.WorkflowNode) error {
current := node
for current != nil {
if current.Type == domain.WorkflowNodeTypeBranch || current.Type == domain.WorkflowNodeTypeExecuteResultBranch {
for _, branch := range current.Branches {
if err := w.runNode(ctx, &branch); err != nil {
if err := w.processNode(ctx, &branch); err != nil {
continue
}
}
}
var runErr error
var processor nodeProcessor
var processor nodes.NodeProcessor
for {
if current.Type != domain.WorkflowNodeTypeBranch && current.Type != domain.WorkflowNodeTypeExecuteResultBranch {
processor, runErr = GetProcessor(current)
processor, runErr = nodes.GetProcessor(current)
if runErr != nil {
break
}
@@ -74,7 +75,7 @@ func (w *workflowProcessor) runNode(ctx context.Context, node *domain.WorkflowNo
return nil
}
func WithWorkflowId(ctx context.Context, id string) context.Context {
func setContextWorkflowId(ctx context.Context, id string) context.Context {
return context.WithValue(ctx, "workflow_id", id)
}

View File

@@ -9,21 +9,22 @@ import (
"github.com/usual2970/certimate/internal/app"
"github.com/usual2970/certimate/internal/domain"
nodeprocessor "github.com/usual2970/certimate/internal/workflow/node-processor"
"github.com/usual2970/certimate/internal/domain/dtos"
processor "github.com/usual2970/certimate/internal/workflow/processor"
)
const defaultRoutines = 10
type workflowRunData struct {
Workflow *domain.Workflow
Options *domain.WorkflowRunReq
Workflow *domain.Workflow
RunTrigger domain.WorkflowTriggerType
}
type workflowRepository interface {
ListEnabledAuto(ctx context.Context) ([]*domain.Workflow, error)
GetById(ctx context.Context, id string) (*domain.Workflow, error)
Save(ctx context.Context, workflow *domain.Workflow) error
SaveRun(ctx context.Context, run *domain.WorkflowRun) error
SaveRun(ctx context.Context, workflowRun *domain.WorkflowRun) error
}
type WorkflowService struct {
@@ -74,7 +75,7 @@ func (s *WorkflowService) InitSchedule(ctx context.Context) error {
scheduler := app.GetScheduler()
for _, workflow := range workflows {
err := scheduler.Add(fmt.Sprintf("workflow#%s", workflow.Id), workflow.TriggerCron, func() {
s.Run(ctx, &domain.WorkflowRunReq{
s.Run(ctx, &dtos.WorkflowRunReq{
WorkflowId: workflow.Id,
Trigger: domain.WorkflowTriggerTypeAuto,
})
@@ -84,18 +85,15 @@ func (s *WorkflowService) InitSchedule(ctx context.Context) error {
return err
}
}
scheduler.Start()
app.GetLogger().Info("workflow schedule started")
return nil
}
func (s *WorkflowService) Run(ctx context.Context, options *domain.WorkflowRunReq) error {
func (s *WorkflowService) Run(ctx context.Context, req *dtos.WorkflowRunReq) error {
// 查询
workflow, err := s.repo.GetById(ctx, options.WorkflowId)
workflow, err := s.repo.GetById(ctx, req.WorkflowId)
if err != nil {
app.GetLogger().Error("failed to get workflow", "id", options.WorkflowId, "err", err)
app.GetLogger().Error("failed to get workflow", "id", req.WorkflowId, "err", err)
return err
}
@@ -113,8 +111,8 @@ func (s *WorkflowService) Run(ctx context.Context, options *domain.WorkflowRunRe
}
s.ch <- &workflowRunData{
Workflow: workflow,
Options: options,
Workflow: workflow,
RunTrigger: req.Trigger,
}
return nil
@@ -123,16 +121,15 @@ func (s *WorkflowService) Run(ctx context.Context, options *domain.WorkflowRunRe
func (s *WorkflowService) run(ctx context.Context, runData *workflowRunData) error {
// 执行
workflow := runData.Workflow
options := runData.Options
run := &domain.WorkflowRun{
WorkflowId: workflow.Id,
Status: domain.WorkflowRunStatusTypeRunning,
Trigger: options.Trigger,
Trigger: runData.RunTrigger,
StartedAt: time.Now(),
EndedAt: time.Now(),
}
processor := nodeprocessor.NewWorkflowProcessor(workflow)
processor := processor.NewWorkflowProcessor(workflow)
if err := processor.Run(ctx); err != nil {
run.Status = domain.WorkflowRunStatusTypeFailed
run.EndedAt = time.Now()
@@ -165,7 +162,7 @@ func (s *WorkflowService) run(ctx context.Context, runData *workflowRunData) err
return nil
}
func (s *WorkflowService) Stop() {
func (s *WorkflowService) Stop(ctx context.Context) {
s.cancel()
s.wg.Wait()
}