diff --git a/README.md b/README.md
index ed79b12f..745cec19 100644
--- a/README.md
+++ b/README.md
@@ -127,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 9c9aab55..ece9591c 100644
--- a/README_EN.md
+++ b/README_EN.md
@@ -126,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 9d52261f..e6ba637a 100644
--- a/go.mod
+++ b/go.mod
@@ -18,6 +18,7 @@ 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
diff --git a/go.sum b/go.sum
index d41faffa..52df7389 100644
--- a/go.sum
+++ b/go.sum
@@ -398,6 +398,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=
diff --git a/internal/applicant/applicant.go b/internal/applicant/applicant.go
index 218b42fc..a89204c6 100644
--- a/internal/applicant/applicant.go
+++ b/internal/applicant/applicant.go
@@ -20,9 +20,9 @@ import (
)
type ApplyCertResult struct {
- Certificate string
- PrivateKey string
+ CertificateChain string
IssuerCertificate string
+ PrivateKey string
ACMECertUrl string
ACMECertStableUrl string
CSR string
@@ -150,9 +150,9 @@ func apply(challengeProvider challenge.Provider, options *applicantOptions) (*Ap
}
return &ApplyCertResult{
- PrivateKey: string(certResource.PrivateKey),
- Certificate: string(certResource.Certificate),
- IssuerCertificate: string(certResource.IssuerCertificate),
+ CertificateChain: 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),
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 dc118482..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"`
diff --git a/internal/domain/provider.go b/internal/domain/provider.go
index dead9c57..34666531 100644
--- a/internal/domain/provider.go
+++ b/internal/domain/provider.go
@@ -17,6 +17,7 @@ const (
AccessProviderTypeBytePlus = AccessProviderType("byteplus")
AccessProviderTypeCloudflare = AccessProviderType("cloudflare")
AccessProviderTypeDogeCloud = AccessProviderType("dogecloud")
+ AccessProviderTypeEdgio = AccessProviderType("edgio")
AccessProviderTypeGoDaddy = AccessProviderType("godaddy")
AccessProviderTypeHuaweiCloud = AccessProviderType("huaweicloud")
AccessProviderTypeKubernetes = AccessProviderType("k8s")
@@ -73,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/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/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..7ed5861a 100644
--- a/internal/workflow/node-processor/apply_node.go
+++ b/internal/workflow/node-processor/apply_node.go
@@ -89,7 +89,7 @@ func (a *applyNode) Run(ctx context.Context) error {
Outputs: a.node.Outputs,
}
- certX509, err := x509.ParseCertificateFromPEM(applyResult.Certificate)
+ certX509, err := x509.ParseCertificateFromPEM(applyResult.CertificateChain)
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.CertificateChain,
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/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx
index 040e52dd..afe49ccd 100644
--- a/ui/src/components/access/AccessForm.tsx
+++ b/ui/src/components/access/AccessForm.tsx
@@ -17,6 +17,7 @@ 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";
@@ -102,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:
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/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 ed2bd310..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;
diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts
index ba1a10f2..574c022a 100644
--- a/ui/src/domain/provider.ts
+++ b/ui/src/domain/provider.ts
@@ -13,6 +13,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
CLOUDFLARE: "cloudflare",
DOGECLOUD: "dogecloud",
GODADDY: "godaddy",
+ EDGIO: "edgio",
HUAWEICLOUD: "huaweicloud",
KUBERNETES: "k8s",
LOCAL: "local",
@@ -64,6 +65,7 @@ export const accessProvidersMap: Map [
type,
{
diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json
index d609d6e7..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/",
diff --git a/ui/src/i18n/locales/en/nls.common.json b/ui/src/i18n/locales/en/nls.common.json
index 7b0199a9..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)",
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 b9107581..dc8d8c93 100644
--- a/ui/src/i18n/locales/zh/nls.access.json
+++ b/ui/src/i18n/locales/zh/nls.access.json
@@ -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/",
diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json
index 0951152a..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",
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",