diff --git a/go.mod b/go.mod index 7e824cad..e5038ab0 100644 --- a/go.mod +++ b/go.mod @@ -73,6 +73,8 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect github.com/alibabacloud-go/alibabacloud-gateway-fc-util v0.0.7 // indirect + github.com/alibabacloud-go/apig-20240327/v3 v3.2.2 // indirect + github.com/alibabacloud-go/cloudapi-20160714/v5 v5.7.2 // indirect github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1 // indirect github.com/alibabacloud-go/tea-fileform v1.1.1 // indirect github.com/alibabacloud-go/tea-oss-sdk v1.1.3 // indirect diff --git a/go.sum b/go.sum index a5cf6ac3..9caf29d6 100644 --- a/go.sum +++ b/go.sum @@ -95,10 +95,14 @@ github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do2 github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 h1:zE8vH9C7JiZLNJJQ5OwjU9mSi4T9ef9u3BURT6LCLC8= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5/go.mod h1:tWnyE9AjF8J8qqLk645oUmVUnFybApTQWklQmi5tY6g= +github.com/alibabacloud-go/apig-20240327/v3 v3.2.2 h1:yH84ePgqtA2tF3ly7Tf3AA5ogl2SC8kqCNG4+zz4yo4= +github.com/alibabacloud-go/apig-20240327/v3 v3.2.2/go.mod h1:XLaCapbSH7olJTs42wisDO9JvX9BGy5acZk0bLNejDs= github.com/alibabacloud-go/cas-20200407/v3 v3.0.4 h1:ngRlctbt135zoujwX0lXSv9m4h1/bmg/yalQS0z1EWc= github.com/alibabacloud-go/cas-20200407/v3 v3.0.4/go.mod h1:6n9MZ9SH3HlSzfe2oKwjOqhJx3dxvW2gMDO+lq8t9U4= github.com/alibabacloud-go/cdn-20180510/v5 v5.2.2 h1:+KJOPukTM+xMyiLOW5qBwYKG2df3Ar7coRsqc1juKO8= github.com/alibabacloud-go/cdn-20180510/v5 v5.2.2/go.mod h1:GnPiPL3HlzCi8SGiLiVgKrAFkP1vTtcF4yGtjsl4wfo= +github.com/alibabacloud-go/cloudapi-20160714/v5 v5.7.2 h1:Ug50clztqiQAy5t0R9Vejibz2Xgxm1Tpw2Y6A9eAwRE= +github.com/alibabacloud-go/cloudapi-20160714/v5 v5.7.2/go.mod h1:l9Zd2FanDUO2UqHJSPnOv+cY9DVT+YXcr97zfpSHywo= github.com/alibabacloud-go/darabonba-array v0.1.0 h1:vR8s7b1fWAQIjEjWnuF0JiKsCvclSRTfDzZHTYqfufY= github.com/alibabacloud-go/darabonba-array v0.1.0/go.mod h1:BLKxr0brnggqOJPqT09DFJ8g3fsDshapUD3C3aOEFaI= github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC2NG0Ax+GpOM5gtupki31XE= diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index df7ba601..1fec85e0 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -9,6 +9,7 @@ import ( p1PanelConsole "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/1panel-console" p1PanelSite "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/1panel-site" pAliyunALB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-alb" + pAliyunAPIGW "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-apigw" pAliyunCAS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cas" pAliyunCASDeploy "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cas-deploy" pAliyunCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cdn" @@ -109,7 +110,9 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { ApiUrl: access.ApiUrl, ApiKey: access.ApiKey, AllowInsecureConnections: access.AllowInsecureConnections, + ResourceType: p1PanelSite.ResourceType(maputil.GetOrDefaultString(options.ProviderDeployConfig, "resourceType", string(p1PanelSite.RESOURCE_TYPE_WEBSITE))), WebsiteId: maputil.GetInt64(options.ProviderDeployConfig, "websiteId"), + CertificateId: maputil.GetInt64(options.ProviderDeployConfig, "certificateId"), }) return deployer, err @@ -118,7 +121,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } } - case domain.DeployProviderTypeAliyunALB, domain.DeployProviderTypeAliyunCAS, domain.DeployProviderTypeAliyunCASDeploy, domain.DeployProviderTypeAliyunCDN, domain.DeployProviderTypeAliyunCLB, domain.DeployProviderTypeAliyunDCDN, domain.DeployProviderTypeAliyunESA, domain.DeployProviderTypeAliyunFC, domain.DeployProviderTypeAliyunLive, domain.DeployProviderTypeAliyunNLB, domain.DeployProviderTypeAliyunOSS, domain.DeployProviderTypeAliyunVOD, domain.DeployProviderTypeAliyunWAF: + case domain.DeployProviderTypeAliyunALB, domain.DeployProviderTypeAliyunAPIGW, domain.DeployProviderTypeAliyunCAS, domain.DeployProviderTypeAliyunCASDeploy, domain.DeployProviderTypeAliyunCDN, domain.DeployProviderTypeAliyunCLB, domain.DeployProviderTypeAliyunDCDN, domain.DeployProviderTypeAliyunESA, domain.DeployProviderTypeAliyunFC, domain.DeployProviderTypeAliyunLive, domain.DeployProviderTypeAliyunNLB, domain.DeployProviderTypeAliyunOSS, domain.DeployProviderTypeAliyunVOD, domain.DeployProviderTypeAliyunWAF: { access := domain.AccessConfigForAliyun{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -138,6 +141,18 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { }) return deployer, err + case domain.DeployProviderTypeAliyunAPIGW: + deployer, err := pAliyunAPIGW.NewDeployer(&pAliyunAPIGW.DeployerConfig{ + AccessKeyId: access.AccessKeyId, + AccessKeySecret: access.AccessKeySecret, + Region: maputil.GetString(options.ProviderDeployConfig, "region"), + ServiceType: pAliyunAPIGW.ServiceType(maputil.GetString(options.ProviderDeployConfig, "serviceType")), + GatewayId: maputil.GetString(options.ProviderDeployConfig, "gatewayId"), + GroupId: maputil.GetString(options.ProviderDeployConfig, "groupId"), + Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + }) + return deployer, err + case domain.DeployProviderTypeAliyunCAS: deployer, err := pAliyunCAS.NewDeployer(&pAliyunCAS.DeployerConfig{ AccessKeyId: access.AccessKeyId, @@ -478,7 +493,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { ApiUrl: access.ApiUrl, ApiKey: access.ApiKey, ApiSecret: access.ApiSecret, - ResourceType: pCdnfly.ResourceType(maputil.GetString(options.ProviderDeployConfig, "resourceType")), + ResourceType: pCdnfly.ResourceType(maputil.GetOrDefaultString(options.ProviderDeployConfig, "resourceType", string(pCdnfly.RESOURCE_TYPE_SITE))), SiteId: maputil.GetString(options.ProviderDeployConfig, "siteId"), CertificateId: maputil.GetString(options.ProviderDeployConfig, "certificateId"), }) diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 4abaca96..5c000b3d 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -147,6 +147,7 @@ const ( DeployProviderType1PanelConsole = DeployProviderType("1panel-console") DeployProviderType1PanelSite = DeployProviderType("1panel-site") DeployProviderTypeAliyunALB = DeployProviderType("aliyun-alb") + DeployProviderTypeAliyunAPIGW = DeployProviderType("aliyun-apigw") DeployProviderTypeAliyunCAS = DeployProviderType("aliyun-cas") DeployProviderTypeAliyunCASDeploy = DeployProviderType("aliyun-casdeploy") DeployProviderTypeAliyunCDN = DeployProviderType("aliyun-cdn") diff --git a/internal/pkg/core/deployer/providers/1panel-site/1panel_site.go b/internal/pkg/core/deployer/providers/1panel-site/1panel_site.go index 6aa34607..24f5daa3 100644 --- a/internal/pkg/core/deployer/providers/1panel-site/1panel_site.go +++ b/internal/pkg/core/deployer/providers/1panel-site/1panel_site.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "errors" + "fmt" "log/slog" "net/url" "strconv" @@ -23,8 +24,14 @@ type DeployerConfig struct { ApiKey string `json:"apiKey"` // 是否允许不安全的连接。 AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` + // 部署资源类型。 + ResourceType ResourceType `json:"resourceType"` // 网站 ID。 - WebsiteId int64 `json:"websiteId"` + // 部署资源类型为 [RESOURCE_TYPE_WEBSITE] 时必填。 + WebsiteId int64 `json:"websiteId,omitempty"` + // 证书 ID。 + // 部署资源类型为 [RESOURCE_TYPE_CERTIFICATE] 时必填。 + CertificateId int64 `json:"certificateId,omitempty"` } type DeployerProvider struct { @@ -73,6 +80,30 @@ func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { } func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 根据部署资源类型决定部署方式 + switch d.config.ResourceType { + case RESOURCE_TYPE_WEBSITE: + if err := d.deployToWebsite(ctx, certPem, privkeyPem); err != nil { + return nil, err + } + + case RESOURCE_TYPE_CERTIFICATE: + if err := d.deployToCertificate(ctx, certPem, privkeyPem); err != nil { + return nil, err + } + + default: + return nil, fmt.Errorf("unsupported resource type: %s", d.config.ResourceType) + } + + return &deployer.DeployResult{}, nil +} + +func (d *DeployerProvider) deployToWebsite(ctx context.Context, certPem string, privkeyPem string) error { + if d.config.WebsiteId == 0 { + return errors.New("config `websiteId` is required") + } + // 获取网站 HTTPS 配置 getHttpsConfReq := &opsdk.GetHttpsConfRequest{ WebsiteID: d.config.WebsiteId, @@ -80,13 +111,13 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe getHttpsConfResp, err := d.sdkClient.GetHttpsConf(getHttpsConfReq) d.logger.Debug("sdk request '1panel.GetHttpsConf'", slog.Any("request", getHttpsConfReq), slog.Any("response", getHttpsConfResp)) if err != nil { - return nil, xerrors.Wrap(err, "failed to execute sdk request '1panel.GetHttpsConf'") + return xerrors.Wrap(err, "failed to execute sdk request '1panel.GetHttpsConf'") } // 上传证书到面板 upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) if err != nil { - return nil, xerrors.Wrap(err, "failed to upload certificate file") + return xerrors.Wrap(err, "failed to upload certificate file") } else { d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } @@ -106,10 +137,42 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe updateHttpsConfResp, err := d.sdkClient.UpdateHttpsConf(updateHttpsConfReq) d.logger.Debug("sdk request '1panel.UpdateHttpsConf'", slog.Any("request", updateHttpsConfReq), slog.Any("response", updateHttpsConfResp)) if err != nil { - return nil, xerrors.Wrap(err, "failed to execute sdk request '1panel.UpdateHttpsConf'") + return xerrors.Wrap(err, "failed to execute sdk request '1panel.UpdateHttpsConf'") } - return &deployer.DeployResult{}, nil + return nil +} + +func (d *DeployerProvider) deployToCertificate(ctx context.Context, certPem string, privkeyPem string) error { + if d.config.CertificateId == 0 { + return errors.New("config `certificateId` is required") + } + + // 获取证书详情 + getWebsiteSSLReq := &opsdk.GetWebsiteSSLRequest{ + SSLID: d.config.CertificateId, + } + getWebsiteSSLResp, err := d.sdkClient.GetWebsiteSSL(getWebsiteSSLReq) + d.logger.Debug("sdk request '1panel.GetWebsiteSSL'", slog.Any("request", getWebsiteSSLReq), slog.Any("response", getWebsiteSSLResp)) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request '1panel.GetWebsiteSSL'") + } + + // 更新证书 + uploadWebsiteSSLReq := &opsdk.UploadWebsiteSSLRequest{ + Type: "paste", + SSLID: d.config.CertificateId, + Description: getWebsiteSSLResp.Data.Description, + Certificate: certPem, + PrivateKey: privkeyPem, + } + uploadWebsiteSSLResp, err := d.sdkClient.UploadWebsiteSSL(uploadWebsiteSSLReq) + d.logger.Debug("sdk request '1panel.UploadWebsiteSSL'", slog.Any("request", uploadWebsiteSSLReq), slog.Any("response", uploadWebsiteSSLResp)) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request '1panel.UploadWebsiteSSL'") + } + + return nil } func createSdkClient(apiUrl, apiKey string, allowInsecure bool) (*opsdk.Client, error) { diff --git a/internal/pkg/core/deployer/providers/1panel-site/1panel_site_test.go b/internal/pkg/core/deployer/providers/1panel-site/1panel_site_test.go index 1be2444d..5815fd5e 100644 --- a/internal/pkg/core/deployer/providers/1panel-site/1panel_site_test.go +++ b/internal/pkg/core/deployer/providers/1panel-site/1panel_site_test.go @@ -20,7 +20,7 @@ var ( ) func init() { - argsPrefix := "CERTIMATE_DEPLOYER_1PANELCONSOLE_" + argsPrefix := "CERTIMATE_DEPLOYER_1PANELSITE_" flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") @@ -32,12 +32,12 @@ func init() { /* Shell command to run this test: - go test -v ./1panel_console_test.go -args \ - --CERTIMATE_DEPLOYER_1PANELCONSOLE_INPUTCERTPATH="/path/to/your-input-cert.pem" \ - --CERTIMATE_DEPLOYER_1PANELCONSOLE_INPUTKEYPATH="/path/to/your-input-key.pem" \ - --CERTIMATE_DEPLOYER_1PANELCONSOLE_APIURL="http://127.0.0.1:20410" \ - --CERTIMATE_DEPLOYER_1PANELCONSOLE_APIKEY="your-api-key" \ - --CERTIMATE_DEPLOYER_1PANELCONSOLE_WEBSITEID="your-website-id" + go test -v ./1panel_site_test.go -args \ + --CERTIMATE_DEPLOYER_1PANELSITE_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_1PANELSITE_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_1PANELSITE_APIURL="http://127.0.0.1:20410" \ + --CERTIMATE_DEPLOYER_1PANELSITE_APIKEY="your-api-key" \ + --CERTIMATE_DEPLOYER_1PANELSITE_WEBSITEID="your-website-id" */ func TestDeploy(t *testing.T) { flag.Parse() @@ -55,8 +55,9 @@ func TestDeploy(t *testing.T) { deployer, err := provider.NewDeployer(&provider.DeployerConfig{ ApiUrl: fApiUrl, ApiKey: fApiKey, - WebsiteId: fWebsiteId, AllowInsecureConnections: true, + ResourceType: provider.RESOURCE_TYPE_WEBSITE, + WebsiteId: fWebsiteId, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/internal/pkg/core/deployer/providers/1panel-site/consts.go b/internal/pkg/core/deployer/providers/1panel-site/consts.go new file mode 100644 index 00000000..caba1d5c --- /dev/null +++ b/internal/pkg/core/deployer/providers/1panel-site/consts.go @@ -0,0 +1,10 @@ +package onepanelsite + +type ResourceType string + +const ( + // 资源类型:替换指定网站的证书。 + RESOURCE_TYPE_WEBSITE = ResourceType("website") + // 资源类型:替换指定证书。 + RESOURCE_TYPE_CERTIFICATE = ResourceType("certificate") +) diff --git a/internal/pkg/core/deployer/providers/aliyun-apigw/aliyun_apigw.go b/internal/pkg/core/deployer/providers/aliyun-apigw/aliyun_apigw.go new file mode 100644 index 00000000..12b43616 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-apigw/aliyun_apigw.go @@ -0,0 +1,269 @@ +package aliyunapigw + +import ( + "context" + "errors" + "fmt" + "log/slog" + "strings" + "time" + + aliapig "github.com/alibabacloud-go/apig-20240327/v3/client" + alicloudapi "github.com/alibabacloud-go/cloudapi-20160714/v5/client" + aliopen "github.com/alibabacloud-go/darabonba-openapi/v2/client" + "github.com/alibabacloud-go/tea/tea" + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas" +) + +type DeployerConfig struct { + // 阿里云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 阿里云 AccessKeySecret。 + AccessKeySecret string `json:"accessKeySecret"` + // 阿里云地域。 + Region string `json:"region"` + // 服务类型。 + ServiceType ServiceType `json:"serviceType"` + // API 网关 ID。 + // 服务类型为 [SERVICE_TYPE_CLOUDNATIVE] 时必填。 + GatewayId string `json:"gatewayId,omitempty"` + // API 分组 ID。 + // 服务类型为 [SERVICE_TYPE_TRADITIONAL] 时必填。 + GroupId string `json:"groupId,omitempty"` + // 自定义域名(支持泛域名)。 + Domain string `json:"domain"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sdkClients *wSdkClients + sslUploader uploader.Uploader +} + +type wSdkClients struct { + CloudNativeAPIGateway *aliapig.Client + TraditionalAPIGateway *alicloudapi.Client +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + clients, err := createSdkClients(config.AccessKeyId, config.AccessKeySecret, config.Region) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk clients") + } + + uploader, err := createSslUploader(config.AccessKeyId, config.AccessKeySecret, config.Region) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &DeployerProvider{ + config: config, + logger: slog.Default(), + sdkClients: clients, + sslUploader: uploader, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { + if logger == nil { + d.logger = slog.Default() + } else { + d.logger = logger + } + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + switch d.config.ServiceType { + case SERVICE_TYPE_TRADITIONAL: + if err := d.deployToTraditional(ctx, certPem, privkeyPem); err != nil { + return nil, err + } + + case SERVICE_TYPE_CLOUDNATIVE: + if err := d.deployToCloudNative(ctx, certPem, privkeyPem); err != nil { + return nil, err + } + + default: + return nil, xerrors.Errorf("unsupported service type: %s", string(d.config.ServiceType)) + } + + return &deployer.DeployResult{}, nil +} + +func (d *DeployerProvider) deployToTraditional(ctx context.Context, certPem string, privkeyPem string) error { + if d.config.GroupId == "" { + return errors.New("config `groupId` is required") + } + if d.config.Domain == "" { + return errors.New("config `domain` is required") + } + + // 为自定义域名添加 SSL 证书 + // REF: https://help.aliyun.com/zh/api-gateway/traditional-api-gateway/developer-reference/api-cloudapi-2016-07-14-setdomaincertificate + setDomainCertificateReq := &alicloudapi.SetDomainCertificateRequest{ + GroupId: tea.String(d.config.GroupId), + DomainName: tea.String(d.config.Domain), + CertificateName: tea.String(fmt.Sprintf("certimate_%d", time.Now().UnixMilli())), + CertificateBody: tea.String(certPem), + CertificatePrivateKey: tea.String(privkeyPem), + } + setDomainCertificateResp, err := d.sdkClients.TraditionalAPIGateway.SetDomainCertificate(setDomainCertificateReq) + d.logger.Debug("sdk request 'apigateway.SetDomainCertificate'", slog.Any("request", setDomainCertificateReq), slog.Any("response", setDomainCertificateResp)) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'apigateway.SetDomainCertificate'") + } + + return nil +} + +func (d *DeployerProvider) deployToCloudNative(ctx context.Context, certPem string, privkeyPem string) error { + if d.config.GatewayId == "" { + return errors.New("config `gatewayId` is required") + } + if d.config.Domain == "" { + return errors.New("config `domain` is required") + } + + // 遍历查询域名列表,获取域名 ID + // REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-listdomains + var domainId string + listDomainsPageNumber := int32(1) + listDomainsPageSize := int32(10) + for { + listDomainsReq := &aliapig.ListDomainsRequest{ + GatewayId: tea.String(d.config.GatewayId), + NameLike: tea.String(d.config.Domain), + PageNumber: tea.Int32(listDomainsPageNumber), + PageSize: tea.Int32(listDomainsPageSize), + } + listDomainsResp, err := d.sdkClients.CloudNativeAPIGateway.ListDomains(listDomainsReq) + d.logger.Debug("sdk request 'apig.ListDomains'", slog.Any("request", listDomainsReq), slog.Any("response", listDomainsResp)) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'apig.ListDomains'") + } + + if listDomainsResp.Body.Data.Items != nil { + for _, domainInfo := range listDomainsResp.Body.Data.Items { + if strings.EqualFold(tea.StringValue(domainInfo.Name), d.config.Domain) { + domainId = tea.StringValue(domainInfo.DomainId) + break + } + } + + if domainId != "" { + break + } + } + + if listDomainsResp.Body.Data.Items == nil || len(listDomainsResp.Body.Data.Items) < int(listDomainsPageSize) { + break + } else { + listDomainsPageNumber++ + } + } + if domainId == "" { + return errors.New("domain not found") + } + + // 查询域名 + // REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-getdomain + getDomainReq := &aliapig.GetDomainRequest{} + getDomainResp, err := d.sdkClients.CloudNativeAPIGateway.GetDomain(tea.String(domainId), getDomainReq) + d.logger.Debug("sdk request 'apig.GetDomain'", slog.Any("domainId", domainId), slog.Any("request", getDomainReq), slog.Any("response", getDomainResp)) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'apig.GetDomain'") + } + + // 上传证书到 CAS + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return xerrors.Wrap(err, "failed to upload certificate file") + } else { + d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) + } + + // 更新域名 + // REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-updatedomain + updateDomainReq := &aliapig.UpdateDomainRequest{ + Protocol: tea.String("HTTPS"), + ForceHttps: getDomainResp.Body.Data.ForceHttps, + MTLSEnabled: getDomainResp.Body.Data.MTLSEnabled, + Http2Option: getDomainResp.Body.Data.Http2Option, + TlsMin: getDomainResp.Body.Data.TlsMin, + TlsMax: getDomainResp.Body.Data.TlsMax, + TlsCipherSuitesConfig: getDomainResp.Body.Data.TlsCipherSuitesConfig, + CertIdentifier: tea.String(upres.ExtendedData["certIdentifier"].(string)), + } + updateDomainResp, err := d.sdkClients.CloudNativeAPIGateway.UpdateDomain(tea.String(domainId), updateDomainReq) + d.logger.Debug("sdk request 'apig.UpdateDomain'", slog.Any("domainId", domainId), slog.Any("request", updateDomainReq), slog.Any("response", updateDomainResp)) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'apig.UpdateDomain'") + } + + return nil +} + +func createSdkClients(accessKeyId, accessKeySecret, region string) (*wSdkClients, error) { + // 接入点一览 https://api.aliyun.com/product/APIG + cloudNativeAPIGEndpoint := fmt.Sprintf("apig.%s.aliyuncs.com", region) + cloudNativeAPIGConfig := &aliopen.Config{ + AccessKeyId: tea.String(accessKeyId), + AccessKeySecret: tea.String(accessKeySecret), + Endpoint: tea.String(cloudNativeAPIGEndpoint), + } + cloudNativeAPIGClient, err := aliapig.NewClient(cloudNativeAPIGConfig) + if err != nil { + return nil, err + } + + // 接入点一览 https://api.aliyun.com/product/CloudAPI + traditionalAPIGEndpoint := fmt.Sprintf("apigateway.%s.aliyuncs.com", region) + traditionalAPIGConfig := &aliopen.Config{ + AccessKeyId: tea.String(accessKeyId), + AccessKeySecret: tea.String(accessKeySecret), + Endpoint: tea.String(traditionalAPIGEndpoint), + } + traditionalAPIGClient, err := alicloudapi.NewClient(traditionalAPIGConfig) + if err != nil { + return nil, err + } + + return &wSdkClients{ + CloudNativeAPIGateway: cloudNativeAPIGClient, + TraditionalAPIGateway: traditionalAPIGClient, + }, nil +} + +func createSslUploader(accessKeyId, accessKeySecret, region string) (uploader.Uploader, error) { + casRegion := region + if casRegion != "" { + // 阿里云 CAS 服务接入点是独立于 APIGateway 服务的 + // 国内版固定接入点:华东一杭州 + // 国际版固定接入点:亚太东南一新加坡 + if casRegion != "" && !strings.HasPrefix(casRegion, "cn-") { + casRegion = "ap-southeast-1" + } else { + casRegion = "cn-hangzhou" + } + } + + uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ + AccessKeyId: accessKeyId, + AccessKeySecret: accessKeySecret, + Region: casRegion, + }) + return uploader, err +} diff --git a/internal/pkg/core/deployer/providers/aliyun-apigw/aliyun_apigw_test.go b/internal/pkg/core/deployer/providers/aliyun-apigw/aliyun_apigw_test.go new file mode 100644 index 00000000..dab028e2 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-apigw/aliyun_apigw_test.go @@ -0,0 +1,95 @@ +package aliyunapigw_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-apigw" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fAccessKeySecret string + fRegion string + fServiceType string + fGatewayId string + fGroupId string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNAPIGW_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "") + flag.StringVar(&fRegion, argsPrefix+"REGION", "", "") + flag.StringVar(&fGatewayId, argsPrefix+"GATEWARYID", "", "") + flag.StringVar(&fGroupId, argsPrefix+"GROUPID", "", "") + flag.StringVar(&fServiceType, argsPrefix+"SERVICETYPE", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v ./aliyun_apigw_test.go -args \ + --CERTIMATE_DEPLOYER_ALIYUNAPIGW_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNAPIGW_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNAPIGW_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_ALIYUNAPIGW_ACCESSKEYSECRET="your-access-key-secret" \ + --CERTIMATE_DEPLOYER_ALIYUNAPIGW_REGION="cn-hangzhou" \ + --CERTIMATE_DEPLOYER_ALIYUNAPIGW_GATEWAYID="your-api-gateway-id" \ + --CERTIMATE_DEPLOYER_ALIYUNAPIGW_GROUPID="your-api-group-id" \ + --CERTIMATE_DEPLOYER_ALIYUNAPIGW_SERVICETYPE="cloudnative" \ + --CERTIMATE_DEPLOYER_ALIYUNAPIGW_DOMAIN="example.com" +*/ +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("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("GATEWAYID: %v", fGatewayId), + fmt.Sprintf("GROUPID: %v", fGroupId), + fmt.Sprintf("SERVICETYPE: %v", fServiceType), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Region: fRegion, + ServiceType: provider.ServiceType(fServiceType), + GatewayId: fGatewayId, + GroupId: fGroupId, + Domain: fDomain, + }) + 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/aliyun-apigw/consts.go b/internal/pkg/core/deployer/providers/aliyun-apigw/consts.go new file mode 100644 index 00000000..b53c7645 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-apigw/consts.go @@ -0,0 +1,10 @@ +package aliyunapigw + +type ServiceType string + +const ( + // 服务类型:原 API 网关。 + SERVICE_TYPE_TRADITIONAL = ServiceType("traditional") + // 服务类型:云原生 API 网关。 + SERVICE_TYPE_CLOUDNATIVE = ServiceType("cloudnative") +) diff --git a/internal/pkg/vendors/1panel-sdk/api.go b/internal/pkg/vendors/1panel-sdk/api.go index 8b0a6d09..8fa15393 100644 --- a/internal/pkg/vendors/1panel-sdk/api.go +++ b/internal/pkg/vendors/1panel-sdk/api.go @@ -17,6 +17,12 @@ func (c *Client) SearchWebsiteSSL(req *SearchWebsiteSSLRequest) (*SearchWebsiteS return resp, err } +func (c *Client) GetWebsiteSSL(req *GetWebsiteSSLRequest) (*GetWebsiteSSLResponse, error) { + resp := &GetWebsiteSSLResponse{} + err := c.sendRequestWithResult(http.MethodGet, fmt.Sprintf("/websites/ssl/%d", req.SSLID), req, resp) + return resp, err +} + func (c *Client) UploadWebsiteSSL(req *UploadWebsiteSSLRequest) (*UploadWebsiteSSLResponse, error) { resp := &UploadWebsiteSSLResponse{} err := c.sendRequestWithResult(http.MethodPost, "/websites/ssl/upload", req, resp) diff --git a/internal/pkg/vendors/1panel-sdk/models.go b/internal/pkg/vendors/1panel-sdk/models.go index af1bdbe9..13f144d9 100644 --- a/internal/pkg/vendors/1panel-sdk/models.go +++ b/internal/pkg/vendors/1panel-sdk/models.go @@ -59,6 +59,28 @@ type SearchWebsiteSSLResponse struct { } `json:"data,omitempty"` } +type GetWebsiteSSLRequest struct { + SSLID int64 `json:"-"` +} + +type GetWebsiteSSLResponse struct { + baseResponse + Data *struct { + ID int64 `json:"id"` + Provider string `json:"provider"` + Description string `json:"description"` + PrimaryDomain string `json:"primaryDomain"` + Domains string `json:"domains"` + Type string `json:"type"` + Organization string `json:"organization"` + Status string `json:"status"` + StartDate string `json:"startDate"` + ExpireDate string `json:"expireDate"` + CreatedAt string `json:"createdAt"` + UpdatedAt string `json:"updatedAt"` + } `json:"data,omitempty"` +} + type UploadWebsiteSSLRequest struct { Type string `json:"type"` SSLID int64 `json:"sslID"` diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index 636ff5ac..24d256de 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -18,6 +18,7 @@ import { useWorkflowStore } from "@/stores/workflow"; import DeployNodeConfigForm1PanelConsoleConfig from "./DeployNodeConfigForm1PanelConsoleConfig"; import DeployNodeConfigForm1PanelSiteConfig from "./DeployNodeConfigForm1PanelSiteConfig"; import DeployNodeConfigFormAliyunALBConfig from "./DeployNodeConfigFormAliyunALBConfig"; +import DeployNodeConfigFormAliyunAPIGWConfig from "./DeployNodeConfigFormAliyunAPIGWConfig"; import DeployNodeConfigFormAliyunCASConfig from "./DeployNodeConfigFormAliyunCASConfig"; import DeployNodeConfigFormAliyunCASDeployConfig from "./DeployNodeConfigFormAliyunCASDeployConfig"; import DeployNodeConfigFormAliyunCDNConfig from "./DeployNodeConfigFormAliyunCDNConfig"; @@ -178,6 +179,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOY_PROVIDERS.ALIYUN_ALB: return ; + case DEPLOY_PROVIDERS.ALIYUN_APIGW: + return ; case DEPLOY_PROVIDERS.ALIYUN_CAS: return ; case DEPLOY_PROVIDERS.ALIYUN_CAS_DEPLOY: diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm1PanelSiteConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm1PanelSiteConfig.tsx index f5a26450..94a9bce2 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm1PanelSiteConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm1PanelSiteConfig.tsx @@ -1,10 +1,14 @@ import { useTranslation } from "react-i18next"; -import { Form, type FormInstance, Input } from "antd"; +import { Form, type FormInstance, Input, Select } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; +import Show from "@/components/Show"; + type DeployNodeConfigForm1PanelSiteConfigFieldValues = Nullish<{ - websiteId: string | number; + resourceType: string; + websiteId?: string | number; + certificateId?: string | number; }>; export type DeployNodeConfigForm1PanelSiteConfigProps = { @@ -15,8 +19,13 @@ export type DeployNodeConfigForm1PanelSiteConfigProps = { onValuesChange?: (values: DeployNodeConfigForm1PanelSiteConfigFieldValues) => void; }; +const RESOURCE_TYPE_WEBSITE = "website" as const; +const RESOURCE_TYPE_CERTIFICATE = "certificate" as const; + const initFormModel = (): DeployNodeConfigForm1PanelSiteConfigFieldValues => { - return {}; + return { + resourceType: RESOURCE_TYPE_WEBSITE, + }; }; const DeployNodeConfigForm1PanelSiteConfig = ({ @@ -29,12 +38,28 @@ const DeployNodeConfigForm1PanelSiteConfig = ({ const { t } = useTranslation(); const formSchema = z.object({ - websiteId: z.union([z.string(), z.number()]).refine((v) => { - return /^\d+$/.test(v + "") && +v > 0; - }, t("workflow_node.deploy.form.1panel_site_website_id.placeholder")), + resourceType: z.union([z.literal(RESOURCE_TYPE_WEBSITE), z.literal(RESOURCE_TYPE_CERTIFICATE)], { + message: t("workflow_node.deploy.form.1panel_site_resource_type.placeholder"), + }), + websiteId: z + .union([z.string(), z.number().int()]) + .nullish() + .refine((v) => { + if (fieldResourceType !== RESOURCE_TYPE_WEBSITE) return true; + return /^\d+$/.test(v + "") && +v! > 0; + }, t("workflow_node.deploy.form.1panel_site_website_id.placeholder")), + certificateId: z + .union([z.string(), z.number().int()]) + .nullish() + .refine((v) => { + if (fieldResourceType !== RESOURCE_TYPE_CERTIFICATE) return true; + return /^\d+$/.test(v + "") && +v! > 0; + }, t("workflow_node.deploy.form.1panel_site_certificate_id.placeholder")), }); const formRule = createSchemaFieldRule(formSchema); + const fieldResourceType = Form.useWatch("resourceType", formInst); + const handleFormChange = (_: unknown, values: z.infer) => { onValuesChange?.(values); }; @@ -48,14 +73,38 @@ const DeployNodeConfigForm1PanelSiteConfig = ({ name={formName} onValuesChange={handleFormChange} > - } - > - + + + + + } + > + + + + + + } + > + + + ); }; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunAPIGWConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunAPIGWConfig.tsx new file mode 100644 index 00000000..430859a7 --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunAPIGWConfig.tsx @@ -0,0 +1,133 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; +import { validDomainName } from "@/utils/validators"; + +type DeployNodeConfigFormAliyunAPIGWConfigFieldValues = Nullish<{ + serviceType: string; + region: string; + gatewayId?: string; + groupId?: string; + domain?: string; +}>; + +export type DeployNodeConfigFormAliyunAPIGWConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormAliyunAPIGWConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormAliyunAPIGWConfigFieldValues) => void; +}; + +const SERVICE_TYPE_CLOUDNATIVE = "cloudnative" as const; +const SERVICE_TYPE_TRADITIONAL = "traditional" as const; + +const initFormModel = (): DeployNodeConfigFormAliyunAPIGWConfigFieldValues => { + return {}; +}; + +const DeployNodeConfigFormAliyunAPIGWConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: DeployNodeConfigFormAliyunAPIGWConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + serviceType: z.union([z.literal(SERVICE_TYPE_CLOUDNATIVE), z.literal(SERVICE_TYPE_TRADITIONAL)], { + message: t("workflow_node.deploy.form.aliyun_apigw_service_type.placeholder"), + }), + region: z + .string({ message: t("workflow_node.deploy.form.aliyun_apigw_region.placeholder") }) + .nonempty(t("workflow_node.deploy.form.aliyun_apigw_region.placeholder")) + .trim(), + gatewayId: z + .string() + .nullish() + .refine((v) => fieldServiceType !== SERVICE_TYPE_CLOUDNATIVE || !!v?.trim(), t("workflow_node.deploy.form.aliyun_apigw_gateway_id.placeholder")), + groupId: z + .string() + .nullish() + .refine((v) => fieldServiceType !== SERVICE_TYPE_TRADITIONAL || !!v?.trim(), t("workflow_node.deploy.form.aliyun_apigw_group_id.placeholder")), + domain: z + .string() + .nonempty(t("workflow_node.deploy.form.aliyun_apigw_domain.placeholder")) + .refine((v) => validDomainName(v!, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const fieldServiceType = Form.useWatch("serviceType", formInst); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ + + + + } + > + + + + + } + > + + + + + + } + > + + + + + } + > + + +
+ ); +}; + +export default DeployNodeConfigFormAliyunAPIGWConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormCdnflyConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormCdnflyConfig.tsx index 4ce459ac..45662e75 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormCdnflyConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormCdnflyConfig.tsx @@ -7,6 +7,7 @@ import Show from "@/components/Show"; type DeployNodeConfigFormCdnflyConfigFieldValues = Nullish<{ resourceType: string; + siteId?: string | number; certificateId?: string | number; }>; @@ -34,10 +35,13 @@ const DeployNodeConfigFormCdnflyConfig = ({ form: formInst, formName, disabled, resourceType: z.union([z.literal(RESOURCE_TYPE_SITE), z.literal(RESOURCE_TYPE_CERTIFICATE)], { message: t("workflow_node.deploy.form.cdnfly_resource_type.placeholder"), }), - siteId: z.union([z.string(), z.number().int()]).refine((v) => { - if (fieldResourceType !== RESOURCE_TYPE_SITE) return true; - return /^\d+$/.test(v + "") && +v > 0; - }, t("workflow_node.deploy.form.cdnfly_site_id.placeholder")), + siteId: z + .union([z.string(), z.number().int()]) + .nullish() + .refine((v) => { + if (fieldResourceType !== RESOURCE_TYPE_SITE) return true; + return /^\d+$/.test(v + "") && +v! > 0; + }, t("workflow_node.deploy.form.cdnfly_site_id.placeholder")), certificateId: z .union([z.string(), z.number().int()]) .nullish() diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormLocalConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormLocalConfig.tsx index 1ca2fe8c..afde53b3 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormLocalConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormLocalConfig.tsx @@ -39,8 +39,8 @@ const SHELLENV_POWERSHELL = "powershell" as const; const initFormModel = (): DeployNodeConfigFormLocalConfigFieldValues => { return { format: FORMAT_PEM, - certPath: "/etc/ssl/certs/cert.crt", - keyPath: "/etc/ssl/certs/cert.key", + certPath: "/etc/ssl/certimate/cert.crt", + keyPath: "/etc/ssl/certimate/cert.key", shellEnv: SHELLENV_SH, }; }; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx index 65a7df7b..c6d5db5a 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx @@ -35,8 +35,8 @@ const FORMAT_JKS = CERTIFICATE_FORMATS.JKS; const initFormModel = (): DeployNodeConfigFormSSHConfigFieldValues => { return { format: FORMAT_PEM, - certPath: "/etc/ssl/certs/cert.crt", - keyPath: "/etc/ssl/certs/cert.key", + certPath: "/etc/ssl/certimate/cert.crt", + keyPath: "/etc/ssl/certimate/cert.key", }; }; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 73c7c06c..a91e34cf 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -309,6 +309,7 @@ export const DEPLOY_PROVIDERS = Object.freeze({ ["1PANEL_CONSOLE"]: `${ACCESS_PROVIDERS["1PANEL"]}-console`, ["1PANEL_SITE"]: `${ACCESS_PROVIDERS["1PANEL"]}-site`, ALIYUN_ALB: `${ACCESS_PROVIDERS.ALIYUN}-alb`, + ALIYUN_APIGW: `${ACCESS_PROVIDERS.ALIYUN}-apigw`, ALIYUN_CAS: `${ACCESS_PROVIDERS.ALIYUN}-cas`, ALIYUN_CAS_DEPLOY: `${ACCESS_PROVIDERS.ALIYUN}-casdeploy`, ALIYUN_CDN: `${ACCESS_PROVIDERS.ALIYUN}-cdn`, @@ -427,6 +428,7 @@ export const deployProvidersMap: Maphttps://slb.console.aliyun.com/alb", + "workflow_node.deploy.form.aliyun_apigw_service_type.label": "Alibaba Cloud API gateway type", + "workflow_node.deploy.form.aliyun_apigw_service_type.placeholder": "Please select Alibaba Cloud API gateway type", + "workflow_node.deploy.form.aliyun_apigw_service_type.option.cloudnative.label": "Cloud-native API gateway", + "workflow_node.deploy.form.aliyun_apigw_service_type.option.traditional.label": "Traditional API gateway", + "workflow_node.deploy.form.aliyun_apigw_region.label": "Alibaba Cloud API gateway region", + "workflow_node.deploy.form.aliyun_apigw_region.placeholder": "Please enter Alibaba Cloud API gateway region (e.g. cn-hangzhou)", + "workflow_node.deploy.form.aliyun_apigw_region.tooltip": "For more information, see https://www.alibabacloud.com/help/en/api-gateway/cloud-native-api-gateway/product-overview/regions", + "workflow_node.deploy.form.aliyun_apigw_gateway_id.label": "Alibaba Cloud API gateway ID", + "workflow_node.deploy.form.aliyun_apigw_gateway_id.placeholder": "Please enter Alibaba Cloud API gateway ID", + "workflow_node.deploy.form.aliyun_apigw_gateway_id.tooltip": "For more information, see https://apigw.console.aliyun.com", + "workflow_node.deploy.form.aliyun_apigw_group_id.label": "Alibaba Cloud API group ID", + "workflow_node.deploy.form.aliyun_apigw_group_id.placeholder": "Please enter Alibaba Cloud API group ID", + "workflow_node.deploy.form.aliyun_apigw_group_id.tooltip": "For more information, see https://apigateway.console.aliyun.com", + "workflow_node.deploy.form.aliyun_apigw_domain.label": "Alibaba Cloud API gateway domain", + "workflow_node.deploy.form.aliyun_apigw_domain.placeholder": "Please enter Alibaba Cloud API gateway domain", + "workflow_node.deploy.form.aliyun_apigw_domain.tooltip": "For more information, see https://apigw.console.aliyun.com or https://apigateway.console.aliyun.com", "workflow_node.deploy.form.aliyun_cas_region.label": "Alibaba Cloud CAS region", "workflow_node.deploy.form.aliyun_cas_region.placeholder": "Please enter Alibaba Cloud CAS region (e.g. cn-hangzhou)", "workflow_node.deploy.form.aliyun_cas_region.tooltip": "For more information, see https://www.alibabacloud.com/help/en/ssl-certificate/developer-reference/endpoints", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index 8443edc7..86fd3e5b 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -5,6 +5,7 @@ "provider.acmehttpreq": "Http Request (ACME Proxy)", "provider.aliyun": "阿里云", "provider.aliyun.alb": "阿里云 - 应用型负载均衡 ALB", + "provider.aliyun.apigw": "阿里云 - API 网关", "provider.aliyun.cas_upload": "阿里云 - 上传到数字证书管理服务 CAS", "provider.aliyun.cas_deploy": "阿里云 - 通过数字证书管理服务 CAS 创建部署任务", "provider.aliyun.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 e37ca069..d3b5b494 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -102,9 +102,16 @@ "workflow_node.deploy.form.certificate.tooltip": "待部署证书来自之前的申请或上传节点。如果选项为空请先确保前序节点配置正确。", "workflow_node.deploy.form.params_config.label": "参数设置", "workflow_node.deploy.form.1panel_console_auto_restart.label": "部署后自动重启面板服务", + "workflow_node.deploy.form.1panel_site_resource_type.label": "证书替换方式", + "workflow_node.deploy.form.1panel_site_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.1panel_site_resource_type.option.website.label": "替换指定网站的证书", + "workflow_node.deploy.form.1panel_site_resource_type.option.certificate.label": "替换指定证书", "workflow_node.deploy.form.1panel_site_website_id.label": "1Panel 网站 ID", "workflow_node.deploy.form.1panel_site_website_id.placeholder": "请输入 1Panel 网站 ID", "workflow_node.deploy.form.1panel_site_website_id.tooltip": "请在 1Panel 管理面板查看。", + "workflow_node.deploy.form.1panel_site_certificate_id.label": "1Panel 证书 ID", + "workflow_node.deploy.form.1panel_site_certificate_id.placeholder": "请输入 1Panel 证书 ID", + "workflow_node.deploy.form.1panel_site_certificate_id.tooltip": "请在 1Panel 管理面板查看。", "workflow_node.deploy.form.aliyun_alb_resource_type.label": "证书替换方式", "workflow_node.deploy.form.aliyun_alb_resource_type.placeholder": "请选择证书替换方式", "workflow_node.deploy.form.aliyun_alb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS/QUIC 监听的证书", @@ -121,6 +128,22 @@ "workflow_node.deploy.form.aliyun_alb_snidomain.label": "阿里云 ALB 扩展域名(可选)", "workflow_node.deploy.form.aliyun_alb_snidomain.placeholder": "请输入阿里云 ALB 扩展域名(支持泛域名)", "workflow_node.deploy.form.aliyun_alb_snidomain.tooltip": "这是什么?请参阅 https://slb.console.aliyun.com/alb

不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。", + "workflow_node.deploy.form.aliyun_apigw_service_type.label": "阿里云 API 网关服务类型", + "workflow_node.deploy.form.aliyun_apigw_service_type.placeholder": "请选择阿里云 API 网关服务类型", + "workflow_node.deploy.form.aliyun_apigw_service_type.option.cloudnative.label": "云原生 API 网关", + "workflow_node.deploy.form.aliyun_apigw_service_type.option.traditional.label": "原 API 网关", + "workflow_node.deploy.form.aliyun_apigw_region.label": "阿里云 API 网关服务地域", + "workflow_node.deploy.form.aliyun_apigw_region.placeholder": "请输入阿里云 API 网关地域(例如:cn-hangzhou)", + "workflow_node.deploy.form.aliyun_apigw_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/product-overview/regions", + "workflow_node.deploy.form.aliyun_apigw_gateway_id.label": "阿里云 API 网关 ID", + "workflow_node.deploy.form.aliyun_apigw_gateway_id.placeholder": "请输入阿里云 API 网关 ID", + "workflow_node.deploy.form.aliyun_apigw_gateway_id.tooltip": "这是什么?请参阅 https://apigw.console.aliyun.com", + "workflow_node.deploy.form.aliyun_apigw_group_id.label": "阿里云 API 分组 ID", + "workflow_node.deploy.form.aliyun_apigw_group_id.placeholder": "请输入阿里云 API 分组 ID", + "workflow_node.deploy.form.aliyun_apigw_group_id.tooltip": "这是什么?请参阅 https://apigateway.console.aliyun.com", + "workflow_node.deploy.form.aliyun_apigw_domain.label": "阿里云 API 网关自定义域名", + "workflow_node.deploy.form.aliyun_apigw_domain.placeholder": "请输入阿里云 API 网关自定义域名(支持泛域名)", + "workflow_node.deploy.form.aliyun_apigw_domain.tooltip": "这是什么?请参阅 https://apigw.console.aliyun.comhttps://apigateway.console.aliyun.com", "workflow_node.deploy.form.aliyun_cas_region.label": "阿里云 CAS 服务地域", "workflow_node.deploy.form.aliyun_cas_region.placeholder": "请输入阿里云 CAS 服务地域(例如:cn-hangzhou)", "workflow_node.deploy.form.aliyun_cas_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/ssl-certificate/developer-reference/endpoints",