mirror of
https://github.com/woodchen-ink/certimate.git
synced 2025-07-19 01:41:55 +08:00
Merge branch 'feat/new-workflow' of github.com:fudiwei/certimate into next
This commit is contained in:
commit
138e08e985
68
go.mod
68
go.mod
@ -28,7 +28,7 @@ require (
|
|||||||
github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0
|
github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0
|
||||||
github.com/pkg/sftp v1.13.7
|
github.com/pkg/sftp v1.13.7
|
||||||
github.com/pocketbase/dbx v1.11.0
|
github.com/pocketbase/dbx v1.11.0
|
||||||
github.com/pocketbase/pocketbase v0.24.4
|
github.com/pocketbase/pocketbase v0.25.0
|
||||||
github.com/povsister/scp v0.0.0-20240802064259-28781e87b246
|
github.com/povsister/scp v0.0.0-20240802064259-28781e87b246
|
||||||
github.com/qiniu/go-sdk/v7 v7.25.2
|
github.com/qiniu/go-sdk/v7 v7.25.2
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1084
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1084
|
||||||
@ -42,7 +42,7 @@ require (
|
|||||||
github.com/volcengine/volc-sdk-golang v1.0.193
|
github.com/volcengine/volc-sdk-golang v1.0.193
|
||||||
github.com/volcengine/volcengine-go-sdk v1.0.178
|
github.com/volcengine/volcengine-go-sdk v1.0.178
|
||||||
golang.org/x/crypto v0.32.0
|
golang.org/x/crypto v0.32.0
|
||||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8
|
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c
|
||||||
k8s.io/api v0.32.1
|
k8s.io/api v0.32.1
|
||||||
k8s.io/apimachinery v0.32.1
|
k8s.io/apimachinery v0.32.1
|
||||||
k8s.io/client-go v0.32.1
|
k8s.io/client-go v0.32.1
|
||||||
@ -108,7 +108,6 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7 // indirect
|
|
||||||
github.com/BurntSushi/toml v1.4.0 // indirect
|
github.com/BurntSushi/toml v1.4.0 // indirect
|
||||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
|
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
|
||||||
github.com/alibabacloud-go/dcdn-20180115/v3 v3.5.0
|
github.com/alibabacloud-go/dcdn-20180115/v3 v3.5.0
|
||||||
@ -120,25 +119,25 @@ require (
|
|||||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.83 // indirect
|
github.com/aliyun/alibaba-cloud-sdk-go v1.63.83 // indirect
|
||||||
github.com/aliyun/credentials-go v1.4.3 // indirect
|
github.com/aliyun/credentials-go v1.4.3 // indirect
|
||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2 v1.33.0
|
github.com/aws/aws-sdk-go-v2 v1.36.0
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.29.1
|
github.com/aws/aws-sdk-go-v2/config v1.29.5
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.54
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.58
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.24 // indirect
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.52 // indirect
|
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.58 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.28 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.28 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.28 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.31 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.2 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.5 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.9 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.9 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.12 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.73.2 // indirect
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.75.3 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.11 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sso v1.24.14 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.10 // indirect
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.9 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sts v1.33.13 // indirect
|
||||||
github.com/aws/smithy-go v1.22.1 // indirect
|
github.com/aws/smithy-go v1.22.2 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||||
github.com/cloudflare/cloudflare-go v0.114.0 // indirect
|
github.com/cloudflare/cloudflare-go v0.114.0 // indirect
|
||||||
@ -152,7 +151,6 @@ require (
|
|||||||
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
|
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
|
||||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
|
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.4 // indirect
|
github.com/goccy/go-json v0.10.4 // indirect
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
|
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
@ -160,11 +158,9 @@ require (
|
|||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
|
||||||
github.com/kr/fs v0.1.0 // indirect
|
github.com/kr/fs v0.1.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
|
||||||
github.com/miekg/dns v1.1.62 // indirect
|
github.com/miekg/dns v1.1.62 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
@ -175,31 +171,31 @@ require (
|
|||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/spf13/cast v1.7.1 // indirect
|
github.com/spf13/cast v1.7.1 // indirect
|
||||||
github.com/spf13/cobra v1.8.1 // indirect
|
github.com/spf13/cobra v1.8.1 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.6 // indirect
|
||||||
github.com/stretchr/objx v0.5.2 // indirect
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
github.com/stretchr/testify v1.10.0 // indirect
|
github.com/stretchr/testify v1.10.0 // indirect
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1084 // indirect
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1084 // indirect
|
||||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||||
go.opencensus.io v0.24.0 // indirect
|
go.opencensus.io v0.24.0 // indirect
|
||||||
gocloud.dev v0.40.0 // indirect
|
gocloud.dev v0.40.0 // indirect
|
||||||
golang.org/x/image v0.23.0 // indirect
|
golang.org/x/image v0.24.0 // indirect
|
||||||
golang.org/x/mod v0.22.0 // indirect
|
golang.org/x/mod v0.22.0 // indirect
|
||||||
golang.org/x/net v0.34.0 // indirect
|
golang.org/x/net v0.34.0 // indirect
|
||||||
golang.org/x/oauth2 v0.25.0 // indirect
|
golang.org/x/oauth2 v0.26.0 // indirect
|
||||||
golang.org/x/sync v0.10.0
|
golang.org/x/sync v0.11.0
|
||||||
golang.org/x/sys v0.29.0 // indirect
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
golang.org/x/term v0.28.0 // indirect
|
golang.org/x/term v0.28.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/text v0.22.0 // indirect
|
||||||
golang.org/x/time v0.9.0
|
golang.org/x/time v0.9.0
|
||||||
golang.org/x/tools v0.29.0 // indirect
|
golang.org/x/tools v0.29.0 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
|
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
|
||||||
google.golang.org/api v0.217.0 // indirect
|
google.golang.org/api v0.219.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250204164813-702378808489 // indirect
|
||||||
google.golang.org/grpc v1.69.4 // indirect
|
google.golang.org/grpc v1.70.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.3 // indirect
|
google.golang.org/protobuf v1.36.4 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
modernc.org/libc v1.61.9 // indirect
|
modernc.org/libc v1.61.11 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.8.2 // indirect
|
modernc.org/memory v1.8.2 // indirect
|
||||||
modernc.org/sqlite v1.34.5 // indirect
|
modernc.org/sqlite v1.34.5 // indirect
|
||||||
|
156
go.sum
156
go.sum
@ -1,5 +1,5 @@
|
|||||||
cel.dev/expr v0.18.0 h1:CJ6drgk+Hf96lkLikr4rFf19WrU0BOWEihyZnI2TAzo=
|
cel.dev/expr v0.19.0 h1:lXuo+nDhpyJSpWxpPVi5cPUwzKb+dsdOiw6IreM5yt0=
|
||||||
cel.dev/expr v0.18.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
|
cel.dev/expr v0.19.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
|
||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||||
@ -49,8 +49,6 @@ cloud.google.com/go/storage v1.47.0/go.mod h1:Ks0vP374w0PW6jOUameJbapbQKXqkjGd/O
|
|||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
|
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
|
|
||||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
|
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ=
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
|
||||||
@ -90,8 +88,6 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapp
|
|||||||
github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
|
github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
|
||||||
github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
|
github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
|
||||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
|
||||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||||
github.com/Shopify/sarama v1.30.1/go.mod h1:hGgx05L/DiW8XYBXeJdKIN6V2QUy2H6JqME5VT1NLRw=
|
github.com/Shopify/sarama v1.30.1/go.mod h1:hGgx05L/DiW8XYBXeJdKIN6V2QUy2H6JqME5VT1NLRw=
|
||||||
@ -212,52 +208,52 @@ github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm
|
|||||||
github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
|
github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
|
||||||
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
|
github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.33.0 h1:Evgm4DI9imD81V0WwD+TN4DCwjUMdc94TrduMLbgZJs=
|
github.com/aws/aws-sdk-go-v2 v1.36.0 h1:b1wM5CcE65Ujwn565qcwgtOTT1aT4ADOHHgglKjG7fk=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.33.0/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U=
|
github.com/aws/aws-sdk-go-v2 v1.36.0/go.mod h1:5PMILGVKiW32oDzjj6RU52yrNrDPUHcbZQYr1sM7qmM=
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8=
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8 h1:zAxi9p3wsZMIaVCdoiQp2uZ9k1LsZvmAnoTBeZPXom0=
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc=
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8/go.mod h1:3XkePX5dSaxveLAYY7nsbsZZrKxCyEuE5pM4ziFxyGg=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.29.1 h1:JZhGawAyZ/EuJeBtbQYnaoftczcb2drR2Iq36Wgz4sQ=
|
github.com/aws/aws-sdk-go-v2/config v1.29.5 h1:4lS2IB+wwkj5J43Tq/AwvnscBerBJtQQ6YS7puzCI1k=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.29.1/go.mod h1:7bR2YD5euaxBhzt2y/oDkt3uNRb6tjFp98GlTFueRwk=
|
github.com/aws/aws-sdk-go-v2/config v1.29.5/go.mod h1:SNzldMlDVbN6nWxM7XsUiNXPSa1LWlqiXtvh/1PrJGg=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.54 h1:4UmqeOqJPvdvASZWrKlhzpRahAulBfyTJQUaYy4+hEI=
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.58 h1:/d7FUpAPU8Lf2KUdjniQvfNdlMID0Sd9pS23FJ3SS9Y=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.54/go.mod h1:RTdfo0P0hbbTxIhmQrOsC/PquBZGabEPnCaxxKRPSnI=
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.58/go.mod h1:aVYW33Ow10CyMQGFgC0ptMRIqJWvJ4nxZb0sUiuQT/A=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.24 h1:5grmdTdMsovn9kPZPI23Hhvp0ZyNm5cRO+IZFIYiAfw=
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27 h1:7lOW8NUwE9UZekS1DYoiPdVAqZ6A+LheHWb+mHbNOq8=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.24/go.mod h1:zqi7TVKTswH3Ozq28PkmBmgzG1tona7mo9G2IJg4Cis=
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27/go.mod h1:w1BASFIPOPUae7AgaH4SbjNbfdkxuggLyGfNFTn8ITY=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.52 h1:6kI83R98XOnnyzHv9g9KTYXFawMyeQq8NeEERWMAwJk=
|
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.58 h1:/BsEGAyMai+KdXS+CMHlLhB5miAO19wOqE6tj8azWPM=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.52/go.mod h1:Juj7unpf3CIrWpEyJZhRJ6rJl9IYX7Hd8HOlwaZq/LE=
|
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.58/go.mod h1:KHM3lfl/sAJBCoLI1Lsg5w4SD2VDYWwQi7vxbKhw7TI=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.28 h1:igORFSiH3bfq4lxKFkTSYDhJEUCYo6C8VKiWJjYwQuQ=
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31 h1:lWm9ucLSRFiI4dQQafLrEOmEDGry3Swrz0BIRdiHJqQ=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.28/go.mod h1:3So8EA/aAYm36L7XIvCVwLa0s5N0P7o2b1oqnx/2R4g=
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31/go.mod h1:Huu6GG0YTfbPphQkDSo4dEGmQRTKb9k9G7RdtyQWxuI=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.28 h1:1mOW9zAUMhTSrMDssEHS/ajx8JcAj/IcftzcmNlmVLI=
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31 h1:ACxDklUKKXb48+eg5ROZXi1vDgfMyfIA/WyvqHcHI0o=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.28/go.mod h1:kGlXVIWDfvt2Ox5zEaNglmq0hXPHgQFNMix33Tw22jA=
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31/go.mod h1:yadnfsDwqXeVaohbGc/RaD287PuyRw2wugkh5ZL2J6k=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 h1:Pg9URiobXy85kgFev3og2CuOZ8JZUBENF+dcgWBaYNk=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.28 h1:7kpeALOUeThs2kEjlAxlADAVfxKmkYAedlpZ3kdoSJ4=
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.31 h1:8IwBjuLdqIO1dGB+dZ9zJEl8wzY3bVYxcs0Xyu/Lsc0=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.28/go.mod h1:pyaOYEdp1MJWgtXLy6q80r3DhsVdOIOZNB9hdTcJIvI=
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.31/go.mod h1:8tMBcuVjL4kP/ECEIWTCWtwV2kj6+ouEKl4cqR4iWLw=
|
||||||
github.com/aws/aws-sdk-go-v2/service/acm v1.30.13 h1:aPCPsgDxQqOS3zPJKYJQVh02q8stjSQ1haHaUucCAUM=
|
github.com/aws/aws-sdk-go-v2/service/acm v1.30.13 h1:aPCPsgDxQqOS3zPJKYJQVh02q8stjSQ1haHaUucCAUM=
|
||||||
github.com/aws/aws-sdk-go-v2/service/acm v1.30.13/go.mod h1:3pfuOCVLzWu3aiavTB9bOIdZpVadNYt6fyZdp+fDOSU=
|
github.com/aws/aws-sdk-go-v2/service/acm v1.30.13/go.mod h1:3pfuOCVLzWu3aiavTB9bOIdZpVadNYt6fyZdp+fDOSU=
|
||||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.44.5 h1:oBLlEuSL5G9W8M4GtEVdNi+xsQP+9lphVkbYf38Isgs=
|
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.44.5 h1:oBLlEuSL5G9W8M4GtEVdNi+xsQP+9lphVkbYf38Isgs=
|
||||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.44.5/go.mod h1:H/t3dGwvHy2WJ+ZwyDBWva7ttsoxSxt5qC1OMcc0iJ0=
|
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.44.5/go.mod h1:H/t3dGwvHy2WJ+ZwyDBWva7ttsoxSxt5qC1OMcc0iJ0=
|
||||||
github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o=
|
github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y=
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 h1:D4oz8/CzT9bAEYtVhSBmFj2dNOtaHOtMKc2vHBwYizA=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE=
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2/go.mod h1:Za3IHqTQ+yNcRHxu1OFucBh0ACZT4j4VQFF0BqpZcLY=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.2 h1:e6um6+DWYQP1XCa+E9YVtG/9v1qk5lyAOelMOVwSyO8=
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.5 h1:siiQ+jummya9OLPDEyHVb2dLW4aOMe22FGDd0sAfuSw=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.2/go.mod h1:dIW8puxSbYLSPv/ju0d9A3CpwXdtqvJtYKDMVmPLOWE=
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.5/go.mod h1:iHVx2J9pWzITdP5MJY6qWfG34TfD9EA+Qi3eV6qQCXw=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.9 h1:TQmKDyETFGiXVhZfQ/I0cCFziqqX58pi4tKJGYGFSz0=
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12 h1:O+8vD2rGjfihBewr5bT+QUfYUHIxCVgG61LHoT59shM=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.9/go.mod h1:HVLPK2iHQBUx7HfZeOQSEu3v2ubZaAY2YPbAm5/WUyY=
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12/go.mod h1:usVdWJaosa66NMvmCrr08NcWDBRv4E6+YFG2pUdw1Lk=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.9 h1:2aInXbh02XsbO0KobPGMNXyv2QP73VDKsWPNJARj/+4=
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.12 h1:tkVNm99nkJnFo1H9IIQb5QkCiPcvCDn3Pos+IeTbGRA=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.9/go.mod h1:dgXS1i+HgWnYkPXqNoPIPKeUsUUYHaUbThC90aDnNiE=
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.12/go.mod h1:dIVlquSPUMqEJtx2/W17SM2SuESRaVEhEV9alcMqxjw=
|
||||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.48.1 h1:njgAP7Rtt4DGdTGFPhJ4gaZXCD1CDj/SZDa5W4ZgSTs=
|
github.com/aws/aws-sdk-go-v2/service/route53 v1.48.1 h1:njgAP7Rtt4DGdTGFPhJ4gaZXCD1CDj/SZDa5W4ZgSTs=
|
||||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.48.1/go.mod h1:TN4PcCL0lvqmYcv+AV8iZFC4Sd0FM06QDaoBXrFEftU=
|
github.com/aws/aws-sdk-go-v2/service/route53 v1.48.1/go.mod h1:TN4PcCL0lvqmYcv+AV8iZFC4Sd0FM06QDaoBXrFEftU=
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.73.2 h1:F3h8VYq9ZLBXYurmwrT8W0SPhgCcU0q+0WZJfT1dFt0=
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.75.3 h1:JBod0SnNqcWQ0+uAyzeRFG1zCHotW8DukumYYyNy0zo=
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.73.2/go.mod h1:jGJ/v7FIi7Ys9t54tmEFnrxuaWeJLpwNgKp2DXAVhOU=
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.75.3/go.mod h1:FHSHmyEUkzRbaFFqqm6bkLAOQHgqhsLmfCahvCBMiyA=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.11 h1:kuIyu4fTT38Kj7YCC7ouNbVZSSpqkZ+LzIfhCr6Dg+I=
|
github.com/aws/aws-sdk-go-v2/service/sso v1.24.14 h1:c5WJ3iHz7rLIgArznb3JCSQT3uUMiz9DLZhIX+1G8ok=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.11/go.mod h1:Ro744S4fKiCCuZECXgOi760TiYylUM8ZBf6OGiZzJtY=
|
github.com/aws/aws-sdk-go-v2/service/sso v1.24.14/go.mod h1:+JJQTxB6N4niArC14YNtxcQtwEqzS3o9Z32n7q33Rfs=
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.10 h1:l+dgv/64iVlQ3WsBbnn+JSbkj01jIi+SM0wYsj3y/hY=
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13 h1:f1L/JtUkVODD+k1+IiSJUUv8A++2qVr+Xvb3xWXETMU=
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.10/go.mod h1:Fzsj6lZEb8AkTE5S68OhcbBqeWPsR8RnGuKPr8Todl8=
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13/go.mod h1:tvqlFoja8/s0o+UruA1Nrezo/df0PzdunMDDurUfg6U=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.9 h1:BRVDbewN6VZcwr+FBOszDKvYeXY1kJ+GGMCcpghlw0U=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.33.13 h1:3LXNnmtH3TURctC23hnC0p/39Q5gre3FI7BNOiDcVWc=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.9/go.mod h1:f6vjfZER1M17Fokn0IzssOTMT2N8ZSq+7jnNF0tArvw=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.33.13/go.mod h1:7Yn+p66q/jt38qMoVfNvjbm3D89mGBnkwDcijgtih8w=
|
||||||
github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
|
github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
|
||||||
github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro=
|
github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
|
||||||
github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
||||||
github.com/baidubce/bce-sdk-go v0.9.214 h1:bsVfwMh/emI6vreEveUEq9xAr6xtHLycTAGy2K7kvKM=
|
github.com/baidubce/bce-sdk-go v0.9.214 h1:bsVfwMh/emI6vreEveUEq9xAr6xtHLycTAGy2K7kvKM=
|
||||||
github.com/baidubce/bce-sdk-go v0.9.214/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg=
|
github.com/baidubce/bce-sdk-go v0.9.214/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg=
|
||||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||||
@ -305,8 +301,6 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
|
|||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
|
|
||||||
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
|
||||||
github.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
|
github.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@ -431,8 +425,6 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
|||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
|
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
|
||||||
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
|
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||||
@ -560,8 +552,6 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
|
|||||||
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
|
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
|
||||||
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
||||||
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
||||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
|
||||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.132 h1:5LqzrJa8LADcY0sDEdV35e8nbwI7RoUQEt+KXWvWoY0=
|
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.132 h1:5LqzrJa8LADcY0sDEdV35e8nbwI7RoUQEt+KXWvWoY0=
|
||||||
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.132/go.mod h1:JWz2ujO9X3oU5wb6kXp+DpR2UuDj2SldDbX8T0FSuhI=
|
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.132/go.mod h1:JWz2ujO9X3oU5wb6kXp+DpR2UuDj2SldDbX8T0FSuhI=
|
||||||
@ -601,8 +591,6 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
|
|||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||||
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
|
||||||
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs=
|
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs=
|
||||||
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw=
|
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw=
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
@ -634,7 +622,6 @@ github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjS
|
|||||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
|
||||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
@ -649,9 +636,6 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
|
|||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||||
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||||
@ -739,8 +723,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
|
|||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pocketbase/dbx v1.11.0 h1:LpZezioMfT3K4tLrqA55wWFw1EtH1pM4tzSVa7kgszU=
|
github.com/pocketbase/dbx v1.11.0 h1:LpZezioMfT3K4tLrqA55wWFw1EtH1pM4tzSVa7kgszU=
|
||||||
github.com/pocketbase/dbx v1.11.0/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs=
|
github.com/pocketbase/dbx v1.11.0/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs=
|
||||||
github.com/pocketbase/pocketbase v0.24.4 h1:kw/c23HccoxMV/19U9QlDcvNJgQ66vlUrxGQDZicWKM=
|
github.com/pocketbase/pocketbase v0.25.0 h1:/4YQq1hd0muvhzbERyUTVNh88N0BCj5diqK0jtLN6k8=
|
||||||
github.com/pocketbase/pocketbase v0.24.4/go.mod h1:EfXV/8RUY76jA6g1RPNHjOuW7wTd2bz0QlvAI/RU8YY=
|
github.com/pocketbase/pocketbase v0.25.0/go.mod h1:tOtOv7f3vJhAiyUluIwV9JPuKeknZRQ9F6uJE3W/ntI=
|
||||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||||
github.com/povsister/scp v0.0.0-20240802064259-28781e87b246 h1:c4D8BPWLOxxdaxQLfLKQXH2YXY/E9yo3jrDSL54XrTw=
|
github.com/povsister/scp v0.0.0-20240802064259-28781e87b246 h1:c4D8BPWLOxxdaxQLfLKQXH2YXY/E9yo3jrDSL54XrTw=
|
||||||
@ -805,8 +789,9 @@ github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
|||||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||||
|
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||||
github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||||
github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
|
github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
|
||||||
@ -964,14 +949,14 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
|||||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
|
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc=
|
||||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
||||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
|
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
|
||||||
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
@ -1056,8 +1041,8 @@ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4Iltr
|
|||||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
|
golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE=
|
||||||
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@ -1071,8 +1056,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@ -1147,8 +1132,8 @@ golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
@ -1172,7 +1157,6 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
@ -1180,8 +1164,8 @@ golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
@ -1269,8 +1253,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
|
|||||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||||
google.golang.org/api v0.217.0 h1:GYrUtD289o4zl1AhiTZL0jvQGa2RDLyC+kX1N/lfGOU=
|
google.golang.org/api v0.219.0 h1:nnKIvxKs/06jWawp2liznTBnMRQBEPpGo7I+oEypTX0=
|
||||||
google.golang.org/api v0.217.0/go.mod h1:qMc2E8cBAbQlRypBTBWHklNJlaZZJBwDv81B1Iu8oSI=
|
google.golang.org/api v0.219.0/go.mod h1:K6OmjGm+NtLrIkHxv1U3a0qIf/0JOvAHd5O/6AoyKYE=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
@ -1313,8 +1297,8 @@ google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD
|
|||||||
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc=
|
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q=
|
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08=
|
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250204164813-702378808489 h1:5bKytslY8ViY0Cj/ewmRtrWHW64bNF03cAatUUFCdFI=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250204164813-702378808489/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
@ -1332,8 +1316,8 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv
|
|||||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||||
google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A=
|
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
|
||||||
google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
|
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
@ -1349,8 +1333,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
|
|||||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
|
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||||
google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
@ -1408,15 +1392,15 @@ k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJ
|
|||||||
k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||||
modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0=
|
modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0=
|
||||||
modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||||
modernc.org/ccgo/v4 v4.23.13 h1:PFiaemQwE/jdwi8XEHyEV+qYWoIuikLP3T4rvDeJb00=
|
modernc.org/ccgo/v4 v4.23.15 h1:wFDan71KnYqeHz4eF63vmGE6Q6Pc0PUGDpP0PRMYjDc=
|
||||||
modernc.org/ccgo/v4 v4.23.13/go.mod h1:vdN4h2WR5aEoNondUx26K7G8X+nuBscYnAEWSRmN2/0=
|
modernc.org/ccgo/v4 v4.23.15/go.mod h1:nJX30dks/IWuBOnVa7VRii9Me4/9TZ1SC9GNtmARTy0=
|
||||||
modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8=
|
modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8=
|
||||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||||
modernc.org/gc/v2 v2.6.1 h1:+Qf6xdG8l7B27TQ8D8lw/iFMUj1RXRBOuMUWziJOsk8=
|
modernc.org/gc/v2 v2.6.2 h1:YBXi5Kqp6aCK3fIxwKQ3/fErvawVKwjOLItxj1brGds=
|
||||||
modernc.org/gc/v2 v2.6.1/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
modernc.org/gc/v2 v2.6.2/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||||
modernc.org/libc v1.61.9 h1:PLSBXVkifXGELtJ5BOnBUyAHr7lsatNwFU/RRo4kfJM=
|
modernc.org/libc v1.61.11 h1:6sZG8uB6EMMG7iTLPTndi8jyTdgAQNIeLGjCFICACZw=
|
||||||
modernc.org/libc v1.61.9/go.mod h1:61xrnzk/aR8gr5bR7Uj/lLFLuXu2/zMpIjcry63Eumk=
|
modernc.org/libc v1.61.11/go.mod h1:HHX+srFdn839oaJRd0W8hBM3eg+mieyZCAjWwB08/nM=
|
||||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI=
|
modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI=
|
||||||
|
@ -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
|
ReplacedARIAcctId 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.ReplacedARIAcctId = 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.ReplacedARIAcctId != 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: 暂时使用代理模式以兼容之前版本代码,后续重新实现此处逻辑
|
||||||
|
@ -35,8 +35,8 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
|
|||||||
case domain.ApplyDNSProviderTypeACMEHttpReq:
|
case domain.ApplyDNSProviderTypeACMEHttpReq:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForACMEHttpReq{}
|
access := domain.AccessConfigForACMEHttpReq{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
applicant, err := providerACMEHttpReq.NewChallengeProvider(&providerACMEHttpReq.ACMEHttpReqApplicantConfig{
|
applicant, err := providerACMEHttpReq.NewChallengeProvider(&providerACMEHttpReq.ACMEHttpReqApplicantConfig{
|
||||||
@ -52,8 +52,8 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
|
|||||||
case domain.ApplyDNSProviderTypeAliyun, domain.ApplyDNSProviderTypeAliyunDNS:
|
case domain.ApplyDNSProviderTypeAliyun, domain.ApplyDNSProviderTypeAliyunDNS:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForAliyun{}
|
access := domain.AccessConfigForAliyun{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
applicant, err := providerAliyun.NewChallengeProvider(&providerAliyun.AliyunApplicantConfig{
|
applicant, err := providerAliyun.NewChallengeProvider(&providerAliyun.AliyunApplicantConfig{
|
||||||
@ -68,8 +68,8 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
|
|||||||
case domain.ApplyDNSProviderTypeAWS, domain.ApplyDNSProviderTypeAWSRoute53:
|
case domain.ApplyDNSProviderTypeAWS, domain.ApplyDNSProviderTypeAWSRoute53:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForAWS{}
|
access := domain.AccessConfigForAWS{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
applicant, err := providerAWSRoute53.NewChallengeProvider(&providerAWSRoute53.AWSRoute53ApplicantConfig{
|
applicant, err := providerAWSRoute53.NewChallengeProvider(&providerAWSRoute53.AWSRoute53ApplicantConfig{
|
||||||
@ -86,8 +86,8 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
|
|||||||
case domain.ApplyDNSProviderTypeAzureDNS:
|
case domain.ApplyDNSProviderTypeAzureDNS:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForAzure{}
|
access := domain.AccessConfigForAzure{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
applicant, err := providerAzureDNS.NewChallengeProvider(&providerAzureDNS.AzureDNSApplicantConfig{
|
applicant, err := providerAzureDNS.NewChallengeProvider(&providerAzureDNS.AzureDNSApplicantConfig{
|
||||||
@ -104,8 +104,8 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
|
|||||||
case domain.ApplyDNSProviderTypeCloudflare:
|
case domain.ApplyDNSProviderTypeCloudflare:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForCloudflare{}
|
access := domain.AccessConfigForCloudflare{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
applicant, err := providerCloudflare.NewChallengeProvider(&providerCloudflare.CloudflareApplicantConfig{
|
applicant, err := providerCloudflare.NewChallengeProvider(&providerCloudflare.CloudflareApplicantConfig{
|
||||||
@ -119,8 +119,8 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
|
|||||||
case domain.ApplyDNSProviderTypeClouDNS:
|
case domain.ApplyDNSProviderTypeClouDNS:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForClouDNS{}
|
access := domain.AccessConfigForClouDNS{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
applicant, err := providerClouDNS.NewChallengeProvider(&providerClouDNS.ClouDNSApplicantConfig{
|
applicant, err := providerClouDNS.NewChallengeProvider(&providerClouDNS.ClouDNSApplicantConfig{
|
||||||
@ -135,8 +135,8 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
|
|||||||
case domain.ApplyDNSProviderTypeGname:
|
case domain.ApplyDNSProviderTypeGname:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForGname{}
|
access := domain.AccessConfigForGname{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
applicant, err := providerGname.NewChallengeProvider(&providerGname.GnameApplicantConfig{
|
applicant, err := providerGname.NewChallengeProvider(&providerGname.GnameApplicantConfig{
|
||||||
@ -151,8 +151,8 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
|
|||||||
case domain.ApplyDNSProviderTypeGoDaddy:
|
case domain.ApplyDNSProviderTypeGoDaddy:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForGoDaddy{}
|
access := domain.AccessConfigForGoDaddy{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
applicant, err := providerGoDaddy.NewChallengeProvider(&providerGoDaddy.GoDaddyApplicantConfig{
|
applicant, err := providerGoDaddy.NewChallengeProvider(&providerGoDaddy.GoDaddyApplicantConfig{
|
||||||
@ -167,8 +167,8 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
|
|||||||
case domain.ApplyDNSProviderTypeHuaweiCloud, domain.ApplyDNSProviderTypeHuaweiCloudDNS:
|
case domain.ApplyDNSProviderTypeHuaweiCloud, domain.ApplyDNSProviderTypeHuaweiCloudDNS:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForHuaweiCloud{}
|
access := domain.AccessConfigForHuaweiCloud{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
applicant, err := providerHuaweiCloud.NewChallengeProvider(&providerHuaweiCloud.HuaweiCloudApplicantConfig{
|
applicant, err := providerHuaweiCloud.NewChallengeProvider(&providerHuaweiCloud.HuaweiCloudApplicantConfig{
|
||||||
@ -184,8 +184,8 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
|
|||||||
case domain.ApplyDNSProviderTypeNameDotCom:
|
case domain.ApplyDNSProviderTypeNameDotCom:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForNameDotCom{}
|
access := domain.AccessConfigForNameDotCom{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
applicant, err := providerNameDotCom.NewChallengeProvider(&providerNameDotCom.NameDotComApplicantConfig{
|
applicant, err := providerNameDotCom.NewChallengeProvider(&providerNameDotCom.NameDotComApplicantConfig{
|
||||||
@ -200,8 +200,8 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
|
|||||||
case domain.ApplyDNSProviderTypeNameSilo:
|
case domain.ApplyDNSProviderTypeNameSilo:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForNameSilo{}
|
access := domain.AccessConfigForNameSilo{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
applicant, err := providerNameSilo.NewChallengeProvider(&providerNameSilo.NameSiloApplicantConfig{
|
applicant, err := providerNameSilo.NewChallengeProvider(&providerNameSilo.NameSiloApplicantConfig{
|
||||||
@ -215,8 +215,8 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
|
|||||||
case domain.ApplyDNSProviderTypeNS1:
|
case domain.ApplyDNSProviderTypeNS1:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForNS1{}
|
access := domain.AccessConfigForNS1{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
applicant, err := providerNS1.NewChallengeProvider(&providerNS1.NS1ApplicantConfig{
|
applicant, err := providerNS1.NewChallengeProvider(&providerNS1.NS1ApplicantConfig{
|
||||||
@ -230,8 +230,8 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
|
|||||||
case domain.ApplyDNSProviderTypePowerDNS:
|
case domain.ApplyDNSProviderTypePowerDNS:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForPowerDNS{}
|
access := domain.AccessConfigForPowerDNS{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
applicant, err := providerPowerDNS.NewChallengeProvider(&providerPowerDNS.PowerDNSApplicantConfig{
|
applicant, err := providerPowerDNS.NewChallengeProvider(&providerPowerDNS.PowerDNSApplicantConfig{
|
||||||
@ -246,8 +246,8 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
|
|||||||
case domain.ApplyDNSProviderTypeRainYun:
|
case domain.ApplyDNSProviderTypeRainYun:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForRainYun{}
|
access := domain.AccessConfigForRainYun{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
applicant, err := providerRainYun.NewChallengeProvider(&providerRainYun.RainYunApplicantConfig{
|
applicant, err := providerRainYun.NewChallengeProvider(&providerRainYun.RainYunApplicantConfig{
|
||||||
@ -261,8 +261,8 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
|
|||||||
case domain.ApplyDNSProviderTypeTencentCloud, domain.ApplyDNSProviderTypeTencentCloudDNS:
|
case domain.ApplyDNSProviderTypeTencentCloud, domain.ApplyDNSProviderTypeTencentCloudDNS:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForTencentCloud{}
|
access := domain.AccessConfigForTencentCloud{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
applicant, err := providerTencentCloud.NewChallengeProvider(&providerTencentCloud.TencentCloudApplicantConfig{
|
applicant, err := providerTencentCloud.NewChallengeProvider(&providerTencentCloud.TencentCloudApplicantConfig{
|
||||||
@ -277,8 +277,8 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
|
|||||||
case domain.ApplyDNSProviderTypeVolcEngine, domain.ApplyDNSProviderTypeVolcEngineDNS:
|
case domain.ApplyDNSProviderTypeVolcEngine, domain.ApplyDNSProviderTypeVolcEngineDNS:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForVolcEngine{}
|
access := domain.AccessConfigForVolcEngine{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
applicant, err := providerVolcEngine.NewChallengeProvider(&providerVolcEngine.VolcEngineApplicantConfig{
|
applicant, err := providerVolcEngine.NewChallengeProvider(&providerVolcEngine.VolcEngineApplicantConfig{
|
||||||
@ -293,8 +293,8 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
|
|||||||
case domain.ApplyDNSProviderTypeWestcn:
|
case domain.ApplyDNSProviderTypeWestcn:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForWestcn{}
|
access := domain.AccessConfigForWestcn{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
applicant, err := providerWestcn.NewChallengeProvider(&providerWestcn.WestcnApplicantConfig{
|
applicant, err := providerWestcn.NewChallengeProvider(&providerWestcn.WestcnApplicantConfig{
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -30,18 +30,18 @@ type certificateRepository interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CertificateService struct {
|
type CertificateService struct {
|
||||||
repo certificateRepository
|
certRepo certificateRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCertificateService(repo certificateRepository) *CertificateService {
|
func NewCertificateService(certRepo certificateRepository) *CertificateService {
|
||||||
return &CertificateService{
|
return &CertificateService{
|
||||||
repo: repo,
|
certRepo: certRepo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CertificateService) InitSchedule(ctx context.Context) error {
|
func (s *CertificateService) InitSchedule(ctx context.Context) error {
|
||||||
app.GetScheduler().MustAdd("certificateExpireSoonNotify", "0 0 * * *", func() {
|
app.GetScheduler().MustAdd("certificateExpireSoonNotify", "0 0 * * *", func() {
|
||||||
certs, err := s.repo.ListExpireSoon(context.Background())
|
certs, err := s.certRepo.ListExpireSoon(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.GetLogger().Error("failed to get certificates which expire soon", "err", err)
|
app.GetLogger().Error("failed to get certificates which expire soon", "err", err)
|
||||||
return
|
return
|
||||||
@ -59,8 +59,8 @@ func (s *CertificateService) InitSchedule(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CertificateService) ArchiveFile(ctx context.Context, req *dtos.CertificateArchiveFileReq) ([]byte, error) {
|
func (s *CertificateService) ArchiveFile(ctx context.Context, req *dtos.CertificateArchiveFileReq) (*dtos.CertificateArchiveFileResp, error) {
|
||||||
certificate, err := s.repo.GetById(ctx, req.CertificateId)
|
certificate, err := s.certRepo.GetById(ctx, req.CertificateId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -69,6 +69,10 @@ func (s *CertificateService) ArchiveFile(ctx context.Context, req *dtos.Certific
|
|||||||
zipWriter := zip.NewWriter(&buf)
|
zipWriter := zip.NewWriter(&buf)
|
||||||
defer zipWriter.Close()
|
defer zipWriter.Close()
|
||||||
|
|
||||||
|
resp := &dtos.CertificateArchiveFileResp{
|
||||||
|
FileFormat: "zip",
|
||||||
|
}
|
||||||
|
|
||||||
switch strings.ToUpper(req.Format) {
|
switch strings.ToUpper(req.Format) {
|
||||||
case "", "PEM":
|
case "", "PEM":
|
||||||
{
|
{
|
||||||
@ -97,7 +101,8 @@ func (s *CertificateService) ArchiveFile(ctx context.Context, req *dtos.Certific
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
resp.FileBytes = buf.Bytes()
|
||||||
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
case "PFX":
|
case "PFX":
|
||||||
@ -134,7 +139,8 @@ func (s *CertificateService) ArchiveFile(ctx context.Context, req *dtos.Certific
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
resp.FileBytes = buf.Bytes()
|
||||||
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
case "JKS":
|
case "JKS":
|
||||||
@ -171,7 +177,8 @@ func (s *CertificateService) ArchiveFile(ctx context.Context, req *dtos.Certific
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
resp.FileBytes = buf.Bytes()
|
||||||
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -180,25 +187,30 @@ func (s *CertificateService) ArchiveFile(ctx context.Context, req *dtos.Certific
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *CertificateService) ValidateCertificate(ctx context.Context, req *dtos.CertificateValidateCertificateReq) (*dtos.CertificateValidateCertificateResp, error) {
|
func (s *CertificateService) ValidateCertificate(ctx context.Context, req *dtos.CertificateValidateCertificateReq) (*dtos.CertificateValidateCertificateResp, error) {
|
||||||
info, err := certs.ParseCertificateFromPEM(req.Certificate)
|
certX509, err := certs.ParseCertificateFromPEM(req.Certificate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if time.Now().After(certX509.NotAfter) {
|
||||||
|
return nil, fmt.Errorf("certificate has expired at %s", certX509.NotAfter.UTC().Format(time.RFC3339))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &dtos.CertificateValidateCertificateResp{
|
||||||
|
IsValid: true,
|
||||||
|
Domains: strings.Join(certX509.DNSNames, ";"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CertificateService) ValidatePrivateKey(ctx context.Context, req *dtos.CertificateValidatePrivateKeyReq) (*dtos.CertificateValidatePrivateKeyResp, error) {
|
||||||
|
_, err := certcrypto.ParsePEMPrivateKey([]byte(req.PrivateKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if time.Now().After(info.NotAfter) {
|
return &dtos.CertificateValidatePrivateKeyResp{
|
||||||
return nil, errors.New("证书已过期")
|
IsValid: true,
|
||||||
}
|
|
||||||
|
|
||||||
return &dtos.CertificateValidateCertificateResp{
|
|
||||||
Domains: strings.Join(info.DNSNames, ";"),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CertificateService) ValidatePrivateKey(ctx context.Context, req *dtos.CertificateValidatePrivateKeyReq) error {
|
|
||||||
_, err := certcrypto.ParsePEMPrivateKey([]byte(req.PrivateKey))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildExpireSoonNotification(certificates []*domain.Certificate) *struct {
|
func buildExpireSoonNotification(certificates []*domain.Certificate) *struct {
|
||||||
Subject string
|
Subject string
|
||||||
Message string
|
Message string
|
||||||
|
@ -54,8 +54,8 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger,
|
|||||||
case domain.DeployProviderTypeAliyunALB, domain.DeployProviderTypeAliyunCDN, domain.DeployProviderTypeAliyunCLB, domain.DeployProviderTypeAliyunDCDN, domain.DeployProviderTypeAliyunLive, domain.DeployProviderTypeAliyunNLB, domain.DeployProviderTypeAliyunOSS, domain.DeployProviderTypeAliyunWAF:
|
case domain.DeployProviderTypeAliyunALB, domain.DeployProviderTypeAliyunCDN, domain.DeployProviderTypeAliyunCLB, domain.DeployProviderTypeAliyunDCDN, domain.DeployProviderTypeAliyunLive, domain.DeployProviderTypeAliyunNLB, domain.DeployProviderTypeAliyunOSS, domain.DeployProviderTypeAliyunWAF:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForAliyun{}
|
access := domain.AccessConfigForAliyun{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch options.Provider {
|
switch options.Provider {
|
||||||
@ -146,8 +146,8 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger,
|
|||||||
case domain.DeployProviderTypeAWSCloudFront:
|
case domain.DeployProviderTypeAWSCloudFront:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForAWS{}
|
access := domain.AccessConfigForAWS{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch options.Provider {
|
switch options.Provider {
|
||||||
@ -168,8 +168,8 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger,
|
|||||||
case domain.DeployProviderTypeBaiduCloudCDN:
|
case domain.DeployProviderTypeBaiduCloudCDN:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForBaiduCloud{}
|
access := domain.AccessConfigForBaiduCloud{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch options.Provider {
|
switch options.Provider {
|
||||||
@ -189,8 +189,8 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger,
|
|||||||
case domain.DeployProviderTypeBytePlusCDN:
|
case domain.DeployProviderTypeBytePlusCDN:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForBytePlus{}
|
access := domain.AccessConfigForBytePlus{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch options.Provider {
|
switch options.Provider {
|
||||||
@ -210,8 +210,8 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger,
|
|||||||
case domain.DeployProviderTypeDogeCloudCDN:
|
case domain.DeployProviderTypeDogeCloudCDN:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForDogeCloud{}
|
access := domain.AccessConfigForDogeCloud{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
deployer, err := providerDogeCDN.NewWithLogger(&providerDogeCDN.DogeCloudCDNDeployerConfig{
|
deployer, err := providerDogeCDN.NewWithLogger(&providerDogeCDN.DogeCloudCDNDeployerConfig{
|
||||||
@ -225,8 +225,8 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger,
|
|||||||
case domain.DeployProviderTypeEdgioApplications:
|
case domain.DeployProviderTypeEdgioApplications:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForEdgio{}
|
access := domain.AccessConfigForEdgio{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
deployer, err := providerEdgioApplications.NewWithLogger(&providerEdgioApplications.EdgioApplicationsDeployerConfig{
|
deployer, err := providerEdgioApplications.NewWithLogger(&providerEdgioApplications.EdgioApplicationsDeployerConfig{
|
||||||
@ -240,8 +240,8 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger,
|
|||||||
case domain.DeployProviderTypeHuaweiCloudCDN, domain.DeployProviderTypeHuaweiCloudELB:
|
case domain.DeployProviderTypeHuaweiCloudCDN, domain.DeployProviderTypeHuaweiCloudELB:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForHuaweiCloud{}
|
access := domain.AccessConfigForHuaweiCloud{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch options.Provider {
|
switch options.Provider {
|
||||||
@ -291,8 +291,8 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger,
|
|||||||
case domain.DeployProviderTypeKubernetesSecret:
|
case domain.DeployProviderTypeKubernetesSecret:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForKubernetes{}
|
access := domain.AccessConfigForKubernetes{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
deployer, err := providerK8sSecret.NewWithLogger(&providerK8sSecret.K8sSecretDeployerConfig{
|
deployer, err := providerK8sSecret.NewWithLogger(&providerK8sSecret.K8sSecretDeployerConfig{
|
||||||
@ -309,8 +309,8 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger,
|
|||||||
case domain.DeployProviderTypeQiniuCDN, domain.DeployProviderTypeQiniuPili:
|
case domain.DeployProviderTypeQiniuCDN, domain.DeployProviderTypeQiniuPili:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForQiniu{}
|
access := domain.AccessConfigForQiniu{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch options.Provider {
|
switch options.Provider {
|
||||||
@ -339,8 +339,8 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger,
|
|||||||
case domain.DeployProviderTypeSSH:
|
case domain.DeployProviderTypeSSH:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForSSH{}
|
access := domain.AccessConfigForSSH{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
deployer, err := providerSSH.NewWithLogger(&providerSSH.SshDeployerConfig{
|
deployer, err := providerSSH.NewWithLogger(&providerSSH.SshDeployerConfig{
|
||||||
@ -367,8 +367,8 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger,
|
|||||||
case domain.DeployProviderTypeTencentCloudCDN, domain.DeployProviderTypeTencentCloudCLB, domain.DeployProviderTypeTencentCloudCOS, domain.DeployProviderTypeTencentCloudCSS, domain.DeployProviderTypeTencentCloudECDN, domain.DeployProviderTypeTencentCloudEO:
|
case domain.DeployProviderTypeTencentCloudCDN, domain.DeployProviderTypeTencentCloudCLB, domain.DeployProviderTypeTencentCloudCOS, domain.DeployProviderTypeTencentCloudCSS, domain.DeployProviderTypeTencentCloudECDN, domain.DeployProviderTypeTencentCloudEO:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForTencentCloud{}
|
access := domain.AccessConfigForTencentCloud{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch options.Provider {
|
switch options.Provider {
|
||||||
@ -435,8 +435,8 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger,
|
|||||||
case domain.DeployProviderTypeUCloudUCDN, domain.DeployProviderTypeUCloudUS3:
|
case domain.DeployProviderTypeUCloudUCDN, domain.DeployProviderTypeUCloudUS3:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForUCloud{}
|
access := domain.AccessConfigForUCloud{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch options.Provider {
|
switch options.Provider {
|
||||||
@ -468,8 +468,8 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger,
|
|||||||
case domain.DeployProviderTypeVolcEngineCDN, domain.DeployProviderTypeVolcEngineCLB, domain.DeployProviderTypeVolcEngineDCDN, domain.DeployProviderTypeVolcEngineLive, domain.DeployProviderTypeVolcEngineTOS:
|
case domain.DeployProviderTypeVolcEngineCDN, domain.DeployProviderTypeVolcEngineCLB, domain.DeployProviderTypeVolcEngineDCDN, domain.DeployProviderTypeVolcEngineLive, domain.DeployProviderTypeVolcEngineTOS:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForVolcEngine{}
|
access := domain.AccessConfigForVolcEngine{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch options.Provider {
|
switch options.Provider {
|
||||||
@ -525,8 +525,8 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger,
|
|||||||
case domain.DeployProviderTypeWebhook:
|
case domain.DeployProviderTypeWebhook:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForWebhook{}
|
access := domain.AccessConfigForWebhook{}
|
||||||
if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
|
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err)
|
return nil, nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
deployer, err := providerWebhook.NewWithLogger(&providerWebhook.WebhookDeployerConfig{
|
deployer, err := providerWebhook.NewWithLogger(&providerWebhook.WebhookDeployerConfig{
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
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"
|
||||||
|
|
||||||
@ -8,22 +14,81 @@ 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"`
|
||||||
|
SerialNumber string `json:"serialNumber" db:"serialNumber"`
|
||||||
Certificate string `json:"certificate" db:"certificate"`
|
Certificate string `json:"certificate" db:"certificate"`
|
||||||
PrivateKey string `json:"privateKey" db:"privateKey"`
|
PrivateKey string `json:"privateKey" db:"privateKey"`
|
||||||
|
Issuer string `json:"issuer" db:"issuer"`
|
||||||
IssuerCertificate string `json:"issuerCertificate" db:"issuerCertificate"`
|
IssuerCertificate string `json:"issuerCertificate" db:"issuerCertificate"`
|
||||||
|
KeyAlgorithm CertificateKeyAlgorithmType `json:"keyAlgorithm" db:"keyAlgorithm"`
|
||||||
EffectAt time.Time `json:"effectAt" db:"effectAt"`
|
EffectAt time.Time `json:"effectAt" db:"effectAt"`
|
||||||
ExpireAt time.Time `json:"expireAt" db:"expireAt"`
|
ExpireAt time.Time `json:"expireAt" db:"expireAt"`
|
||||||
|
ACMEAccountUrl string `json:"acmeAccountUrl" db:"acmeAccountUrl"`
|
||||||
ACMECertUrl string `json:"acmeCertUrl" db:"acmeCertUrl"`
|
ACMECertUrl string `json:"acmeCertUrl" db:"acmeCertUrl"`
|
||||||
ACMECertStableUrl string `json:"acmeCertStableUrl" db:"acmeCertStableUrl"`
|
ACMECertStableUrl string `json:"acmeCertStableUrl" db:"acmeCertStableUrl"`
|
||||||
WorkflowId string `json:"workflowId" db:"workflowId"`
|
WorkflowId string `json:"workflowId" db:"workflowId"`
|
||||||
WorkflowNodeId string `json:"workflowNodeId" db:"workflowNodeId"`
|
WorkflowNodeId string `json:"workflowNodeId" db:"workflowNodeId"`
|
||||||
|
WorkflowRunId string `json:"workflowRunId" db:"workflowRunId"`
|
||||||
WorkflowOutputId string `json:"workflowOutputId" db:"workflowOutputId"`
|
WorkflowOutputId string `json:"workflowOutputId" db:"workflowOutputId"`
|
||||||
DeletedAt *time.Time `json:"deleted" db:"deleted"`
|
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
|
||||||
|
|
||||||
const (
|
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")
|
||||||
|
)
|
||||||
|
@ -5,22 +5,24 @@ type CertificateArchiveFileReq struct {
|
|||||||
Format string `json:"format"`
|
Format string `json:"format"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CertificateArchiveFileResp struct {
|
||||||
|
FileBytes []byte `json:"fileBytes"`
|
||||||
|
FileFormat string `json:"fileFormat"`
|
||||||
|
}
|
||||||
|
|
||||||
type CertificateValidateCertificateReq struct {
|
type CertificateValidateCertificateReq struct {
|
||||||
Certificate string `json:"certificate"`
|
Certificate string `json:"certificate"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CertificateValidateCertificateResp struct {
|
type CertificateValidateCertificateResp struct {
|
||||||
Domains string `json:"domains"`
|
IsValid bool `json:"isValid"`
|
||||||
|
Domains string `json:"domains,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CertificateValidatePrivateKeyReq struct {
|
type CertificateValidatePrivateKeyReq struct {
|
||||||
PrivateKey string `json:"privateKey"`
|
PrivateKey string `json:"privateKey"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CertificateUploadReq struct {
|
type CertificateValidatePrivateKeyResp struct {
|
||||||
WorkflowId string `json:"workflowId"`
|
IsValid bool `json:"isValid"`
|
||||||
WorkflowNodeId string `json:"workflowNodeId"`
|
|
||||||
CertificateId string `json:"certificateId"`
|
|
||||||
Certificate string `json:"certificate"`
|
|
||||||
PrivateKey string `json:"privateKey"`
|
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import "github.com/usual2970/certimate/internal/domain"
|
|||||||
|
|
||||||
type WorkflowStartRunReq struct {
|
type WorkflowStartRunReq struct {
|
||||||
WorkflowId string `json:"-"`
|
WorkflowId string `json:"-"`
|
||||||
Trigger domain.WorkflowTriggerType `json:"trigger"`
|
RunTrigger domain.WorkflowTriggerType `json:"trigger"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WorkflowCancelRunReq struct {
|
type WorkflowCancelRunReq struct {
|
||||||
|
@ -55,8 +55,8 @@ type WorkflowNode struct {
|
|||||||
Inputs []WorkflowNodeIO `json:"inputs"`
|
Inputs []WorkflowNodeIO `json:"inputs"`
|
||||||
Outputs []WorkflowNodeIO `json:"outputs"`
|
Outputs []WorkflowNodeIO `json:"outputs"`
|
||||||
|
|
||||||
Next *WorkflowNode `json:"next"`
|
Next *WorkflowNode `json:"next,omitempty"`
|
||||||
Branches []WorkflowNode `json:"branches"`
|
Branches []WorkflowNode `json:"branches,omitempty"`
|
||||||
|
|
||||||
Validated bool `json:"validated"`
|
Validated bool `json:"validated"`
|
||||||
}
|
}
|
||||||
@ -64,6 +64,7 @@ type WorkflowNode struct {
|
|||||||
type WorkflowNodeConfigForApply struct {
|
type WorkflowNodeConfigForApply struct {
|
||||||
Domains string `json:"domains"` // 域名列表,以半角逗号分隔
|
Domains string `json:"domains"` // 域名列表,以半角逗号分隔
|
||||||
ContactEmail string `json:"contactEmail"` // 联系邮箱
|
ContactEmail string `json:"contactEmail"` // 联系邮箱
|
||||||
|
ChallengeType string `json:"challengeType"` // TODO: 验证方式。目前仅支持 dns-01
|
||||||
Provider string `json:"provider"` // DNS 提供商
|
Provider string `json:"provider"` // DNS 提供商
|
||||||
ProviderAccessId string `json:"providerAccessId"` // DNS 提供商授权记录 ID
|
ProviderAccessId string `json:"providerAccessId"` // DNS 提供商授权记录 ID
|
||||||
ProviderConfig map[string]any `json:"providerConfig"` // DNS 提供商额外配置
|
ProviderConfig map[string]any `json:"providerConfig"` // DNS 提供商额外配置
|
||||||
|
@ -5,6 +5,7 @@ const CollectionNameWorkflowOutput = "workflow_output"
|
|||||||
type WorkflowOutput struct {
|
type WorkflowOutput struct {
|
||||||
Meta
|
Meta
|
||||||
WorkflowId string `json:"workflowId" db:"workflow"`
|
WorkflowId string `json:"workflowId" db:"workflow"`
|
||||||
|
RunId string `json:"runId" db:"runId"`
|
||||||
NodeId string `json:"nodeId" db:"nodeId"`
|
NodeId string `json:"nodeId" db:"nodeId"`
|
||||||
Node *WorkflowNode `json:"node" db:"node"`
|
Node *WorkflowNode `json:"node" db:"node"`
|
||||||
Outputs []WorkflowNodeIO `json:"outputs" db:"outputs"`
|
Outputs []WorkflowNodeIO `json:"outputs" db:"outputs"`
|
||||||
|
@ -31,17 +31,26 @@ const (
|
|||||||
type WorkflowRunLog struct {
|
type WorkflowRunLog struct {
|
||||||
NodeId string `json:"nodeId"`
|
NodeId string `json:"nodeId"`
|
||||||
NodeName string `json:"nodeName"`
|
NodeName string `json:"nodeName"`
|
||||||
|
Records []WorkflowRunLogRecord `json:"records"`
|
||||||
Error string `json:"error"`
|
Error string `json:"error"`
|
||||||
Outputs []WorkflowRunLogOutput `json:"outputs"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type WorkflowRunLogOutput struct {
|
type WorkflowRunLogRecord struct {
|
||||||
Time string `json:"time"`
|
Time string `json:"time"`
|
||||||
Title string `json:"title"`
|
Level WorkflowRunLogLevel `json:"level"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
Error string `json:"error"`
|
Error string `json:"error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WorkflowRunLogLevel string
|
||||||
|
|
||||||
|
const (
|
||||||
|
WorkflowRunLogLevelDebug WorkflowRunLogLevel = "DEBUG"
|
||||||
|
WorkflowRunLogLevelInfo WorkflowRunLogLevel = "INFO"
|
||||||
|
WorkflowRunLogLevelWarn WorkflowRunLogLevel = "WARN"
|
||||||
|
WorkflowRunLogLevelError WorkflowRunLogLevel = "ERROR"
|
||||||
|
)
|
||||||
|
|
||||||
type WorkflowRunLogs []WorkflowRunLog
|
type WorkflowRunLogs []WorkflowRunLog
|
||||||
|
|
||||||
func (r WorkflowRunLogs) ErrorString() string {
|
func (r WorkflowRunLogs) ErrorString() string {
|
||||||
|
@ -173,8 +173,6 @@ func (d *DNSProvider) addOrUpdateDNSRecord(domain, subDomain, value string) erro
|
|||||||
_, err := d.client.ModifyDomainResolution(request)
|
_, err := d.client.ModifyDomainResolution(request)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DNSProvider) removeDNSRecord(domain, subDomain, value string) error {
|
func (d *DNSProvider) removeDNSRecord(domain, subDomain, value string) error {
|
||||||
|
@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
xerrors "github.com/pkg/errors"
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/logger"
|
"github.com/usual2970/certimate/internal/pkg/core/logger"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/certs"
|
||||||
edgsdk "github.com/usual2970/certimate/internal/pkg/vendors/edgio-sdk/applications/v7"
|
edgsdk "github.com/usual2970/certimate/internal/pkg/vendors/edgio-sdk/applications/v7"
|
||||||
edgsdkDtos "github.com/usual2970/certimate/internal/pkg/vendors/edgio-sdk/applications/v7/dtos"
|
edgsdkDtos "github.com/usual2970/certimate/internal/pkg/vendors/edgio-sdk/applications/v7/dtos"
|
||||||
)
|
)
|
||||||
@ -57,7 +57,10 @@ func NewWithLogger(config *EdgioApplicationsDeployerConfig, logger logger.Logger
|
|||||||
|
|
||||||
func (d *EdgioApplicationsDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
func (d *EdgioApplicationsDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||||
// 提取 Edgio 所需的服务端证书和中间证书内容
|
// 提取 Edgio 所需的服务端证书和中间证书内容
|
||||||
privateCertPem, intermediateCertPem := extractCertChains(certPem)
|
privateCertPem, intermediateCertPem, err := certs.ExtractCertificatesFromPEM(certPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// 上传 TLS 证书
|
// 上传 TLS 证书
|
||||||
// REF: https://docs.edg.io/rest_api/#tag/tls-certs/operation/postConfigV01TlsCerts
|
// REF: https://docs.edg.io/rest_api/#tag/tls-certs/operation/postConfigV01TlsCerts
|
||||||
@ -81,32 +84,3 @@ func createSdkClient(clientId, clientSecret string) (*edgsdk.EdgioClient, error)
|
|||||||
client := edgsdk.NewEdgioClient(clientId, clientSecret, "", "")
|
client := edgsdk.NewEdgioClient(clientId, clientSecret, "", "")
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractCertChains(certPem string) (primaryCertPem string, intermediateCertPem string) {
|
|
||||||
pemBlocks := make([]*pem.Block, 0)
|
|
||||||
pemData := []byte(certPem)
|
|
||||||
for {
|
|
||||||
block, rest := pem.Decode(pemData)
|
|
||||||
if block == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
pemBlocks = append(pemBlocks, block)
|
|
||||||
pemData = rest
|
|
||||||
}
|
|
||||||
|
|
||||||
primaryCertPem = ""
|
|
||||||
intermediateCertPem = ""
|
|
||||||
|
|
||||||
if len(pemBlocks) > 0 {
|
|
||||||
primaryCertPem = string(pem.EncodeToMemory(pemBlocks[0]))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(pemBlocks) > 1 {
|
|
||||||
for i := 1; i < len(pemBlocks); i++ {
|
|
||||||
intermediateCertPem += string(pem.EncodeToMemory(pemBlocks[i]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return primaryCertPem, intermediateCertPem
|
|
||||||
}
|
|
||||||
|
@ -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'")
|
||||||
}
|
}
|
||||||
|
48
internal/pkg/utils/certs/extractor.go
Normal file
48
internal/pkg/utils/certs/extractor.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package certs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 从 PEM 编码的证书字符串解析并提取服务器证书和中间证书。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - certPem: 证书 PEM 内容。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - serverCertPem: 服务器证书的 PEM 内容。
|
||||||
|
// - interCertPem: 中间证书的 PEM 内容。
|
||||||
|
// - err: 错误。
|
||||||
|
func ExtractCertificatesFromPEM(certPem string) (serverCertPem string, interCertPem string, err error) {
|
||||||
|
pemBlocks := make([]*pem.Block, 0)
|
||||||
|
pemData := []byte(certPem)
|
||||||
|
for {
|
||||||
|
block, rest := pem.Decode(pemData)
|
||||||
|
if block == nil || block.Type != "CERTIFICATE" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
pemBlocks = append(pemBlocks, block)
|
||||||
|
pemData = rest
|
||||||
|
}
|
||||||
|
|
||||||
|
serverCertPem = ""
|
||||||
|
interCertPem = ""
|
||||||
|
|
||||||
|
if len(pemBlocks) == 0 {
|
||||||
|
return "", "", errors.New("failed to decode PEM block")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pemBlocks) > 0 {
|
||||||
|
serverCertPem = string(pem.EncodeToMemory(pemBlocks[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pemBlocks) > 1 {
|
||||||
|
for i := 1; i < len(pemBlocks); i++ {
|
||||||
|
interCertPem += string(pem.EncodeToMemory(pemBlocks[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return serverCertPem, interCertPem, nil
|
||||||
|
}
|
@ -13,6 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// 从 PEM 编码的证书字符串解析并返回一个 x509.Certificate 对象。
|
// 从 PEM 编码的证书字符串解析并返回一个 x509.Certificate 对象。
|
||||||
|
// PEM 内容可能是包含多张证书的证书链,但只返回第一个证书(即服务器证书)。
|
||||||
//
|
//
|
||||||
// 入参:
|
// 入参:
|
||||||
// - certPem: 证书 PEM 内容。
|
// - certPem: 证书 PEM 内容。
|
||||||
|
@ -183,7 +183,7 @@ func GetValueOrDefaultAsBool(dict map[string]any, key string, defaultValue bool)
|
|||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将字典解码为指定类型的结构体。
|
// 将字典填充到指定类型的结构体。
|
||||||
// 与 [json.Unmarshal] 类似,但传入的是一个 [map[string]interface{}] 对象而非 JSON 格式的字符串。
|
// 与 [json.Unmarshal] 类似,但传入的是一个 [map[string]interface{}] 对象而非 JSON 格式的字符串。
|
||||||
//
|
//
|
||||||
// 入参:
|
// 入参:
|
||||||
@ -191,8 +191,8 @@ func GetValueOrDefaultAsBool(dict map[string]any, key string, defaultValue bool)
|
|||||||
// - output: 结构体指针。
|
// - output: 结构体指针。
|
||||||
//
|
//
|
||||||
// 出参:
|
// 出参:
|
||||||
// - 错误信息。如果解码失败,则返回错误信息。
|
// - 错误信息。如果填充失败,则返回错误信息。
|
||||||
func Decode(dict map[string]any, output any) error {
|
func Populate(dict map[string]any, output any) error {
|
||||||
config := &mapstructure.DecoderConfig{
|
config := &mapstructure.DecoderConfig{
|
||||||
Metadata: nil,
|
Metadata: nil,
|
||||||
Result: output,
|
Result: output,
|
||||||
@ -207,3 +207,8 @@ func Decode(dict map[string]any, output any) error {
|
|||||||
|
|
||||||
return decoder.Decode(dict)
|
return decoder.Decode(dict)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use [Populate] instead.
|
||||||
|
func Decode(dict map[string]any, output any) error {
|
||||||
|
return Populate(dict, output)
|
||||||
|
}
|
||||||
|
2
internal/pkg/vendors/gname-sdk/client.go
vendored
2
internal/pkg/vendors/gname-sdk/client.go
vendored
@ -150,7 +150,7 @@ func (c *GnameClient) sendRequestWithResult(path string, params map[string]any,
|
|||||||
if err := json.Unmarshal(resp.Body(), &jsonResp); err != nil {
|
if err := json.Unmarshal(resp.Body(), &jsonResp); err != nil {
|
||||||
return fmt.Errorf("failed to parse response: %w", err)
|
return fmt.Errorf("failed to parse response: %w", err)
|
||||||
}
|
}
|
||||||
if err := maps.Decode(jsonResp, &result); err != nil {
|
if err := maps.Populate(jsonResp, &result); err != nil {
|
||||||
return fmt.Errorf("failed to parse response: %w", err)
|
return fmt.Errorf("failed to parse response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
29
internal/pkg/vendors/qiniu-sdk/auth.go
vendored
Normal file
29
internal/pkg/vendors/qiniu-sdk/auth.go
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package qiniusdk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/qiniu/go-sdk/v7/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
type transport struct {
|
||||||
|
http.RoundTripper
|
||||||
|
mac *auth.Credentials
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTransport(mac *auth.Credentials, tr http.RoundTripper) *transport {
|
||||||
|
if tr == nil {
|
||||||
|
tr = http.DefaultTransport
|
||||||
|
}
|
||||||
|
return &transport{tr, mac}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
|
||||||
|
token, err := t.mac.SignRequestV2(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Authorization", "Qiniu "+token)
|
||||||
|
return t.RoundTripper.RoundTrip(req)
|
||||||
|
}
|
122
internal/pkg/vendors/qiniu-sdk/client.go
vendored
122
internal/pkg/vendors/qiniu-sdk/client.go
vendored
@ -1,48 +1,40 @@
|
|||||||
package qiniusdk
|
package qiniusdk
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qiniu/go-sdk/v7/auth"
|
"github.com/qiniu/go-sdk/v7/auth"
|
||||||
|
"github.com/qiniu/go-sdk/v7/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
const qiniuHost = "https://api.qiniu.com"
|
const qiniuHost = "https://api.qiniu.com"
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
mac *auth.Credentials
|
client *client.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(mac *auth.Credentials) *Client {
|
func NewClient(mac *auth.Credentials) *Client {
|
||||||
if mac == nil {
|
if mac == nil {
|
||||||
mac = auth.Default()
|
mac = auth.Default()
|
||||||
}
|
}
|
||||||
return &Client{mac: mac}
|
|
||||||
|
client := client.DefaultClient
|
||||||
|
client.Transport = newTransport(mac, nil)
|
||||||
|
return &Client{client: &client}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetDomainInfo(domain string) (*GetDomainInfoResponse, error) {
|
func (c *Client) GetDomainInfo(ctx context.Context, domain string) (*GetDomainInfoResponse, error) {
|
||||||
respBytes, err := c.sendReq(http.MethodGet, fmt.Sprintf("domain/%s", domain), nil)
|
resp := new(GetDomainInfoResponse)
|
||||||
if 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
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := &GetDomainInfoResponse{}
|
|
||||||
err = json.Unmarshal(respBytes, resp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if resp.Code != nil && *resp.Code != 0 && *resp.Code != 200 {
|
|
||||||
return nil, fmt.Errorf("code: %d, error: %s", *resp.Code, *resp.Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
||||||
@ -50,30 +42,14 @@ func (c *Client) ModifyDomainHttpsConf(domain, certId string, forceHttps, http2E
|
|||||||
Http2Enable: http2Enable,
|
Http2Enable: http2Enable,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
resp := new(ModifyDomainHttpsConfResponse)
|
||||||
reqBytes, err := json.Marshal(req)
|
if err := c.client.CallWithJson(ctx, resp, http.MethodPut, c.urlf("domain/%s/httpsconf", domain), nil, req); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
respBytes, err := c.sendReq(http.MethodPut, fmt.Sprintf("domain/%s/httpsconf", domain), bytes.NewReader(reqBytes))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := &ModifyDomainHttpsConfResponse{}
|
|
||||||
err = json.Unmarshal(respBytes, resp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if resp.Code != nil && *resp.Code != 0 && *resp.Code != 200 {
|
|
||||||
return nil, fmt.Errorf("code: %d, error: %s", *resp.Code, *resp.Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
||||||
@ -81,83 +57,29 @@ func (c *Client) EnableDomainHttps(domain, certId string, forceHttps, http2Enabl
|
|||||||
Http2Enable: http2Enable,
|
Http2Enable: http2Enable,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
resp := new(EnableDomainHttpsResponse)
|
||||||
reqBytes, err := json.Marshal(req)
|
if err := c.client.CallWithJson(ctx, resp, http.MethodPut, c.urlf("domain/%s/sslize", domain), nil, req); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
respBytes, err := c.sendReq(http.MethodPut, fmt.Sprintf("domain/%s/sslize", domain), bytes.NewReader(reqBytes))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := &EnableDomainHttpsResponse{}
|
|
||||||
err = json.Unmarshal(respBytes, resp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if resp.Code != nil && *resp.Code != 0 && *resp.Code != 200 {
|
|
||||||
return nil, fmt.Errorf("code: %d, error: %s", *resp.Code, *resp.Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
||||||
Certificate: certificate,
|
Certificate: certificate,
|
||||||
PrivateKey: privateKey,
|
PrivateKey: privateKey,
|
||||||
}
|
}
|
||||||
|
resp := new(UploadSslCertResponse)
|
||||||
reqBytes, err := json.Marshal(req)
|
if err := c.client.CallWithJson(ctx, resp, http.MethodPost, c.urlf("sslcert"), nil, req); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
respBytes, err := c.sendReq(http.MethodPost, "sslcert", bytes.NewReader(reqBytes))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := &UploadSslCertResponse{}
|
|
||||||
err = json.Unmarshal(respBytes, resp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if resp.Code != nil && *resp.Code != 0 && *resp.Code != 200 {
|
|
||||||
return nil, fmt.Errorf("qiniu api error, code: %d, error: %s", *resp.Code, *resp.Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) sendReq(method string, path string, body io.Reader) ([]byte, error) {
|
func (c *Client) urlf(pathf string, pathargs ...any) string {
|
||||||
|
path := fmt.Sprintf(pathf, pathargs...)
|
||||||
path = strings.TrimPrefix(path, "/")
|
path = strings.TrimPrefix(path, "/")
|
||||||
|
return qiniuHost + "/" + path
|
||||||
req, err := http.NewRequest(method, fmt.Sprintf("%s/%s", qiniuHost, path), body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
req.Header.Add("Content-Type", "application/json")
|
|
||||||
|
|
||||||
if err := c.mac.AddToken(auth.TokenQBox, req); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
client := http.Client{}
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
r, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return r, nil
|
|
||||||
}
|
}
|
||||||
|
2
internal/pkg/vendors/qiniu-sdk/models.go
vendored
2
internal/pkg/vendors/qiniu-sdk/models.go
vendored
@ -13,7 +13,7 @@ type UploadSslCertRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UploadSslCertResponse struct {
|
type UploadSslCertResponse struct {
|
||||||
*BaseResponse
|
BaseResponse
|
||||||
CertID string `json:"certID"`
|
CertID string `json:"certID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,52 @@ 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("workflowRunId", certificate.WorkflowRunId)
|
||||||
|
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,14 +138,19 @@ 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"),
|
||||||
|
WorkflowRunId: record.GetString("workflowRunId"),
|
||||||
WorkflowNodeId: record.GetString("workflowNodeId"),
|
WorkflowNodeId: record.GetString("workflowNodeId"),
|
||||||
WorkflowOutputId: record.GetString("workflowOutputId"),
|
WorkflowOutputId: record.GetString("workflowOutputId"),
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ func (r *WorkflowRepository) ListEnabledAuto(ctx context.Context) ([]*domain.Wor
|
|||||||
"enabled={:enabled} && trigger={:trigger}",
|
"enabled={:enabled} && trigger={:trigger}",
|
||||||
"-created",
|
"-created",
|
||||||
0, 0,
|
0, 0,
|
||||||
dbx.Params{"enabled": true, "trigger": domain.WorkflowTriggerTypeAuto},
|
dbx.Params{"enabled": true, "trigger": string(domain.WorkflowTriggerTypeAuto)},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -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,65 +95,6 @@ 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) {
|
|
||||||
collection, err := app.GetApp().FindCollectionByNameOrId(domain.CollectionNameWorkflowRun)
|
|
||||||
if err != nil {
|
|
||||||
return workflowRun, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var workflowRunRecord *core.Record
|
|
||||||
if workflowRun.Id == "" {
|
|
||||||
workflowRunRecord = core.NewRecord(collection)
|
|
||||||
} else {
|
|
||||||
workflowRunRecord, err = app.GetApp().FindRecordById(domain.CollectionNameWorkflowRun, workflowRun.Id)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
|
||||||
return workflowRun, err
|
|
||||||
}
|
|
||||||
workflowRunRecord = core.NewRecord(collection)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = app.GetApp().RunInTransaction(func(txApp core.App) error {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
workflowRecord, err := txApp.FindRecordById(domain.CollectionNameWorkflow, workflowRun.WorkflowId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
workflowRecord.IgnoreUnchangedFields(true)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
workflowRun.Id = workflowRunRecord.Id
|
|
||||||
workflowRun.CreatedAt = workflowRunRecord.GetDateTime("created").Time()
|
|
||||||
workflowRun.UpdatedAt = workflowRunRecord.GetDateTime("updated").Time()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return workflowRun, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return workflowRun, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *WorkflowRepository) castRecordToModel(record *core.Record) (*domain.Workflow, error) {
|
func (r *WorkflowRepository) castRecordToModel(record *core.Record) (*domain.Workflow, error) {
|
||||||
if record == nil {
|
if record == nil {
|
||||||
return nil, fmt.Errorf("record is nil")
|
return nil, fmt.Errorf("record is nil")
|
||||||
|
@ -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,103 +35,128 @@ 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(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(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 {
|
||||||
|
panic("certificate is nil")
|
||||||
|
} else {
|
||||||
|
if certificate.WorkflowId != "" && certificate.WorkflowId != workflowOutput.WorkflowId {
|
||||||
|
return workflowOutput, fmt.Errorf("certificate #%s is not belong to workflow #%s", certificate.Id, workflowOutput.WorkflowId)
|
||||||
|
}
|
||||||
|
if certificate.WorkflowRunId != "" && certificate.WorkflowRunId != workflowOutput.RunId {
|
||||||
|
return workflowOutput, fmt.Errorf("certificate #%s is not belong to workflow run #%s", certificate.Id, workflowOutput.RunId)
|
||||||
|
}
|
||||||
|
if certificate.WorkflowNodeId != "" && certificate.WorkflowNodeId != workflowOutput.NodeId {
|
||||||
|
return workflowOutput, fmt.Errorf("certificate #%s is not belong to workflow node #%s", certificate.Id, workflowOutput.NodeId)
|
||||||
|
}
|
||||||
|
if certificate.WorkflowOutputId != "" && certificate.WorkflowOutputId != workflowOutput.Id {
|
||||||
|
return workflowOutput, fmt.Errorf("certificate #%s is not belong to workflow output #%s", certificate.Id, workflowOutput.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
certificate.WorkflowId = workflowOutput.WorkflowId
|
||||||
|
certificate.WorkflowRunId = workflowOutput.RunId
|
||||||
|
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 {
|
||||||
return nil, errors.New("failed to unmarshal node")
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
outputs := make([]domain.WorkflowNodeIO, 0)
|
outputs := make([]domain.WorkflowNodeIO, 0)
|
||||||
if err := record.UnmarshalJSONField("outputs", &outputs); err != nil {
|
if err := record.UnmarshalJSONField("outputs", &outputs); err != nil {
|
||||||
return nil, errors.New("failed to unmarshal output")
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
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(),
|
||||||
UpdatedAt: record.GetDateTime("updated").Time(),
|
UpdatedAt: record.GetDateTime("updated").Time(),
|
||||||
},
|
},
|
||||||
WorkflowId: record.GetString("workflowId"),
|
WorkflowId: record.GetString("workflowId"),
|
||||||
|
RunId: record.GetString("runId"),
|
||||||
NodeId: record.GetString("nodeId"),
|
NodeId: record.GetString("nodeId"),
|
||||||
Node: node,
|
Node: node,
|
||||||
Outputs: outputs,
|
Outputs: outputs,
|
||||||
Succeeded: record.GetBool("succeeded"),
|
Succeeded: record.GetBool("succeeded"),
|
||||||
}
|
}
|
||||||
|
return workflowOutput, nil
|
||||||
return rs, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存节点输出
|
func (r *WorkflowOutputRepository) saveRecord(workflowOutput *domain.WorkflowOutput) (*core.Record, error) {
|
||||||
func (r *WorkflowOutputRepository) Save(ctx context.Context, output *domain.WorkflowOutput, certificate *domain.Certificate, cb func(id string) error) error {
|
|
||||||
var record *core.Record
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if output.Id == "" {
|
|
||||||
collection, err := app.GetApp().FindCollectionByNameOrId(domain.CollectionNameWorkflowOutput)
|
collection, err := app.GetApp().FindCollectionByNameOrId(domain.CollectionNameWorkflowOutput)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var record *core.Record
|
||||||
|
if workflowOutput.Id == "" {
|
||||||
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, workflowOutput.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return record, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
record.Set("workflowId", output.WorkflowId)
|
record.Set("workflowId", workflowOutput.WorkflowId)
|
||||||
record.Set("nodeId", output.NodeId)
|
record.Set("runId", workflowOutput.RunId)
|
||||||
record.Set("node", output.Node)
|
record.Set("nodeId", workflowOutput.NodeId)
|
||||||
record.Set("outputs", output.Outputs)
|
record.Set("node", workflowOutput.Node)
|
||||||
record.Set("succeeded", output.Succeeded)
|
record.Set("outputs", workflowOutput.Outputs)
|
||||||
|
record.Set("succeeded", workflowOutput.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, nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
124
internal/repository/workflow_run.go
Normal file
124
internal/repository/workflow_run.go
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/pocketbase/pocketbase/core"
|
||||||
|
"github.com/usual2970/certimate/internal/app"
|
||||||
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WorkflowRunRepository struct{}
|
||||||
|
|
||||||
|
func NewWorkflowRunRepository() *WorkflowRunRepository {
|
||||||
|
return &WorkflowRunRepository{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *WorkflowRunRepository) GetById(ctx context.Context, id string) (*domain.WorkflowRun, error) {
|
||||||
|
record, err := app.GetApp().FindRecordById(domain.CollectionNameWorkflowRun, id)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil, domain.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.castRecordToModel(record)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *WorkflowRunRepository) Save(ctx context.Context, workflowRun *domain.WorkflowRun) (*domain.WorkflowRun, error) {
|
||||||
|
collection, err := app.GetApp().FindCollectionByNameOrId(domain.CollectionNameWorkflowRun)
|
||||||
|
if err != nil {
|
||||||
|
return workflowRun, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var record *core.Record
|
||||||
|
if workflowRun.Id == "" {
|
||||||
|
record = core.NewRecord(collection)
|
||||||
|
} else {
|
||||||
|
record, err = app.GetApp().FindRecordById(collection, workflowRun.Id)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return workflowRun, err
|
||||||
|
}
|
||||||
|
record = core.NewRecord(collection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = app.GetApp().RunInTransaction(func(txApp core.App) error {
|
||||||
|
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 = txApp.Save(record)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
workflowRun.Id = record.Id
|
||||||
|
workflowRun.CreatedAt = record.GetDateTime("created").Time()
|
||||||
|
workflowRun.UpdatedAt = record.GetDateTime("updated").Time()
|
||||||
|
|
||||||
|
// 事务级联更新所属工作流的最后运行记录
|
||||||
|
workflowRecord, err := txApp.FindRecordById(domain.CollectionNameWorkflow, workflowRun.WorkflowId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if workflowRun.Id == workflowRecord.GetString("lastRunId") {
|
||||||
|
workflowRecord.IgnoreUnchangedFields(true)
|
||||||
|
workflowRecord.Set("lastRunStatus", record.GetString("status"))
|
||||||
|
err = txApp.Save(workflowRecord)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if workflowRecord.GetDateTime("lastRunTime").Time().IsZero() || workflowRun.StartedAt.After(workflowRecord.GetDateTime("lastRunTime").Time()) {
|
||||||
|
workflowRecord.IgnoreUnchangedFields(true)
|
||||||
|
workflowRecord.Set("lastRunId", record.Id)
|
||||||
|
workflowRecord.Set("lastRunStatus", record.GetString("status"))
|
||||||
|
workflowRecord.Set("lastRunTime", record.GetString("startedAt"))
|
||||||
|
err = txApp.Save(workflowRecord)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return workflowRun, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return workflowRun, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *WorkflowRunRepository) castRecordToModel(record *core.Record) (*domain.WorkflowRun, error) {
|
||||||
|
if record == nil {
|
||||||
|
return nil, fmt.Errorf("record is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
logs := make([]domain.WorkflowRunLog, 0)
|
||||||
|
if err := record.UnmarshalJSONField("logs", &logs); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
workflowRun := &domain.WorkflowRun{
|
||||||
|
Meta: domain.Meta{
|
||||||
|
Id: record.Id,
|
||||||
|
CreatedAt: record.GetDateTime("created").Time(),
|
||||||
|
UpdatedAt: record.GetDateTime("updated").Time(),
|
||||||
|
},
|
||||||
|
WorkflowId: record.GetString("workflowId"),
|
||||||
|
Status: domain.WorkflowRunStatusType(record.GetString("status")),
|
||||||
|
Trigger: domain.WorkflowTriggerType(record.GetString("trigger")),
|
||||||
|
StartedAt: record.GetDateTime("startedAt").Time(),
|
||||||
|
EndedAt: record.GetDateTime("endedAt").Time(),
|
||||||
|
Logs: logs,
|
||||||
|
Error: record.GetString("error"),
|
||||||
|
}
|
||||||
|
return workflowRun, nil
|
||||||
|
}
|
@ -11,9 +11,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type certificateService interface {
|
type certificateService interface {
|
||||||
ArchiveFile(ctx context.Context, req *dtos.CertificateArchiveFileReq) ([]byte, error)
|
ArchiveFile(ctx context.Context, req *dtos.CertificateArchiveFileReq) (*dtos.CertificateArchiveFileResp, error)
|
||||||
ValidateCertificate(ctx context.Context, req *dtos.CertificateValidateCertificateReq) (*dtos.CertificateValidateCertificateResp, error)
|
ValidateCertificate(ctx context.Context, req *dtos.CertificateValidateCertificateReq) (*dtos.CertificateValidateCertificateResp, error)
|
||||||
ValidatePrivateKey(ctx context.Context, req *dtos.CertificateValidatePrivateKeyReq) error
|
ValidatePrivateKey(ctx context.Context, req *dtos.CertificateValidatePrivateKeyReq) (*dtos.CertificateValidatePrivateKeyResp, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type CertificateHandler struct {
|
type CertificateHandler struct {
|
||||||
@ -38,10 +38,10 @@ func (handler *CertificateHandler) archiveFile(e *core.RequestEvent) error {
|
|||||||
return resp.Err(e, err)
|
return resp.Err(e, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if bt, err := handler.service.ArchiveFile(e.Request.Context(), req); err != nil {
|
if res, err := handler.service.ArchiveFile(e.Request.Context(), req); err != nil {
|
||||||
return resp.Err(e, err)
|
return resp.Err(e, err)
|
||||||
} else {
|
} else {
|
||||||
return resp.Ok(e, bt)
|
return resp.Ok(e, res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,10 +51,10 @@ func (handler *CertificateHandler) validateCertificate(e *core.RequestEvent) err
|
|||||||
return resp.Err(e, err)
|
return resp.Err(e, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rs, err := handler.service.ValidateCertificate(e.Request.Context(), req); err != nil {
|
if res, err := handler.service.ValidateCertificate(e.Request.Context(), req); err != nil {
|
||||||
return resp.Err(e, err)
|
return resp.Err(e, err)
|
||||||
} else {
|
} else {
|
||||||
return resp.Ok(e, rs)
|
return resp.Ok(e, res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,9 +64,9 @@ func (handler *CertificateHandler) validatePrivateKey(e *core.RequestEvent) erro
|
|||||||
return resp.Err(e, err)
|
return resp.Err(e, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := handler.service.ValidatePrivateKey(e.Request.Context(), req); err != nil {
|
if res, err := handler.service.ValidatePrivateKey(e.Request.Context(), req); err != nil {
|
||||||
return resp.Err(e, err)
|
return resp.Err(e, err)
|
||||||
} else {
|
} else {
|
||||||
return resp.Ok(e, nil)
|
return resp.Ok(e, res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
type workflowService interface {
|
type workflowService interface {
|
||||||
StartRun(ctx context.Context, req *dtos.WorkflowStartRunReq) error
|
StartRun(ctx context.Context, req *dtos.WorkflowStartRunReq) error
|
||||||
CancelRun(ctx context.Context, req *dtos.WorkflowCancelRunReq) error
|
CancelRun(ctx context.Context, req *dtos.WorkflowCancelRunReq) error
|
||||||
Stop(ctx context.Context)
|
Shutdown(ctx context.Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
type WorkflowHandler struct {
|
type WorkflowHandler struct {
|
||||||
|
@ -27,13 +27,14 @@ func Register(router *router.Router[*core.RequestEvent]) {
|
|||||||
certificateSvc = certificate.NewCertificateService(certificateRepo)
|
certificateSvc = certificate.NewCertificateService(certificateRepo)
|
||||||
|
|
||||||
workflowRepo := repository.NewWorkflowRepository()
|
workflowRepo := repository.NewWorkflowRepository()
|
||||||
workflowSvc = workflow.NewWorkflowService(workflowRepo)
|
workflowRunRepo := repository.NewWorkflowRunRepository()
|
||||||
|
workflowSvc = workflow.NewWorkflowService(workflowRepo, workflowRunRepo)
|
||||||
|
|
||||||
statisticsRepo := repository.NewStatisticsRepository()
|
statisticsRepo := repository.NewStatisticsRepository()
|
||||||
statisticsSvc = statistics.NewStatisticsService(statisticsRepo)
|
statisticsSvc = statistics.NewStatisticsService(statisticsRepo)
|
||||||
|
|
||||||
notifyRepo := repository.NewSettingsRepository()
|
settingsRepo := repository.NewSettingsRepository()
|
||||||
notifySvc = notify.NewNotifyService(notifyRepo)
|
notifySvc = notify.NewNotifyService(settingsRepo)
|
||||||
|
|
||||||
group := router.Group("/api")
|
group := router.Group("/api")
|
||||||
group.Bind(apis.RequireSuperuserAuth())
|
group.Bind(apis.RequireSuperuserAuth())
|
||||||
@ -45,6 +46,6 @@ func Register(router *router.Router[*core.RequestEvent]) {
|
|||||||
|
|
||||||
func Unregister() {
|
func Unregister() {
|
||||||
if workflowSvc != nil {
|
if workflowSvc != nil {
|
||||||
workflowSvc.Stop(context.Background())
|
workflowSvc.Shutdown(context.Background())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,6 @@ type certificateService interface {
|
|||||||
InitSchedule(ctx context.Context) error
|
InitSchedule(ctx context.Context) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCertificateScheduler(service certificateService) error {
|
func InitCertificateScheduler(service certificateService) error {
|
||||||
return service.InitSchedule(context.Background())
|
return service.InitSchedule(context.Background())
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package scheduler
|
package scheduler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/usual2970/certimate/internal/app"
|
||||||
"github.com/usual2970/certimate/internal/certificate"
|
"github.com/usual2970/certimate/internal/certificate"
|
||||||
"github.com/usual2970/certimate/internal/repository"
|
"github.com/usual2970/certimate/internal/repository"
|
||||||
"github.com/usual2970/certimate/internal/workflow"
|
"github.com/usual2970/certimate/internal/workflow"
|
||||||
@ -8,12 +9,17 @@ import (
|
|||||||
|
|
||||||
func Register() {
|
func Register() {
|
||||||
workflowRepo := repository.NewWorkflowRepository()
|
workflowRepo := repository.NewWorkflowRepository()
|
||||||
workflowSvc := workflow.NewWorkflowService(workflowRepo)
|
workflowRunRepo := repository.NewWorkflowRunRepository()
|
||||||
|
workflowSvc := workflow.NewWorkflowService(workflowRepo, workflowRunRepo)
|
||||||
|
|
||||||
certificateRepo := repository.NewCertificateRepository()
|
certificateRepo := repository.NewCertificateRepository()
|
||||||
certificateSvc := certificate.NewCertificateService(certificateRepo)
|
certificateSvc := certificate.NewCertificateService(certificateRepo)
|
||||||
|
|
||||||
NewCertificateScheduler(certificateSvc)
|
if err := InitWorkflowScheduler(workflowSvc); err != nil {
|
||||||
|
app.GetLogger().Error("failed to init workflow scheduler", "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
NewWorkflowScheduler(workflowSvc)
|
if err := InitCertificateScheduler(certificateSvc); err != nil {
|
||||||
|
app.GetLogger().Error("failed to init certificate scheduler", "err", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,6 @@ type workflowService interface {
|
|||||||
InitSchedule(ctx context.Context) error
|
InitSchedule(ctx context.Context) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWorkflowScheduler(service workflowService) error {
|
func InitWorkflowScheduler(service workflowService) error {
|
||||||
return service.InitSchedule(context.Background())
|
return service.InitSchedule(context.Background())
|
||||||
}
|
}
|
||||||
|
@ -11,15 +11,15 @@ type statisticsRepository interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type StatisticsService struct {
|
type StatisticsService struct {
|
||||||
repo statisticsRepository
|
statRepo statisticsRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStatisticsService(repo statisticsRepository) *StatisticsService {
|
func NewStatisticsService(statRepo statisticsRepository) *StatisticsService {
|
||||||
return &StatisticsService{
|
return &StatisticsService{
|
||||||
repo: repo,
|
statRepo: statRepo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StatisticsService) Get(ctx context.Context) (*domain.Statistics, error) {
|
func (s *StatisticsService) Get(ctx context.Context) (*domain.Statistics, error) {
|
||||||
return s.repo.Get(ctx)
|
return s.statRepo.Get(ctx)
|
||||||
}
|
}
|
||||||
|
280
internal/workflow/dispatcher/dispatcher.go
Normal file
280
internal/workflow/dispatcher/dispatcher.go
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
package dispatcher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/app"
|
||||||
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
var maxWorkers = 16
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
envMaxWorkers := os.Getenv("CERTIMATE_WORKFLOW_MAX_WORKERS")
|
||||||
|
if n, err := strconv.Atoi(envMaxWorkers); err != nil && n > 0 {
|
||||||
|
maxWorkers = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type workflowWorker struct {
|
||||||
|
Data *WorkflowWorkerData
|
||||||
|
Cancel context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
type WorkflowWorkerData struct {
|
||||||
|
WorkflowId string
|
||||||
|
WorkflowContent *domain.WorkflowNode
|
||||||
|
RunId string
|
||||||
|
}
|
||||||
|
|
||||||
|
type WorkflowDispatcher struct {
|
||||||
|
semaphore chan struct{}
|
||||||
|
|
||||||
|
queue []*WorkflowWorkerData
|
||||||
|
queueMutex sync.Mutex
|
||||||
|
|
||||||
|
workers map[string]*workflowWorker // key: WorkflowId
|
||||||
|
workerIdMap map[string]string // key: RunId, value: WorkflowId
|
||||||
|
workerMutex sync.Mutex
|
||||||
|
|
||||||
|
chWork chan *WorkflowWorkerData
|
||||||
|
chCandi chan struct{}
|
||||||
|
|
||||||
|
wg sync.WaitGroup
|
||||||
|
|
||||||
|
workflowRepo workflowRepository
|
||||||
|
workflowRunRepo workflowRunRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func newWorkflowDispatcher(workflowRepo workflowRepository, workflowRunRepo workflowRunRepository) *WorkflowDispatcher {
|
||||||
|
dispatcher := &WorkflowDispatcher{
|
||||||
|
semaphore: make(chan struct{}, maxWorkers),
|
||||||
|
|
||||||
|
queue: make([]*WorkflowWorkerData, 0),
|
||||||
|
queueMutex: sync.Mutex{},
|
||||||
|
|
||||||
|
workers: make(map[string]*workflowWorker),
|
||||||
|
workerIdMap: make(map[string]string),
|
||||||
|
workerMutex: sync.Mutex{},
|
||||||
|
|
||||||
|
chWork: make(chan *WorkflowWorkerData),
|
||||||
|
chCandi: make(chan struct{}, 1),
|
||||||
|
|
||||||
|
workflowRepo: workflowRepo,
|
||||||
|
workflowRunRepo: workflowRunRepo,
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-dispatcher.chWork:
|
||||||
|
dispatcher.dequeueWorker()
|
||||||
|
|
||||||
|
case <-dispatcher.chCandi:
|
||||||
|
dispatcher.dequeueWorker()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return dispatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WorkflowDispatcher) Dispatch(data *WorkflowWorkerData) {
|
||||||
|
if data == nil {
|
||||||
|
panic("worker data is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
w.enqueueWorker(data)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case w.chWork <- data:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WorkflowDispatcher) Cancel(runId string) {
|
||||||
|
hasWorker := false
|
||||||
|
|
||||||
|
// 取消正在执行的 WorkflowRun
|
||||||
|
w.workerMutex.Lock()
|
||||||
|
if workflowId, ok := w.workerIdMap[runId]; ok {
|
||||||
|
if worker, ok := w.workers[workflowId]; ok {
|
||||||
|
hasWorker = true
|
||||||
|
worker.Cancel()
|
||||||
|
delete(w.workers, workflowId)
|
||||||
|
delete(w.workerIdMap, runId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.workerMutex.Unlock()
|
||||||
|
|
||||||
|
// 移除排队中的 WorkflowRun
|
||||||
|
w.queueMutex.Lock()
|
||||||
|
w.queue = slices.Filter(w.queue, func(d *WorkflowWorkerData) bool {
|
||||||
|
return d.RunId != runId
|
||||||
|
})
|
||||||
|
w.queueMutex.Unlock()
|
||||||
|
|
||||||
|
// 已挂起,查询 WorkflowRun 并更新其状态为 Canceled
|
||||||
|
if !hasWorker {
|
||||||
|
if run, err := w.workflowRunRepo.GetById(context.Background(), runId); err == nil {
|
||||||
|
if run.Status == domain.WorkflowRunStatusTypePending || run.Status == domain.WorkflowRunStatusTypeRunning {
|
||||||
|
run.Status = domain.WorkflowRunStatusTypeCanceled
|
||||||
|
w.workflowRunRepo.Save(context.Background(), run)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WorkflowDispatcher) Shutdown() {
|
||||||
|
// 清空排队中的 WorkflowRun
|
||||||
|
w.queueMutex.Lock()
|
||||||
|
w.queue = make([]*WorkflowWorkerData, 0)
|
||||||
|
w.queueMutex.Unlock()
|
||||||
|
|
||||||
|
// 等待所有正在执行的 WorkflowRun 完成
|
||||||
|
w.workerMutex.Lock()
|
||||||
|
for _, worker := range w.workers {
|
||||||
|
worker.Cancel()
|
||||||
|
delete(w.workers, worker.Data.WorkflowId)
|
||||||
|
delete(w.workerIdMap, worker.Data.RunId)
|
||||||
|
}
|
||||||
|
w.workerMutex.Unlock()
|
||||||
|
w.wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WorkflowDispatcher) enqueueWorker(data *WorkflowWorkerData) {
|
||||||
|
w.queueMutex.Lock()
|
||||||
|
defer w.queueMutex.Unlock()
|
||||||
|
w.queue = append(w.queue, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WorkflowDispatcher) dequeueWorker() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case w.semaphore <- struct{}{}:
|
||||||
|
default:
|
||||||
|
// 达到最大并发数
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.queueMutex.Lock()
|
||||||
|
if len(w.queue) == 0 {
|
||||||
|
w.queueMutex.Unlock()
|
||||||
|
<-w.semaphore
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := w.queue[0]
|
||||||
|
w.queue = w.queue[1:]
|
||||||
|
w.queueMutex.Unlock()
|
||||||
|
|
||||||
|
// 检查是否有相同 WorkflowId 的 WorkflowRun 正在执行
|
||||||
|
// 如果有,则重新排队,以保证同一个工作流同一时间内只有一个正在执行
|
||||||
|
// 即不同 WorkflowId 的任务并行化,相同 WorkflowId 的任务串行化
|
||||||
|
w.workerMutex.Lock()
|
||||||
|
if _, exists := w.workers[data.WorkflowId]; exists {
|
||||||
|
w.queueMutex.Lock()
|
||||||
|
w.queue = append(w.queue, data)
|
||||||
|
w.queueMutex.Unlock()
|
||||||
|
w.workerMutex.Unlock()
|
||||||
|
|
||||||
|
<-w.semaphore
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
w.workers[data.WorkflowId] = &workflowWorker{data, cancel}
|
||||||
|
w.workerIdMap[data.RunId] = data.WorkflowId
|
||||||
|
w.workerMutex.Unlock()
|
||||||
|
|
||||||
|
w.wg.Add(1)
|
||||||
|
go w.work(ctx, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WorkflowDispatcher) work(ctx context.Context, data *WorkflowWorkerData) {
|
||||||
|
defer func() {
|
||||||
|
<-w.semaphore
|
||||||
|
w.workerMutex.Lock()
|
||||||
|
delete(w.workers, data.WorkflowId)
|
||||||
|
delete(w.workerIdMap, data.RunId)
|
||||||
|
w.workerMutex.Unlock()
|
||||||
|
|
||||||
|
w.wg.Done()
|
||||||
|
|
||||||
|
// 尝试取出排队中的其他 WorkflowRun 继续执行
|
||||||
|
select {
|
||||||
|
case w.chCandi <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 查询 WorkflowRun
|
||||||
|
run, err := w.workflowRunRepo.GetById(ctx, data.RunId)
|
||||||
|
if err != nil {
|
||||||
|
if !(errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded)) {
|
||||||
|
app.GetLogger().Error(fmt.Sprintf("failed to get workflow run #%s", data.RunId), "err", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} else if run.Status != domain.WorkflowRunStatusTypePending {
|
||||||
|
return
|
||||||
|
} else if ctx.Err() != nil {
|
||||||
|
run.Status = domain.WorkflowRunStatusTypeCanceled
|
||||||
|
w.workflowRunRepo.Save(ctx, run)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新 WorkflowRun 状态为 Running
|
||||||
|
run.Status = domain.WorkflowRunStatusTypeRunning
|
||||||
|
if _, err := w.workflowRunRepo.Save(ctx, run); err != nil {
|
||||||
|
if !(errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded)) {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行工作流
|
||||||
|
invoker := newWorkflowInvokerWithData(w.workflowRunRepo, data)
|
||||||
|
if runErr := invoker.Invoke(ctx); runErr != nil {
|
||||||
|
if errors.Is(runErr, context.Canceled) {
|
||||||
|
run.Status = domain.WorkflowRunStatusTypeCanceled
|
||||||
|
run.Logs = invoker.GetLogs()
|
||||||
|
} else {
|
||||||
|
run.Status = domain.WorkflowRunStatusTypeFailed
|
||||||
|
run.EndedAt = time.Now()
|
||||||
|
run.Logs = invoker.GetLogs()
|
||||||
|
run.Error = runErr.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := w.workflowRunRepo.Save(ctx, run); err != nil {
|
||||||
|
if !(errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded)) {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新 WorkflowRun 状态为 Succeeded/Failed
|
||||||
|
run.EndedAt = time.Now()
|
||||||
|
run.Logs = invoker.GetLogs()
|
||||||
|
run.Error = domain.WorkflowRunLogs(invoker.GetLogs()).ErrorString()
|
||||||
|
if run.Error == "" {
|
||||||
|
run.Status = domain.WorkflowRunStatusTypeSucceeded
|
||||||
|
} else {
|
||||||
|
run.Status = domain.WorkflowRunStatusTypeFailed
|
||||||
|
}
|
||||||
|
if _, err := w.workflowRunRepo.Save(ctx, run); err != nil {
|
||||||
|
if !(errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded)) {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
115
internal/workflow/dispatcher/invoker.go
Normal file
115
internal/workflow/dispatcher/invoker.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package dispatcher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
|
nodes "github.com/usual2970/certimate/internal/workflow/node-processor"
|
||||||
|
)
|
||||||
|
|
||||||
|
type workflowInvoker struct {
|
||||||
|
workflowId string
|
||||||
|
workflowContent *domain.WorkflowNode
|
||||||
|
runId string
|
||||||
|
runLogs []domain.WorkflowRunLog
|
||||||
|
|
||||||
|
workflowRunRepo workflowRunRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func newWorkflowInvokerWithData(workflowRunRepo workflowRunRepository, data *WorkflowWorkerData) *workflowInvoker {
|
||||||
|
if data == nil {
|
||||||
|
panic("worker data is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 待优化,日志与执行解耦
|
||||||
|
return &workflowInvoker{
|
||||||
|
workflowId: data.WorkflowId,
|
||||||
|
workflowContent: data.WorkflowContent,
|
||||||
|
runId: data.RunId,
|
||||||
|
runLogs: make([]domain.WorkflowRunLog, 0),
|
||||||
|
|
||||||
|
workflowRunRepo: workflowRunRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *workflowInvoker) Invoke(ctx context.Context) error {
|
||||||
|
ctx = context.WithValue(ctx, "workflow_id", w.workflowId)
|
||||||
|
ctx = context.WithValue(ctx, "workflow_run_id", w.runId)
|
||||||
|
return w.processNode(ctx, w.workflowContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *workflowInvoker) GetLogs() []domain.WorkflowRunLog {
|
||||||
|
return w.runLogs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *workflowInvoker) processNode(ctx context.Context, node *domain.WorkflowNode) error {
|
||||||
|
current := node
|
||||||
|
for current != nil {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
if current.Type == domain.WorkflowNodeTypeBranch || current.Type == domain.WorkflowNodeTypeExecuteResultBranch {
|
||||||
|
for _, branch := range current.Branches {
|
||||||
|
if err := w.processNode(ctx, &branch); err != nil {
|
||||||
|
// 并行分支的某一分支发生错误时,忽略此错误,继续执行其他分支
|
||||||
|
if !(errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var processor nodes.NodeProcessor
|
||||||
|
var procErr error
|
||||||
|
for {
|
||||||
|
if current.Type != domain.WorkflowNodeTypeBranch && current.Type != domain.WorkflowNodeTypeExecuteResultBranch {
|
||||||
|
processor, procErr = nodes.GetProcessor(current)
|
||||||
|
if procErr != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
procErr = processor.Process(ctx)
|
||||||
|
log := processor.GetLog(ctx)
|
||||||
|
if log != nil {
|
||||||
|
w.runLogs = append(w.runLogs, *log)
|
||||||
|
|
||||||
|
// TODO: 待优化,把 /pkg/core/* 包下的输出写入到 DEBUG 级别的日志中
|
||||||
|
if run, err := w.workflowRunRepo.GetById(ctx, w.runId); err == nil {
|
||||||
|
run.Logs = w.runLogs
|
||||||
|
w.workflowRunRepo.Save(ctx, run)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if procErr != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 优化可读性
|
||||||
|
if procErr != nil && current.Next != nil && current.Next.Type != domain.WorkflowNodeTypeExecuteResultBranch {
|
||||||
|
return procErr
|
||||||
|
} else if procErr != nil && current.Next != nil && current.Next.Type == domain.WorkflowNodeTypeExecuteResultBranch {
|
||||||
|
current = w.getBranchByType(current.Next.Branches, domain.WorkflowNodeTypeExecuteFailure)
|
||||||
|
} else if procErr == nil && current.Next != nil && current.Next.Type == domain.WorkflowNodeTypeExecuteResultBranch {
|
||||||
|
current = w.getBranchByType(current.Next.Branches, domain.WorkflowNodeTypeExecuteSuccess)
|
||||||
|
} else {
|
||||||
|
current = current.Next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *workflowInvoker) getBranchByType(branches []domain.WorkflowNode, nodeType domain.WorkflowNodeType) *domain.WorkflowNode {
|
||||||
|
for _, branch := range branches {
|
||||||
|
if branch.Type == nodeType {
|
||||||
|
return &branch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
32
internal/workflow/dispatcher/singleton.go
Normal file
32
internal/workflow/dispatcher/singleton.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package dispatcher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type workflowRepository interface {
|
||||||
|
GetById(ctx context.Context, id string) (*domain.Workflow, error)
|
||||||
|
Save(ctx context.Context, workflow *domain.Workflow) (*domain.Workflow, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type workflowRunRepository interface {
|
||||||
|
GetById(ctx context.Context, id string) (*domain.WorkflowRun, error)
|
||||||
|
Save(ctx context.Context, workflowRun *domain.WorkflowRun) (*domain.WorkflowRun, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
instance *WorkflowDispatcher
|
||||||
|
intanceOnce sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetSingletonDispatcher(workflowRepo workflowRepository, workflowRunRepo workflowRunRepository) *WorkflowDispatcher {
|
||||||
|
// TODO: 待优化构造过程
|
||||||
|
intanceOnce.Do(func() {
|
||||||
|
instance = newWorkflowDispatcher(workflowRepo, workflowRunRepo)
|
||||||
|
})
|
||||||
|
|
||||||
|
return instance
|
||||||
|
}
|
@ -65,13 +65,13 @@ func onWorkflowRecordCreateOrUpdate(ctx context.Context, record *core.Record) er
|
|||||||
|
|
||||||
// 反之,重新添加定时任务
|
// 反之,重新添加定时任务
|
||||||
err := scheduler.Add(fmt.Sprintf("workflow#%s", workflowId), record.GetString("triggerCron"), func() {
|
err := scheduler.Add(fmt.Sprintf("workflow#%s", workflowId), record.GetString("triggerCron"), func() {
|
||||||
NewWorkflowService(repository.NewWorkflowRepository()).StartRun(ctx, &dtos.WorkflowStartRunReq{
|
workflowSrv := NewWorkflowService(repository.NewWorkflowRepository(), repository.NewWorkflowRunRepository())
|
||||||
|
workflowSrv.StartRun(ctx, &dtos.WorkflowStartRunReq{
|
||||||
WorkflowId: workflowId,
|
WorkflowId: workflowId,
|
||||||
Trigger: domain.WorkflowTriggerTypeAuto,
|
RunTrigger: domain.WorkflowTriggerTypeAuto,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.GetLogger().Error("add cron job failed", "err", err)
|
|
||||||
return fmt.Errorf("add cron job failed: %w", err)
|
return fmt.Errorf("add cron job failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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"
|
||||||
@ -16,103 +15,92 @@ import (
|
|||||||
|
|
||||||
type applyNode struct {
|
type applyNode struct {
|
||||||
node *domain.WorkflowNode
|
node *domain.WorkflowNode
|
||||||
|
*nodeLogger
|
||||||
|
|
||||||
certRepo certificateRepository
|
certRepo certificateRepository
|
||||||
outputRepo workflowOutputRepository
|
outputRepo workflowOutputRepository
|
||||||
*nodeLogger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApplyNode(node *domain.WorkflowNode) *applyNode {
|
func NewApplyNode(node *domain.WorkflowNode) *applyNode {
|
||||||
return &applyNode{
|
return &applyNode{
|
||||||
node: node,
|
node: node,
|
||||||
nodeLogger: NewNodeLogger(node),
|
nodeLogger: newNodeLogger(node),
|
||||||
outputRepo: repository.NewWorkflowOutputRepository(),
|
|
||||||
certRepo: repository.NewCertificateRepository(),
|
certRepo: repository.NewCertificateRepository(),
|
||||||
|
outputRepo: repository.NewWorkflowOutputRepository(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 申请节点根据申请类型执行不同的操作
|
func (n *applyNode) Process(ctx context.Context) error {
|
||||||
func (a *applyNode) Run(ctx context.Context) error {
|
n.AppendLogRecord(ctx, domain.WorkflowRunLogLevelInfo, "进入申请证书节点")
|
||||||
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.AppendLogRecord(ctx, domain.WorkflowRunLogLevelError, "查询申请记录失败", 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.AppendLogRecord(ctx, domain.WorkflowRunLogLevelInfo, 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.AppendLogRecord(ctx, domain.WorkflowRunLogLevelError, "获取申请对象失败", 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.AppendLogRecord(ctx, domain.WorkflowRunLogLevelError, "申请失败", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
a.AddOutput(ctx, a.node.Name, "申请成功")
|
n.AppendLogRecord(ctx, domain.WorkflowRunLogLevelInfo, "申请成功")
|
||||||
|
|
||||||
// 解析证书并生成实体
|
// 解析证书并生成实体
|
||||||
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.AppendLogRecord(ctx, domain.WorkflowRunLogLevelError, "解析证书失败", 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: 先保持一个节点始终只有一个输出,后续增加版本控制
|
output := &domain.WorkflowOutput{
|
||||||
currentOutput := &domain.WorkflowOutput{
|
|
||||||
WorkflowId: getContextWorkflowId(ctx),
|
WorkflowId: getContextWorkflowId(ctx),
|
||||||
NodeId: a.node.Id,
|
RunId: getContextWorkflowRunId(ctx),
|
||||||
Node: a.node,
|
NodeId: n.node.Id,
|
||||||
|
Node: n.node,
|
||||||
Succeeded: true,
|
Succeeded: true,
|
||||||
Outputs: a.node.Outputs,
|
Outputs: n.node.Outputs,
|
||||||
}
|
}
|
||||||
if lastOutput != nil {
|
if _, err := n.outputRepo.SaveWithCertificate(ctx, output, certificate); err != nil {
|
||||||
currentOutput.Id = lastOutput.Id
|
n.AppendLogRecord(ctx, domain.WorkflowRunLogLevelError, "保存申请记录失败", err.Error())
|
||||||
}
|
|
||||||
if err := a.outputRepo.Save(ctx, currentOutput, certificate, func(id string) error {
|
|
||||||
if certificate != nil {
|
|
||||||
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.AppendLogRecord(ctx, domain.WorkflowRunLogLevelInfo, "保存申请记录成功")
|
||||||
|
|
||||||
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,11 +118,13 @@ 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)
|
||||||
|
if lastCertificate != nil {
|
||||||
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 expirationTime > renewalInterval {
|
||||||
return true, fmt.Sprintf("已申请过证书,且证书尚未临近过期(到期尚余 %d 天,预计距 %d 天时续期)", int(expirationTime.Hours()/24), currentNodeConfig.SkipBeforeExpiryDays)
|
return true, fmt.Sprintf("已申请过证书,且证书尚未临近过期(尚余 %d 天过期,不足 %d 天时续期)", int(expirationTime.Hours()/24), currentNodeConfig.SkipBeforeExpiryDays)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,15 +14,11 @@ type conditionNode struct {
|
|||||||
func NewConditionNode(node *domain.WorkflowNode) *conditionNode {
|
func NewConditionNode(node *domain.WorkflowNode) *conditionNode {
|
||||||
return &conditionNode{
|
return &conditionNode{
|
||||||
node: node,
|
node: node,
|
||||||
nodeLogger: NewNodeLogger(node),
|
nodeLogger: newNodeLogger(node),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 条件节点没有任何操作
|
func (n *conditionNode) Process(ctx context.Context) error {
|
||||||
func (c *conditionNode) Run(ctx context.Context) error {
|
// 此类型节点不需要执行任何操作,直接返回
|
||||||
c.AddOutput(ctx,
|
|
||||||
c.node.Name,
|
|
||||||
"完成",
|
|
||||||
)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -13,95 +13,91 @@ import (
|
|||||||
|
|
||||||
type deployNode struct {
|
type deployNode struct {
|
||||||
node *domain.WorkflowNode
|
node *domain.WorkflowNode
|
||||||
|
*nodeLogger
|
||||||
|
|
||||||
certRepo certificateRepository
|
certRepo certificateRepository
|
||||||
outputRepo workflowOutputRepository
|
outputRepo workflowOutputRepository
|
||||||
*nodeLogger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDeployNode(node *domain.WorkflowNode) *deployNode {
|
func NewDeployNode(node *domain.WorkflowNode) *deployNode {
|
||||||
return &deployNode{
|
return &deployNode{
|
||||||
node: node,
|
node: node,
|
||||||
nodeLogger: NewNodeLogger(node),
|
nodeLogger: newNodeLogger(node),
|
||||||
outputRepo: repository.NewWorkflowOutputRepository(),
|
|
||||||
certRepo: repository.NewCertificateRepository(),
|
certRepo: repository.NewCertificateRepository(),
|
||||||
|
outputRepo: repository.NewWorkflowOutputRepository(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *deployNode) Run(ctx context.Context) error {
|
func (n *deployNode) Process(ctx context.Context) error {
|
||||||
d.AddOutput(ctx, d.node.Name, "开始执行")
|
n.AppendLogRecord(ctx, domain.WorkflowRunLogLevelInfo, "开始执行")
|
||||||
|
|
||||||
// 查询上次执行结果
|
// 查询上次执行结果
|
||||||
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.AppendLogRecord(ctx, domain.WorkflowRunLogLevelError, "查询部署记录失败", 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.AppendLogRecord(ctx, domain.WorkflowRunLogLevelError, "证书来源配置错误", 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.AppendLogRecord(ctx, domain.WorkflowRunLogLevelError, "获取证书失败", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检测是否可以跳过本次执行
|
// 检测是否可以跳过本次执行
|
||||||
if skippable, skipReason := d.checkCanSkip(ctx, lastOutput); skippable {
|
if lastOutput != nil && certificate.CreatedAt.Before(lastOutput.UpdatedAt) {
|
||||||
if certificate.CreatedAt.Before(lastOutput.UpdatedAt) {
|
if skippable, skipReason := n.checkCanSkip(ctx, lastOutput); skippable {
|
||||||
d.AddOutput(ctx, d.node.Name, "已部署过且证书未更新")
|
n.AppendLogRecord(ctx, domain.WorkflowRunLogLevelInfo, skipReason)
|
||||||
} else {
|
|
||||||
d.AddOutput(ctx, d.node.Name, skipReason)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化部署器
|
// 初始化部署器
|
||||||
deploy, err := deployer.NewWithDeployNode(d.node, struct {
|
deployer, 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.AppendLogRecord(ctx, domain.WorkflowRunLogLevelError, "获取部署对象失败", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 部署证书
|
// 部署证书
|
||||||
if err := deploy.Deploy(ctx); err != nil {
|
if err := deployer.Deploy(ctx); err != nil {
|
||||||
d.AddOutput(ctx, d.node.Name, "部署失败", err.Error())
|
n.AppendLogRecord(ctx, domain.WorkflowRunLogLevelError, "部署失败", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
d.AddOutput(ctx, d.node.Name, "部署成功")
|
n.AppendLogRecord(ctx, domain.WorkflowRunLogLevelInfo, "部署成功")
|
||||||
|
|
||||||
// 保存执行结果
|
// 保存执行结果
|
||||||
// TODO: 先保持一个节点始终只有一个输出,后续增加版本控制
|
output := &domain.WorkflowOutput{
|
||||||
currentOutput := &domain.WorkflowOutput{
|
|
||||||
Meta: domain.Meta{},
|
|
||||||
WorkflowId: getContextWorkflowId(ctx),
|
WorkflowId: getContextWorkflowId(ctx),
|
||||||
NodeId: d.node.Id,
|
RunId: getContextWorkflowRunId(ctx),
|
||||||
Node: d.node,
|
NodeId: n.node.Id,
|
||||||
|
Node: n.node,
|
||||||
Succeeded: true,
|
Succeeded: true,
|
||||||
}
|
}
|
||||||
if lastOutput != nil {
|
if _, err := n.outputRepo.Save(ctx, output); err != nil {
|
||||||
currentOutput.Id = lastOutput.Id
|
n.AppendLogRecord(ctx, domain.WorkflowRunLogLevelError, "保存部署记录失败", err.Error())
|
||||||
}
|
|
||||||
if err := d.outputRepo.Save(ctx, currentOutput, nil, nil); err != nil {
|
|
||||||
d.AddOutput(ctx, d.node.Name, "保存部署记录失败", err.Error())
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
d.AddOutput(ctx, d.node.Name, "保存部署记录成功")
|
n.AppendLogRecord(ctx, domain.WorkflowRunLogLevelInfo, "保存部署记录成功")
|
||||||
|
|
||||||
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, "配置项变化:主机提供商授权"
|
||||||
|
@ -14,14 +14,13 @@ type executeFailureNode struct {
|
|||||||
func NewExecuteFailureNode(node *domain.WorkflowNode) *executeFailureNode {
|
func NewExecuteFailureNode(node *domain.WorkflowNode) *executeFailureNode {
|
||||||
return &executeFailureNode{
|
return &executeFailureNode{
|
||||||
node: node,
|
node: node,
|
||||||
nodeLogger: NewNodeLogger(node),
|
nodeLogger: newNodeLogger(node),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *executeFailureNode) Run(ctx context.Context) error {
|
func (n *executeFailureNode) Process(ctx context.Context) error {
|
||||||
e.AddOutput(ctx,
|
// 此类型节点不需要执行任何操作,直接返回
|
||||||
e.node.Name,
|
n.AppendLogRecord(ctx, domain.WorkflowRunLogLevelInfo, "进入执行失败分支")
|
||||||
"进入执行失败分支",
|
|
||||||
)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -14,14 +14,13 @@ type executeSuccessNode struct {
|
|||||||
func NewExecuteSuccessNode(node *domain.WorkflowNode) *executeSuccessNode {
|
func NewExecuteSuccessNode(node *domain.WorkflowNode) *executeSuccessNode {
|
||||||
return &executeSuccessNode{
|
return &executeSuccessNode{
|
||||||
node: node,
|
node: node,
|
||||||
nodeLogger: NewNodeLogger(node),
|
nodeLogger: newNodeLogger(node),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *executeSuccessNode) Run(ctx context.Context) error {
|
func (n *executeSuccessNode) Process(ctx context.Context) error {
|
||||||
e.AddOutput(ctx,
|
// 此类型节点不需要执行任何操作,直接返回
|
||||||
e.node.Name,
|
n.AppendLogRecord(ctx, domain.WorkflowRunLogLevelInfo, "进入执行成功分支")
|
||||||
"进入执行成功分支",
|
|
||||||
)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -10,43 +10,45 @@ import (
|
|||||||
|
|
||||||
type notifyNode struct {
|
type notifyNode struct {
|
||||||
node *domain.WorkflowNode
|
node *domain.WorkflowNode
|
||||||
settingsRepo settingsRepository
|
|
||||||
*nodeLogger
|
*nodeLogger
|
||||||
|
|
||||||
|
settingsRepo settingsRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNotifyNode(node *domain.WorkflowNode) *notifyNode {
|
func NewNotifyNode(node *domain.WorkflowNode) *notifyNode {
|
||||||
return ¬ifyNode{
|
return ¬ifyNode{
|
||||||
node: node,
|
node: node,
|
||||||
nodeLogger: NewNodeLogger(node),
|
nodeLogger: newNodeLogger(node),
|
||||||
|
|
||||||
settingsRepo: repository.NewSettingsRepository(),
|
settingsRepo: repository.NewSettingsRepository(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *notifyNode) Run(ctx context.Context) error {
|
func (n *notifyNode) Process(ctx context.Context) error {
|
||||||
n.AddOutput(ctx, n.node.Name, "开始执行")
|
n.AppendLogRecord(ctx, domain.WorkflowRunLogLevelInfo, "进入推送通知节点")
|
||||||
|
|
||||||
nodeConfig := n.node.GetConfigForNotify()
|
nodeConfig := n.node.GetConfigForNotify()
|
||||||
|
|
||||||
// 获取通知配置
|
// 获取通知配置
|
||||||
settings, err := n.settingsRepo.GetByName(ctx, "notifyChannels")
|
settings, err := n.settingsRepo.GetByName(ctx, "notifyChannels")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
n.AddOutput(ctx, n.node.Name, "获取通知配置失败", err.Error())
|
n.AppendLogRecord(ctx, domain.WorkflowRunLogLevelError, "获取通知配置失败", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取通知渠道
|
// 获取通知渠道
|
||||||
channelConfig, err := settings.GetNotifyChannelConfig(nodeConfig.Channel)
|
channelConfig, err := settings.GetNotifyChannelConfig(nodeConfig.Channel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
n.AddOutput(ctx, n.node.Name, "获取通知渠道配置失败", err.Error())
|
n.AppendLogRecord(ctx, domain.WorkflowRunLogLevelError, "获取通知渠道配置失败", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送通知
|
// 发送通知
|
||||||
if err := notify.SendToChannel(nodeConfig.Subject, nodeConfig.Message, nodeConfig.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())
|
n.AppendLogRecord(ctx, domain.WorkflowRunLogLevelError, "发送通知失败", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
n.AddOutput(ctx, n.node.Name, "发送通知成功")
|
n.AppendLogRecord(ctx, domain.WorkflowRunLogLevelInfo, "发送通知成功")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type NodeProcessor interface {
|
type NodeProcessor interface {
|
||||||
Run(ctx context.Context) error
|
Process(ctx context.Context) error
|
||||||
Log(ctx context.Context) *domain.WorkflowRunLog
|
|
||||||
AddOutput(ctx context.Context, title, content string, err ...string)
|
GetLog(ctx context.Context) *domain.WorkflowRunLog
|
||||||
|
AppendLogRecord(ctx context.Context, level domain.WorkflowRunLogLevel, content string, err ...string)
|
||||||
}
|
}
|
||||||
|
|
||||||
type nodeLogger struct {
|
type nodeLogger struct {
|
||||||
@ -23,39 +24,41 @@ 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 {
|
||||||
GetByName(ctx context.Context, name string) (*domain.Settings, error)
|
GetByName(ctx context.Context, name string) (*domain.Settings, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNodeLogger(node *domain.WorkflowNode) *nodeLogger {
|
func newNodeLogger(node *domain.WorkflowNode) *nodeLogger {
|
||||||
return &nodeLogger{
|
return &nodeLogger{
|
||||||
log: &domain.WorkflowRunLog{
|
log: &domain.WorkflowRunLog{
|
||||||
NodeId: node.Id,
|
NodeId: node.Id,
|
||||||
NodeName: node.Name,
|
NodeName: node.Name,
|
||||||
Outputs: make([]domain.WorkflowRunLogOutput, 0),
|
Records: make([]domain.WorkflowRunLogRecord, 0),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *nodeLogger) Log(ctx context.Context) *domain.WorkflowRunLog {
|
func (l *nodeLogger) GetLog(ctx context.Context) *domain.WorkflowRunLog {
|
||||||
return l.log
|
return l.log
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *nodeLogger) AddOutput(ctx context.Context, title, content string, err ...string) {
|
func (l *nodeLogger) AppendLogRecord(ctx context.Context, level domain.WorkflowRunLogLevel, content string, err ...string) {
|
||||||
output := domain.WorkflowRunLogOutput{
|
record := domain.WorkflowRunLogRecord{
|
||||||
Time: time.Now().UTC().Format(time.RFC3339),
|
Time: time.Now().UTC().Format(time.RFC3339),
|
||||||
Title: title,
|
Level: level,
|
||||||
Content: content,
|
Content: content,
|
||||||
}
|
}
|
||||||
if len(err) > 0 {
|
if len(err) > 0 {
|
||||||
output.Error = err[0]
|
record.Error = err[0]
|
||||||
l.log.Error = err[0]
|
l.log.Error = err[0]
|
||||||
}
|
}
|
||||||
l.log.Outputs = append(l.log.Outputs, output)
|
|
||||||
|
l.log.Records = append(l.log.Records, record)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetProcessor(node *domain.WorkflowNode) (NodeProcessor, error) {
|
func GetProcessor(node *domain.WorkflowNode) (NodeProcessor, error) {
|
||||||
@ -83,3 +86,7 @@ func GetProcessor(node *domain.WorkflowNode) (NodeProcessor, error) {
|
|||||||
func getContextWorkflowId(ctx context.Context) string {
|
func getContextWorkflowId(ctx context.Context) string {
|
||||||
return ctx.Value("workflow_id").(string)
|
return ctx.Value("workflow_id").(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getContextWorkflowRunId(ctx context.Context) string {
|
||||||
|
return ctx.Value("workflow_run_id").(string)
|
||||||
|
}
|
||||||
|
@ -14,13 +14,13 @@ type startNode struct {
|
|||||||
func NewStartNode(node *domain.WorkflowNode) *startNode {
|
func NewStartNode(node *domain.WorkflowNode) *startNode {
|
||||||
return &startNode{
|
return &startNode{
|
||||||
node: node,
|
node: node,
|
||||||
nodeLogger: NewNodeLogger(node),
|
nodeLogger: newNodeLogger(node),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *startNode) Run(ctx context.Context) error {
|
func (n *startNode) Process(ctx context.Context) error {
|
||||||
// 开始节点没有任何操作
|
// 此类型节点不需要执行任何操作,直接返回
|
||||||
s.AddOutput(ctx, s.node.Name, "完成")
|
n.AppendLogRecord(ctx, domain.WorkflowRunLogLevelInfo, "进入开始节点")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -13,89 +13,93 @@ import (
|
|||||||
|
|
||||||
type uploadNode struct {
|
type uploadNode struct {
|
||||||
node *domain.WorkflowNode
|
node *domain.WorkflowNode
|
||||||
outputRepo workflowOutputRepository
|
|
||||||
*nodeLogger
|
*nodeLogger
|
||||||
|
|
||||||
|
certRepo certificateRepository
|
||||||
|
outputRepo workflowOutputRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUploadNode(node *domain.WorkflowNode) *uploadNode {
|
func NewUploadNode(node *domain.WorkflowNode) *uploadNode {
|
||||||
return &uploadNode{
|
return &uploadNode{
|
||||||
node: node,
|
node: node,
|
||||||
nodeLogger: NewNodeLogger(node),
|
nodeLogger: newNodeLogger(node),
|
||||||
|
|
||||||
|
certRepo: repository.NewCertificateRepository(),
|
||||||
outputRepo: repository.NewWorkflowOutputRepository(),
|
outputRepo: repository.NewWorkflowOutputRepository(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run 上传证书节点执行
|
func (n *uploadNode) Process(ctx context.Context) error {
|
||||||
// 包含上传证书的工作流,理论上应该手动执行,如果每天定时执行,也只是重新保存一下
|
n.AppendLogRecord(ctx, domain.WorkflowRunLogLevelInfo, "进入上传证书节点")
|
||||||
func (n *uploadNode) Run(ctx context.Context) error {
|
|
||||||
n.AddOutput(ctx,
|
|
||||||
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.AppendLogRecord(ctx, domain.WorkflowRunLogLevelError, "查询申请记录失败", err.Error())
|
||||||
n.AddOutput(ctx,
|
|
||||||
n.node.Name,
|
|
||||||
"解析证书失败",
|
|
||||||
)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检测是否可以跳过本次执行
|
||||||
|
if skippable, skipReason := n.checkCanSkip(ctx, lastOutput); skippable {
|
||||||
|
n.AppendLogRecord(ctx, domain.WorkflowRunLogLevelInfo, skipReason)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查证书是否过期
|
||||||
|
// 如果证书过期,则直接返回错误
|
||||||
|
certX509, err := certs.ParseCertificateFromPEM(nodeConfig.Certificate)
|
||||||
|
if err != nil {
|
||||||
|
n.AppendLogRecord(ctx, domain.WorkflowRunLogLevelError, "解析证书失败")
|
||||||
|
return err
|
||||||
|
}
|
||||||
if time.Now().After(certX509.NotAfter) {
|
if time.Now().After(certX509.NotAfter) {
|
||||||
n.AddOutput(ctx,
|
n.AppendLogRecord(ctx, domain.WorkflowRunLogLevelWarn, "证书已过期")
|
||||||
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: 先保持一个节点始终只有一个输出,后续增加版本控制
|
output := &domain.WorkflowOutput{
|
||||||
currentOutput := &domain.WorkflowOutput{
|
|
||||||
WorkflowId: getContextWorkflowId(ctx),
|
WorkflowId: getContextWorkflowId(ctx),
|
||||||
|
RunId: getContextWorkflowRunId(ctx),
|
||||||
NodeId: n.node.Id,
|
NodeId: n.node.Id,
|
||||||
Node: n.node,
|
Node: n.node,
|
||||||
Succeeded: true,
|
Succeeded: true,
|
||||||
Outputs: n.node.Outputs,
|
Outputs: n.node.Outputs,
|
||||||
}
|
}
|
||||||
|
if _, err := n.outputRepo.SaveWithCertificate(ctx, output, certificate); err != nil {
|
||||||
// 查询上次执行结果
|
n.AppendLogRecord(ctx, domain.WorkflowRunLogLevelError, "保存上传记录失败", err.Error())
|
||||||
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
|
return err
|
||||||
}
|
}
|
||||||
if lastOutput != nil {
|
n.AppendLogRecord(ctx, domain.WorkflowRunLogLevelInfo, "保存上传记录成功")
|
||||||
currentOutput.Id = lastOutput.Id
|
|
||||||
}
|
|
||||||
if err := n.outputRepo.Save(ctx, currentOutput, certificate, func(id string) error {
|
|
||||||
if certificate != nil {
|
|
||||||
certificate.WorkflowOutputId = id
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}); err != nil {
|
|
||||||
n.AddOutput(ctx, n.node.Name, "保存上传记录失败", err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
n.AddOutput(ctx, n.node.Name, "保存上传记录成功")
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *uploadNode) checkCanSkip(ctx context.Context, lastOutput *domain.WorkflowOutput) (skip bool, reason string) {
|
||||||
|
if lastOutput != nil && lastOutput.Succeeded {
|
||||||
|
// 比较和上次上传时的关键配置(即影响证书上传的)参数是否一致
|
||||||
|
currentNodeConfig := n.node.GetConfigForUpload()
|
||||||
|
lastNodeConfig := lastOutput.Node.GetConfigForUpload()
|
||||||
|
if strings.TrimSpace(currentNodeConfig.Certificate) != strings.TrimSpace(lastNodeConfig.Certificate) {
|
||||||
|
return false, "配置项变化:证书"
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(currentNodeConfig.PrivateKey) != strings.TrimSpace(lastNodeConfig.PrivateKey) {
|
||||||
|
return false, "配置项变化:私钥"
|
||||||
|
}
|
||||||
|
|
||||||
|
lastCertificate, _ := n.certRepo.GetByWorkflowNodeId(ctx, n.node.Id)
|
||||||
|
if lastCertificate != nil {
|
||||||
|
return true, "已上传过证书"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
@ -1,89 +0,0 @@
|
|||||||
package processor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
|
||||||
nodes "github.com/usual2970/certimate/internal/workflow/node-processor"
|
|
||||||
)
|
|
||||||
|
|
||||||
type workflowProcessor struct {
|
|
||||||
workflow *domain.Workflow
|
|
||||||
logs []domain.WorkflowRunLog
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewWorkflowProcessor(workflow *domain.Workflow) *workflowProcessor {
|
|
||||||
return &workflowProcessor{
|
|
||||||
workflow: workflow,
|
|
||||||
logs: make([]domain.WorkflowRunLog, 0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *workflowProcessor) Run(ctx context.Context) error {
|
|
||||||
ctx = setContextWorkflowId(ctx, w.workflow.Id)
|
|
||||||
return w.processNode(ctx, w.workflow.Content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *workflowProcessor) GetRunLogs() []domain.WorkflowRunLog {
|
|
||||||
return w.logs
|
|
||||||
}
|
|
||||||
|
|
||||||
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.processNode(ctx, &branch); err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var processor nodes.NodeProcessor
|
|
||||||
var runErr error
|
|
||||||
for {
|
|
||||||
if current.Type != domain.WorkflowNodeTypeBranch && current.Type != domain.WorkflowNodeTypeExecuteResultBranch {
|
|
||||||
processor, runErr = nodes.GetProcessor(current)
|
|
||||||
if runErr != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
runErr = processor.Run(ctx)
|
|
||||||
log := processor.Log(ctx)
|
|
||||||
if log != nil {
|
|
||||||
w.logs = append(w.logs, *log)
|
|
||||||
}
|
|
||||||
if runErr != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if runErr != nil && current.Next != nil && current.Next.Type != domain.WorkflowNodeTypeExecuteResultBranch {
|
|
||||||
return runErr
|
|
||||||
} else if runErr != nil && current.Next != nil && current.Next.Type == domain.WorkflowNodeTypeExecuteResultBranch {
|
|
||||||
current = getBranchByType(current.Next.Branches, domain.WorkflowNodeTypeExecuteFailure)
|
|
||||||
} else if runErr == nil && current.Next != nil && current.Next.Type == domain.WorkflowNodeTypeExecuteResultBranch {
|
|
||||||
current = getBranchByType(current.Next.Branches, domain.WorkflowNodeTypeExecuteSuccess)
|
|
||||||
} else {
|
|
||||||
current = current.Next
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setContextWorkflowId(ctx context.Context, id string) context.Context {
|
|
||||||
return context.WithValue(ctx, "workflow_id", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getBranchByType(branches []domain.WorkflowNode, nodeType domain.WorkflowNodeType) *domain.WorkflowNode {
|
|
||||||
for _, branch := range branches {
|
|
||||||
if branch.Type == nodeType {
|
|
||||||
return &branch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -4,70 +4,64 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/app"
|
"github.com/usual2970/certimate/internal/app"
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
"github.com/usual2970/certimate/internal/domain/dtos"
|
"github.com/usual2970/certimate/internal/domain/dtos"
|
||||||
processor "github.com/usual2970/certimate/internal/workflow/processor"
|
"github.com/usual2970/certimate/internal/workflow/dispatcher"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultRoutines = 10
|
|
||||||
|
|
||||||
type workflowRunData struct {
|
|
||||||
Workflow *domain.Workflow
|
|
||||||
RunTrigger domain.WorkflowTriggerType
|
|
||||||
}
|
|
||||||
|
|
||||||
type workflowRepository interface {
|
type workflowRepository interface {
|
||||||
ListEnabledAuto(ctx context.Context) ([]*domain.Workflow, error)
|
ListEnabledAuto(ctx context.Context) ([]*domain.Workflow, error)
|
||||||
GetById(ctx context.Context, id string) (*domain.Workflow, error)
|
GetById(ctx context.Context, id string) (*domain.Workflow, error)
|
||||||
Save(ctx context.Context, workflow *domain.Workflow) (*domain.Workflow, error)
|
Save(ctx context.Context, workflow *domain.Workflow) (*domain.Workflow, error)
|
||||||
SaveRun(ctx context.Context, workflowRun *domain.WorkflowRun) (*domain.WorkflowRun, error)
|
}
|
||||||
|
|
||||||
|
type workflowRunRepository interface {
|
||||||
|
GetById(ctx context.Context, id string) (*domain.WorkflowRun, error)
|
||||||
|
Save(ctx context.Context, workflowRun *domain.WorkflowRun) (*domain.WorkflowRun, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type WorkflowService struct {
|
type WorkflowService struct {
|
||||||
ch chan *workflowRunData
|
dispatcher *dispatcher.WorkflowDispatcher
|
||||||
repo workflowRepository
|
|
||||||
wg sync.WaitGroup
|
workflowRepo workflowRepository
|
||||||
cancel context.CancelFunc
|
workflowRunRepo workflowRunRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWorkflowService(repo workflowRepository) *WorkflowService {
|
func NewWorkflowService(workflowRepo workflowRepository, workflowRunRepo workflowRunRepository) *WorkflowService {
|
||||||
srv := &WorkflowService{
|
srv := &WorkflowService{
|
||||||
repo: repo,
|
dispatcher: dispatcher.GetSingletonDispatcher(workflowRepo, workflowRunRepo),
|
||||||
ch: make(chan *workflowRunData, 1),
|
|
||||||
|
workflowRepo: workflowRepo,
|
||||||
|
workflowRunRepo: workflowRunRepo,
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
srv.cancel = cancel
|
|
||||||
|
|
||||||
srv.wg.Add(defaultRoutines)
|
|
||||||
for i := 0; i < defaultRoutines; i++ {
|
|
||||||
go srv.run(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
return srv
|
return srv
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *WorkflowService) InitSchedule(ctx context.Context) error {
|
func (s *WorkflowService) InitSchedule(ctx context.Context) error {
|
||||||
workflows, err := s.repo.ListEnabledAuto(ctx)
|
workflows, err := s.workflowRepo.ListEnabledAuto(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduler := app.GetScheduler()
|
scheduler := app.GetScheduler()
|
||||||
for _, workflow := range workflows {
|
for _, workflow := range workflows {
|
||||||
|
var errs []error
|
||||||
|
|
||||||
err := scheduler.Add(fmt.Sprintf("workflow#%s", workflow.Id), workflow.TriggerCron, func() {
|
err := scheduler.Add(fmt.Sprintf("workflow#%s", workflow.Id), workflow.TriggerCron, func() {
|
||||||
s.StartRun(ctx, &dtos.WorkflowStartRunReq{
|
s.StartRun(ctx, &dtos.WorkflowStartRunReq{
|
||||||
WorkflowId: workflow.Id,
|
WorkflowId: workflow.Id,
|
||||||
Trigger: domain.WorkflowTriggerTypeAuto,
|
RunTrigger: domain.WorkflowTriggerTypeAuto,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.GetLogger().Error("failed to add schedule", "err", err)
|
errs = append(errs, err)
|
||||||
return err
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errors.Join(errs...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,97 +69,56 @@ func (s *WorkflowService) InitSchedule(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *WorkflowService) StartRun(ctx context.Context, req *dtos.WorkflowStartRunReq) error {
|
func (s *WorkflowService) StartRun(ctx context.Context, req *dtos.WorkflowStartRunReq) error {
|
||||||
workflow, err := s.repo.GetById(ctx, req.WorkflowId)
|
workflow, err := s.workflowRepo.GetById(ctx, req.WorkflowId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.GetLogger().Error("failed to get workflow", "id", req.WorkflowId, "err", err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if workflow.LastRunStatus == domain.WorkflowRunStatusTypeRunning {
|
if workflow.LastRunStatus == domain.WorkflowRunStatusTypePending || workflow.LastRunStatus == domain.WorkflowRunStatusTypeRunning {
|
||||||
return errors.New("workflow is running")
|
return errors.New("workflow is already pending or running")
|
||||||
}
|
}
|
||||||
|
|
||||||
workflow.LastRunTime = time.Now()
|
|
||||||
workflow.LastRunStatus = domain.WorkflowRunStatusTypePending
|
|
||||||
workflow.LastRunId = ""
|
|
||||||
if resp, err := s.repo.Save(ctx, workflow); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
workflow = resp
|
|
||||||
}
|
|
||||||
|
|
||||||
s.ch <- &workflowRunData{
|
|
||||||
Workflow: workflow,
|
|
||||||
RunTrigger: req.Trigger,
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *WorkflowService) CancelRun(ctx context.Context, req *dtos.WorkflowCancelRunReq) error {
|
|
||||||
// TODO: 取消运行,防止因为某些原因意外挂起(如进程被杀死)导致工作流一直处于 running 状态无法重新运行
|
|
||||||
|
|
||||||
return errors.New("TODO: 尚未实现")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *WorkflowService) Stop(ctx context.Context) {
|
|
||||||
s.cancel()
|
|
||||||
s.wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *WorkflowService) run(ctx context.Context) {
|
|
||||||
defer s.wg.Done()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case data := <-s.ch:
|
|
||||||
if err := s.runWithData(ctx, data); err != nil {
|
|
||||||
app.GetLogger().Error("failed to run workflow", "id", data.Workflow.Id, "err", err)
|
|
||||||
}
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *WorkflowService) runWithData(ctx context.Context, runData *workflowRunData) error {
|
|
||||||
workflow := runData.Workflow
|
|
||||||
run := &domain.WorkflowRun{
|
run := &domain.WorkflowRun{
|
||||||
WorkflowId: workflow.Id,
|
WorkflowId: workflow.Id,
|
||||||
Status: domain.WorkflowRunStatusTypeRunning,
|
Status: domain.WorkflowRunStatusTypePending,
|
||||||
Trigger: runData.RunTrigger,
|
Trigger: req.RunTrigger,
|
||||||
StartedAt: time.Now(),
|
StartedAt: time.Now(),
|
||||||
}
|
}
|
||||||
if resp, err := s.repo.SaveRun(ctx, run); err != nil {
|
if resp, err := s.workflowRunRepo.Save(ctx, run); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
run = resp
|
run = resp
|
||||||
}
|
}
|
||||||
|
|
||||||
processor := processor.NewWorkflowProcessor(workflow)
|
s.dispatcher.Dispatch(&dispatcher.WorkflowWorkerData{
|
||||||
if runErr := processor.Run(ctx); runErr != nil {
|
WorkflowId: workflow.Id,
|
||||||
run.Status = domain.WorkflowRunStatusTypeFailed
|
WorkflowContent: workflow.Content,
|
||||||
run.EndedAt = time.Now()
|
RunId: run.Id,
|
||||||
run.Logs = processor.GetRunLogs()
|
})
|
||||||
run.Error = runErr.Error()
|
|
||||||
if _, err := s.repo.SaveRun(ctx, run); err != nil {
|
|
||||||
app.GetLogger().Error("failed to save workflow run", "err", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("failed to run workflow: %w", runErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
run.EndedAt = time.Now()
|
|
||||||
run.Logs = processor.GetRunLogs()
|
|
||||||
run.Error = domain.WorkflowRunLogs(run.Logs).ErrorString()
|
|
||||||
if run.Error == "" {
|
|
||||||
run.Status = domain.WorkflowRunStatusTypeSucceeded
|
|
||||||
} else {
|
|
||||||
run.Status = domain.WorkflowRunStatusTypeFailed
|
|
||||||
}
|
|
||||||
if _, err := s.repo.SaveRun(ctx, run); err != nil {
|
|
||||||
app.GetLogger().Error("failed to save workflow run", "err", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *WorkflowService) CancelRun(ctx context.Context, req *dtos.WorkflowCancelRunReq) error {
|
||||||
|
workflow, err := s.workflowRepo.GetById(ctx, req.WorkflowId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
workflowRun, err := s.workflowRunRepo.GetById(ctx, req.RunId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if workflowRun.WorkflowId != workflow.Id {
|
||||||
|
return errors.New("workflow run not found")
|
||||||
|
} else if workflowRun.Status != domain.WorkflowRunStatusTypePending && workflowRun.Status != domain.WorkflowRunStatusTypeRunning {
|
||||||
|
return errors.New("workflow run is not pending or running")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.dispatcher.Cancel(workflowRun.Id)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *WorkflowService) Shutdown(ctx context.Context) {
|
||||||
|
s.dispatcher.Shutdown()
|
||||||
|
}
|
||||||
|
3
main.go
3
main.go
@ -18,7 +18,8 @@ import (
|
|||||||
"github.com/usual2970/certimate/internal/scheduler"
|
"github.com/usual2970/certimate/internal/scheduler"
|
||||||
"github.com/usual2970/certimate/internal/workflow"
|
"github.com/usual2970/certimate/internal/workflow"
|
||||||
"github.com/usual2970/certimate/ui"
|
"github.com/usual2970/certimate/ui"
|
||||||
//_ "github.com/usual2970/certimate/migrations"
|
|
||||||
|
_ "github.com/usual2970/certimate/migrations"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -12,16 +12,16 @@ func init() {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
record, _ := app.FindAuthRecordByEmail(core.CollectionNameSuperusers, "admin@certimate.fun")
|
||||||
|
if record == nil {
|
||||||
record := core.NewRecord(superusers)
|
record := core.NewRecord(superusers)
|
||||||
record.Set("email", "admin@certimate.fun")
|
record.Set("email", "admin@certimate.fun")
|
||||||
record.Set("password", "1234567890")
|
record.Set("password", "1234567890")
|
||||||
return app.Save(record)
|
return app.Save(record)
|
||||||
}, func(app core.App) error {
|
|
||||||
record, _ := app.FindAuthRecordByEmail(core.CollectionNameSuperusers, "admin@certimate.fun")
|
|
||||||
if record == nil {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return app.Delete(record)
|
return nil
|
||||||
|
}, func(app core.App) error {
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
67
migrations/1738839725_updated_certificate.go
Normal file
67
migrations/1738839725_updated_certificate.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
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` + "`" + `)",
|
||||||
|
"CREATE INDEX ` + "`" + `idx_2cRXqNDyyp` + "`" + ` ON ` + "`" + `certificate` + "`" + ` (` + "`" + `workflowRunId` + "`" + `)"
|
||||||
|
]
|
||||||
|
}`), &collection); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// add field
|
||||||
|
if err := collection.Fields.AddMarshaledJSONAt(15, []byte(`{
|
||||||
|
"cascadeDelete": false,
|
||||||
|
"collectionId": "qjp8lygssgwyqyz",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "relation3917999135",
|
||||||
|
"maxSelect": 1,
|
||||||
|
"minSelect": 0,
|
||||||
|
"name": "workflowRunId",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "relation"
|
||||||
|
}`)); 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": [
|
||||||
|
"CREATE INDEX ` + "`" + `idx_Jx8TXzDCmw` + "`" + ` ON ` + "`" + `certificate` + "`" + ` (` + "`" + `workflowId` + "`" + `)",
|
||||||
|
"CREATE INDEX ` + "`" + `idx_kcKpgAZapk` + "`" + ` ON ` + "`" + `certificate` + "`" + ` (` + "`" + `workflowNodeId` + "`" + `)"
|
||||||
|
]
|
||||||
|
}`), &collection); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove field
|
||||||
|
collection.Fields.RemoveById("relation3917999135")
|
||||||
|
|
||||||
|
return app.Save(collection)
|
||||||
|
})
|
||||||
|
}
|
63
migrations/1738840633_updated_workflow_output.go
Normal file
63
migrations/1738840633_updated_workflow_output.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
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("bqnxb95f2cooowp")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update collection data
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"indexes": [
|
||||||
|
"CREATE INDEX ` + "`" + `idx_BYoQPsz4my` + "`" + ` ON ` + "`" + `workflow_output` + "`" + ` (` + "`" + `workflowId` + "`" + `)",
|
||||||
|
"CREATE INDEX ` + "`" + `idx_O9zxLETuxJ` + "`" + ` ON ` + "`" + `workflow_output` + "`" + ` (` + "`" + `runId` + "`" + `)"
|
||||||
|
]
|
||||||
|
}`), &collection); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// add field
|
||||||
|
if err := collection.Fields.AddMarshaledJSONAt(2, []byte(`{
|
||||||
|
"cascadeDelete": false,
|
||||||
|
"collectionId": "qjp8lygssgwyqyz",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "relation821863227",
|
||||||
|
"maxSelect": 1,
|
||||||
|
"minSelect": 0,
|
||||||
|
"name": "runId",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "relation"
|
||||||
|
}`)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.Save(collection)
|
||||||
|
}, func(app core.App) error {
|
||||||
|
collection, err := app.FindCollectionByNameOrId("bqnxb95f2cooowp")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update collection data
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"indexes": []
|
||||||
|
}`), &collection); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove field
|
||||||
|
collection.Fields.RemoveById("relation821863227")
|
||||||
|
|
||||||
|
return app.Save(collection)
|
||||||
|
})
|
||||||
|
}
|
58
migrations/1739263253_updated_workflow_run.go
Normal file
58
migrations/1739263253_updated_workflow_run.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pocketbase/pocketbase/core"
|
||||||
|
m "github.com/pocketbase/pocketbase/migrations"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
m.Register(func(app core.App) error {
|
||||||
|
collection, err := app.FindCollectionByNameOrId("qjp8lygssgwyqyz")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update field
|
||||||
|
if err := collection.Fields.AddMarshaledJSONAt(1, []byte(`{
|
||||||
|
"cascadeDelete": true,
|
||||||
|
"collectionId": "tovyif5ax6j62ur",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "m8xfsyyy",
|
||||||
|
"maxSelect": 1,
|
||||||
|
"minSelect": 0,
|
||||||
|
"name": "workflowId",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "relation"
|
||||||
|
}`)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.Save(collection)
|
||||||
|
}, func(app core.App) error {
|
||||||
|
collection, err := app.FindCollectionByNameOrId("qjp8lygssgwyqyz")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update field
|
||||||
|
if err := collection.Fields.AddMarshaledJSONAt(1, []byte(`{
|
||||||
|
"cascadeDelete": false,
|
||||||
|
"collectionId": "tovyif5ax6j62ur",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "m8xfsyyy",
|
||||||
|
"maxSelect": 1,
|
||||||
|
"minSelect": 0,
|
||||||
|
"name": "workflowId",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "relation"
|
||||||
|
}`)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.Save(collection)
|
||||||
|
})
|
||||||
|
}
|
92
migrations/1739263264_updated_workflow_output.go
Normal file
92
migrations/1739263264_updated_workflow_output.go
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pocketbase/pocketbase/core"
|
||||||
|
m "github.com/pocketbase/pocketbase/migrations"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
m.Register(func(app core.App) error {
|
||||||
|
collection, err := app.FindCollectionByNameOrId("bqnxb95f2cooowp")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update field
|
||||||
|
if err := collection.Fields.AddMarshaledJSONAt(1, []byte(`{
|
||||||
|
"cascadeDelete": true,
|
||||||
|
"collectionId": "tovyif5ax6j62ur",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "jka88auc",
|
||||||
|
"maxSelect": 1,
|
||||||
|
"minSelect": 0,
|
||||||
|
"name": "workflowId",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "relation"
|
||||||
|
}`)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update field
|
||||||
|
if err := collection.Fields.AddMarshaledJSONAt(2, []byte(`{
|
||||||
|
"cascadeDelete": true,
|
||||||
|
"collectionId": "qjp8lygssgwyqyz",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "relation821863227",
|
||||||
|
"maxSelect": 1,
|
||||||
|
"minSelect": 0,
|
||||||
|
"name": "runId",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "relation"
|
||||||
|
}`)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.Save(collection)
|
||||||
|
}, func(app core.App) error {
|
||||||
|
collection, err := app.FindCollectionByNameOrId("bqnxb95f2cooowp")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update field
|
||||||
|
if err := collection.Fields.AddMarshaledJSONAt(1, []byte(`{
|
||||||
|
"cascadeDelete": false,
|
||||||
|
"collectionId": "tovyif5ax6j62ur",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "jka88auc",
|
||||||
|
"maxSelect": 1,
|
||||||
|
"minSelect": 0,
|
||||||
|
"name": "workflowId",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "relation"
|
||||||
|
}`)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update field
|
||||||
|
if err := collection.Fields.AddMarshaledJSONAt(2, []byte(`{
|
||||||
|
"cascadeDelete": false,
|
||||||
|
"collectionId": "qjp8lygssgwyqyz",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "relation821863227",
|
||||||
|
"maxSelect": 1,
|
||||||
|
"minSelect": 0,
|
||||||
|
"name": "runId",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "relation"
|
||||||
|
}`)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.Save(collection)
|
||||||
|
})
|
||||||
|
}
|
@ -3,10 +3,14 @@ import { ClientResponseError } from "pocketbase";
|
|||||||
import { type CertificateFormatType } from "@/domain/certificate";
|
import { type CertificateFormatType } from "@/domain/certificate";
|
||||||
import { getPocketBase } from "@/repository/_pocketbase";
|
import { getPocketBase } from "@/repository/_pocketbase";
|
||||||
|
|
||||||
|
type ArchiveRespData = {
|
||||||
|
fileBytes: string;
|
||||||
|
};
|
||||||
|
|
||||||
export const archive = async (certificateId: string, format?: CertificateFormatType) => {
|
export const archive = async (certificateId: string, format?: CertificateFormatType) => {
|
||||||
const pb = getPocketBase();
|
const pb = getPocketBase();
|
||||||
|
|
||||||
const resp = await pb.send<BaseResponse<string>>(`/api/certificates/${encodeURIComponent(certificateId)}/archive`, {
|
const resp = await pb.send<BaseResponse<ArchiveRespData>>(`/api/certificates/${encodeURIComponent(certificateId)}/archive`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@ -24,6 +28,7 @@ export const archive = async (certificateId: string, format?: CertificateFormatT
|
|||||||
};
|
};
|
||||||
|
|
||||||
type ValidateCertificateResp = {
|
type ValidateCertificateResp = {
|
||||||
|
isValid: boolean;
|
||||||
domains: string;
|
domains: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -46,9 +51,13 @@ export const validateCertificate = async (certificate: string) => {
|
|||||||
return resp;
|
return resp;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ValidatePrivateKeyResp = {
|
||||||
|
isValid: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export const validatePrivateKey = async (privateKey: string) => {
|
export const validatePrivateKey = async (privateKey: string) => {
|
||||||
const pb = getPocketBase();
|
const pb = getPocketBase();
|
||||||
const resp = await pb.send<BaseResponse>(`/api/certificates/validate/private-key`, {
|
const resp = await pb.send<BaseResponse<ValidatePrivateKeyResp>>(`/api/certificates/validate/private-key`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
@ -22,7 +22,7 @@ const CertificateDetail = ({ data, ...props }: CertificateDetailProps) => {
|
|||||||
const handleDownloadClick = async (format: CertificateFormatType) => {
|
const handleDownloadClick = async (format: CertificateFormatType) => {
|
||||||
try {
|
try {
|
||||||
const res = await archiveCertificate(data.id, format);
|
const res = await archiveCertificate(data.id, format);
|
||||||
const bstr = atob(res.data);
|
const bstr = atob(res.data.fileBytes);
|
||||||
const u8arr = Uint8Array.from(bstr, (ch) => ch.charCodeAt(0));
|
const u8arr = Uint8Array.from(bstr, (ch) => ch.charCodeAt(0));
|
||||||
const blob = new Blob([u8arr], { type: "application/zip" });
|
const blob = new Blob([u8arr], { type: "application/zip" });
|
||||||
saveAs(blob, `${data.id}-${data.subjectAltNames}.zip`);
|
saveAs(blob, `${data.id}-${data.subjectAltNames}.zip`);
|
||||||
@ -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>
|
||||||
|
|
||||||
|
163
ui/src/components/workflow/WorkflowRunDetail.tsx
Normal file
163
ui/src/components/workflow/WorkflowRunDetail.tsx
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { SelectOutlined as SelectOutlinedIcon } from "@ant-design/icons";
|
||||||
|
import { useRequest } from "ahooks";
|
||||||
|
import { Alert, Button, Divider, Empty, Table, type TableProps, Tooltip, Typography, notification } from "antd";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import { ClientResponseError } from "pocketbase";
|
||||||
|
|
||||||
|
import CertificateDetailDrawer from "@/components/certificate/CertificateDetailDrawer";
|
||||||
|
import Show from "@/components/Show";
|
||||||
|
import { type CertificateModel } from "@/domain/certificate";
|
||||||
|
import { WORKFLOW_RUN_STATUSES, type WorkflowRunModel } from "@/domain/workflowRun";
|
||||||
|
import { listByWorkflowRunId as listCertificateByWorkflowRunId } from "@/repository/certificate";
|
||||||
|
import { getErrMsg } from "@/utils/error";
|
||||||
|
|
||||||
|
export type WorkflowRunDetailProps = {
|
||||||
|
className?: string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
data: WorkflowRunModel;
|
||||||
|
};
|
||||||
|
|
||||||
|
const WorkflowRunDetail = ({ data, ...props }: WorkflowRunDetailProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div {...props}>
|
||||||
|
<Show when={data.status === WORKFLOW_RUN_STATUSES.SUCCEEDED}>
|
||||||
|
<Alert showIcon type="success" message={<Typography.Text type="success">{t("workflow_run.props.status.succeeded")}</Typography.Text>} />
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
<Show when={data.status === WORKFLOW_RUN_STATUSES.FAILED}>
|
||||||
|
<Alert showIcon type="error" message={<Typography.Text type="danger">{t("workflow_run.props.status.failed")}</Typography.Text>} />
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
<div className="my-4">
|
||||||
|
<Typography.Title level={5}>{t("workflow_run.logs")}</Typography.Title>
|
||||||
|
<div className="rounded-md bg-black p-4 text-stone-200">
|
||||||
|
<div className="flex flex-col space-y-4">
|
||||||
|
{data.logs?.map((item, i) => {
|
||||||
|
return (
|
||||||
|
<div key={i} className="flex flex-col space-y-2">
|
||||||
|
<div className="font-semibold">{item.nodeName}</div>
|
||||||
|
<div className="flex flex-col space-y-1">
|
||||||
|
{item.records?.map((output, j) => {
|
||||||
|
return (
|
||||||
|
<div key={j} className="flex space-x-2 text-sm" style={{ wordBreak: "break-word" }}>
|
||||||
|
<div className="whitespace-nowrap">[{dayjs(output.time).format("YYYY-MM-DD HH:mm:ss")}]</div>
|
||||||
|
{output.error ? <div className="text-red-500">{output.error}</div> : <div>{output.content}</div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Show when={data.status === WORKFLOW_RUN_STATUSES.SUCCEEDED}>
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<WorkflowRunArtifacts runId={data.id} />
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const WorkflowRunArtifacts = ({ runId }: { runId: string }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [notificationApi, NotificationContextHolder] = notification.useNotification();
|
||||||
|
|
||||||
|
const tableColumns: TableProps<CertificateModel>["columns"] = [
|
||||||
|
{
|
||||||
|
key: "$index",
|
||||||
|
align: "center",
|
||||||
|
fixed: "left",
|
||||||
|
width: 50,
|
||||||
|
render: (_, __, index) => index + 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "type",
|
||||||
|
title: t("workflow_run_artifact.props.type"),
|
||||||
|
render: () => t("workflow_run_artifact.props.type.certificate"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "name",
|
||||||
|
title: t("workflow_run_artifact.props.name"),
|
||||||
|
ellipsis: true,
|
||||||
|
render: (_, record) => {
|
||||||
|
return (
|
||||||
|
<Typography.Text delete={!!record.deleted} ellipsis>
|
||||||
|
{record.subjectAltNames}
|
||||||
|
</Typography.Text>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "$action",
|
||||||
|
align: "end",
|
||||||
|
width: 120,
|
||||||
|
render: (_, record) => (
|
||||||
|
<Button.Group>
|
||||||
|
<CertificateDetailDrawer
|
||||||
|
data={record}
|
||||||
|
trigger={
|
||||||
|
<Tooltip title={t("certificate.action.view")}>
|
||||||
|
<Button color="primary" disabled={!!record.deleted} icon={<SelectOutlinedIcon />} variant="text" />
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Button.Group>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const [tableData, setTableData] = useState<CertificateModel[]>([]);
|
||||||
|
const { loading: tableLoading } = useRequest(
|
||||||
|
() => {
|
||||||
|
return listCertificateByWorkflowRunId(runId);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
refreshDeps: [runId],
|
||||||
|
onBefore: () => {
|
||||||
|
setTableData([]);
|
||||||
|
},
|
||||||
|
onSuccess: (res) => {
|
||||||
|
setTableData(res.items);
|
||||||
|
},
|
||||||
|
onError: (err) => {
|
||||||
|
if (err instanceof ClientResponseError && err.isAbort) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(err);
|
||||||
|
notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) });
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{NotificationContextHolder}
|
||||||
|
|
||||||
|
<Typography.Title level={5}>{t("workflow_run.artifacts")}</Typography.Title>
|
||||||
|
<Table<CertificateModel>
|
||||||
|
columns={tableColumns}
|
||||||
|
dataSource={tableData}
|
||||||
|
loading={tableLoading}
|
||||||
|
locale={{
|
||||||
|
emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />,
|
||||||
|
}}
|
||||||
|
pagination={false}
|
||||||
|
rowKey={(record) => record.id}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WorkflowRunDetail;
|
@ -1,12 +1,12 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useControllableValue } from "ahooks";
|
import { useControllableValue } from "ahooks";
|
||||||
import { Alert, Drawer, Typography } from "antd";
|
import { Drawer } from "antd";
|
||||||
import dayjs from "dayjs";
|
|
||||||
|
|
||||||
import Show from "@/components/Show";
|
import Show from "@/components/Show";
|
||||||
import { WORKFLOW_RUN_STATUSES, type WorkflowRunModel } from "@/domain/workflowRun";
|
import { type WorkflowRunModel } from "@/domain/workflowRun";
|
||||||
import { useTriggerElement } from "@/hooks";
|
import { useTriggerElement } from "@/hooks";
|
||||||
|
|
||||||
|
import WorkflowRunDetail from "./WorkflowRunDetail";
|
||||||
|
|
||||||
export type WorkflowRunDetailDrawerProps = {
|
export type WorkflowRunDetailDrawerProps = {
|
||||||
data?: WorkflowRunModel;
|
data?: WorkflowRunModel;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
@ -16,8 +16,6 @@ export type WorkflowRunDetailDrawerProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const WorkflowRunDetailDrawer = ({ data, loading, trigger, ...props }: WorkflowRunDetailDrawerProps) => {
|
const WorkflowRunDetailDrawer = ({ data, loading, trigger, ...props }: WorkflowRunDetailDrawerProps) => {
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const [open, setOpen] = useControllableValue<boolean>(props, {
|
const [open, setOpen] = useControllableValue<boolean>(props, {
|
||||||
valuePropName: "open",
|
valuePropName: "open",
|
||||||
defaultValuePropName: "defaultOpen",
|
defaultValuePropName: "defaultOpen",
|
||||||
@ -30,37 +28,19 @@ const WorkflowRunDetailDrawer = ({ data, loading, trigger, ...props }: WorkflowR
|
|||||||
<>
|
<>
|
||||||
{triggerEl}
|
{triggerEl}
|
||||||
|
|
||||||
<Drawer destroyOnClose open={open} loading={loading} placement="right" title={`WorkflowRun #${data?.id}`} width={640} onClose={() => setOpen(false)}>
|
<Drawer
|
||||||
|
afterOpenChange={setOpen}
|
||||||
|
closable
|
||||||
|
destroyOnClose
|
||||||
|
open={open}
|
||||||
|
loading={loading}
|
||||||
|
placement="right"
|
||||||
|
title={`WorkflowRun #${data?.id}`}
|
||||||
|
width={640}
|
||||||
|
onClose={() => setOpen(false)}
|
||||||
|
>
|
||||||
<Show when={!!data}>
|
<Show when={!!data}>
|
||||||
<Show when={data!.status === WORKFLOW_RUN_STATUSES.SUCCEEDED}>
|
<WorkflowRunDetail data={data!} />
|
||||||
<Alert showIcon type="success" message={<Typography.Text type="success">{t("workflow_run.props.status.succeeded")}</Typography.Text>} />
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
<Show when={data!.status === WORKFLOW_RUN_STATUSES.FAILED}>
|
|
||||||
<Alert showIcon type="error" message={<Typography.Text type="danger">{t("workflow_run.props.status.failed")}</Typography.Text>} />
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
<div className="mt-4 rounded-md bg-black p-4 text-stone-200">
|
|
||||||
<div className="flex flex-col space-y-4">
|
|
||||||
{data!.logs?.map((item, i) => {
|
|
||||||
return (
|
|
||||||
<div key={i} className="flex flex-col space-y-2">
|
|
||||||
<div className="font-semibold">{item.nodeName}</div>
|
|
||||||
<div className="flex flex-col space-y-1">
|
|
||||||
{item.outputs?.map((output, j) => {
|
|
||||||
return (
|
|
||||||
<div key={j} className="flex space-x-2 text-sm" style={{ wordBreak: "break-word" }}>
|
|
||||||
<div className="whitespace-nowrap">[{dayjs(output.time).format("YYYY-MM-DD HH:mm:ss")}]</div>
|
|
||||||
{output.error ? <div className="text-red-500">{output.error}</div> : <div>{output.content}</div>}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Show>
|
</Show>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</>
|
</>
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
CheckCircleOutlined as CheckCircleOutlinedIcon,
|
CheckCircleOutlined as CheckCircleOutlinedIcon,
|
||||||
ClockCircleOutlined as ClockCircleOutlinedIcon,
|
ClockCircleOutlined as ClockCircleOutlinedIcon,
|
||||||
CloseCircleOutlined as CloseCircleOutlinedIcon,
|
CloseCircleOutlined as CloseCircleOutlinedIcon,
|
||||||
DeleteOutlined as DeleteOutlinedIcon,
|
DeleteOutlined as DeleteOutlinedIcon,
|
||||||
PauseCircleOutlined as PauseCircleOutlinedIcon,
|
|
||||||
PauseOutlined as PauseOutlinedIcon,
|
PauseOutlined as PauseOutlinedIcon,
|
||||||
SelectOutlined as SelectOutlinedIcon,
|
SelectOutlined as SelectOutlinedIcon,
|
||||||
|
StopOutlined as StopOutlinedIcon,
|
||||||
SyncOutlined as SyncOutlinedIcon,
|
SyncOutlined as SyncOutlinedIcon,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { useRequest } from "ahooks";
|
import { useRequest } from "ahooks";
|
||||||
@ -18,7 +18,12 @@ import { ClientResponseError } from "pocketbase";
|
|||||||
import { cancelRun as cancelWorkflowRun } from "@/api/workflows";
|
import { cancelRun as cancelWorkflowRun } from "@/api/workflows";
|
||||||
import { WORKFLOW_TRIGGERS } from "@/domain/workflow";
|
import { WORKFLOW_TRIGGERS } from "@/domain/workflow";
|
||||||
import { WORKFLOW_RUN_STATUSES, type WorkflowRunModel } from "@/domain/workflowRun";
|
import { WORKFLOW_RUN_STATUSES, type WorkflowRunModel } from "@/domain/workflowRun";
|
||||||
import { list as listWorkflowRuns, remove as removeWorkflowRun } from "@/repository/workflowRun";
|
import {
|
||||||
|
list as listWorkflowRuns,
|
||||||
|
remove as removeWorkflowRun,
|
||||||
|
subscribe as subscribeWorkflowRun,
|
||||||
|
unsubscribe as unsubscribeWorkflowRun,
|
||||||
|
} from "@/repository/workflowRun";
|
||||||
import { getErrMsg } from "@/utils/error";
|
import { getErrMsg } from "@/utils/error";
|
||||||
import WorkflowRunDetailDrawer from "./WorkflowRunDetailDrawer";
|
import WorkflowRunDetailDrawer from "./WorkflowRunDetailDrawer";
|
||||||
|
|
||||||
@ -75,7 +80,7 @@ const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => {
|
|||||||
);
|
);
|
||||||
} else if (record.status === WORKFLOW_RUN_STATUSES.CANCELED) {
|
} else if (record.status === WORKFLOW_RUN_STATUSES.CANCELED) {
|
||||||
return (
|
return (
|
||||||
<Tag icon={<PauseCircleOutlinedIcon />} color="warning">
|
<Tag icon={<StopOutlinedIcon />} color="warning">
|
||||||
{t("workflow_run.props.status.canceled")}
|
{t("workflow_run.props.status.canceled")}
|
||||||
</Tag>
|
</Tag>
|
||||||
);
|
);
|
||||||
@ -211,6 +216,31 @@ const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const items = tableData.filter((e) => e.status === WORKFLOW_RUN_STATUSES.PENDING || e.status === WORKFLOW_RUN_STATUSES.RUNNING);
|
||||||
|
for (const item of items) {
|
||||||
|
subscribeWorkflowRun(item.id, (cb) => {
|
||||||
|
setTableData((prev) => {
|
||||||
|
const index = prev.findIndex((e) => e.id === item.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
prev[index] = cb.record;
|
||||||
|
}
|
||||||
|
return [...prev];
|
||||||
|
});
|
||||||
|
|
||||||
|
if (cb.record.status !== WORKFLOW_RUN_STATUSES.PENDING && cb.record.status !== WORKFLOW_RUN_STATUSES.RUNNING) {
|
||||||
|
unsubscribeWorkflowRun(item.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
for (const item of items) {
|
||||||
|
unsubscribeWorkflowRun(item.id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [tableData]);
|
||||||
|
|
||||||
const handleCancelClick = (workflowRun: WorkflowRunModel) => {
|
const handleCancelClick = (workflowRun: WorkflowRunModel) => {
|
||||||
modalApi.confirm({
|
modalApi.confirm({
|
||||||
title: t("workflow_run.action.cancel"),
|
title: t("workflow_run.action.cancel"),
|
||||||
@ -275,7 +305,7 @@ const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => {
|
|||||||
setPageSize(pageSize);
|
setPageSize(pageSize);
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
rowKey={(record: WorkflowRunModel) => record.id}
|
rowKey={(record) => record.id}
|
||||||
scroll={{ x: "max(100%, 960px)" }}
|
scroll={{ x: "max(100%, 960px)" }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -56,6 +56,7 @@ const ApplyNode = ({ node, disabled }: ApplyNodeProps) => {
|
|||||||
const newNode = produce(node, (draft) => {
|
const newNode = produce(node, (draft) => {
|
||||||
draft.config = {
|
draft.config = {
|
||||||
...newValues,
|
...newValues,
|
||||||
|
challengeType: newValues.challengeType || "dns-01", // 默认使用 DNS-01 认证
|
||||||
};
|
};
|
||||||
draft.validated = true;
|
draft.validated = true;
|
||||||
});
|
});
|
||||||
|
@ -56,6 +56,7 @@ const MULTIPLE_INPUT_DELIMITER = ";";
|
|||||||
|
|
||||||
const initFormModel = (): ApplyNodeConfigFormFieldValues => {
|
const initFormModel = (): ApplyNodeConfigFormFieldValues => {
|
||||||
return {
|
return {
|
||||||
|
challengeType: "dns-01",
|
||||||
keyAlgorithm: "RSA2048",
|
keyAlgorithm: "RSA2048",
|
||||||
skipBeforeExpiryDays: 20,
|
skipBeforeExpiryDays: 20,
|
||||||
};
|
};
|
||||||
@ -74,6 +75,7 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
|||||||
.every((e) => validDomainName(e, { allowWildcard: true }));
|
.every((e) => validDomainName(e, { allowWildcard: true }));
|
||||||
}, t("common.errmsg.domain_invalid")),
|
}, t("common.errmsg.domain_invalid")),
|
||||||
contactEmail: z.string({ message: t("workflow_node.apply.form.contact_email.placeholder") }).email(t("common.errmsg.email_invalid")),
|
contactEmail: z.string({ message: t("workflow_node.apply.form.contact_email.placeholder") }).email(t("common.errmsg.email_invalid")),
|
||||||
|
challengeType: z.string().nullish(),
|
||||||
provider: z.string({ message: t("workflow_node.apply.form.provider.placeholder") }).nonempty(t("workflow_node.apply.form.provider.placeholder")),
|
provider: z.string({ message: t("workflow_node.apply.form.provider.placeholder") }).nonempty(t("workflow_node.apply.form.provider.placeholder")),
|
||||||
providerAccessId: z
|
providerAccessId: z
|
||||||
.string({ message: t("workflow_node.apply.form.provider_access.placeholder") })
|
.string({ message: t("workflow_node.apply.form.provider_access.placeholder") })
|
||||||
@ -235,6 +237,16 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
|||||||
<EmailInput placeholder={t("workflow_node.apply.form.contact_email.placeholder")} />
|
<EmailInput placeholder={t("workflow_node.apply.form.contact_email.placeholder")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="challengeType" label={t("workflow_node.apply.form.challenge_type.label")} rules={[formRule]} hidden>
|
||||||
|
<Select
|
||||||
|
options={["DNS-01"].map((e) => ({
|
||||||
|
label: e,
|
||||||
|
value: e.toLowerCase(),
|
||||||
|
}))}
|
||||||
|
placeholder={t("workflow_node.apply.form.challenge_type.placeholder")}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item name="provider" label={t("workflow_node.apply.form.provider.label")} hidden rules={[formRule]}>
|
<Form.Item name="provider" label={t("workflow_node.apply.form.provider.label")} hidden rules={[formRule]}>
|
||||||
<ApplyDNSProviderSelect
|
<ApplyDNSProviderSelect
|
||||||
allowClear
|
allowClear
|
||||||
|
@ -86,7 +86,7 @@ const NotifyNodeConfigForm = forwardRef<NotifyNodeConfigFormInstance, NotifyNode
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item name="message" label={t("workflow_node.notify.form.message.label")} rules={[formRule]}>
|
<Form.Item name="message" label={t("workflow_node.notify.form.message.label")} rules={[formRule]}>
|
||||||
<Input.TextArea autoSize={{ minRows: 3, maxRows: 10 }} placeholder={t("workflow_node.notify.form.message.placeholder")} />
|
<Input.TextArea autoSize={{ minRows: 3, maxRows: 5 }} placeholder={t("workflow_node.notify.form.message.placeholder")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item className="mb-0">
|
<Form.Item className="mb-0">
|
||||||
|
@ -3,8 +3,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { Flex, Typography } from "antd";
|
import { Flex, Typography } from "antd";
|
||||||
import { produce } from "immer";
|
import { produce } from "immer";
|
||||||
|
|
||||||
import type { WorkflowNodeConfigForUpload } from "@/domain/workflow";
|
import { type WorkflowNodeConfigForUpload, WorkflowNodeType } from "@/domain/workflow";
|
||||||
import { WorkflowNodeType } from "@/domain/workflow";
|
|
||||||
import { useZustandShallowSelector } from "@/hooks";
|
import { useZustandShallowSelector } from "@/hooks";
|
||||||
import { useWorkflowStore } from "@/stores/workflow";
|
import { useWorkflowStore } from "@/stores/workflow";
|
||||||
|
|
||||||
|
@ -137,11 +137,11 @@ 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]}>
|
||||||
<Input.TextArea readOnly autoSize={{ minRows: 5, maxRows: 10 }} placeholder={t("workflow_node.upload.form.certificate.placeholder")} />
|
<Input.TextArea readOnly autoSize={{ minRows: 5, maxRows: 5 }} placeholder={t("workflow_node.upload.form.certificate.placeholder")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
@ -151,7 +151,7 @@ const UploadNodeConfigForm = forwardRef<UploadNodeConfigFormInstance, UploadNode
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item name="privateKey" label={t("workflow_node.upload.form.private_key.label")} rules={[formRule]}>
|
<Form.Item name="privateKey" label={t("workflow_node.upload.form.private_key.label")} rules={[formRule]}>
|
||||||
<Input.TextArea readOnly autoSize={{ minRows: 5, maxRows: 10 }} placeholder={t("workflow_node.upload.form.private_key.placeholder")} />
|
<Input.TextArea readOnly autoSize={{ minRows: 5, maxRows: 5 }} placeholder={t("workflow_node.upload.form.private_key.placeholder")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
|
@ -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;
|
||||||
|
@ -122,6 +122,7 @@ export type WorkflowNodeConfigForStart = {
|
|||||||
export type WorkflowNodeConfigForApply = {
|
export type WorkflowNodeConfigForApply = {
|
||||||
domains: string;
|
domains: string;
|
||||||
contactEmail: string;
|
contactEmail: string;
|
||||||
|
challengeType: string;
|
||||||
provider: string;
|
provider: string;
|
||||||
providerAccessId: string;
|
providerAccessId: string;
|
||||||
providerConfig?: Record<string, unknown>;
|
providerConfig?: Record<string, unknown>;
|
||||||
@ -276,21 +277,21 @@ export const updateNode = (node: WorkflowNode, targetNode: WorkflowNode) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addNode = (node: WorkflowNode, preId: string, targetNode: WorkflowNode) => {
|
export const addNode = (node: WorkflowNode, previousNodeId: string, targetNode: WorkflowNode) => {
|
||||||
return produce(node, (draft) => {
|
return produce(node, (draft) => {
|
||||||
let current = draft;
|
let current = draft;
|
||||||
while (current) {
|
while (current) {
|
||||||
if (current.id === preId && targetNode.type !== WorkflowNodeType.Branch && targetNode.type !== WorkflowNodeType.ExecuteResultBranch) {
|
if (current.id === previousNodeId && targetNode.type !== WorkflowNodeType.Branch && targetNode.type !== WorkflowNodeType.ExecuteResultBranch) {
|
||||||
targetNode.next = current.next;
|
targetNode.next = current.next;
|
||||||
current.next = targetNode;
|
current.next = targetNode;
|
||||||
break;
|
break;
|
||||||
} else if (current.id === preId && (targetNode.type === WorkflowNodeType.Branch || targetNode.type === WorkflowNodeType.ExecuteResultBranch)) {
|
} else if (current.id === previousNodeId && (targetNode.type === WorkflowNodeType.Branch || targetNode.type === WorkflowNodeType.ExecuteResultBranch)) {
|
||||||
targetNode.branches![0].next = current.next;
|
targetNode.branches![0].next = current.next;
|
||||||
current.next = targetNode;
|
current.next = targetNode;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (current.type === WorkflowNodeType.Branch || current.type === WorkflowNodeType.ExecuteResultBranch) {
|
if (current.type === WorkflowNodeType.Branch || current.type === WorkflowNodeType.ExecuteResultBranch) {
|
||||||
current.branches = current.branches!.map((branch) => addNode(branch, preId, targetNode));
|
current.branches = current.branches!.map((branch) => addNode(branch, previousNodeId, targetNode));
|
||||||
}
|
}
|
||||||
current = current.next as WorkflowNode;
|
current = current.next as WorkflowNode;
|
||||||
}
|
}
|
||||||
@ -382,15 +383,15 @@ export const removeBranch = (node: WorkflowNode, branchNodeId: string, branchInd
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 1 个分支的节点,不应该能获取到相邻分支上节点的输出
|
export const getWorkflowOutputBeforeId = (root: WorkflowNode, nodeId: string, type: string): WorkflowNode[] => {
|
||||||
export const getWorkflowOutputBeforeId = (node: WorkflowNode, id: string, type: string): WorkflowNode[] => {
|
// 1 个分支的节点,不应该能获取到相邻分支上节点的输出
|
||||||
const output: WorkflowNode[] = [];
|
const output: WorkflowNode[] = [];
|
||||||
|
|
||||||
const traverse = (current: WorkflowNode, output: WorkflowNode[]) => {
|
const traverse = (current: WorkflowNode, output: WorkflowNode[]) => {
|
||||||
if (!current) {
|
if (!current) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (current.id === id) {
|
if (current.id === nodeId) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -422,7 +423,7 @@ export const getWorkflowOutputBeforeId = (node: WorkflowNode, id: string, type:
|
|||||||
return traverse(current.next as WorkflowNode, output);
|
return traverse(current.next as WorkflowNode, output);
|
||||||
};
|
};
|
||||||
|
|
||||||
traverse(node, output);
|
traverse(root, output);
|
||||||
return output;
|
return output;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -446,21 +447,3 @@ export const isAllNodesValidated = (node: WorkflowNode): boolean => {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
export const getExecuteMethod = (node: WorkflowNode): { trigger: string; triggerCron: string } => {
|
|
||||||
if (node.type === WorkflowNodeType.Start) {
|
|
||||||
const config = node.config as WorkflowNodeConfigForStart;
|
|
||||||
return {
|
|
||||||
trigger: config.trigger ?? "",
|
|
||||||
triggerCron: config.triggerCron ?? "",
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
trigger: "",
|
|
||||||
triggerCron: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { WorkflowModel } from "./workflow";
|
import { type WorkflowModel } from "./workflow";
|
||||||
|
|
||||||
export interface WorkflowRunModel extends BaseModel {
|
export interface WorkflowRunModel extends BaseModel {
|
||||||
workflowId: string;
|
workflowId: string;
|
||||||
@ -16,13 +16,13 @@ export interface WorkflowRunModel extends BaseModel {
|
|||||||
export type WorkflowRunLog = {
|
export type WorkflowRunLog = {
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
nodeName: string;
|
nodeName: string;
|
||||||
outputs?: WorkflowRunLogOutput[];
|
records?: WorkflowRunLogRecord[];
|
||||||
error?: string;
|
error?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkflowRunLogOutput = {
|
export type WorkflowRunLogRecord = {
|
||||||
time: ISO8601String;
|
time: ISO8601String;
|
||||||
title: string;
|
level: string;
|
||||||
content: string;
|
content: string;
|
||||||
error?: string;
|
error?: 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"
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
"workflow_node.start.form.trigger_cron.label": "Cron expression",
|
"workflow_node.start.form.trigger_cron.label": "Cron expression",
|
||||||
"workflow_node.start.form.trigger_cron.placeholder": "Please enter cron expression",
|
"workflow_node.start.form.trigger_cron.placeholder": "Please enter cron expression",
|
||||||
"workflow_node.start.form.trigger_cron.errmsg.invalid": "Please enter a valid cron expression",
|
"workflow_node.start.form.trigger_cron.errmsg.invalid": "Please enter a valid cron expression",
|
||||||
"workflow_node.start.form.trigger_cron.tooltip": "Time zone is based on the server.",
|
"workflow_node.start.form.trigger_cron.tooltip": "Exactly 5 space separated segments. Time zone is based on the server.",
|
||||||
"workflow_node.start.form.trigger_cron.extra": "Expected execution time for the last 5 times:",
|
"workflow_node.start.form.trigger_cron.extra": "Expected execution time for the last 5 times:",
|
||||||
"workflow_node.start.form.trigger_cron.guide": "Tips: If you have multiple workflows, it is recommended to set them to run at multiple times of the day instead of always running at specific times.<br><br>Reference links:<br>1. <a href=\"https://letsencrypt.org/docs/rate-limits/\" target=\"_blank\">Let’s Encrypt rate limits</a><br>2. <a href=\"https://letsencrypt.org/docs/faq/#why-should-my-let-s-encrypt-acme-client-run-at-a-random-time\" target=\"_blank\">Why should my Let’s Encrypt (ACME) client run at a random time?</a>",
|
"workflow_node.start.form.trigger_cron.guide": "Tips: If you have multiple workflows, it is recommended to set them to run at multiple times of the day instead of always running at specific times.<br><br>Reference links:<br>1. <a href=\"https://letsencrypt.org/docs/rate-limits/\" target=\"_blank\">Let’s Encrypt rate limits</a><br>2. <a href=\"https://letsencrypt.org/docs/faq/#why-should-my-let-s-encrypt-acme-client-run-at-a-random-time\" target=\"_blank\">Why should my Let’s Encrypt (ACME) client run at a random time?</a>",
|
||||||
|
|
||||||
|
@ -16,5 +16,12 @@
|
|||||||
"workflow_run.props.trigger.auto": "Timing",
|
"workflow_run.props.trigger.auto": "Timing",
|
||||||
"workflow_run.props.trigger.manual": "Manual",
|
"workflow_run.props.trigger.manual": "Manual",
|
||||||
"workflow_run.props.started_at": "Started at",
|
"workflow_run.props.started_at": "Started at",
|
||||||
"workflow_run.props.ended_at": "Ended at"
|
"workflow_run.props.ended_at": "Ended at",
|
||||||
|
|
||||||
|
"workflow_run.logs": "Logs",
|
||||||
|
"workflow_run.artifacts": "Artifacts",
|
||||||
|
|
||||||
|
"workflow_run_artifact.props.type": "Type",
|
||||||
|
"workflow_run_artifact.props.type.certificate": "Certificate",
|
||||||
|
"workflow_run_artifact.props.name": "Name"
|
||||||
}
|
}
|
||||||
|
@ -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": "更新时间"
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
"workflow_node.start.form.trigger_cron.label": "Cron 表达式",
|
"workflow_node.start.form.trigger_cron.label": "Cron 表达式",
|
||||||
"workflow_node.start.form.trigger_cron.placeholder": "请输入 Cron 表达式",
|
"workflow_node.start.form.trigger_cron.placeholder": "请输入 Cron 表达式",
|
||||||
"workflow_node.start.form.trigger_cron.errmsg.invalid": "请输入正确的 Cron 表达式",
|
"workflow_node.start.form.trigger_cron.errmsg.invalid": "请输入正确的 Cron 表达式",
|
||||||
"workflow_node.start.form.trigger_cron.tooltip": "支持使用任意值(即 <strong>*</strong>)、值列表分隔符(即 <strong>,</strong>)、值的范围(即 <strong>-</strong>)、步骤值(即 <strong>/</strong>)等四种表达式,时区以服务器设置为准。",
|
"workflow_node.start.form.trigger_cron.tooltip": "五段式表达式,支持使用任意值(即 <strong>*</strong>)、值列表分隔符(即 <strong>,</strong>)、值的范围(即 <strong>-</strong>)、步骤值(即 <strong>/</strong>)等四种表达式。时区以服务器设置为准。",
|
||||||
"workflow_node.start.form.trigger_cron.extra": "预计最近 5 次执行时间:",
|
"workflow_node.start.form.trigger_cron.extra": "预计最近 5 次执行时间:",
|
||||||
"workflow_node.start.form.trigger_cron.guide": "小贴士:如果你有多个工作流,建议将它们设置为在一天中的多个时间段运行,而非总是在相同的特定时间。<br><br>参考链接:<br>1. <a href=\"https://letsencrypt.org/zh-cn/docs/rate-limits/\" target=\"_blank\">Let’s Encrypt 速率限制</a><br>2. <a href=\"https://letsencrypt.org/zh-cn/docs/faq/#%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E7%9A%84-let-s-encrypt-acme-%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%90%AF%E5%8A%A8%E6%97%B6%E9%97%B4%E5%BA%94%E5%BD%93%E9%9A%8F%E6%9C%BA\" target=\"_blank\">为什么我的 Let’s Encrypt (ACME) 客户端启动时间应当随机?</a>",
|
"workflow_node.start.form.trigger_cron.guide": "小贴士:如果你有多个工作流,建议将它们设置为在一天中的多个时间段运行,而非总是在相同的特定时间。<br><br>参考链接:<br>1. <a href=\"https://letsencrypt.org/zh-cn/docs/rate-limits/\" target=\"_blank\">Let’s Encrypt 速率限制</a><br>2. <a href=\"https://letsencrypt.org/zh-cn/docs/faq/#%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E7%9A%84-let-s-encrypt-acme-%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%90%AF%E5%8A%A8%E6%97%B6%E9%97%B4%E5%BA%94%E5%BD%93%E9%9A%8F%E6%9C%BA\" target=\"_blank\">为什么我的 Let’s Encrypt (ACME) 客户端启动时间应当随机?</a>",
|
||||||
|
|
||||||
|
@ -16,5 +16,12 @@
|
|||||||
"workflow_run.props.trigger.auto": "定时执行",
|
"workflow_run.props.trigger.auto": "定时执行",
|
||||||
"workflow_run.props.trigger.manual": "手动执行",
|
"workflow_run.props.trigger.manual": "手动执行",
|
||||||
"workflow_run.props.started_at": "开始时间",
|
"workflow_run.props.started_at": "开始时间",
|
||||||
"workflow_run.props.ended_at": "完成时间"
|
"workflow_run.props.ended_at": "完成时间",
|
||||||
|
|
||||||
|
"workflow_run.logs": "日志",
|
||||||
|
"workflow_run.artifacts": "输出产物",
|
||||||
|
|
||||||
|
"workflow_run_artifact.props.type": "类型",
|
||||||
|
"workflow_run_artifact.props.type.certificate": "证书",
|
||||||
|
"workflow_run_artifact.props.name": "名称"
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,8 @@
|
|||||||
:root {
|
:root {
|
||||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||||
line-height: 1.5;
|
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
|
||||||
color-scheme: light dark;
|
|
||||||
color: rgba(255, 255, 255, 0.87);
|
|
||||||
background-color: #242424;
|
|
||||||
|
|
||||||
font-synthesis: none;
|
font-synthesis: none;
|
||||||
|
line-height: 1.5;
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
@ -15,8 +10,7 @@
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: flex;
|
padding: 0;
|
||||||
place-items: center;
|
|
||||||
min-width: 320px;
|
min-width: 320px;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import dayjsUtc from "dayjs/plugin/utc";
|
|||||||
|
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
import "./i18n";
|
import "./i18n";
|
||||||
|
import "./index.css";
|
||||||
import "./global.css";
|
import "./global.css";
|
||||||
|
|
||||||
dayjs.extend(dayjsUtc);
|
dayjs.extend(dayjsUtc);
|
||||||
|
@ -207,7 +207,7 @@ const AccessList = () => {
|
|||||||
setPageSize(pageSize);
|
setPageSize(pageSize);
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
rowKey={(record: AccessModel) => record.id}
|
rowKey={(record) => record.id}
|
||||||
scroll={{ x: "max(100%, 960px)" }}
|
scroll={{ x: "max(100%, 960px)" }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -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,
|
||||||
@ -266,7 +276,7 @@ const CertificateList = () => {
|
|||||||
setPageSize(pageSize);
|
setPageSize(pageSize);
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
rowKey={(record: CertificateModel) => record.id}
|
rowKey={(record) => record.id}
|
||||||
scroll={{ x: "max(100%, 960px)" }}
|
scroll={{ x: "max(100%, 960px)" }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,16 +7,15 @@ import {
|
|||||||
ClockCircleOutlined as ClockCircleOutlinedIcon,
|
ClockCircleOutlined as ClockCircleOutlinedIcon,
|
||||||
CloseCircleOutlined as CloseCircleOutlinedIcon,
|
CloseCircleOutlined as CloseCircleOutlinedIcon,
|
||||||
LockOutlined as LockOutlinedIcon,
|
LockOutlined as LockOutlinedIcon,
|
||||||
PauseCircleOutlined as PauseCircleOutlinedIcon,
|
|
||||||
PlusOutlined as PlusOutlinedIcon,
|
PlusOutlined as PlusOutlinedIcon,
|
||||||
SelectOutlined as SelectOutlinedIcon,
|
SelectOutlined as SelectOutlinedIcon,
|
||||||
SendOutlined as SendOutlinedIcon,
|
SendOutlined as SendOutlinedIcon,
|
||||||
|
StopOutlined as StopOutlinedIcon,
|
||||||
SyncOutlined as SyncOutlinedIcon,
|
SyncOutlined as SyncOutlinedIcon,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { PageHeader } from "@ant-design/pro-components";
|
import { PageHeader } from "@ant-design/pro-components";
|
||||||
import { useRequest } from "ahooks";
|
import { useRequest } from "ahooks";
|
||||||
import type { TableProps } from "antd";
|
import { Button, Card, Col, Divider, Empty, Flex, Grid, Row, Space, Statistic, Table, type TableProps, Tag, Typography, notification, theme } from "antd";
|
||||||
import { Button, Card, Col, Divider, Empty, Flex, Grid, Row, Space, Statistic, Table, Tag, Typography, notification, theme } from "antd";
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import {
|
import {
|
||||||
CalendarClock as CalendarClockIcon,
|
CalendarClock as CalendarClockIcon,
|
||||||
@ -89,7 +88,6 @@ const Dashboard = () => {
|
|||||||
const workflow = record.expand?.workflowId;
|
const workflow = record.expand?.workflowId;
|
||||||
return (
|
return (
|
||||||
<Typography.Link
|
<Typography.Link
|
||||||
type="secondary"
|
|
||||||
ellipsis
|
ellipsis
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (workflow) {
|
if (workflow) {
|
||||||
@ -129,7 +127,7 @@ const Dashboard = () => {
|
|||||||
);
|
);
|
||||||
} else if (record.status === WORKFLOW_RUN_STATUSES.CANCELED) {
|
} else if (record.status === WORKFLOW_RUN_STATUSES.CANCELED) {
|
||||||
return (
|
return (
|
||||||
<Tag icon={<PauseCircleOutlinedIcon />} color="warning">
|
<Tag icon={<StopOutlinedIcon />} color="warning">
|
||||||
{t("workflow_run.props.status.canceled")}
|
{t("workflow_run.props.status.canceled")}
|
||||||
</Tag>
|
</Tag>
|
||||||
);
|
);
|
||||||
@ -178,7 +176,7 @@ const Dashboard = () => {
|
|||||||
() => {
|
() => {
|
||||||
return listWorkflowRuns({
|
return listWorkflowRuns({
|
||||||
page: 1,
|
page: 1,
|
||||||
perPage: 5,
|
perPage: 9,
|
||||||
expand: true,
|
expand: true,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -286,8 +284,9 @@ const Dashboard = () => {
|
|||||||
emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />,
|
emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />,
|
||||||
}}
|
}}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
rowKey={(record: WorkflowRunModel) => record.id}
|
rowKey={(record) => record.id}
|
||||||
scroll={{ x: "max(100%, 960px)" }}
|
scroll={{ x: "max(100%, 960px)" }}
|
||||||
|
size="small"
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -34,7 +34,7 @@ const SettingsPassword = () => {
|
|||||||
onSubmit: async (values) => {
|
onSubmit: async (values) => {
|
||||||
try {
|
try {
|
||||||
await authWithPassword(getAuthStore().record!.email, values.oldPassword);
|
await authWithPassword(getAuthStore().record!.email, values.oldPassword);
|
||||||
await saveAdmin({ password: values.newPassword });
|
await saveAdmin({ password: values.newPassword, passwordConfirm: values.confirmPassword });
|
||||||
|
|
||||||
messageApi.success(t("common.text.operation_succeeded"));
|
messageApi.success(t("common.text.operation_succeeded"));
|
||||||
|
|
||||||
|
@ -42,7 +42,6 @@ const WorkflowDetail = () => {
|
|||||||
useZustandShallowSelector(["workflow", "initialized", "init", "destroy", "setEnabled", "release", "discard"])
|
useZustandShallowSelector(["workflow", "initialized", "init", "destroy", "setEnabled", "release", "discard"])
|
||||||
);
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// TODO: loading & error
|
|
||||||
workflowState.init(workflowId!);
|
workflowState.init(workflowId!);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@ -52,7 +51,7 @@ const WorkflowDetail = () => {
|
|||||||
|
|
||||||
const [tabValue, setTabValue] = useState<"orchestration" | "runs">("orchestration");
|
const [tabValue, setTabValue] = useState<"orchestration" | "runs">("orchestration");
|
||||||
|
|
||||||
const [isRunning, setIsRunning] = useState(false);
|
const [isPendingOrRunning, setIsPendingOrRunning] = useState(false);
|
||||||
const lastRunStatus = useMemo(() => workflow.lastRunStatus, [workflow]);
|
const lastRunStatus = useMemo(() => workflow.lastRunStatus, [workflow]);
|
||||||
|
|
||||||
const [allowDiscard, setAllowDiscard] = useState(false);
|
const [allowDiscard, setAllowDiscard] = useState(false);
|
||||||
@ -60,14 +59,14 @@ const WorkflowDetail = () => {
|
|||||||
const [allowRun, setAllowRun] = useState(false);
|
const [allowRun, setAllowRun] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsRunning(lastRunStatus == WORKFLOW_RUN_STATUSES.RUNNING);
|
setIsPendingOrRunning(lastRunStatus == WORKFLOW_RUN_STATUSES.PENDING || lastRunStatus == WORKFLOW_RUN_STATUSES.RUNNING);
|
||||||
}, [lastRunStatus]);
|
}, [lastRunStatus]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!!workflowId && isRunning) {
|
if (!!workflowId && isPendingOrRunning) {
|
||||||
subscribeWorkflow(workflowId, (e) => {
|
subscribeWorkflow(workflowId, (cb) => {
|
||||||
if (e.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.RUNNING) {
|
if (cb.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.PENDING && cb.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.RUNNING) {
|
||||||
setIsRunning(false);
|
setIsPendingOrRunning(false);
|
||||||
unsubscribeWorkflow(workflowId);
|
unsubscribeWorkflow(workflowId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -76,15 +75,15 @@ const WorkflowDetail = () => {
|
|||||||
unsubscribeWorkflow(workflowId);
|
unsubscribeWorkflow(workflowId);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [workflowId, isRunning]);
|
}, [workflowId, isPendingOrRunning]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const hasReleased = !!workflow.content;
|
const hasReleased = !!workflow.content;
|
||||||
const hasChanges = workflow.hasDraft! || !isEqual(workflow.draft, workflow.content);
|
const hasChanges = workflow.hasDraft! || !isEqual(workflow.draft, workflow.content);
|
||||||
setAllowDiscard(!isRunning && hasReleased && hasChanges);
|
setAllowDiscard(!isPendingOrRunning && hasReleased && hasChanges);
|
||||||
setAllowRelease(!isRunning && hasChanges);
|
setAllowRelease(!isPendingOrRunning && hasChanges);
|
||||||
setAllowRun(hasReleased);
|
setAllowRun(hasReleased);
|
||||||
}, [workflow.content, workflow.draft, workflow.hasDraft, isRunning]);
|
}, [workflow.content, workflow.draft, workflow.hasDraft, isPendingOrRunning]);
|
||||||
|
|
||||||
const handleEnableChange = async () => {
|
const handleEnableChange = async () => {
|
||||||
if (!workflow.enabled && (!workflow.content || !isAllNodesValidated(workflow.content))) {
|
if (!workflow.enabled && (!workflow.content || !isAllNodesValidated(workflow.content))) {
|
||||||
@ -174,12 +173,12 @@ const WorkflowDetail = () => {
|
|||||||
let unsubscribeFn: Awaited<ReturnType<typeof subscribeWorkflow>> | undefined = undefined;
|
let unsubscribeFn: Awaited<ReturnType<typeof subscribeWorkflow>> | undefined = undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setIsRunning(true);
|
setIsPendingOrRunning(true);
|
||||||
|
|
||||||
// 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);
|
setIsPendingOrRunning(false);
|
||||||
unsubscribeFn?.();
|
unsubscribeFn?.();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -188,7 +187,7 @@ const WorkflowDetail = () => {
|
|||||||
|
|
||||||
messageApi.info(t("workflow.detail.orchestration.action.run.prompt"));
|
messageApi.info(t("workflow.detail.orchestration.action.run.prompt"));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setIsRunning(false);
|
setIsPendingOrRunning(false);
|
||||||
unsubscribeFn?.();
|
unsubscribeFn?.();
|
||||||
|
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@ -279,7 +278,7 @@ const WorkflowDetail = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Space>
|
<Space>
|
||||||
<Button disabled={!allowRun} icon={<CaretRightOutlinedIcon />} loading={isRunning} type="primary" onClick={handleRunClick}>
|
<Button disabled={!allowRun} icon={<CaretRightOutlinedIcon />} loading={isPendingOrRunning} type="primary" onClick={handleRunClick}>
|
||||||
{t("workflow.detail.orchestration.action.run")}
|
{t("workflow.detail.orchestration.action.run")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
@ -7,8 +7,8 @@ import {
|
|||||||
CloseCircleOutlined as CloseCircleOutlinedIcon,
|
CloseCircleOutlined as CloseCircleOutlinedIcon,
|
||||||
DeleteOutlined as DeleteOutlinedIcon,
|
DeleteOutlined as DeleteOutlinedIcon,
|
||||||
EditOutlined as EditOutlinedIcon,
|
EditOutlined as EditOutlinedIcon,
|
||||||
PauseCircleOutlined as PauseCircleOutlinedIcon,
|
|
||||||
PlusOutlined as PlusOutlinedIcon,
|
PlusOutlined as PlusOutlinedIcon,
|
||||||
|
StopOutlined as StopOutlinedIcon,
|
||||||
SyncOutlined as SyncOutlinedIcon,
|
SyncOutlined as SyncOutlinedIcon,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
|
|
||||||
@ -170,7 +170,7 @@ const WorkflowList = () => {
|
|||||||
} else if (record.lastRunStatus === WORKFLOW_RUN_STATUSES.FAILED) {
|
} else if (record.lastRunStatus === WORKFLOW_RUN_STATUSES.FAILED) {
|
||||||
icon = <CloseCircleOutlinedIcon style={{ color: themeToken.colorError }} />;
|
icon = <CloseCircleOutlinedIcon style={{ color: themeToken.colorError }} />;
|
||||||
} else if (record.lastRunStatus === WORKFLOW_RUN_STATUSES.CANCELED) {
|
} else if (record.lastRunStatus === WORKFLOW_RUN_STATUSES.CANCELED) {
|
||||||
icon = <PauseCircleOutlinedIcon style={{ color: themeToken.colorWarning }} />;
|
icon = <StopOutlinedIcon style={{ color: themeToken.colorWarning }} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -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,
|
||||||
@ -366,7 +366,7 @@ const WorkflowList = () => {
|
|||||||
setPageSize(pageSize);
|
setPageSize(pageSize);
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
rowKey={(record: WorkflowModel) => record.id}
|
rowKey={(record) => record.id}
|
||||||
scroll={{ x: "max(100%, 960px)" }}
|
scroll={{ x: "max(100%, 960px)" }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,3 +6,11 @@ export const getPocketBase = () => {
|
|||||||
pb = new PocketBase("/");
|
pb = new PocketBase("/");
|
||||||
return pb;
|
return pb;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const COLLECTION_NAME_ADMIN = "_superusers";
|
||||||
|
export const COLLECTION_NAME_ACCESS = "access";
|
||||||
|
export const COLLECTION_NAME_CERTIFICATE = "certificate";
|
||||||
|
export const COLLECTION_NAME_SETTINGS = "settings";
|
||||||
|
export const COLLECTION_NAME_WORKFLOW = "workflow";
|
||||||
|
export const COLLECTION_NAME_WORKFLOW_RUN = "workflow_run";
|
||||||
|
export const COLLECTION_NAME_WORKFLOW_OUTPUT = "workflow_output";
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
import { type AccessModel } from "@/domain/access";
|
import { type AccessModel } from "@/domain/access";
|
||||||
import { getPocketBase } from "./_pocketbase";
|
import { COLLECTION_NAME_ACCESS, getPocketBase } from "./_pocketbase";
|
||||||
|
|
||||||
const COLLECTION_NAME = "access";
|
|
||||||
|
|
||||||
export const list = async () => {
|
export const list = async () => {
|
||||||
return await getPocketBase().collection(COLLECTION_NAME).getFullList<AccessModel>({
|
return await getPocketBase().collection(COLLECTION_NAME_ACCESS).getFullList<AccessModel>({
|
||||||
filter: "deleted=null",
|
filter: "deleted=null",
|
||||||
sort: "-created",
|
sort: "-created",
|
||||||
requestKey: null,
|
requestKey: null,
|
||||||
@ -15,15 +13,15 @@ export const list = async () => {
|
|||||||
|
|
||||||
export const save = async (record: MaybeModelRecord<AccessModel>) => {
|
export const save = async (record: MaybeModelRecord<AccessModel>) => {
|
||||||
if (record.id) {
|
if (record.id) {
|
||||||
return await getPocketBase().collection(COLLECTION_NAME).update<AccessModel>(record.id, record);
|
return await getPocketBase().collection(COLLECTION_NAME_ACCESS).update<AccessModel>(record.id, record);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await getPocketBase().collection(COLLECTION_NAME).create<AccessModel>(record);
|
return await getPocketBase().collection(COLLECTION_NAME_ACCESS).create<AccessModel>(record);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const remove = async (record: MaybeModelRecordWithId<AccessModel>) => {
|
export const remove = async (record: MaybeModelRecordWithId<AccessModel>) => {
|
||||||
await getPocketBase()
|
await getPocketBase()
|
||||||
.collection(COLLECTION_NAME)
|
.collection(COLLECTION_NAME_ACCESS)
|
||||||
.update<AccessModel>(record.id!, { deleted: dayjs.utc().format("YYYY-MM-DD HH:mm:ss") });
|
.update<AccessModel>(record.id!, { deleted: dayjs.utc().format("YYYY-MM-DD HH:mm:ss") });
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
import { getPocketBase } from "./_pocketbase";
|
import { COLLECTION_NAME_ADMIN, getPocketBase } from "./_pocketbase";
|
||||||
|
|
||||||
const COLLECTION_NAME = "_superusers";
|
|
||||||
|
|
||||||
export const authWithPassword = (username: string, password: string) => {
|
export const authWithPassword = (username: string, password: string) => {
|
||||||
return getPocketBase().collection(COLLECTION_NAME).authWithPassword(username, password);
|
return getPocketBase().collection(COLLECTION_NAME_ADMIN).authWithPassword(username, password);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getAuthStore = () => {
|
export const getAuthStore = () => {
|
||||||
return getPocketBase().authStore;
|
return getPocketBase().authStore;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const save = (data: { email: string } | { password: string }) => {
|
export const save = (data: { email: string } | { password: string; passwordConfirm: string }) => {
|
||||||
return getPocketBase()
|
return getPocketBase()
|
||||||
.collection(COLLECTION_NAME)
|
.collection(COLLECTION_NAME_ADMIN)
|
||||||
.update(getAuthStore().record?.id || "", data);
|
.update(getAuthStore().record?.id || "", data);
|
||||||
};
|
};
|
||||||
|
@ -2,9 +2,7 @@ import dayjs from "dayjs";
|
|||||||
import { type RecordListOptions } from "pocketbase";
|
import { type RecordListOptions } from "pocketbase";
|
||||||
|
|
||||||
import { type CertificateModel } from "@/domain/certificate";
|
import { type CertificateModel } from "@/domain/certificate";
|
||||||
import { getPocketBase } from "./_pocketbase";
|
import { COLLECTION_NAME_CERTIFICATE, getPocketBase } from "./_pocketbase";
|
||||||
|
|
||||||
const COLLECTION_NAME = "certificate";
|
|
||||||
|
|
||||||
export type ListCertificateRequest = {
|
export type ListCertificateRequest = {
|
||||||
page?: number;
|
page?: number;
|
||||||
@ -35,12 +33,29 @@ export const list = async (request: ListCertificateRequest) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return pb.collection(COLLECTION_NAME).getList<CertificateModel>(page, perPage, options);
|
return pb.collection(COLLECTION_NAME_CERTIFICATE).getList<CertificateModel>(page, perPage, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const listByWorkflowRunId = async (workflowRunId: string) => {
|
||||||
|
const pb = getPocketBase();
|
||||||
|
|
||||||
|
const options: RecordListOptions = {
|
||||||
|
filter: pb.filter("workflowRunId={:workflowRunId}", {
|
||||||
|
workflowRunId: workflowRunId,
|
||||||
|
}),
|
||||||
|
sort: "-created",
|
||||||
|
requestKey: null,
|
||||||
|
};
|
||||||
|
const items = await pb.collection(COLLECTION_NAME_CERTIFICATE).getFullList<CertificateModel>(options);
|
||||||
|
return {
|
||||||
|
totalItems: items.length,
|
||||||
|
items: items,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const remove = async (record: MaybeModelRecordWithId<CertificateModel>) => {
|
export const remove = async (record: MaybeModelRecordWithId<CertificateModel>) => {
|
||||||
await getPocketBase()
|
await getPocketBase()
|
||||||
.collection(COLLECTION_NAME)
|
.collection(COLLECTION_NAME_CERTIFICATE)
|
||||||
.update<CertificateModel>(record.id!, { deleted: dayjs.utc().format("YYYY-MM-DD HH:mm:ss") });
|
.update<CertificateModel>(record.id!, { deleted: dayjs.utc().format("YYYY-MM-DD HH:mm:ss") });
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
import { ClientResponseError } from "pocketbase";
|
import { ClientResponseError } from "pocketbase";
|
||||||
|
|
||||||
import { type SettingsModel, type SettingsNames } from "@/domain/settings";
|
import { type SettingsModel, type SettingsNames } from "@/domain/settings";
|
||||||
import { getPocketBase } from "./_pocketbase";
|
import { COLLECTION_NAME_SETTINGS, getPocketBase } from "./_pocketbase";
|
||||||
|
|
||||||
const COLLECTION_NAME = "settings";
|
|
||||||
|
|
||||||
export const get = async <T extends NonNullable<unknown>>(name: SettingsNames) => {
|
export const get = async <T extends NonNullable<unknown>>(name: SettingsNames) => {
|
||||||
try {
|
try {
|
||||||
const resp = await getPocketBase().collection(COLLECTION_NAME).getFirstListItem<SettingsModel<T>>(`name='${name}'`, {
|
const resp = await getPocketBase().collection(COLLECTION_NAME_SETTINGS).getFirstListItem<SettingsModel<T>>(`name='${name}'`, {
|
||||||
requestKey: null,
|
requestKey: null,
|
||||||
});
|
});
|
||||||
return resp;
|
return resp;
|
||||||
@ -25,8 +23,8 @@ export const get = async <T extends NonNullable<unknown>>(name: SettingsNames) =
|
|||||||
|
|
||||||
export const save = async <T extends NonNullable<unknown>>(record: MaybeModelRecordWithId<SettingsModel<T>>) => {
|
export const save = async <T extends NonNullable<unknown>>(record: MaybeModelRecordWithId<SettingsModel<T>>) => {
|
||||||
if (record.id) {
|
if (record.id) {
|
||||||
return await getPocketBase().collection(COLLECTION_NAME).update<SettingsModel<T>>(record.id, record);
|
return await getPocketBase().collection(COLLECTION_NAME_SETTINGS).update<SettingsModel<T>>(record.id, record);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await getPocketBase().collection(COLLECTION_NAME).create<SettingsModel<T>>(record);
|
return await getPocketBase().collection(COLLECTION_NAME_SETTINGS).create<SettingsModel<T>>(record);
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import { type RecordListOptions, type RecordSubscription } from "pocketbase";
|
import { type RecordListOptions, type RecordSubscription } from "pocketbase";
|
||||||
|
|
||||||
import { type WorkflowModel } from "@/domain/workflow";
|
import { type WorkflowModel } from "@/domain/workflow";
|
||||||
import { getPocketBase } from "./_pocketbase";
|
import { COLLECTION_NAME_WORKFLOW, getPocketBase } from "./_pocketbase";
|
||||||
|
|
||||||
const COLLECTION_NAME = "workflow";
|
|
||||||
|
|
||||||
export type ListWorkflowRequest = {
|
export type ListWorkflowRequest = {
|
||||||
page?: number;
|
page?: number;
|
||||||
@ -26,11 +24,11 @@ export const list = async (request: ListWorkflowRequest) => {
|
|||||||
options.filter = pb.filter("enabled={:enabled}", { enabled: request.enabled });
|
options.filter = pb.filter("enabled={:enabled}", { enabled: request.enabled });
|
||||||
}
|
}
|
||||||
|
|
||||||
return await pb.collection(COLLECTION_NAME).getList<WorkflowModel>(page, perPage, options);
|
return await pb.collection(COLLECTION_NAME_WORKFLOW).getList<WorkflowModel>(page, perPage, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const get = async (id: string) => {
|
export const get = async (id: string) => {
|
||||||
return await getPocketBase().collection(COLLECTION_NAME).getOne<WorkflowModel>(id, {
|
return await getPocketBase().collection(COLLECTION_NAME_WORKFLOW).getOne<WorkflowModel>(id, {
|
||||||
requestKey: null,
|
requestKey: null,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -38,25 +36,21 @@ export const get = async (id: string) => {
|
|||||||
export const save = async (record: MaybeModelRecord<WorkflowModel>) => {
|
export const save = async (record: MaybeModelRecord<WorkflowModel>) => {
|
||||||
if (record.id) {
|
if (record.id) {
|
||||||
return await getPocketBase()
|
return await getPocketBase()
|
||||||
.collection(COLLECTION_NAME)
|
.collection(COLLECTION_NAME_WORKFLOW)
|
||||||
.update<WorkflowModel>(record.id as string, record);
|
.update<WorkflowModel>(record.id as string, record);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await getPocketBase().collection(COLLECTION_NAME).create<WorkflowModel>(record);
|
return await getPocketBase().collection(COLLECTION_NAME_WORKFLOW).create<WorkflowModel>(record);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const remove = async (record: MaybeModelRecordWithId<WorkflowModel>) => {
|
export const remove = async (record: MaybeModelRecordWithId<WorkflowModel>) => {
|
||||||
return await getPocketBase().collection(COLLECTION_NAME).delete(record.id);
|
return await getPocketBase().collection(COLLECTION_NAME_WORKFLOW).delete(record.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const subscribe = async (id: string, cb: (e: RecordSubscription<WorkflowModel>) => void) => {
|
export const subscribe = async (id: string, cb: (e: RecordSubscription<WorkflowModel>) => void) => {
|
||||||
const pb = getPocketBase();
|
return getPocketBase().collection(COLLECTION_NAME_WORKFLOW).subscribe(id, cb);
|
||||||
|
|
||||||
return pb.collection("workflow").subscribe(id, cb);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const unsubscribe = async (id: string) => {
|
export const unsubscribe = async (id: string) => {
|
||||||
const pb = getPocketBase();
|
return getPocketBase().collection(COLLECTION_NAME_WORKFLOW).unsubscribe(id);
|
||||||
|
|
||||||
return pb.collection("workflow").unsubscribe(id);
|
|
||||||
};
|
};
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { type WorkflowRunModel } from "@/domain/workflowRun";
|
import { type RecordSubscription } from "pocketbase";
|
||||||
|
|
||||||
import { getPocketBase } from "./_pocketbase";
|
import { type WorkflowRunModel } from "@/domain/workflowRun";
|
||||||
|
|
||||||
const COLLECTION_NAME = "workflow_run";
|
import { COLLECTION_NAME_WORKFLOW_RUN, getPocketBase } from "./_pocketbase";
|
||||||
|
|
||||||
export type ListWorkflowRunsRequest = {
|
export type ListWorkflowRunsRequest = {
|
||||||
workflowId?: string;
|
workflowId?: string;
|
||||||
@ -23,7 +23,7 @@ export const list = async (request: ListWorkflowRunsRequest) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return await getPocketBase()
|
return await getPocketBase()
|
||||||
.collection(COLLECTION_NAME)
|
.collection(COLLECTION_NAME_WORKFLOW_RUN)
|
||||||
.getList<WorkflowRunModel>(page, perPage, {
|
.getList<WorkflowRunModel>(page, perPage, {
|
||||||
filter: getPocketBase().filter(filter, params),
|
filter: getPocketBase().filter(filter, params),
|
||||||
sort: "-created",
|
sort: "-created",
|
||||||
@ -33,5 +33,13 @@ export const list = async (request: ListWorkflowRunsRequest) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const remove = async (record: MaybeModelRecordWithId<WorkflowRunModel>) => {
|
export const remove = async (record: MaybeModelRecordWithId<WorkflowRunModel>) => {
|
||||||
return await getPocketBase().collection(COLLECTION_NAME).delete(record.id);
|
return await getPocketBase().collection(COLLECTION_NAME_WORKFLOW_RUN).delete(record.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const subscribe = async (id: string, cb: (e: RecordSubscription<WorkflowRunModel>) => void) => {
|
||||||
|
return getPocketBase().collection(COLLECTION_NAME_WORKFLOW_RUN).subscribe(id, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const unsubscribe = async (id: string) => {
|
||||||
|
return getPocketBase().collection(COLLECTION_NAME_WORKFLOW_RUN).unsubscribe(id);
|
||||||
};
|
};
|
||||||
|
@ -25,14 +25,14 @@ export type WorkflowState = {
|
|||||||
discard(): void;
|
discard(): void;
|
||||||
destroy(): void;
|
destroy(): void;
|
||||||
|
|
||||||
addNode: (node: WorkflowNode, preId: string) => void;
|
addNode: (node: WorkflowNode, previousNodeId: string) => void;
|
||||||
updateNode: (node: WorkflowNode) => void;
|
updateNode: (node: WorkflowNode) => void;
|
||||||
removeNode: (nodeId: string) => void;
|
removeNode: (nodeId: string) => void;
|
||||||
|
|
||||||
addBranch: (branchId: string) => void;
|
addBranch: (branchId: string) => void;
|
||||||
removeBranch: (branchId: string, index: number) => void;
|
removeBranch: (branchId: string, index: number) => void;
|
||||||
|
|
||||||
getWorkflowOuptutBeforeId: (id: string, type: string) => WorkflowNode[];
|
getWorkflowOuptutBeforeId: (nodeId: string, type: string) => WorkflowNode[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
||||||
@ -143,10 +143,10 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
addNode: async (node: WorkflowNode, preId: string) => {
|
addNode: async (node: WorkflowNode, previousNodeId: string) => {
|
||||||
if (!get().initialized) throw "Workflow not initialized yet";
|
if (!get().initialized) throw "Workflow not initialized yet";
|
||||||
|
|
||||||
const root = addNode(get().workflow.draft!, preId, node);
|
const root = addNode(get().workflow.draft!, previousNodeId, node);
|
||||||
const resp = await saveWorkflow({
|
const resp = await saveWorkflow({
|
||||||
id: get().workflow.id!,
|
id: get().workflow.id!,
|
||||||
draft: root,
|
draft: root,
|
||||||
@ -243,7 +243,7 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getWorkflowOuptutBeforeId: (id: string, type: string) => {
|
getWorkflowOuptutBeforeId: (nodeId: string, type: string) => {
|
||||||
return getWorkflowOutputBeforeId(get().workflow.draft as WorkflowNode, id, type);
|
return getWorkflowOutputBeforeId(get().workflow.draft as WorkflowNode, nodeId, type);
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
export const validCronExpression = (expr: string): boolean => {
|
export const validCronExpression = (expr: string): boolean => {
|
||||||
try {
|
try {
|
||||||
parseExpression(expr);
|
parseExpression(expr);
|
||||||
|
|
||||||
|
if (expr.trim().split(" ").length !== 5) return false; // pocketbase 后端仅支持五段式的表达式
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
@ -10,9 +12,8 @@ export const validCronExpression = (expr: string): boolean => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getNextCronExecutions = (expr: string, times = 1): Date[] => {
|
export const getNextCronExecutions = (expr: string, times = 1): Date[] => {
|
||||||
if (!expr) return [];
|
if (!validCronExpression(expr)) return [];
|
||||||
|
|
||||||
try {
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const cron = parseExpression(expr, { currentDate: now, iterator: true });
|
const cron = parseExpression(expr, { currentDate: now, iterator: true });
|
||||||
|
|
||||||
@ -22,7 +23,4 @@ export const getNextCronExecutions = (expr: string, times = 1): Date[] => {
|
|||||||
result.push(next.value.toDate());
|
result.push(next.value.toDate());
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
} catch {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
@ -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