diff --git a/README.md b/README.md
index 3ab77300..745cec19 100644
--- a/README.md
+++ b/README.md
@@ -86,20 +86,21 @@ make local.run
[展开查看]
-| 提供商 | 备注 |
-| :--------------------------------------------- | :-------------------------------------- |
-| [阿里云](https://www.aliyun.com/) | |
-| [腾讯云](https://cloud.tencent.com/) | |
-| [华为云](https://www.huaweicloud.com/) | |
-| [火山引擎](https://www.volcengine.com/) | |
-| [AWS Route53](https://aws.amazon.com/route53/) | |
-| [Azure](https://azure.microsoft.com/) | |
-| [CloudFlare](https://www.cloudflare.com/) | |
-| [GoDaddy](https://www.godaddy.com/) | |
-| [Name.com](https://www.name.com/) | |
-| [NameSilo](https://www.namesilo.com/) | |
-| [PowerDNS](https://www.powerdns.com/) | |
-| ACME 代理 HTTP 请求 | 可申请允许通过 HTTP 请求修改 DNS 的域名 |
+| 提供商 | 备注 |
+| :----------------------------------------------------------------- | :-------------------------------------- |
+| [阿里云](https://www.aliyun.com/) | |
+| [腾讯云](https://cloud.tencent.com/) | |
+| [华为云](https://www.huaweicloud.com/) | |
+| [火山引擎](https://www.volcengine.com/) | |
+| [AWS Route53](https://aws.amazon.com/route53/) | |
+| [Azure](https://azure.microsoft.com/) | |
+| [CloudFlare](https://www.cloudflare.com/) | |
+| [GoDaddy](https://www.godaddy.com/) | |
+| [Name.com](https://www.name.com/) | |
+| [NameSilo](https://www.namesilo.com/) | |
+| [IBM NS1 Connect](https://www.ibm.com/cn-zh/products/ns1-connect/) | |
+| [PowerDNS](https://www.powerdns.com/) | |
+| ACME 代理 HTTP 请求 | 可申请允许通过 HTTP 请求修改 DNS 的域名 |
@@ -126,6 +127,7 @@ make local.run
| [多吉云](https://www.dogecloud.com/) | 可部署到多吉云 CDN |
| [BytePlus](https://www.byteplus.com/) | 可部署到 BytePlus CDN 等服务 |
| [优刻得](https://www.ucloud.cn/) | 可部署到优刻得 US3、UCDN 等服务 |
+| [Edgio](https://edg.io/) | 可部署到 Edgio Applications 等服务 |
diff --git a/README_EN.md b/README_EN.md
index f0f8338e..ece9591c 100644
--- a/README_EN.md
+++ b/README_EN.md
@@ -85,20 +85,21 @@ The following DNS providers are supported:
[Fold/Unfold to view ...]
-| Provider | Remarks |
-| :--------------------------------------------- | :------------------------------------ |
-| [Alibaba Cloud](https://www.alibabacloud.com/) | |
-| [Tencent Cloud](https://www.tencentcloud.com/) | |
-| [Huawei Cloud](https://www.huaweicloud.com/) | |
-| [Volcengine](https://www.volcengine.com/) | |
-| [AWS Route53](https://aws.amazon.com/route53/) | |
-| [Azure DNS](https://azure.microsoft.com/) | |
-| [CloudFlare](https://www.cloudflare.com/) | |
-| [GoDaddy](https://www.godaddy.com/) | |
-| [Name.com](https://www.name.com/) | |
-| [NameSilo](https://www.namesilo.com/) | |
-| [PowerDNS](https://www.powerdns.com/) | |
-| ACME Proxy HTTP Request | Supports managing DNS by HTTP request |
+| Provider | Remarks |
+| :----------------------------------------------------------- | :------------------------------------ |
+| [Alibaba Cloud](https://www.alibabacloud.com/) | |
+| [Tencent Cloud](https://www.tencentcloud.com/) | |
+| [Huawei Cloud](https://www.huaweicloud.com/) | |
+| [Volcengine](https://www.volcengine.com/) | |
+| [AWS Route53](https://aws.amazon.com/route53/) | |
+| [Azure DNS](https://azure.microsoft.com/) | |
+| [CloudFlare](https://www.cloudflare.com/) | |
+| [GoDaddy](https://www.godaddy.com/) | |
+| [Name.com](https://www.name.com/) | |
+| [NameSilo](https://www.namesilo.com/) | |
+| [IBM NS1 Connect](https://www.ibm.com/products/ns1-connect/) | |
+| [PowerDNS](https://www.powerdns.com/) | |
+| ACME Proxy HTTP Request | Supports managing DNS by HTTP request |
@@ -125,6 +126,7 @@ The following hosting providers are supported:
| [Doge Cloud](https://www.dogecloud.com/) | Supports deployment to Doge Cloud CDN |
| [BytePlus](https://www.byteplus.com/) | Supports deployment to BytePlus CDN |
| [UCloud](https://www.ucloud-global.com/) | Supports deployment to UCloud US3, UCDN |
+| [Edgio](https://edg.io/) | Supports deployment to Edgio Applications |
diff --git a/go.mod b/go.mod
index 1ff91571..89e2287e 100644
--- a/go.mod
+++ b/go.mod
@@ -18,8 +18,8 @@ require (
github.com/baidubce/bce-sdk-go v0.9.209
github.com/byteplus-sdk/byteplus-sdk-golang v1.0.35
github.com/go-acme/lego/v4 v4.21.0
+ github.com/go-resty/resty/v2 v2.16.3
github.com/go-viper/mapstructure/v2 v2.2.1
- github.com/gojek/heimdall/v7 v7.0.3
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.128
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61
github.com/nikoksr/notify v1.1.0
@@ -87,6 +87,7 @@ require (
go.mongodb.org/mongo-driver v1.12.0 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
+ gopkg.in/ns1/ns1-go.v2 v2.13.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
@@ -141,7 +142,6 @@ require (
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
github.com/goccy/go-json v0.10.4 // indirect
- github.com/gojek/valkyrie v0.0.0-20180215180059-6aee720afcdf // indirect
github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/go-querystring v1.1.0 // indirect
diff --git a/go.sum b/go.sum
index 8e6555c8..eda86588 100644
--- a/go.sum
+++ b/go.sum
@@ -81,7 +81,6 @@ github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
-github.com/DataDog/datadog-go v3.7.1+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.3 h1:cb3br57K508pQEFgBxn9GDhPS9HefpyMPK1RzmtMNzk=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.3/go.mod h1:itPGVDKf9cC/ov4MdvJ2QZ0khw4bfoo9jzwTJlaxy2k=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.3 h1:xir5X8TS8UBVPWg2jHL+cSTf0jZgqYQSA54TscSt1/0=
@@ -263,7 +262,6 @@ github.com/blinkbean/dingtalk v1.1.3 h1:MbidFZYom7DTFHD/YIs+eaI7kRy52kmWE/sy0xjo
github.com/blinkbean/dingtalk v1.1.3/go.mod h1:9BaLuGSBqY3vT5hstValh48DbsKO7vaHaJnG9pXwbto=
github.com/byteplus-sdk/byteplus-sdk-golang v1.0.35 h1:bM18V4iw9ylRc2LahQaq3k3gjEVJdyQYvptLVZaCa54=
github.com/byteplus-sdk/byteplus-sdk-golang v1.0.35/go.mod h1:7iCaE+dR9EycrJU0GQyMhptbInLbQhsKXiDKDjNi8Vs=
-github.com/cactus/go-statsd-client/statsd v0.0.0-20200423205355-cb0885a1018c/go.mod h1:l/bIBLeOl9eX+wxJAzxS4TveKRtAqlyDpHjhkfO0MEI=
github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
@@ -398,6 +396,8 @@ github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.7.0/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk=
+github.com/go-resty/resty/v2 v2.16.3 h1:zacNT7lt4b8M/io2Ahj6yPypL7bqx9n1iprfQuodV+E=
+github.com/go-resty/resty/v2 v2.16.3/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
@@ -418,10 +418,6 @@ github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14j
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-github.com/gojek/heimdall/v7 v7.0.3 h1:+5sAhl8S0m+qRRL8IVeHCJudFh/XkG3wyO++nvOg+gc=
-github.com/gojek/heimdall/v7 v7.0.3/go.mod h1:Z43HtMid7ysSjmsedPTXAki6jcdcNVnjn5pmsTyiMic=
-github.com/gojek/valkyrie v0.0.0-20180215180059-6aee720afcdf h1:5xRGbUdOmZKoDXkGx5evVLehuCMpuO1hl701bEQqXOM=
-github.com/gojek/valkyrie v0.0.0-20180215180059-6aee720afcdf/go.mod h1:QzhUKaYKJmcbTnCYCAVQrroCOY7vOOI8cSQ4NbuhYf0=
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.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
@@ -807,7 +803,6 @@ github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e/go.mod h1:qNTQ5P5J
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
-github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
@@ -1369,6 +1364,8 @@ gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
+gopkg.in/ns1/ns1-go.v2 v2.13.0 h1:I5NNqI9Bi1SGK92TVkOvLTwux5LNrix/99H2datVh48=
+gopkg.in/ns1/ns1-go.v2 v2.13.0/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/internal/applicant/acme-user.go b/internal/applicant/acme-user.go
index 11aa1fff..3b74d5ca 100644
--- a/internal/applicant/acme-user.go
+++ b/internal/applicant/acme-user.go
@@ -11,7 +11,7 @@ import (
"github.com/go-acme/lego/v4/registration"
"github.com/usual2970/certimate/internal/domain"
- "github.com/usual2970/certimate/internal/pkg/utils/x509"
+ "github.com/usual2970/certimate/internal/pkg/utils/certs"
"github.com/usual2970/certimate/internal/repository"
)
@@ -38,7 +38,7 @@ func newAcmeUser(ca, email string) (*acmeUser, error) {
return nil, err
}
- keyPEM, err := x509.ConvertECPrivateKeyToPEM(key)
+ keyPEM, err := certs.ConvertECPrivateKeyToPEM(key)
if err != nil {
return nil, err
}
@@ -62,7 +62,7 @@ func (u acmeUser) GetRegistration() *registration.Resource {
}
func (u *acmeUser) GetPrivateKey() crypto.PrivateKey {
- rs, _ := x509.ParseECPrivateKeyFromPEM(u.privkey)
+ rs, _ := certs.ParseECPrivateKeyFromPEM(u.privkey)
return rs
}
diff --git a/internal/applicant/applicant.go b/internal/applicant/applicant.go
index 51512416..5b77c95f 100644
--- a/internal/applicant/applicant.go
+++ b/internal/applicant/applicant.go
@@ -15,16 +15,17 @@ import (
"github.com/go-acme/lego/v4/lego"
"github.com/usual2970/certimate/internal/domain"
+ "github.com/usual2970/certimate/internal/pkg/utils/slices"
"github.com/usual2970/certimate/internal/repository"
)
type ApplyCertResult struct {
- Certificate string
- PrivateKey string
- IssuerCertificate string
- ACMECertUrl string
- ACMECertStableUrl string
- CSR string
+ CertificateFullChain string
+ IssuerCertificate string
+ PrivateKey string
+ ACMECertUrl string
+ ACMECertStableUrl string
+ CSR string
}
type Applicant interface {
@@ -61,13 +62,13 @@ func NewWithApplyNode(node *domain.WorkflowNode) (Applicant, error) {
}
options := &applicantOptions{
- Domains: strings.Split(node.GetConfigString("domains"), ";"),
+ Domains: slices.Filter(strings.Split(node.GetConfigString("domains"), ";"), func(s string) bool { return s != "" }),
ContactEmail: node.GetConfigString("contactEmail"),
Provider: domain.ApplyDNSProviderType(node.GetConfigString("provider")),
ProviderAccessConfig: accessConfig,
ProviderApplyConfig: node.GetConfigMap("providerConfig"),
KeyAlgorithm: node.GetConfigString("keyAlgorithm"),
- Nameservers: strings.Split(node.GetConfigString("nameservers"), ";"),
+ Nameservers: slices.Filter(strings.Split(node.GetConfigString("nameservers"), ";"), func(s string) bool { return s != "" }),
PropagationTimeout: node.GetConfigInt32("propagationTimeout"),
DisableFollowCNAME: node.GetConfigBool("disableFollowCNAME"),
}
@@ -149,12 +150,12 @@ func apply(challengeProvider challenge.Provider, options *applicantOptions) (*Ap
}
return &ApplyCertResult{
- PrivateKey: string(certResource.PrivateKey),
- Certificate: string(certResource.Certificate),
- IssuerCertificate: string(certResource.IssuerCertificate),
- ACMECertUrl: certResource.CertURL,
- ACMECertStableUrl: certResource.CertStableURL,
- CSR: string(certResource.CSR),
+ CertificateFullChain: strings.TrimSpace(string(certResource.Certificate)),
+ IssuerCertificate: strings.TrimSpace(string(certResource.IssuerCertificate)),
+ PrivateKey: strings.TrimSpace(string(certResource.PrivateKey)),
+ ACMECertUrl: certResource.CertURL,
+ ACMECertStableUrl: certResource.CertStableURL,
+ CSR: string(certResource.CSR),
}, nil
}
diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go
index 795afe08..8e42f4f2 100644
--- a/internal/applicant/providers.go
+++ b/internal/applicant/providers.go
@@ -15,6 +15,7 @@ import (
providerHuaweiCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/huaweicloud"
providerNameDotCom "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namedotcom"
providerNameSilo "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namesilo"
+ providerNS1 "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/ns1"
providerPowerDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/powerdns"
providerTencentCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/tencentcloud"
providerVolcEngine "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/volcengine"
@@ -167,6 +168,20 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err
}
+ case domain.ApplyDNSProviderTypeNS1:
+ {
+ access := domain.AccessConfigForNS1{}
+ if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
+ return nil, fmt.Errorf("failed to decode provider access config: %w", err)
+ }
+
+ applicant, err := providerNS1.NewChallengeProvider(&providerNS1.NS1ApplicantConfig{
+ ApiKey: access.ApiKey,
+ PropagationTimeout: options.PropagationTimeout,
+ })
+ return applicant, err
+ }
+
case domain.ApplyDNSProviderTypePowerDNS:
{
access := domain.AccessConfigForPowerDNS{}
diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go
index 6cb192f9..6fd3457c 100644
--- a/internal/deployer/providers.go
+++ b/internal/deployer/providers.go
@@ -15,6 +15,7 @@ import (
providerBaiduCloudCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baiducloud-cdn"
providerBytePlusCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/byteplus-cdn"
providerDogeCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/dogecloud-cdn"
+ providerEdgioApplications "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/edgio-applications"
providerHuaweiCloudCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/huaweicloud-cdn"
providerHuaweiCloudELB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/huaweicloud-elb"
providerK8sSecret "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/k8s-secret"
@@ -175,6 +176,21 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger,
return deployer, logger, err
}
+ case domain.DeployProviderTypeEdgioApplications:
+ {
+ access := domain.AccessConfigForEdgio{}
+ if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
+ return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err)
+ }
+
+ deployer, err := providerEdgioApplications.NewWithLogger(&providerEdgioApplications.EdgioApplicationsDeployerConfig{
+ ClientId: access.ClientId,
+ ClientSecret: access.ClientSecret,
+ EnvironmentId: maps.GetValueAsString(options.ProviderDeployConfig, "environmentId"),
+ }, logger)
+ return deployer, logger, err
+ }
+
case domain.DeployProviderTypeHuaweiCloudCDN, domain.DeployProviderTypeHuaweiCloudELB:
{
access := domain.AccessConfigForHuaweiCloud{}
diff --git a/internal/domain/access.go b/internal/domain/access.go
index 1893b4ab..c9b1d2a9 100644
--- a/internal/domain/access.go
+++ b/internal/domain/access.go
@@ -66,6 +66,11 @@ type AccessConfigForDogeCloud struct {
SecretKey string `json:"secretKey"`
}
+type AccessConfigForEdgio struct {
+ ClientId string `json:"clientId"`
+ ClientSecret string `json:"clientSecret"`
+}
+
type AccessConfigForGoDaddy struct {
ApiKey string `json:"apiKey"`
ApiSecret string `json:"apiSecret"`
@@ -91,6 +96,10 @@ type AccessConfigForNameSilo struct {
ApiKey string `json:"apiKey"`
}
+type AccessConfigForNS1 struct {
+ ApiKey string `json:"apiKey"`
+}
+
type AccessConfigForPowerDNS struct {
ApiUrl string `json:"apiUrl"`
ApiKey string `json:"apiKey"`
diff --git a/internal/domain/provider.go b/internal/domain/provider.go
index b90d9b38..34666531 100644
--- a/internal/domain/provider.go
+++ b/internal/domain/provider.go
@@ -17,12 +17,14 @@ const (
AccessProviderTypeBytePlus = AccessProviderType("byteplus")
AccessProviderTypeCloudflare = AccessProviderType("cloudflare")
AccessProviderTypeDogeCloud = AccessProviderType("dogecloud")
+ AccessProviderTypeEdgio = AccessProviderType("edgio")
AccessProviderTypeGoDaddy = AccessProviderType("godaddy")
AccessProviderTypeHuaweiCloud = AccessProviderType("huaweicloud")
AccessProviderTypeKubernetes = AccessProviderType("k8s")
AccessProviderTypeLocal = AccessProviderType("local")
AccessProviderTypeNameDotCom = AccessProviderType("namedotcom")
AccessProviderTypeNameSilo = AccessProviderType("namesilo")
+ AccessProviderTypeNS1 = AccessProviderType("ns1")
AccessProviderTypePowerDNS = AccessProviderType("powerdns")
AccessProviderTypeQiniu = AccessProviderType("qiniu")
AccessProviderTypeSSH = AccessProviderType("ssh")
@@ -54,6 +56,7 @@ const (
ApplyDNSProviderTypeHuaweiCloudDNS = ApplyDNSProviderType("huaweicloud-dns")
ApplyDNSProviderTypeNameDotCom = ApplyDNSProviderType("namedotcom")
ApplyDNSProviderTypeNameSilo = ApplyDNSProviderType("namesilo")
+ ApplyDNSProviderTypeNS1 = ApplyDNSProviderType("ns1")
ApplyDNSProviderTypePowerDNS = ApplyDNSProviderType("powerdns")
ApplyDNSProviderTypeTencentCloud = ApplyDNSProviderType("tencentcloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeTencentCloudDNS]
ApplyDNSProviderTypeTencentCloudDNS = ApplyDNSProviderType("tencentcloud-dns")
@@ -71,34 +74,35 @@ type DeployProviderType string
NOTICE: If you add new constant, please keep ASCII order.
*/
const (
- DeployProviderTypeAliyunALB = DeployProviderType("aliyun-alb")
- DeployProviderTypeAliyunCDN = DeployProviderType("aliyun-cdn")
- DeployProviderTypeAliyunCLB = DeployProviderType("aliyun-clb")
- DeployProviderTypeAliyunDCDN = DeployProviderType("aliyun-dcdn")
- DeployProviderTypeAliyunLive = DeployProviderType("aliyun-live")
- DeployProviderTypeAliyunNLB = DeployProviderType("aliyun-nlb")
- DeployProviderTypeAliyunOSS = DeployProviderType("aliyun-oss")
- DeployProviderTypeBaiduCloudCDN = DeployProviderType("baiducloud-cdn")
- DeployProviderTypeBytePlusCDN = DeployProviderType("byteplus-cdn")
- DeployProviderTypeDogeCloudCDN = DeployProviderType("dogecloud-cdn")
- DeployProviderTypeHuaweiCloudCDN = DeployProviderType("huaweicloud-cdn")
- DeployProviderTypeHuaweiCloudELB = DeployProviderType("huaweicloud-elb")
- DeployProviderTypeKubernetesSecret = DeployProviderType("k8s-secret")
- DeployProviderTypeLocal = DeployProviderType("local")
- DeployProviderTypeQiniuCDN = DeployProviderType("qiniu-cdn")
- DeployProviderTypeSSH = DeployProviderType("ssh")
- DeployProviderTypeTencentCloudCDN = DeployProviderType("tencentcloud-cdn")
- DeployProviderTypeTencentCloudCLB = DeployProviderType("tencentcloud-clb")
- DeployProviderTypeTencentCloudCOS = DeployProviderType("tencentcloud-cos")
- DeployProviderTypeTencentCloudCSS = DeployProviderType("tencentcloud-css")
- DeployProviderTypeTencentCloudECDN = DeployProviderType("tencentcloud-ecdn")
- DeployProviderTypeTencentCloudEO = DeployProviderType("tencentcloud-eo")
- DeployProviderTypeUCloudUCDN = DeployProviderType("ucloud-ucdn")
- DeployProviderTypeUCloudUS3 = DeployProviderType("ucloud-us3")
- DeployProviderTypeVolcEngineCDN = DeployProviderType("volcengine-cdn")
- DeployProviderTypeVolcEngineCLB = DeployProviderType("volcengine-clb")
- DeployProviderTypeVolcEngineDCDN = DeployProviderType("volcengine-dcdn")
- DeployProviderTypeVolcEngineLive = DeployProviderType("volcengine-live")
- DeployProviderTypeVolcEngineTOS = DeployProviderType("volcengine-tos")
- DeployProviderTypeWebhook = DeployProviderType("webhook")
+ DeployProviderTypeAliyunALB = DeployProviderType("aliyun-alb")
+ DeployProviderTypeAliyunCDN = DeployProviderType("aliyun-cdn")
+ DeployProviderTypeAliyunCLB = DeployProviderType("aliyun-clb")
+ DeployProviderTypeAliyunDCDN = DeployProviderType("aliyun-dcdn")
+ DeployProviderTypeAliyunLive = DeployProviderType("aliyun-live")
+ DeployProviderTypeAliyunNLB = DeployProviderType("aliyun-nlb")
+ DeployProviderTypeAliyunOSS = DeployProviderType("aliyun-oss")
+ DeployProviderTypeBaiduCloudCDN = DeployProviderType("baiducloud-cdn")
+ DeployProviderTypeBytePlusCDN = DeployProviderType("byteplus-cdn")
+ DeployProviderTypeDogeCloudCDN = DeployProviderType("dogecloud-cdn")
+ DeployProviderTypeEdgioApplications = DeployProviderType("edgio-applications")
+ DeployProviderTypeHuaweiCloudCDN = DeployProviderType("huaweicloud-cdn")
+ DeployProviderTypeHuaweiCloudELB = DeployProviderType("huaweicloud-elb")
+ DeployProviderTypeKubernetesSecret = DeployProviderType("k8s-secret")
+ DeployProviderTypeLocal = DeployProviderType("local")
+ DeployProviderTypeQiniuCDN = DeployProviderType("qiniu-cdn")
+ DeployProviderTypeSSH = DeployProviderType("ssh")
+ DeployProviderTypeTencentCloudCDN = DeployProviderType("tencentcloud-cdn")
+ DeployProviderTypeTencentCloudCLB = DeployProviderType("tencentcloud-clb")
+ DeployProviderTypeTencentCloudCOS = DeployProviderType("tencentcloud-cos")
+ DeployProviderTypeTencentCloudCSS = DeployProviderType("tencentcloud-css")
+ DeployProviderTypeTencentCloudECDN = DeployProviderType("tencentcloud-ecdn")
+ DeployProviderTypeTencentCloudEO = DeployProviderType("tencentcloud-eo")
+ DeployProviderTypeUCloudUCDN = DeployProviderType("ucloud-ucdn")
+ DeployProviderTypeUCloudUS3 = DeployProviderType("ucloud-us3")
+ DeployProviderTypeVolcEngineCDN = DeployProviderType("volcengine-cdn")
+ DeployProviderTypeVolcEngineCLB = DeployProviderType("volcengine-clb")
+ DeployProviderTypeVolcEngineDCDN = DeployProviderType("volcengine-dcdn")
+ DeployProviderTypeVolcEngineLive = DeployProviderType("volcengine-live")
+ DeployProviderTypeVolcEngineTOS = DeployProviderType("volcengine-tos")
+ DeployProviderTypeWebhook = DeployProviderType("webhook")
)
diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/ns1/ns1.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/ns1/ns1.go
new file mode 100644
index 00000000..7403ecb8
--- /dev/null
+++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/ns1/ns1.go
@@ -0,0 +1,33 @@
+package ns1
+
+import (
+ "errors"
+ "time"
+
+ "github.com/go-acme/lego/v4/challenge"
+ "github.com/go-acme/lego/v4/providers/dns/ns1"
+)
+
+type NS1ApplicantConfig struct {
+ ApiKey string `json:"apiKey"`
+ PropagationTimeout int32 `json:"propagationTimeout,omitempty"`
+}
+
+func NewChallengeProvider(config *NS1ApplicantConfig) (challenge.Provider, error) {
+ if config == nil {
+ return nil, errors.New("config is nil")
+ }
+
+ providerConfig := ns1.NewDefaultConfig()
+ providerConfig.APIKey = config.ApiKey
+ if config.PropagationTimeout != 0 {
+ providerConfig.PropagationTimeout = time.Duration(config.PropagationTimeout) * time.Second
+ }
+
+ provider, err := ns1.NewDNSProviderConfig(providerConfig)
+ if err != nil {
+ return nil, err
+ }
+
+ return provider, nil
+}
diff --git a/internal/pkg/core/deployer/providers/edgio-applications/edgio_applications.go b/internal/pkg/core/deployer/providers/edgio-applications/edgio_applications.go
new file mode 100644
index 00000000..d4d70e04
--- /dev/null
+++ b/internal/pkg/core/deployer/providers/edgio-applications/edgio_applications.go
@@ -0,0 +1,112 @@
+package edgioapplications
+
+import (
+ "context"
+ "encoding/pem"
+ "errors"
+
+ xerrors "github.com/pkg/errors"
+
+ "github.com/usual2970/certimate/internal/pkg/core/deployer"
+ "github.com/usual2970/certimate/internal/pkg/core/logger"
+ 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"
+)
+
+type EdgioApplicationsDeployerConfig struct {
+ // Edgio ClientId。
+ ClientId string `json:"clientId"`
+ // Edgio ClientSecret。
+ ClientSecret string `json:"clientSecret"`
+ // Edgio 环境 ID。
+ EnvironmentId string `json:"environmentId"`
+}
+
+type EdgioApplicationsDeployer struct {
+ config *EdgioApplicationsDeployerConfig
+ logger logger.Logger
+ sdkClient *edgsdk.EdgioClient
+}
+
+var _ deployer.Deployer = (*EdgioApplicationsDeployer)(nil)
+
+func New(config *EdgioApplicationsDeployerConfig) (*EdgioApplicationsDeployer, error) {
+ return NewWithLogger(config, logger.NewNilLogger())
+}
+
+func NewWithLogger(config *EdgioApplicationsDeployerConfig, logger logger.Logger) (*EdgioApplicationsDeployer, error) {
+ if config == nil {
+ return nil, errors.New("config is nil")
+ }
+
+ if logger == nil {
+ return nil, errors.New("logger is nil")
+ }
+
+ client, err := createSdkClient(config.ClientId, config.ClientSecret)
+ if err != nil {
+ return nil, xerrors.Wrap(err, "failed to create sdk client")
+ }
+
+ return &EdgioApplicationsDeployer{
+ logger: logger,
+ config: config,
+ sdkClient: client,
+ }, nil
+}
+
+func (d *EdgioApplicationsDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
+ // 提取 Edgio 所需的服务端证书和中间证书内容
+ privateCertPem, intermediateCertPem := extractCertChains(certPem)
+
+ // 上传 TLS 证书
+ // REF: https://docs.edg.io/rest_api/#tag/tls-certs/operation/postConfigV01TlsCerts
+ uploadTlsCertReq := edgsdkDtos.UploadTlsCertRequest{
+ EnvironmentID: d.config.EnvironmentId,
+ PrimaryCert: privateCertPem,
+ IntermediateCert: intermediateCertPem,
+ PrivateKey: privkeyPem,
+ }
+ uploadTlsCertResp, err := d.sdkClient.UploadTlsCert(uploadTlsCertReq)
+ if err != nil {
+ return nil, xerrors.Wrap(err, "failed to execute sdk request 'edgio.UploadTlsCert'")
+ }
+
+ d.logger.Logt("已上传 TLS 证书", uploadTlsCertResp)
+
+ return &deployer.DeployResult{}, nil
+}
+
+func createSdkClient(clientId, clientSecret string) (*edgsdk.EdgioClient, error) {
+ client := edgsdk.NewEdgioClient(clientId, clientSecret, "", "")
+ 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
+}
diff --git a/internal/pkg/core/deployer/providers/edgio-applications/edgio_applications_test.go b/internal/pkg/core/deployer/providers/edgio-applications/edgio_applications_test.go
new file mode 100644
index 00000000..04f7a4cb
--- /dev/null
+++ b/internal/pkg/core/deployer/providers/edgio-applications/edgio_applications_test.go
@@ -0,0 +1,75 @@
+package edgioapplications_test
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "os"
+ "strings"
+ "testing"
+
+ provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/edgio-applications"
+)
+
+var (
+ fInputCertPath string
+ fInputKeyPath string
+ fClientId string
+ fClientSecret string
+ fEnvironmentId string
+)
+
+func init() {
+ argsPrefix := "CERTIMATE_DEPLOYER_EDGIOAPPLICATIONS_"
+
+ flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
+ flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
+ flag.StringVar(&fClientId, argsPrefix+"CLIENTID", "", "")
+ flag.StringVar(&fClientSecret, argsPrefix+"CLIENTSECRET", "", "")
+ flag.StringVar(&fEnvironmentId, argsPrefix+"ENVIRONMENTID", "", "")
+}
+
+/*
+Shell command to run this test:
+
+ go test -v ./edgio_applications_test.go -args \
+ --CERTIMATE_DEPLOYER_EDGIOAPPLICATIONS_INPUTCERTPATH="/path/to/your-input-cert.pem" \
+ --CERTIMATE_DEPLOYER_EDGIOAPPLICATIONS_INPUTKEYPATH="/path/to/your-input-key.pem" \
+ --CERTIMATE_DEPLOYER_EDGIOAPPLICATIONS_CLIENTID="your-client-id" \
+ --CERTIMATE_DEPLOYER_EDGIOAPPLICATIONS_CLIENTSECRET="your-client-secret" \
+ --CERTIMATE_DEPLOYER_EDGIOAPPLICATIONS_ENVIRONMENTID="your-enviroment-id"
+*/
+func TestDeploy(t *testing.T) {
+ flag.Parse()
+
+ t.Run("Deploy", func(t *testing.T) {
+ t.Log(strings.Join([]string{
+ "args:",
+ fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
+ fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
+ fmt.Sprintf("CLIENTID: %v", fClientId),
+ fmt.Sprintf("CLIENTSECRET: %v", fClientSecret),
+ fmt.Sprintf("ENVIRONMENTID: %v", fEnvironmentId),
+ }, "\n"))
+
+ deployer, err := provider.New(&provider.EdgioApplicationsDeployerConfig{
+ ClientId: fClientId,
+ ClientSecret: fClientSecret,
+ EnvironmentId: fEnvironmentId,
+ })
+ if err != nil {
+ t.Errorf("err: %+v", err)
+ return
+ }
+
+ fInputCertData, _ := os.ReadFile(fInputCertPath)
+ fInputKeyData, _ := os.ReadFile(fInputKeyPath)
+ res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
+ if err != nil {
+ t.Errorf("err: %+v", err)
+ return
+ }
+
+ t.Logf("ok: %v", res)
+ })
+}
diff --git a/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret.go b/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret.go
index 9e74e794..60020ec6 100644
--- a/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret.go
+++ b/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret.go
@@ -14,7 +14,7 @@ import (
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/logger"
- "github.com/usual2970/certimate/internal/pkg/utils/x509"
+ "github.com/usual2970/certimate/internal/pkg/utils/certs"
)
type K8sSecretDeployerConfig struct {
@@ -75,7 +75,7 @@ func (d *K8sSecretDeployer) Deploy(ctx context.Context, certPem string, privkeyP
return nil, errors.New("config `secretDataKeyForKey` is required")
}
- certX509, err := x509.ParseCertificateFromPEM(certPem)
+ certX509, err := certs.ParseCertificateFromPEM(certPem)
if err != nil {
return nil, err
}
diff --git a/internal/pkg/core/deployer/providers/local/local.go b/internal/pkg/core/deployer/providers/local/local.go
index 344936d9..e3c3d84e 100644
--- a/internal/pkg/core/deployer/providers/local/local.go
+++ b/internal/pkg/core/deployer/providers/local/local.go
@@ -12,8 +12,8 @@ import (
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/logger"
- "github.com/usual2970/certimate/internal/pkg/utils/fs"
- "github.com/usual2970/certimate/internal/pkg/utils/x509"
+ "github.com/usual2970/certimate/internal/pkg/utils/certs"
+ "github.com/usual2970/certimate/internal/pkg/utils/files"
)
type LocalDeployerConfig struct {
@@ -84,41 +84,41 @@ func (d *LocalDeployer) Deploy(ctx context.Context, certPem string, privkeyPem s
// 写入证书和私钥文件
switch d.config.OutputFormat {
case OUTPUT_FORMAT_PEM:
- if err := fs.WriteFileString(d.config.OutputCertPath, certPem); err != nil {
+ if err := files.WriteString(d.config.OutputCertPath, certPem); err != nil {
return nil, xerrors.Wrap(err, "failed to save certificate file")
}
d.logger.Logt("certificate file saved")
- if err := fs.WriteFileString(d.config.OutputKeyPath, privkeyPem); err != nil {
+ if err := files.WriteString(d.config.OutputKeyPath, privkeyPem); err != nil {
return nil, xerrors.Wrap(err, "failed to save private key file")
}
d.logger.Logt("private key file saved")
case OUTPUT_FORMAT_PFX:
- pfxData, err := x509.TransformCertificateFromPEMToPFX(certPem, privkeyPem, d.config.PfxPassword)
+ pfxData, err := certs.TransformCertificateFromPEMToPFX(certPem, privkeyPem, d.config.PfxPassword)
if err != nil {
return nil, xerrors.Wrap(err, "failed to transform certificate to PFX")
}
d.logger.Logt("certificate transformed to PFX")
- if err := fs.WriteFile(d.config.OutputCertPath, pfxData); err != nil {
+ if err := files.Write(d.config.OutputCertPath, pfxData); err != nil {
return nil, xerrors.Wrap(err, "failed to save certificate file")
}
d.logger.Logt("certificate file saved")
case OUTPUT_FORMAT_JKS:
- jksData, err := x509.TransformCertificateFromPEMToJKS(certPem, privkeyPem, d.config.JksAlias, d.config.JksKeypass, d.config.JksStorepass)
+ jksData, err := certs.TransformCertificateFromPEMToJKS(certPem, privkeyPem, d.config.JksAlias, d.config.JksKeypass, d.config.JksStorepass)
if err != nil {
return nil, xerrors.Wrap(err, "failed to transform certificate to JKS")
}
d.logger.Logt("certificate transformed to JKS")
- if err := fs.WriteFile(d.config.OutputCertPath, jksData); err != nil {
+ if err := files.Write(d.config.OutputCertPath, jksData); err != nil {
return nil, xerrors.Wrap(err, "failed to save certificate file")
}
diff --git a/internal/pkg/core/deployer/providers/ssh/ssh.go b/internal/pkg/core/deployer/providers/ssh/ssh.go
index 6d86434c..e09c35b6 100644
--- a/internal/pkg/core/deployer/providers/ssh/ssh.go
+++ b/internal/pkg/core/deployer/providers/ssh/ssh.go
@@ -14,7 +14,7 @@ import (
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/logger"
- "github.com/usual2970/certimate/internal/pkg/utils/x509"
+ "github.com/usual2970/certimate/internal/pkg/utils/certs"
)
type SshDeployerConfig struct {
@@ -125,7 +125,7 @@ func (d *SshDeployer) Deploy(ctx context.Context, certPem string, privkeyPem str
d.logger.Logt("private key file uploaded")
case OUTPUT_FORMAT_PFX:
- pfxData, err := x509.TransformCertificateFromPEMToPFX(certPem, privkeyPem, d.config.PfxPassword)
+ pfxData, err := certs.TransformCertificateFromPEMToPFX(certPem, privkeyPem, d.config.PfxPassword)
if err != nil {
return nil, xerrors.Wrap(err, "failed to transform certificate to PFX")
}
@@ -139,7 +139,7 @@ func (d *SshDeployer) Deploy(ctx context.Context, certPem string, privkeyPem str
d.logger.Logt("certificate file uploaded")
case OUTPUT_FORMAT_JKS:
- jksData, err := x509.TransformCertificateFromPEMToJKS(certPem, privkeyPem, d.config.JksAlias, d.config.JksKeypass, d.config.JksStorepass)
+ jksData, err := certs.TransformCertificateFromPEMToJKS(certPem, privkeyPem, d.config.JksAlias, d.config.JksKeypass, d.config.JksStorepass)
if err != nil {
return nil, xerrors.Wrap(err, "failed to transform certificate to JKS")
}
diff --git a/internal/pkg/core/deployer/providers/webhook/webhook.go b/internal/pkg/core/deployer/providers/webhook/webhook.go
index d81da684..694befc5 100644
--- a/internal/pkg/core/deployer/providers/webhook/webhook.go
+++ b/internal/pkg/core/deployer/providers/webhook/webhook.go
@@ -1,20 +1,18 @@
package webhook
import (
- "bytes"
"context"
"encoding/json"
"errors"
- "io"
"strings"
"time"
- "github.com/gojek/heimdall/v7/httpclient"
+ "github.com/go-resty/resty/v2"
xerrors "github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/logger"
- "github.com/usual2970/certimate/internal/pkg/utils/x509"
+ "github.com/usual2970/certimate/internal/pkg/utils/certs"
)
type WebhookDeployerConfig struct {
@@ -27,7 +25,7 @@ type WebhookDeployerConfig struct {
type WebhookDeployer struct {
config *WebhookDeployerConfig
logger logger.Logger
- httpClient *httpclient.Client
+ httpClient *resty.Client
}
var _ deployer.Deployer = (*WebhookDeployer)(nil)
@@ -45,7 +43,10 @@ func NewWithLogger(config *WebhookDeployerConfig, logger logger.Logger) (*Webhoo
return nil, errors.New("logger is nil")
}
- client := httpclient.NewClient(httpclient.WithHTTPTimeout(30 * time.Second))
+ client := resty.New().
+ SetTimeout(30 * time.Second).
+ SetRetryCount(3).
+ SetRetryWaitTime(5 * time.Second)
return &WebhookDeployer{
config: config,
@@ -55,7 +56,7 @@ func NewWithLogger(config *WebhookDeployerConfig, logger logger.Logger) (*Webhoo
}
func (d *WebhookDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
- certX509, err := x509.ParseCertificateFromPEM(certPem)
+ certX509, err := certs.ParseCertificateFromPEM(certPem)
if err != nil {
return nil, xerrors.Wrap(err, "failed to parse x509")
}
@@ -72,25 +73,20 @@ func (d *WebhookDeployer) Deploy(ctx context.Context, certPem string, privkeyPem
replaceJsonValueRecursively(webhookData, "${CERTIFICATE}", certPem)
replaceJsonValueRecursively(webhookData, "${PRIVATE_KEY}", privkeyPem)
- reqBody, _ := json.Marshal(&webhookData)
- resp, err := d.httpClient.Post(d.config.WebhookUrl, bytes.NewReader(reqBody), map[string][]string{"Content-Type": {"application/json"}})
+ resp, err := d.httpClient.R().
+ SetContext(ctx).
+ SetHeader("Content-Type", "application/json").
+ SetBody(webhookData).
+ Post(d.config.WebhookUrl)
if err != nil {
return nil, xerrors.Wrap(err, "failed to send webhook request")
- }
- defer resp.Body.Close()
-
- respBody, err := io.ReadAll(resp.Body)
- if err != nil {
- return nil, xerrors.Wrap(err, "failed to read response body")
+ } else if resp.StatusCode() != 200 {
+ return nil, xerrors.Errorf("unexpected webhook response status code: %d", resp.StatusCode())
}
- d.logger.Logt("Webhook Response", string(respBody))
+ d.logger.Logt("Webhook request sent", resp.String())
- return &deployer.DeployResult{
- ExtendedData: map[string]any{
- "responseText": string(respBody),
- },
- }, nil
+ return &deployer.DeployResult{}, nil
}
func replaceJsonValueRecursively(data interface{}, oldStr, newStr string) interface{} {
diff --git a/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go b/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go
index 7649f618..ac71202e 100644
--- a/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go
+++ b/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go
@@ -13,7 +13,7 @@ import (
xerrors "github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- "github.com/usual2970/certimate/internal/pkg/utils/x509"
+ "github.com/usual2970/certimate/internal/pkg/utils/certs"
)
type AliyunCASUploaderConfig struct {
@@ -54,7 +54,7 @@ func New(config *AliyunCASUploaderConfig) (*AliyunCASUploader, error) {
func (u *AliyunCASUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
// 解析证书内容
- certX509, err := x509.ParseCertificateFromPEM(certPem)
+ certX509, err := certs.ParseCertificateFromPEM(certPem)
if err != nil {
return nil, err
}
@@ -90,12 +90,12 @@ func (u *AliyunCASUploader) Upload(ctx context.Context, certPem string, privkeyP
if *getUserCertificateDetailResp.Body.Cert == certPem {
isSameCert = true
} else {
- oldCertX509, err := x509.ParseCertificateFromPEM(*getUserCertificateDetailResp.Body.Cert)
+ oldCertX509, err := certs.ParseCertificateFromPEM(*getUserCertificateDetailResp.Body.Cert)
if err != nil {
continue
}
- isSameCert = x509.EqualCertificate(certX509, oldCertX509)
+ isSameCert = certs.EqualCertificate(certX509, oldCertX509)
}
// 如果已存在相同证书,直接返回已有的证书信息
diff --git a/internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go b/internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go
index 2df26ebc..63779b48 100644
--- a/internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go
+++ b/internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go
@@ -16,7 +16,7 @@ import (
xerrors "github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- "github.com/usual2970/certimate/internal/pkg/utils/x509"
+ "github.com/usual2970/certimate/internal/pkg/utils/certs"
)
type AliyunSLBUploaderConfig struct {
@@ -57,7 +57,7 @@ func New(config *AliyunSLBUploaderConfig) (*AliyunSLBUploader, error) {
func (u *AliyunSLBUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
// 解析证书内容
- certX509, err := x509.ParseCertificateFromPEM(certPem)
+ certX509, err := certs.ParseCertificateFromPEM(certPem)
if err != nil {
return nil, err
}
diff --git a/internal/pkg/core/uploader/providers/byteplus-cdn/byteplus_cdn.go b/internal/pkg/core/uploader/providers/byteplus-cdn/byteplus_cdn.go
index e564bba1..f1bf2b46 100644
--- a/internal/pkg/core/uploader/providers/byteplus-cdn/byteplus_cdn.go
+++ b/internal/pkg/core/uploader/providers/byteplus-cdn/byteplus_cdn.go
@@ -14,7 +14,7 @@ import (
xerrors "github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- "github.com/usual2970/certimate/internal/pkg/utils/x509"
+ "github.com/usual2970/certimate/internal/pkg/utils/certs"
)
type ByteplusCDNUploaderConfig struct {
@@ -48,7 +48,7 @@ func New(config *ByteplusCDNUploaderConfig) (*ByteplusCDNUploader, error) {
func (u *ByteplusCDNUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
// 解析证书内容
- certX509, err := x509.ParseCertificateFromPEM(certPem)
+ certX509, err := certs.ParseCertificateFromPEM(certPem)
if err != nil {
return nil, err
}
diff --git a/internal/pkg/core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go b/internal/pkg/core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go
index 83ccc0b7..2ea7d031 100644
--- a/internal/pkg/core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go
+++ b/internal/pkg/core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go
@@ -17,7 +17,7 @@ import (
xerrors "github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- "github.com/usual2970/certimate/internal/pkg/utils/x509"
+ "github.com/usual2970/certimate/internal/pkg/utils/certs"
hwsdk "github.com/usual2970/certimate/internal/pkg/vendors/huaweicloud-sdk"
)
@@ -59,7 +59,7 @@ func New(config *HuaweiCloudELBUploaderConfig) (*HuaweiCloudELBUploader, error)
func (u *HuaweiCloudELBUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
// 解析证书内容
- certX509, err := x509.ParseCertificateFromPEM(certPem)
+ certX509, err := certs.ParseCertificateFromPEM(certPem)
if err != nil {
return nil, err
}
@@ -85,12 +85,12 @@ func (u *HuaweiCloudELBUploader) Upload(ctx context.Context, certPem string, pri
if certDetail.Certificate == certPem {
isSameCert = true
} else {
- oldCertX509, err := x509.ParseCertificateFromPEM(certDetail.Certificate)
+ oldCertX509, err := certs.ParseCertificateFromPEM(certDetail.Certificate)
if err != nil {
continue
}
- isSameCert = x509.EqualCertificate(certX509, oldCertX509)
+ isSameCert = certs.EqualCertificate(certX509, oldCertX509)
}
// 如果已存在相同证书,直接返回已有的证书信息
diff --git a/internal/pkg/core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go b/internal/pkg/core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go
index 0c6721a1..c0618da0 100644
--- a/internal/pkg/core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go
+++ b/internal/pkg/core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go
@@ -13,7 +13,7 @@ import (
xerrors "github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- "github.com/usual2970/certimate/internal/pkg/utils/x509"
+ "github.com/usual2970/certimate/internal/pkg/utils/certs"
hwsdk "github.com/usual2970/certimate/internal/pkg/vendors/huaweicloud-sdk"
)
@@ -55,7 +55,7 @@ func New(config *HuaweiCloudSCMUploaderConfig) (*HuaweiCloudSCMUploader, error)
func (u *HuaweiCloudSCMUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
// 解析证书内容
- certX509, err := x509.ParseCertificateFromPEM(certPem)
+ certX509, err := certs.ParseCertificateFromPEM(certPem)
if err != nil {
return nil, err
}
@@ -94,12 +94,12 @@ func (u *HuaweiCloudSCMUploader) Upload(ctx context.Context, certPem string, pri
if *exportCertificateResp.Certificate == certPem {
isSameCert = true
} else {
- oldCertX509, err := x509.ParseCertificateFromPEM(*exportCertificateResp.Certificate)
+ oldCertX509, err := certs.ParseCertificateFromPEM(*exportCertificateResp.Certificate)
if err != nil {
continue
}
- isSameCert = x509.EqualCertificate(certX509, oldCertX509)
+ isSameCert = certs.EqualCertificate(certX509, oldCertX509)
}
// 如果已存在相同证书,直接返回已有的证书信息
diff --git a/internal/pkg/core/uploader/providers/qiniu-sslcert/qiniu_sslcert.go b/internal/pkg/core/uploader/providers/qiniu-sslcert/qiniu_sslcert.go
index d5912988..a599cbe2 100644
--- a/internal/pkg/core/uploader/providers/qiniu-sslcert/qiniu_sslcert.go
+++ b/internal/pkg/core/uploader/providers/qiniu-sslcert/qiniu_sslcert.go
@@ -10,7 +10,7 @@ import (
"github.com/qiniu/go-sdk/v7/auth"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- "github.com/usual2970/certimate/internal/pkg/utils/x509"
+ "github.com/usual2970/certimate/internal/pkg/utils/certs"
qiniuEx "github.com/usual2970/certimate/internal/pkg/vendors/qiniu-sdk"
)
@@ -49,7 +49,7 @@ func New(config *QiniuSSLCertUploaderConfig) (*QiniuSSLCertUploader, error) {
func (u *QiniuSSLCertUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
// 解析证书内容
- certX509, err := x509.ParseCertificateFromPEM(certPem)
+ certX509, err := certs.ParseCertificateFromPEM(certPem)
if err != nil {
return nil, err
}
diff --git a/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go b/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go
index a8bc7173..223c108e 100644
--- a/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go
+++ b/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go
@@ -16,7 +16,7 @@ import (
uAuth "github.com/ucloud/ucloud-sdk-go/ucloud/auth"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- x509util "github.com/usual2970/certimate/internal/pkg/utils/x509"
+ "github.com/usual2970/certimate/internal/pkg/utils/certs"
usdkSsl "github.com/usual2970/certimate/internal/pkg/vendors/ucloud-sdk/ussl"
)
@@ -94,7 +94,7 @@ func (u *UCloudUSSLUploader) Upload(ctx context.Context, certPem string, privkey
func (u *UCloudUSSLUploader) getExistCert(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
// 解析证书内容
- certX509, err := x509util.ParseCertificateFromPEM(certPem)
+ certX509, err := certs.ParseCertificateFromPEM(certPem)
if err != nil {
return nil, err
}
diff --git a/internal/pkg/core/uploader/providers/volcengine-cdn/volcengine_cdn.go b/internal/pkg/core/uploader/providers/volcengine-cdn/volcengine_cdn.go
index 7a766895..23f05281 100644
--- a/internal/pkg/core/uploader/providers/volcengine-cdn/volcengine_cdn.go
+++ b/internal/pkg/core/uploader/providers/volcengine-cdn/volcengine_cdn.go
@@ -15,7 +15,7 @@ import (
ve "github.com/volcengine/volcengine-go-sdk/volcengine"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- "github.com/usual2970/certimate/internal/pkg/utils/x509"
+ "github.com/usual2970/certimate/internal/pkg/utils/certs"
)
type VolcEngineCDNUploaderConfig struct {
@@ -49,7 +49,7 @@ func New(config *VolcEngineCDNUploaderConfig) (*VolcEngineCDNUploader, error) {
func (u *VolcEngineCDNUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
// 解析证书内容
- certX509, err := x509.ParseCertificateFromPEM(certPem)
+ certX509, err := certs.ParseCertificateFromPEM(certPem)
if err != nil {
return nil, err
}
diff --git a/internal/pkg/core/uploader/providers/volcengine-live/volcengine_live.go b/internal/pkg/core/uploader/providers/volcengine-live/volcengine_live.go
index b71c5d9d..c7861cb6 100644
--- a/internal/pkg/core/uploader/providers/volcengine-live/volcengine_live.go
+++ b/internal/pkg/core/uploader/providers/volcengine-live/volcengine_live.go
@@ -12,7 +12,7 @@ import (
ve "github.com/volcengine/volcengine-go-sdk/volcengine"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
- "github.com/usual2970/certimate/internal/pkg/utils/x509"
+ "github.com/usual2970/certimate/internal/pkg/utils/certs"
)
type VolcEngineLiveUploaderConfig struct {
@@ -46,7 +46,7 @@ func New(config *VolcEngineLiveUploaderConfig) (*VolcEngineLiveUploader, error)
func (u *VolcEngineLiveUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
// 解析证书内容
- certX509, err := x509.ParseCertificateFromPEM(certPem)
+ certX509, err := certs.ParseCertificateFromPEM(certPem)
if err != nil {
return nil, err
}
@@ -75,12 +75,12 @@ func (u *VolcEngineLiveUploader) Upload(ctx context.Context, certPem string, pri
if certificate == certPem {
isSameCert = true
} else {
- oldCertX509, err := x509.ParseCertificateFromPEM(certificate)
+ oldCertX509, err := certs.ParseCertificateFromPEM(certificate)
if err != nil {
continue
}
- isSameCert = x509.EqualCertificate(certX509, oldCertX509)
+ isSameCert = certs.EqualCertificate(certX509, oldCertX509)
}
// 如果已存在相同证书,直接返回已有的证书信息
diff --git a/internal/pkg/utils/x509/common.go b/internal/pkg/utils/certs/common.go
similarity index 97%
rename from internal/pkg/utils/x509/common.go
rename to internal/pkg/utils/certs/common.go
index f5557962..fe5d041a 100644
--- a/internal/pkg/utils/x509/common.go
+++ b/internal/pkg/utils/certs/common.go
@@ -1,4 +1,4 @@
-package x509
+package certs
import (
"crypto/x509"
diff --git a/internal/pkg/utils/x509/converter.go b/internal/pkg/utils/certs/converter.go
similarity index 97%
rename from internal/pkg/utils/x509/converter.go
rename to internal/pkg/utils/certs/converter.go
index c5522f27..a3272c45 100644
--- a/internal/pkg/utils/x509/converter.go
+++ b/internal/pkg/utils/certs/converter.go
@@ -1,4 +1,4 @@
-package x509
+package certs
import (
"crypto/ecdsa"
diff --git a/internal/pkg/utils/x509/parser.go b/internal/pkg/utils/certs/parser.go
similarity index 99%
rename from internal/pkg/utils/x509/parser.go
rename to internal/pkg/utils/certs/parser.go
index d9604526..d03d6395 100644
--- a/internal/pkg/utils/x509/parser.go
+++ b/internal/pkg/utils/certs/parser.go
@@ -1,4 +1,4 @@
-package x509
+package certs
import (
"crypto/ecdsa"
diff --git a/internal/pkg/utils/x509/transformer.go b/internal/pkg/utils/certs/transformer.go
similarity index 99%
rename from internal/pkg/utils/x509/transformer.go
rename to internal/pkg/utils/certs/transformer.go
index 6170d88a..6918a3b4 100644
--- a/internal/pkg/utils/x509/transformer.go
+++ b/internal/pkg/utils/certs/transformer.go
@@ -1,4 +1,4 @@
-package x509
+package certs
import (
"bytes"
diff --git a/internal/pkg/utils/fs/fs.go b/internal/pkg/utils/files/files.go
similarity index 79%
rename from internal/pkg/utils/fs/fs.go
rename to internal/pkg/utils/files/files.go
index 47b0cafb..654bf164 100644
--- a/internal/pkg/utils/fs/fs.go
+++ b/internal/pkg/utils/files/files.go
@@ -1,4 +1,4 @@
-package fs
+package files
import (
"os"
@@ -7,7 +7,7 @@ import (
xerrors "github.com/pkg/errors"
)
-// 与 [WriteFile] 类似,但写入的是字符串内容。
+// 与 [Write] 类似,但写入的是字符串内容。
//
// 入参:
// - path: 文件路径。
@@ -15,8 +15,8 @@ import (
//
// 出参:
// - 错误。
-func WriteFileString(path string, content string) error {
- return WriteFile(path, []byte(content))
+func WriteString(path string, content string) error {
+ return Write(path, []byte(content))
}
// 将数据写入指定路径的文件。
@@ -29,7 +29,7 @@ func WriteFileString(path string, content string) error {
//
// 出参:
// - 错误。
-func WriteFile(path string, data []byte) error {
+func Write(path string, data []byte) error {
dir := filepath.Dir(path)
err := os.MkdirAll(dir, os.ModePerm)
diff --git a/internal/pkg/utils/slices/slices.go b/internal/pkg/utils/slices/slices.go
new file mode 100644
index 00000000..e4e4d746
--- /dev/null
+++ b/internal/pkg/utils/slices/slices.go
@@ -0,0 +1,69 @@
+package slices
+
+// 创建给定切片一部分的浅拷贝,其包含通过所提供函数实现的测试的所有元素。
+//
+// 入参:
+// - slice: 原切片。
+// - filter: 为切片中的每个元素执行的函数。它应该返回一个布尔值以指使是否将元素保留在结果切片中。
+//
+// 出参:
+// - 新切片。
+func Filter[T any](slice []T, filter func(T) bool) []T {
+ var result []T
+ for _, item := range slice {
+ if filter(item) {
+ result = append(result, item)
+ }
+ }
+ return result
+}
+
+// 创建一个新切片,这个新切片由原切片中的每个元素都调用一次提供的函数后的返回值组成。
+//
+// 入参:
+// - slice: 原切片。
+// - mapper: 为切片中的每个元素执行的函数。它的返回值作为一个元素被添加为新切片中。
+//
+// 出参:
+// - 新切片。
+func Map[T1 any, T2 any](slice []T1, mapper func(T1) T2) []T2 {
+ result := make([]T2, 0, len(slice))
+ for _, item := range slice {
+ result = append(result, mapper(item))
+ }
+ return result
+}
+
+// 测试切片中是否每个元素都通过了由提供的函数实现的测试。
+//
+// 入参:
+// - slice: 切片。
+// - condition: 为切片中的每个元素执行的函数。它应该返回一个布尔值以指示元素是否通过测试。
+//
+// 出参:
+// - 测试结果。
+func Every[T any](slice []T, condition func(T) bool) bool {
+ for _, item := range slice {
+ if !condition(item) {
+ return false
+ }
+ }
+ return true
+}
+
+// 测试切片中是否至少有一个元素通过了由提供的函数实现的测试。
+//
+// 入参:
+// - slice: 切片。
+// - condition: 为切片中的每个元素执行的函数。它应该返回一个布尔值以指示元素是否通过测试。
+//
+// 出参:
+// - 测试结果。
+func Some[T any](slice []T, condition func(T) bool) bool {
+ for _, item := range slice {
+ if condition(item) {
+ return true
+ }
+ }
+ return false
+}
diff --git a/internal/pkg/vendors/edgio-sdk/applications/v7/README.md b/internal/pkg/vendors/edgio-sdk/applications/v7/README.md
new file mode 100644
index 00000000..fae60236
--- /dev/null
+++ b/internal/pkg/vendors/edgio-sdk/applications/v7/README.md
@@ -0,0 +1,3 @@
+```shell
+git clone https://github.com/Edgio/terraform-provider-edgio.git
+```
diff --git a/internal/pkg/vendors/edgio-sdk/applications/v7/dtos/cdn_configuration.go b/internal/pkg/vendors/edgio-sdk/applications/v7/dtos/cdn_configuration.go
new file mode 100644
index 00000000..2ae5d434
--- /dev/null
+++ b/internal/pkg/vendors/edgio-sdk/applications/v7/dtos/cdn_configuration.go
@@ -0,0 +1,93 @@
+package dtos
+
+import "encoding/json"
+
+type CDNConfiguration struct {
+ ConfigurationID string `json:"id"`
+ EnvironmentID string `json:"environment_id"`
+ Rules json.RawMessage `json:"rules"`
+ Origins []Origin `json:"origins"`
+ Hostnames []Hostname `json:"hostnames"`
+ Experiments *[]string `json:"experiments,omitempty"`
+ EdgeFunctionsSources *map[string]string `json:"edge_functions_sources,omitempty"`
+ EdgeFunctionInitScript *string `json:"edge_function_init_script,omitempty"`
+}
+
+type Origin struct {
+ Name string `json:"name"`
+ Type *string `json:"type,omitempty"`
+ Hosts []Host `json:"hosts"`
+ Balancer *string `json:"balancer,omitempty"`
+ OverrideHostHeader *string `json:"override_host_header,omitempty"`
+ Shields *Shields `json:"shields,omitempty"`
+ PciCertifiedShields *bool `json:"pci_certified_shields,omitempty"`
+ TLSVerify *TLSVerify `json:"tls_verify,omitempty"`
+ Retry *Retry `json:"retry,omitempty"`
+}
+
+type Host struct {
+ Weight *int64 `json:"weight,omitempty"`
+ DNSMaxTTL *int64 `json:"dns_max_ttl,omitempty"`
+ DNSPreference *string `json:"dns_preference,omitempty"`
+ MaxHardPool *int64 `json:"max_hard_pool,omitempty"`
+ DNSMinTTL *int64 `json:"dns_min_ttl,omitempty"`
+ Location *[]Location `json:"location,omitempty"`
+ MaxPool *int64 `json:"max_pool,omitempty"`
+ Balancer *string `json:"balancer,omitempty"`
+ Scheme *string `json:"scheme,omitempty"`
+ OverrideHostHeader *string `json:"override_host_header,omitempty"`
+ SNIHintAndStrictSanCheck *string `json:"sni_hint_and_strict_san_check,omitempty"`
+ UseSNI *bool `json:"use_sni,omitempty"`
+}
+
+type Location struct {
+ Port *int64 `json:"port,omitempty"`
+ Hostname *string `json:"hostname,omitempty"`
+}
+
+type Shields struct {
+ Apac *string `json:"apac,omitempty"`
+ Emea *string `json:"emea,omitempty"`
+ USWest *string `json:"us_west,omitempty"`
+ USEast *string `json:"us_east,omitempty"`
+}
+
+type TLSVerify struct {
+ UseSNI *bool `json:"use_sni,omitempty"`
+ SNIHintAndStrictSanCheck *string `json:"sni_hint_and_strict_san_check,omitempty"`
+ AllowSelfSignedCerts *bool `json:"allow_self_signed_certs,omitempty"`
+ PinnedCerts *[]string `json:"pinned_certs,omitempty"`
+}
+
+type Retry struct {
+ StatusCodes *[]int64 `json:"status_codes,omitempty"`
+ IgnoreRetryAfterHeader *bool `json:"ignore_retry_after_header,omitempty"`
+ AfterSeconds *int64 `json:"after_seconds,omitempty"`
+ MaxRequests *int64 `json:"max_requests,omitempty"`
+ MaxWaitSeconds *int64 `json:"max_wait_seconds,omitempty"`
+}
+
+type Hostname struct {
+ Hostname *string `json:"hostname,omitempty"`
+ DefaultOriginName *string `json:"default_origin_name,omitempty"`
+ ReportCode *int64 `json:"report_code,omitempty"`
+ TLS *TLS `json:"tls,omitempty"`
+ Directory *string `json:"directory,omitempty"`
+}
+
+type TLS struct {
+ NPN *bool `json:"npn,omitempty"`
+ ALPN *bool `json:"alpn,omitempty"`
+ Protocols *string `json:"protocols,omitempty"`
+ UseSigAlgs *bool `json:"use_sigalgs,omitempty"`
+ SNI *bool `json:"sni,omitempty"`
+ SniStrict *bool `json:"sni_strict,omitempty"`
+ SniHostMatch *bool `json:"sni_host_match,omitempty"`
+ ClientRenegotiation *bool `json:"client_renegotiation,omitempty"`
+ Options *string `json:"options,omitempty"`
+ CipherList *string `json:"cipher_list,omitempty"`
+ NamedCurve *string `json:"named_curve,omitempty"`
+ OCSP *bool `json:"oscp,omitempty"`
+ PEM *string `json:"pem,omitempty"`
+ CA *string `json:"ca,omitempty"`
+}
diff --git a/internal/pkg/vendors/edgio-sdk/applications/v7/dtos/environment.go b/internal/pkg/vendors/edgio-sdk/applications/v7/dtos/environment.go
new file mode 100644
index 00000000..74d99dd6
--- /dev/null
+++ b/internal/pkg/vendors/edgio-sdk/applications/v7/dtos/environment.go
@@ -0,0 +1,29 @@
+package dtos
+
+import (
+ "time"
+)
+
+type Environment struct {
+ Type string `json:"@type"`
+ IdLink string `json:"@id"`
+ Id string `json:"id"`
+ PropertyID string `json:"property_id"`
+ LegacyAccountNumber string `json:"legacy_account_number"`
+ Name string `json:"name"`
+ CanMembersDeploy bool `json:"can_members_deploy"`
+ OnlyMaintainersCanDeploy bool `json:"only_maintainers_can_deploy"`
+ HttpRequestLogging bool `json:"http_request_logging"`
+ DefaultDomainName string `json:"default_domain_name"`
+ PciCompliance bool `json:"pci_compliance"`
+ DnsDomainName string `json:"dns_domain_name"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
+}
+
+type EnvironmentsResponse struct {
+ Type string `json:"@type"`
+ Id string `json:"@id"`
+ TotalItems int `json:"total_items"`
+ Items []Environment `json:"items"`
+}
diff --git a/internal/pkg/vendors/edgio-sdk/applications/v7/dtos/property.go b/internal/pkg/vendors/edgio-sdk/applications/v7/dtos/property.go
new file mode 100644
index 00000000..378ab999
--- /dev/null
+++ b/internal/pkg/vendors/edgio-sdk/applications/v7/dtos/property.go
@@ -0,0 +1,18 @@
+package dtos
+
+import "time"
+
+type Property struct {
+ IdLink string `json:"@id"`
+ Id string `json:"id"`
+ OrganizationID string `json:"organization_id"`
+ Slug string `json:"slug"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
+}
+
+type Properties struct {
+ ID string `json:"@id"`
+ TotalItems int `json:"total_items"`
+ Items []Property `json:"items"`
+}
diff --git a/internal/pkg/vendors/edgio-sdk/applications/v7/dtos/purge.go b/internal/pkg/vendors/edgio-sdk/applications/v7/dtos/purge.go
new file mode 100644
index 00000000..67ad2612
--- /dev/null
+++ b/internal/pkg/vendors/edgio-sdk/applications/v7/dtos/purge.go
@@ -0,0 +1,18 @@
+package dtos
+
+import "time"
+
+type PurgeResponse struct {
+ ID string `json:"id"`
+ Status string `json:"status"`
+ CreatedAt time.Time `json:"created_at"`
+ CompletedAt time.Time `json:"completed_at"`
+ ProgressPercentage float32 `json:"progress_percentage"`
+}
+
+type PurgeRequest struct {
+ EnvironmentID string `json:"environment_id"`
+ PurgeType string `json:"purge_type"`
+ Values []string `json:"values"`
+ Hostname *string `json:"hostname"`
+}
diff --git a/internal/pkg/vendors/edgio-sdk/applications/v7/dtos/tls_cert.go b/internal/pkg/vendors/edgio-sdk/applications/v7/dtos/tls_cert.go
new file mode 100644
index 00000000..08da9b4c
--- /dev/null
+++ b/internal/pkg/vendors/edgio-sdk/applications/v7/dtos/tls_cert.go
@@ -0,0 +1,30 @@
+package dtos
+
+type TLSCertResponse struct {
+ ID string `json:"id"`
+ EnvironmentID string `json:"environment_id"`
+ PrimaryCert string `json:"primary_cert"`
+ IntermediateCert string `json:"intermediate_cert"`
+ Expiration string `json:"expiration"`
+ Status string `json:"status"`
+ Generated bool `json:"generated"`
+ Serial string `json:"serial"`
+ CommonName string `json:"common_name"`
+ AlternativeNames []string `json:"alternative_names"`
+ ActivationError string `json:"activation_error"`
+ CreatedAt string `json:"created_at"`
+ UpdatedAt string `json:"updated_at"`
+}
+
+type UploadTlsCertRequest struct {
+ EnvironmentID string `json:"environment_id"`
+ PrimaryCert string `json:"primary_cert"`
+ IntermediateCert string `json:"intermediate_cert"`
+ PrivateKey string `json:"private_key"`
+}
+
+type TLSCertSResponse struct {
+ EnvironmentID string `json:"environment_id"`
+ TotalItems int32 `json:"total_items"`
+ Certificates []TLSCertResponse `json:"items"`
+}
diff --git a/internal/pkg/vendors/edgio-sdk/applications/v7/edgio_client.go b/internal/pkg/vendors/edgio-sdk/applications/v7/edgio_client.go
new file mode 100644
index 00000000..a03436fc
--- /dev/null
+++ b/internal/pkg/vendors/edgio-sdk/applications/v7/edgio_client.go
@@ -0,0 +1,546 @@
+package edgio_api
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "time"
+
+ "github.com/go-resty/resty/v2"
+
+ "github.com/usual2970/certimate/internal/pkg/vendors/edgio-sdk/applications/v7/dtos"
+)
+
+// AccessTokenResponse represents the response from the token endpoint.
+type AccessTokenResponse struct {
+ AccessToken string `json:"access_token"`
+ ExpiresIn int `json:"expires_in"`
+ TokenType string `json:"token_type"`
+ Scope string `json:"scope"`
+}
+
+// TokenCache represents a cached token. The token is stored along
+// with its expiry time. Because different endpoints require different
+// scopes, we store the token with the scope as the key, so that we
+// can fetch the token from the cache based on the scope.
+type TokenCache struct {
+ AccessToken string
+ Expiry time.Time
+}
+
+type EdgioClient struct {
+ client *resty.Client
+ clientID string
+ clientSecret string
+ tokenURL string
+ apiURL string
+ tokenCache map[string]TokenCache
+}
+
+func NewEdgioClient(clientID, clientSecret, tokenURL, apiURL string) *EdgioClient {
+ client := resty.New().
+ SetTimeout(30 * time.Second).
+ SetRetryCount(3).
+ SetRetryWaitTime(5 * time.Second).
+ SetRetryMaxWaitTime(20 * time.Second)
+
+ if tokenURL == "" {
+ tokenURL = "https://id.edgio.app/connect/token"
+ }
+
+ if apiURL == "" {
+ apiURL = "https://edgioapis.com"
+ }
+
+ return &EdgioClient{
+ client: client,
+ clientID: clientID,
+ clientSecret: clientSecret,
+ tokenURL: tokenURL,
+ apiURL: apiURL,
+ tokenCache: make(map[string]TokenCache),
+ }
+}
+
+func (c *EdgioClient) getToken(scope string) (string, error) {
+ if cachedToken, exists := c.tokenCache[scope]; exists && time.Now().Before(cachedToken.Expiry) {
+ return cachedToken.AccessToken, nil
+ }
+
+ var tokenResp AccessTokenResponse
+ resp, err := c.client.R().
+ SetFormData(map[string]string{
+ "client_id": c.clientID,
+ "client_secret": c.clientSecret,
+ "grant_type": "client_credentials",
+ "scope": scope,
+ }).
+ SetResult(&tokenResp).
+ Post(c.tokenURL)
+ if err != nil {
+ return "", fmt.Errorf("failed to request token: %w", err)
+ }
+
+ if resp.IsError() {
+ return "", fmt.Errorf("unexpected status code for getToken: %d", resp.StatusCode())
+ }
+
+ c.tokenCache[scope] = TokenCache{
+ AccessToken: tokenResp.AccessToken,
+ Expiry: time.Now().Add(time.Duration(tokenResp.ExpiresIn) * time.Second),
+ }
+
+ return tokenResp.AccessToken, nil
+}
+
+func (c *EdgioClient) GetProperty(ctx context.Context, propertyID string) (*dtos.Property, error) {
+ token, err := c.getToken("app.accounts")
+ if err != nil {
+ return nil, fmt.Errorf("failed to get token: %w", err)
+ }
+
+ url := fmt.Sprintf("%s/accounts/v0.1/properties/%s", c.apiURL, propertyID)
+
+ var property dtos.Property
+ resp, err := c.client.R().
+ SetContext(ctx).
+ SetAuthToken(token).
+ SetResult(&property).
+ Get(url)
+ if err != nil {
+ return nil, fmt.Errorf("failed to send request: %w", err)
+ }
+
+ if resp.IsError() {
+ return nil, fmt.Errorf("unexpected status code for getSpecificProperty: %d, %s", resp.StatusCode(), resp.Request.URL)
+ }
+
+ return &property, nil
+}
+
+func (c *EdgioClient) GetProperties(page int, pageSize int, organizationID string) (*dtos.Properties, error) {
+ token, err := c.getToken("app.accounts")
+ if err != nil {
+ return nil, fmt.Errorf("failed to get token: %w", err)
+ }
+
+ url := fmt.Sprintf("%s/accounts/v0.1/properties", c.apiURL)
+
+ var propertiesResp dtos.Properties
+ resp, err := c.client.R().
+ SetAuthToken(token).
+ SetQueryParams(map[string]string{
+ "page": fmt.Sprintf("%d", page),
+ "page_size": fmt.Sprintf("%d", pageSize),
+ "organization_id": organizationID,
+ }).
+ SetResult(&propertiesResp).
+ Get(url)
+ if err != nil {
+ return nil, fmt.Errorf("failed to send request: %w", err)
+ }
+
+ if resp.IsError() {
+ return nil, fmt.Errorf("unexpected status code for getProperties: %d, %s", resp.StatusCode(), resp.Body())
+ }
+
+ return &propertiesResp, nil
+}
+
+func (c *EdgioClient) CreateProperty(ctx context.Context, organizationID, slug string) (*dtos.Property, error) {
+ token, err := c.getToken("app.accounts")
+ if err != nil {
+ return nil, fmt.Errorf("failed to get token: %w", err)
+ }
+
+ url := fmt.Sprintf("%s/accounts/v0.1/properties", c.apiURL)
+
+ var createdProperty dtos.Property
+ resp, err := c.client.R().
+ SetContext(ctx).
+ SetAuthToken(token).
+ SetHeader("Content-Type", "application/json").
+ SetBody(map[string]string{
+ "organization_id": organizationID,
+ "slug": slug,
+ }).
+ SetResult(&createdProperty).
+ Post(url)
+ if err != nil {
+ return nil, fmt.Errorf("failed to send request: %w", err)
+ }
+
+ if resp.IsError() {
+ return nil, fmt.Errorf("unexpected status code for createProperty: %d, response: %s", resp.StatusCode(), resp.String())
+ }
+
+ return &createdProperty, nil
+}
+
+func (c *EdgioClient) DeleteProperty(propertyID string) error {
+ token, err := c.getToken("app.accounts")
+ if err != nil {
+ return fmt.Errorf("failed to get token: %w", err)
+ }
+
+ url := fmt.Sprintf("%s/accounts/v0.1/properties/%s", c.apiURL, propertyID)
+
+ resp, err := c.client.R().
+ SetAuthToken(token).
+ Delete(url)
+ if err != nil {
+ return fmt.Errorf("error sending DELETE request: %w", err)
+ }
+
+ if resp.IsError() {
+ return fmt.Errorf("error deleting property: status code %d", resp.StatusCode())
+ }
+
+ return nil
+}
+
+func (c *EdgioClient) UpdateProperty(ctx context.Context, propertyID string, slug string) (*dtos.Property, error) {
+ token, err := c.getToken("app.accounts")
+ if err != nil {
+ return nil, fmt.Errorf("failed to get token: %w", err)
+ }
+
+ url := fmt.Sprintf("%s/accounts/v0.1/properties/%s", c.apiURL, propertyID)
+
+ requestBody := map[string]interface{}{
+ "slug": slug,
+ }
+
+ var updatedProperty dtos.Property
+ resp, err := c.client.R().
+ SetContext(ctx).
+ SetAuthToken(token).
+ SetBody(requestBody).
+ SetResult(&updatedProperty).
+ Patch(url)
+ if err != nil {
+ return nil, fmt.Errorf("failed to send request: %w", err)
+ }
+
+ if resp.IsError() {
+ return nil, fmt.Errorf("unexpected status code for updateProperty: %d", resp.StatusCode())
+ }
+
+ return &updatedProperty, nil
+}
+
+func (c *EdgioClient) GetEnvironments(page, pageSize int, propertyID string) (*dtos.EnvironmentsResponse, error) {
+ token, err := c.getToken("app.accounts")
+ if err != nil {
+ return nil, fmt.Errorf("failed to get token: %w", err)
+ }
+
+ url := fmt.Sprintf("%s/accounts/v0.1/environments", c.apiURL)
+
+ resp, err := c.client.R().
+ SetAuthToken(token).
+ SetQueryParams(map[string]string{
+ "page": fmt.Sprintf("%d", page),
+ "page_size": fmt.Sprintf("%d", pageSize),
+ "property_id": propertyID,
+ }).
+ SetResult(&dtos.EnvironmentsResponse{}).
+ Get(url)
+ if err != nil {
+ return nil, err
+ }
+
+ if resp.IsError() {
+ return nil, fmt.Errorf("error response: %s", resp.String())
+ }
+
+ return resp.Result().(*dtos.EnvironmentsResponse), nil
+}
+
+func (c *EdgioClient) GetEnvironment(environmentID string) (*dtos.Environment, error) {
+ token, err := c.getToken("app.accounts")
+ if err != nil {
+ return nil, fmt.Errorf("failed to get token: %w", err)
+ }
+
+ url := fmt.Sprintf("%s/accounts/v0.1/environments/%s", c.apiURL, environmentID)
+
+ resp, err := c.client.R().
+ SetPathParams(map[string]string{
+ "environment_id": environmentID,
+ }).
+ SetAuthToken(token).
+ SetResult(&dtos.Environment{}).
+ Get(url)
+ if err != nil {
+ return nil, err
+ }
+
+ if resp.IsError() {
+ return nil, fmt.Errorf("error response: %s", resp.String())
+ }
+
+ return resp.Result().(*dtos.Environment), nil
+}
+
+func (c *EdgioClient) CreateEnvironment(propertyID, name string, onlyMaintainersCanDeploy, httpRequestLogging bool) (*dtos.Environment, error) {
+ token, err := c.getToken("app.accounts")
+ if err != nil {
+ return nil, fmt.Errorf("failed to get token: %w", err)
+ }
+
+ url := fmt.Sprintf("%s/accounts/v0.1/environments", c.apiURL)
+
+ body := map[string]interface{}{
+ "property_id": propertyID,
+ "name": name,
+ "only_maintainers_can_deploy": onlyMaintainersCanDeploy,
+ "http_request_logging": httpRequestLogging,
+ }
+
+ resp, err := c.client.R().
+ SetBody(body).
+ SetAuthToken(token).
+ SetResult(&dtos.Environment{}).
+ Post(url)
+ if err != nil {
+ return nil, err
+ }
+
+ if resp.IsError() {
+ return nil, fmt.Errorf("error response: %s", resp.String())
+ }
+
+ return resp.Result().(*dtos.Environment), nil
+}
+
+func (c *EdgioClient) UpdateEnvironment(environmentID, name string, onlyMaintainersCanDeploy, httpRequestLogging, preserveCache bool) (*dtos.Environment, error) {
+ token, err := c.getToken("app.accounts")
+ if err != nil {
+ return nil, fmt.Errorf("failed to get token: %w", err)
+ }
+
+ url := fmt.Sprintf("%s/accounts/v0.1/environments/%s", c.apiURL, environmentID)
+
+ body := map[string]interface{}{
+ "name": name,
+ // as can_members_deploy is depricated, but update api is not
+ // we need to use it to map onlyMaintainersCanDeploy
+ "only_maintainers_can_deploy": onlyMaintainersCanDeploy,
+ "http_request_logging": httpRequestLogging,
+ "preserve_cache": preserveCache,
+ }
+
+ resp, err := c.client.R().
+ SetPathParams(map[string]string{
+ "environment_id": environmentID,
+ }).
+ SetBody(body).
+ SetAuthToken(token).
+ SetResult(&dtos.Environment{}).
+ Patch(url)
+ if err != nil {
+ return nil, err
+ }
+
+ if resp.IsError() {
+ return nil, fmt.Errorf("error response: %s", resp.String())
+ }
+
+ return resp.Result().(*dtos.Environment), nil
+}
+
+func (c *EdgioClient) DeleteEnvironment(environmentID string) error {
+ token, err := c.getToken("app.accounts")
+ if err != nil {
+ return fmt.Errorf("failed to get token: %w", err)
+ }
+
+ url := fmt.Sprintf("%s/accounts/v0.1/environments/%s", c.apiURL, environmentID)
+
+ resp, err := c.client.R().
+ SetPathParams(map[string]string{
+ "environment_id": environmentID,
+ }).
+ SetAuthToken(token).
+ SetResult(&dtos.Environment{}).
+ Delete(url)
+ if err != nil {
+ return err
+ }
+
+ if resp.IsError() {
+ return fmt.Errorf("error response: %s", resp.String())
+ }
+
+ return nil
+}
+
+func (c *EdgioClient) GetTlsCert(tlsCertId string) (*dtos.TLSCertResponse, error) {
+ token, err := c.getToken("app.config")
+ if err != nil {
+ return nil, fmt.Errorf("failed to get token: %w", err)
+ }
+
+ url := fmt.Sprintf("%s/config/v0.1/tls-certs/%s", c.apiURL, tlsCertId)
+
+ var tlsCertResponse dtos.TLSCertResponse
+ resp, err := c.client.R().
+ SetAuthToken(token).
+ SetResult(&tlsCertResponse).
+ Get(url)
+ if err != nil {
+ return nil, fmt.Errorf("error response: %s", err)
+ }
+
+ if resp.IsError() {
+ return nil, fmt.Errorf("error response: %s", resp.String())
+ }
+
+ return &tlsCertResponse, nil
+}
+
+func (c *EdgioClient) UploadTlsCert(req dtos.UploadTlsCertRequest) (*dtos.TLSCertResponse, error) {
+ token, err := c.getToken("app.config")
+ if err != nil {
+ return nil, fmt.Errorf("failed to get token: %w", err)
+ }
+
+ url := fmt.Sprintf("%s/config/v0.1/tls-certs", c.apiURL)
+ response := &dtos.TLSCertResponse{}
+
+ resp, err := c.client.R().
+ SetAuthToken(token).
+ SetHeader("Content-Type", "application/json").
+ SetBody(req).
+ SetResult(response).
+ Post(url)
+ if err != nil {
+ return nil, fmt.Errorf("failed to upload TLS certificate: %w", err)
+ }
+
+ if resp.IsError() {
+ return nil, fmt.Errorf("API responded with error: %s", resp.String())
+ }
+
+ return response, nil
+}
+
+func (c *EdgioClient) GenerateTlsCert(environmentId string) (*dtos.TLSCertResponse, error) {
+ token, err := c.getToken("app.config")
+ if err != nil {
+ return nil, fmt.Errorf("failed to get token: %w", err)
+ }
+
+ url := fmt.Sprintf("%s/config/v0.1/tls-certs/generate", c.apiURL)
+ request := map[string]interface{}{
+ "environment_id": environmentId,
+ }
+ response := &dtos.TLSCertResponse{}
+
+ resp, err := c.client.R().
+ SetAuthToken(token).
+ SetHeader("Content-Type", "application/json").
+ SetBody(request).
+ SetResult(response).
+ Post(url)
+ if err != nil {
+ return nil, fmt.Errorf("failed to upload TLS certificate: %w", err)
+ }
+
+ if resp.IsError() {
+ return nil, fmt.Errorf("API responded with error: %s", resp.String())
+ }
+
+ return response, nil
+}
+
+func (c *EdgioClient) GetTlsCerts(page int, pageSize int, environmentID string) (*dtos.TLSCertSResponse, error) {
+ token, err := c.getToken("app.config")
+ if err != nil {
+ return nil, fmt.Errorf("failed to get token: %w", err)
+ }
+
+ url := fmt.Sprintf("%s/config/v0.1/tls-certs", c.apiURL)
+
+ var tlsCertsResponse dtos.TLSCertSResponse
+ resp, err := c.client.R().
+ SetAuthToken(token).
+ SetQueryParams(map[string]string{
+ "page": fmt.Sprintf("%d", page),
+ "page_size": fmt.Sprintf("%d", pageSize),
+ "environment_id": environmentID,
+ }).
+ SetResult(&tlsCertsResponse).
+ Get(url)
+ if err != nil {
+ return nil, fmt.Errorf("failed to send request: %w", err)
+ }
+
+ if resp.IsError() {
+ return nil, fmt.Errorf("unexpected status code for getTlsCerts: %d", resp.StatusCode())
+ }
+
+ return &tlsCertsResponse, nil
+}
+
+func (c *EdgioClient) UploadCdnConfiguration(config *dtos.CDNConfiguration) (*dtos.CDNConfiguration, error) {
+ fmt.Println("------------------------------------------------------------------------- uploading")
+
+ token, err := c.getToken("app.config")
+ if err != nil {
+ return nil, fmt.Errorf("failed to get token: %w", err)
+ }
+
+ url := fmt.Sprintf("%s/config/v0.1/configs", c.apiURL)
+ var response dtos.CDNConfiguration
+
+ // Convert config to json
+ jsonBody, _ := json.MarshalIndent(config, "", " ")
+ jsonString := string(jsonBody)
+ fmt.Println("------------------------- config report code: ", config.Hostnames[0].ReportCode == nil)
+ fmt.Println("------------------------- config report code value: ", config.Hostnames[0].ReportCode)
+ fmt.Println("----------------------------------- jsonBody: ", jsonString)
+
+ resp, err := c.client.R().
+ SetAuthToken(token).
+ SetHeader("Content-Type", "application/json").
+ SetBody(config).
+ SetResult(&response).
+ Post(url)
+ if err != nil {
+ return nil, fmt.Errorf("failed to upload CDN configuration: %w", err)
+ }
+
+ if resp.IsError() {
+ return nil, fmt.Errorf("unexpected status code for uploadCdnConfiguration: %d, %s", resp.StatusCode(), resp.Body())
+ }
+
+ return &response, nil
+}
+
+func (c *EdgioClient) GetCDNConfiguration(configID string) (*dtos.CDNConfiguration, error) {
+ fmt.Println("------------------------------------------------------------------------- reading config")
+
+ token, err := c.getToken("app.config")
+ if err != nil {
+ return nil, fmt.Errorf("failed to get token: %w", err)
+ }
+
+ url := fmt.Sprintf("https://edgioapis.com/config/v0.1/configs/%s", configID)
+ var response dtos.CDNConfiguration
+
+ resp, err := c.client.R().
+ SetAuthToken(token).
+ SetResult(&response).
+ Get(url)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get CDN configuration: %w", err)
+ }
+
+ if resp.IsError() {
+ return nil, fmt.Errorf("unexpected status code for GetCDNConfiguration: %d", resp.StatusCode())
+ }
+
+ return &response, nil
+}
diff --git a/internal/pkg/vendors/edgio-sdk/applications/v7/edgio_client_interface.go b/internal/pkg/vendors/edgio-sdk/applications/v7/edgio_client_interface.go
new file mode 100644
index 00000000..ea5fa958
--- /dev/null
+++ b/internal/pkg/vendors/edgio-sdk/applications/v7/edgio_client_interface.go
@@ -0,0 +1,26 @@
+package edgio_api
+
+import (
+ "context"
+
+ "github.com/usual2970/certimate/internal/pkg/vendors/edgio-sdk/applications/v7/dtos"
+)
+
+type EdgioClientInterface interface {
+ GetProperty(ctx context.Context, propertyID string) (*dtos.Property, error)
+ GetProperties(page int, pageSize int, organizationID string) (*dtos.Properties, error)
+ CreateProperty(ctx context.Context, organizationID, slug string) (*dtos.Property, error)
+ DeleteProperty(propertyID string) error
+ UpdateProperty(ctx context.Context, propertyID string, slug string) (*dtos.Property, error)
+ GetEnvironments(page, pageSize int, propertyID string) (*dtos.EnvironmentsResponse, error)
+ GetEnvironment(environmentID string) (*dtos.Environment, error)
+ CreateEnvironment(propertyID, name string, onlyMaintainersCanDeploy, httpRequestLogging bool) (*dtos.Environment, error)
+ UpdateEnvironment(environmentID, name string, onlyMaintainersCanDeploy, httpRequestLogging, preserveCache bool) (*dtos.Environment, error)
+ DeleteEnvironment(environmentID string) error
+ GetTlsCert(tlsCertId string) (*dtos.TLSCertResponse, error)
+ UploadTlsCert(req dtos.UploadTlsCertRequest) (*dtos.TLSCertResponse, error)
+ GenerateTlsCert(environmentId string) (*dtos.TLSCertResponse, error)
+ GetTlsCerts(page int, pageSize int, environmentID string) (*dtos.TLSCertSResponse, error)
+ UploadCdnConfiguration(config *dtos.CDNConfiguration) (*dtos.CDNConfiguration, error)
+ GetCDNConfiguration(configID string) (*dtos.CDNConfiguration, error)
+}
diff --git a/internal/workflow/node-processor/apply_node.go b/internal/workflow/node-processor/apply_node.go
index d214ca63..a08e0c3d 100644
--- a/internal/workflow/node-processor/apply_node.go
+++ b/internal/workflow/node-processor/apply_node.go
@@ -7,7 +7,7 @@ import (
"github.com/usual2970/certimate/internal/applicant"
"github.com/usual2970/certimate/internal/domain"
- "github.com/usual2970/certimate/internal/pkg/utils/x509"
+ "github.com/usual2970/certimate/internal/pkg/utils/certs"
"github.com/usual2970/certimate/internal/repository"
)
@@ -89,7 +89,7 @@ func (a *applyNode) Run(ctx context.Context) error {
Outputs: a.node.Outputs,
}
- certX509, err := x509.ParseCertificateFromPEM(applyResult.Certificate)
+ certX509, err := certs.ParseCertificateFromPEM(applyResult.CertificateFullChain)
if err != nil {
a.AddOutput(ctx, a.node.Name, "解析证书失败", err.Error())
return err
@@ -98,7 +98,7 @@ func (a *applyNode) Run(ctx context.Context) error {
certificate := &domain.Certificate{
Source: domain.CertificateSourceTypeWorkflow,
SubjectAltNames: strings.Join(certX509.DNSNames, ";"),
- Certificate: applyResult.Certificate,
+ Certificate: applyResult.CertificateFullChain,
PrivateKey: applyResult.PrivateKey,
IssuerCertificate: applyResult.IssuerCertificate,
ACMECertUrl: applyResult.ACMECertUrl,
diff --git a/ui/public/imgs/providers/edgio.svg b/ui/public/imgs/providers/edgio.svg
new file mode 100644
index 00000000..72f21b47
--- /dev/null
+++ b/ui/public/imgs/providers/edgio.svg
@@ -0,0 +1 @@
+
diff --git a/ui/public/imgs/providers/ns1.svg b/ui/public/imgs/providers/ns1.svg
new file mode 100644
index 00000000..07811134
--- /dev/null
+++ b/ui/public/imgs/providers/ns1.svg
@@ -0,0 +1 @@
+
diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx
index b398013a..afe49ccd 100644
--- a/ui/src/components/access/AccessForm.tsx
+++ b/ui/src/components/access/AccessForm.tsx
@@ -17,10 +17,12 @@ import AccessFormBaiduCloudConfig from "./AccessFormBaiduCloudConfig";
import AccessFormBytePlusConfig from "./AccessFormBytePlusConfig";
import AccessFormCloudflareConfig from "./AccessFormCloudflareConfig";
import AccessFormDogeCloudConfig from "./AccessFormDogeCloudConfig";
+import AccessFormEdgioConfig from "./AccessFormEdgioConfig";
import AccessFormGoDaddyConfig from "./AccessFormGoDaddyConfig";
import AccessFormHuaweiCloudConfig from "./AccessFormHuaweiCloudConfig";
import AccessFormKubernetesConfig from "./AccessFormKubernetesConfig";
import AccessFormLocalConfig from "./AccessFormLocalConfig";
+import AccessFormNS1Config from "./AccessFormNS1Config";
import AccessFormNameDotComConfig from "./AccessFormNameDotComConfig";
import AccessFormNameSiloConfig from "./AccessFormNameSiloConfig";
import AccessFormPowerDNSConfig from "./AccessFormPowerDNSConfig";
@@ -101,6 +103,8 @@ const AccessForm = forwardRef(({ className,
return ;
case ACCESS_PROVIDERS.GODADDY:
return ;
+ case ACCESS_PROVIDERS.EDGIO:
+ return ;
case ACCESS_PROVIDERS.HUAWEICLOUD:
return ;
case ACCESS_PROVIDERS.KUBERNETES:
@@ -111,6 +115,8 @@ const AccessForm = forwardRef(({ className,
return ;
case ACCESS_PROVIDERS.NAMESILO:
return ;
+ case ACCESS_PROVIDERS.NS1:
+ return ;
case ACCESS_PROVIDERS.POWERDNS:
return ;
case ACCESS_PROVIDERS.QINIU:
diff --git a/ui/src/components/access/AccessFormEdgioConfig.tsx b/ui/src/components/access/AccessFormEdgioConfig.tsx
new file mode 100644
index 00000000..d70ece6e
--- /dev/null
+++ b/ui/src/components/access/AccessFormEdgioConfig.tsx
@@ -0,0 +1,76 @@
+import { useTranslation } from "react-i18next";
+import { Form, type FormInstance, Input } from "antd";
+import { createSchemaFieldRule } from "antd-zod";
+import { z } from "zod";
+
+import { type AccessConfigForEdgio } from "@/domain/access";
+
+type AccessFormEdgioConfigFieldValues = Nullish;
+
+export type AccessFormEdgioConfigProps = {
+ form: FormInstance;
+ formName: string;
+ disabled?: boolean;
+ initialValues?: AccessFormEdgioConfigFieldValues;
+ onValuesChange?: (values: AccessFormEdgioConfigFieldValues) => void;
+};
+
+const initFormModel = (): AccessFormEdgioConfigFieldValues => {
+ return {
+ clientId: "",
+ clientSecret: "",
+ };
+};
+
+const AccessFormEdgioConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormEdgioConfigProps) => {
+ const { t } = useTranslation();
+
+ const formSchema = z.object({
+ clientId: z
+ .string()
+ .min(1, t("access.form.edgio_client_id.placeholder"))
+ .max(64, t("common.errmsg.string_max", { max: 64 }))
+ .trim(),
+ clientSecret: z
+ .string()
+ .min(1, t("access.form.edgio_client_secret.placeholder"))
+ .max(64, t("common.errmsg.string_max", { max: 64 }))
+ .trim(),
+ });
+ const formRule = createSchemaFieldRule(formSchema);
+
+ const handleFormChange = (_: unknown, values: z.infer) => {
+ onValuesChange?.(values);
+ };
+
+ return (
+ }
+ >
+
+
+
+ }
+ >
+
+
+
+ );
+};
+
+export default AccessFormEdgioConfig;
diff --git a/ui/src/components/access/AccessFormNS1Config.tsx b/ui/src/components/access/AccessFormNS1Config.tsx
new file mode 100644
index 00000000..5b12feb0
--- /dev/null
+++ b/ui/src/components/access/AccessFormNS1Config.tsx
@@ -0,0 +1,61 @@
+import { useTranslation } from "react-i18next";
+import { Form, type FormInstance, Input } from "antd";
+import { createSchemaFieldRule } from "antd-zod";
+import { z } from "zod";
+
+import { type AccessConfigForNS1 } from "@/domain/access";
+
+type AccessFormNS1ConfigFieldValues = Nullish;
+
+export type AccessFormNS1ConfigProps = {
+ form: FormInstance;
+ formName: string;
+ disabled?: boolean;
+ initialValues?: AccessFormNS1ConfigFieldValues;
+ onValuesChange?: (values: AccessFormNS1ConfigFieldValues) => void;
+};
+
+const initFormModel = (): AccessFormNS1ConfigFieldValues => {
+ return {
+ apiKey: "",
+ };
+};
+
+const AccessFormNS1Config = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormNS1ConfigProps) => {
+ const { t } = useTranslation();
+
+ const formSchema = z.object({
+ apiKey: z
+ .string()
+ .min(1, t("access.form.ns1_api_key.placeholder"))
+ .max(64, t("common.errmsg.string_max", { max: 64 }))
+ .trim(),
+ });
+ const formRule = createSchemaFieldRule(formSchema);
+
+ const handleFormChange = (_: unknown, values: z.infer) => {
+ onValuesChange?.(values);
+ };
+
+ return (
+ }
+ >
+
+
+
+ );
+};
+
+export default AccessFormNS1Config;
diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx
index cbeb6c7a..77516024 100644
--- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx
+++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx
@@ -25,6 +25,7 @@ import DeployNodeConfigFormAliyunOSSConfig from "./DeployNodeConfigFormAliyunOSS
import DeployNodeConfigFormBaiduCloudCDNConfig from "./DeployNodeConfigFormBaiduCloudCDNConfig";
import DeployNodeConfigFormBytePlusCDNConfig from "./DeployNodeConfigFormBytePlusCDNConfig";
import DeployNodeConfigFormDogeCloudCDNConfig from "./DeployNodeConfigFormDogeCloudCDNConfig";
+import DeployNodeConfigFormEdgioApplicationsConfig from "./DeployNodeConfigFormEdgioApplicationsConfig";
import DeployNodeConfigFormHuaweiCloudCDNConfig from "./DeployNodeConfigFormHuaweiCloudCDNConfig";
import DeployNodeConfigFormHuaweiCloudELBConfig from "./DeployNodeConfigFormHuaweiCloudELBConfig";
import DeployNodeConfigFormKubernetesSecretConfig from "./DeployNodeConfigFormKubernetesSecretConfig";
@@ -134,6 +135,8 @@ const DeployNodeConfigForm = forwardRef;
case DEPLOY_PROVIDERS.DOGECLOUD_CDN:
return ;
+ case DEPLOY_PROVIDERS.EDGIO_APPLICATIONS:
+ return ;
case DEPLOY_PROVIDERS.HUAWEICLOUD_CDN:
return ;
case DEPLOY_PROVIDERS.HUAWEICLOUD_ELB:
diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormEdgioApplicationsConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormEdgioApplicationsConfig.tsx
new file mode 100644
index 00000000..2a6929d8
--- /dev/null
+++ b/ui/src/components/workflow/node/DeployNodeConfigFormEdgioApplicationsConfig.tsx
@@ -0,0 +1,65 @@
+import { useTranslation } from "react-i18next";
+import { Form, type FormInstance, Input } from "antd";
+import { createSchemaFieldRule } from "antd-zod";
+import { z } from "zod";
+
+type DeployNodeConfigFormEdgioApplicationsConfigFieldValues = Nullish<{
+ environmentId: string;
+}>;
+
+export type DeployNodeConfigFormEdgioApplicationsConfigProps = {
+ form: FormInstance;
+ formName: string;
+ disabled?: boolean;
+ initialValues?: DeployNodeConfigFormEdgioApplicationsConfigFieldValues;
+ onValuesChange?: (values: DeployNodeConfigFormEdgioApplicationsConfigFieldValues) => void;
+};
+
+const initFormModel = (): DeployNodeConfigFormEdgioApplicationsConfigFieldValues => {
+ return {};
+};
+
+const DeployNodeConfigFormEdgioApplicationsConfig = ({
+ form: formInst,
+ formName,
+ disabled,
+ initialValues,
+ onValuesChange,
+}: DeployNodeConfigFormEdgioApplicationsConfigProps) => {
+ const { t } = useTranslation();
+
+ const formSchema = z.object({
+ environmentId: z
+ .string({ message: t("workflow_node.deploy.form.edgio_applications_environment_id.placeholder") })
+ .min(1, t("workflow_node.deploy.form.edgio_applications_environment_id.placeholder"))
+ .max(64, t("common.errmsg.string_max", { max: 64 }))
+ .trim(),
+ });
+ const formRule = createSchemaFieldRule(formSchema);
+
+ const handleFormChange = (_: unknown, values: z.infer) => {
+ onValuesChange?.(values);
+ };
+
+ return (
+ }
+ >
+
+
+
+ );
+};
+
+export default DeployNodeConfigFormEdgioApplicationsConfig;
diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts
index 01d97d04..0c0d6986 100644
--- a/ui/src/domain/access.ts
+++ b/ui/src/domain/access.ts
@@ -16,6 +16,7 @@ export interface AccessModel extends BaseModel {
| AccessConfigForBytePlus
| AccessConfigForCloudflare
| AccessConfigForDogeCloud
+ | AccessConfigForEdgio
| AccessConfigForGoDaddy
| AccessConfigForHuaweiCloud
| AccessConfigForKubernetes
@@ -77,6 +78,11 @@ export type AccessConfigForDogeCloud = {
secretKey: string;
};
+export type AccessConfigForEdgio = {
+ clientId: string;
+ clientSecret: string;
+};
+
export type AccessConfigForGoDaddy = {
apiKey: string;
apiSecret: string;
@@ -102,6 +108,10 @@ export type AccessConfigForNameSilo = {
apiKey: string;
};
+export type AccessConfigForNS1 = {
+ apiKey: string;
+};
+
export type AccessConfigForPowerDNS = {
apiUrl: string;
apiKey: string;
diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts
index 42250e9d..574c022a 100644
--- a/ui/src/domain/provider.ts
+++ b/ui/src/domain/provider.ts
@@ -13,11 +13,13 @@ export const ACCESS_PROVIDERS = Object.freeze({
CLOUDFLARE: "cloudflare",
DOGECLOUD: "dogecloud",
GODADDY: "godaddy",
+ EDGIO: "edgio",
HUAWEICLOUD: "huaweicloud",
KUBERNETES: "k8s",
LOCAL: "local",
NAMEDOTCOM: "namedotcom",
NAMESILO: "namesilo",
+ NS1: "ns1",
POWERDNS: "powerdns",
QINIU: "qiniu",
SSH: "ssh",
@@ -63,11 +65,13 @@ export const accessProvidersMap: Map [
@@ -163,6 +169,7 @@ export const DEPLOY_PROVIDERS = Object.freeze({
BAIDUCLOUD_CDN: `${ACCESS_PROVIDERS.BAIDUCLOUD}-cdn`,
BYTEPLUS_CDN: `${ACCESS_PROVIDERS.BYTEPLUS}-cdn`,
DOGECLOUD_CDN: `${ACCESS_PROVIDERS.DOGECLOUD}-cdn`,
+ EDGIO_APPLICATIONS: `${ACCESS_PROVIDERS.EDGIO}-applications`,
HUAWEICLOUD_CDN: `${ACCESS_PROVIDERS.HUAWEICLOUD}-cdn`,
HUAWEICLOUD_ELB: `${ACCESS_PROVIDERS.HUAWEICLOUD}-elb`,
KUBERNETES_SECRET: `${ACCESS_PROVIDERS.KUBERNETES}-secret`,
@@ -230,6 +237,7 @@ export const deployProvidersMap: Map [
type,
{
diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json
index 66228013..47f59120 100644
--- a/ui/src/i18n/locales/en/nls.access.json
+++ b/ui/src/i18n/locales/en/nls.access.json
@@ -78,6 +78,12 @@
"access.form.dogecloud_secret_key.label": "Doge Cloud SecretKey",
"access.form.dogecloud_secret_key.placeholder": "Please enter Doge Cloud SecretKey",
"access.form.dogecloud_secret_key.tooltip": "For more information, see https://console.dogecloud.com/",
+ "access.form.edgio_client_id.label": "Edgio ClientId",
+ "access.form.edgio_client_id.placeholder": "Please enter Edgio ClientId",
+ "access.form.edgio_client_id.tooltip": "For more information, see https://docs.edg.io/applications/v7/rest_api/authentication#administering-api-clients",
+ "access.form.edgio_client_secret.label": "Edgio ClientSecret",
+ "access.form.edgio_client_secret.placeholder": "Please enter Edgio ClientSecret",
+ "access.form.edgio_client_secret.tooltip": "For more information, see https://docs.edg.io/applications/v7/rest_api/authentication#administering-api-clients",
"access.form.godaddy_api_key.label": "GoDaddy API key",
"access.form.godaddy_api_key.placeholder": "Please enter GoDaddy API key",
"access.form.godaddy_api_key.tooltip": "For more information, see https://developer.godaddy.com/",
@@ -103,6 +109,9 @@
"access.form.namesilo_api_key.label": "NameSilo API key",
"access.form.namesilo_api_key.placeholder": "Please enter NameSilo API key",
"access.form.namesilo_api_key.tooltip": "For more information, see https://www.namesilo.com/support/v2/articles/account-options/api-manager",
+ "access.form.ns1_api_key.label": "NS1 API key",
+ "access.form.ns1_api_key.placeholder": "Please enter NS1 API key",
+ "access.form.ns1_api_key.tooltip": "For more information, see https://www.ibm.com/docs/en/ns1-connect?topic=introduction-using-api",
"access.form.powerdns_api_url.label": "PowerDNS API URL",
"access.form.powerdns_api_url.placeholder": "Please enter PowerDNS API URL",
"access.form.powerdns_api_url.tooltip": "For more information, see https://doc.powerdns.com/authoritative/http-api/index.html#endpoints-and-objects-in-the-api",
diff --git a/ui/src/i18n/locales/en/nls.common.json b/ui/src/i18n/locales/en/nls.common.json
index dc8ea864..53eeb515 100644
--- a/ui/src/i18n/locales/en/nls.common.json
+++ b/ui/src/i18n/locales/en/nls.common.json
@@ -56,6 +56,8 @@
"common.provider.cloudflare": "Cloudflare",
"common.provider.dogecloud": "Doge Cloud",
"common.provider.dogecloud.cdn": "Doge Cloud - Content Delivery Network (CDN)",
+ "common.provider.edgio": "Edgio",
+ "common.provider.edgio.applications": "Edgio - Applications",
"common.provider.godaddy": "GoDaddy",
"common.provider.huaweicloud": "Huawei Cloud",
"common.provider.huaweicloud.cdn": "Huawei Cloud - Content Delivery Network (CDN)",
@@ -66,6 +68,7 @@
"common.provider.local": "Local deployment",
"common.provider.namedotcom": "Name.com",
"common.provider.namesilo": "NameSilo",
+ "common.provider.ns1": "NS1 (IBM NS1 Connect)",
"common.provider.powerdns": "PowerDNS",
"common.provider.qiniu": "Qiniu",
"common.provider.qiniu.cdn": "Qiniu - Content Delivery Network (CDN)",
diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json
index 7275d08b..89d8ae71 100644
--- a/ui/src/i18n/locales/en/nls.workflow.nodes.json
+++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json
@@ -148,6 +148,9 @@
"workflow_node.deploy.form.dogecloud_cdn_domain.label": "Doge Cloud CDN domain",
"workflow_node.deploy.form.dogecloud_cdn_domain.placeholder": "Please enter Doge Cloud CDN domain name",
"workflow_node.deploy.form.dogecloud_cdn_domain.tooltip": "For more information, see https://console.dogecloud.com/",
+ "workflow_node.deploy.form.edgio_applications_environment_id.label": "Edgio Applications environment ID",
+ "workflow_node.deploy.form.edgio_applications_environment_id.placeholder": "Please enter Edgio Applications environment ID",
+ "workflow_node.deploy.form.edgio_applications_environment_id.tooltip": "For more information, see https://edgio.app/",
"workflow_node.deploy.form.huaweicloud_cdn_region.label": "Huawei Cloud region",
"workflow_node.deploy.form.huaweicloud_cdn_region.placeholder": "Please enter Huawei Cloud region (e.g. cn-north-1)",
"workflow_node.deploy.form.huaweicloud_cdn_region.tooltip": "For more information, see https://console-intl.huaweicloud.com/apiexplorer/#/endpoint",
diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json
index e24c125b..dc8d8c93 100644
--- a/ui/src/i18n/locales/zh/nls.access.json
+++ b/ui/src/i18n/locales/zh/nls.access.json
@@ -45,14 +45,14 @@
"access.form.aws_secret_access_key.label": "AWS SecretAccessKey",
"access.form.aws_secret_access_key.placeholder": "请输入 AWS SecretAccessKey",
"access.form.aws_secret_access_key.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/IAM/latest/UserGuide/id_credentials_access-keys.html",
- "access.form.azure_tenant_id.label": "Azure TenantId",
- "access.form.azure_tenant_id.placeholder": "请输入 Azure TenantId",
+ "access.form.azure_tenant_id.label": "Azure 租户 ID",
+ "access.form.azure_tenant_id.placeholder": "请输入 Azure 租户 ID",
"access.form.azure_tenant_id.tooltip": "这是什么?请参阅 https://learn.microsoft.com/zh-cn/azure/azure-portal/get-subscription-tenant-id",
- "access.form.azure_client_id.label": "Azure ClientId",
- "access.form.azure_client_id.placeholder": "请输入 Azure ClientId",
+ "access.form.azure_client_id.label": "Azure 客户端 ID",
+ "access.form.azure_client_id.placeholder": "请输入 Azure 客户端 ID",
"access.form.azure_client_id.tooltip": "这是什么?请参阅 https://learn.microsoft.com/zh-cn/azure/azure-monitor/logs/api/register-app-for-token",
- "access.form.azure_client_secret.label": "Azure ClientSecret",
- "access.form.azure_client_secret.placeholder": "请输入 Azure ClientSecret",
+ "access.form.azure_client_secret.label": "Azure 客户端密码",
+ "access.form.azure_client_secret.placeholder": "请输入 Azure 客户端密码",
"access.form.azure_client_secret.tooltip": "这是什么?请参阅 https://learn.microsoft.com/zh-cn/azure/azure-monitor/logs/api/register-app-for-token",
"access.form.azure_cloud_name.label": "Azure 主权云环境(可选)",
"access.form.azure_cloud_name.placeholder": "请输入 Azure 主权云环境(例如:public)",
@@ -78,6 +78,12 @@
"access.form.dogecloud_secret_key.label": "多吉云 SecretKey",
"access.form.dogecloud_secret_key.placeholder": "请输入多吉云 SecretKey",
"access.form.dogecloud_secret_key.tooltip": "这是什么?请参阅 https://console.dogecloud.com/",
+ "access.form.edgio_client_id.label": "Edgio 客户端 ID",
+ "access.form.edgio_client_id.placeholder": "请输入 Edgio 客户端 ID",
+ "access.form.edgio_client_id.tooltip": "这是什么?请参阅 https://docs.edg.io/applications/v7/rest_api/authentication#administering-api-clients",
+ "access.form.edgio_client_secret.label": "Edgio 客户端密码",
+ "access.form.edgio_client_secret.placeholder": "请输入 Edgio 客户端密码",
+ "access.form.edgio_client_secret.tooltip": "这是什么?请参阅 https://docs.edg.io/applications/v7/rest_api/authentication#administering-api-clients",
"access.form.godaddy_api_key.label": "GoDaddy API Key",
"access.form.godaddy_api_key.placeholder": "请输入 GoDaddy API Key",
"access.form.godaddy_api_key.tooltip": "这是什么?请参阅 https://developer.godaddy.com/",
@@ -103,6 +109,9 @@
"access.form.namesilo_api_key.label": "NameSilo API Key",
"access.form.namesilo_api_key.placeholder": "请输入 NameSilo API Key",
"access.form.namesilo_api_key.tooltip": "这是什么?请参阅 https://www.namesilo.com/support/v2/articles/account-options/api-manager",
+ "access.form.ns1_api_key.label": "NS1 API Key",
+ "access.form.ns1_api_key.placeholder": "请输入 NS1 API Key",
+ "access.form.ns1_api_key.tooltip": "这是什么?请参阅 https://www.ibm.com/docs/zh/ns1-connect?topic=introduction-using-api",
"access.form.powerdns_api_url.label": "PowerDNS API URL",
"access.form.powerdns_api_url.placeholder": "请输入 PowerDNS API URL",
"access.form.powerdns_api_url.tooltip": "这是什么?请参阅 https://doc.powerdns.com/authoritative/http-api/index.html#endpoints-and-objects-in-the-api",
diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json
index 80d6ccc9..87fce337 100644
--- a/ui/src/i18n/locales/zh/nls.common.json
+++ b/ui/src/i18n/locales/zh/nls.common.json
@@ -56,6 +56,8 @@
"common.provider.cloudflare": "Cloudflare",
"common.provider.dogecloud": "多吉云",
"common.provider.dogecloud.cdn": "多吉云 - 内容分发网络 CDN",
+ "common.provider.edgio": "Edgio",
+ "common.provider.edgio.applications": "Edgio - Applications",
"common.provider.godaddy": "GoDaddy",
"common.provider.huaweicloud": "华为云",
"common.provider.huaweicloud.cdn": "华为云 - 内容分发网络 CDN",
@@ -66,6 +68,7 @@
"common.provider.local": "本地部署",
"common.provider.namedotcom": "Name.com",
"common.provider.namesilo": "NameSilo",
+ "common.provider.ns1": "NS1(IBM NS1 Connect)",
"common.provider.powerdns": "PowerDNS",
"common.provider.qiniu": "七牛云",
"common.provider.qiniu.cdn": "七牛云 - 内容分发网络 CDN",
diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json
index 00b24d75..b6291e06 100644
--- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json
+++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json
@@ -148,6 +148,9 @@
"workflow_node.deploy.form.dogecloud_cdn_domain.label": "多吉云 CDN 加速域名",
"workflow_node.deploy.form.dogecloud_cdn_domain.placeholder": "请输入多吉云 CDN 加速域名",
"workflow_node.deploy.form.dogecloud_cdn_domain.tooltip": "这是什么?请参阅 https://console.dogecloud.com",
+ "workflow_node.deploy.form.edgio_applications_environment_id.label": "Edgio Applications 环境 ID",
+ "workflow_node.deploy.form.edgio_applications_environment_id.placeholder": "请输入 Edgio Applications 环境 ID",
+ "workflow_node.deploy.form.edgio_applications_environment_id.tooltip": "这是什么?请参阅 https://edgio.app/",
"workflow_node.deploy.form.huaweicloud_cdn_region.label": "华为云区域",
"workflow_node.deploy.form.huaweicloud_cdn_region.placeholder": "请输入华为云区域(例如:cn-north-1)",
"workflow_node.deploy.form.huaweicloud_cdn_region.tooltip": "这是什么?请参阅 https://console.huaweicloud.com/apiexplorer/#/endpoint",