diff --git a/go.mod b/go.mod index c9cdb902..0827c9df 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 f7267783..0b164a0d 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 c2136d20..421ab2f3 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" @@ -117,7 +118,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 { @@ -137,6 +138,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, diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 668612f7..aae360a4 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -146,6 +146,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/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/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index 5565c985..893460ff 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"; @@ -177,6 +178,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/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/domain/provider.ts b/ui/src/domain/provider.ts index dbd81a0f..830bc369 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -305,6 +305,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`, @@ -422,6 +423,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 216d3363..25679334 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 5fcb201d..03ce4875 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -121,6 +121,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",