mirror of https://github.com/usual2970/certimate
				
				
				
			feat: add huaweicloud waf uploader
							parent
							
								
									b734ffcf9d
								
							
						
					
					
						commit
						a6f1f21c18
					
				
							
								
								
									
										2
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										2
									
								
								go.mod
								
								
								
								
							| 
						 | 
				
			
			@ -24,7 +24,7 @@ require (
 | 
			
		|||
	github.com/go-acme/lego/v4 v4.21.0
 | 
			
		||||
	github.com/go-resty/resty/v2 v2.16.5
 | 
			
		||||
	github.com/go-viper/mapstructure/v2 v2.2.1
 | 
			
		||||
	github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.135
 | 
			
		||||
	github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.136
 | 
			
		||||
	github.com/nikoksr/notify v1.3.0
 | 
			
		||||
	github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0
 | 
			
		||||
	github.com/pkg/sftp v1.13.7
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										2
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										2
									
								
								go.sum
								
								
								
								
							| 
						 | 
				
			
			@ -558,6 +558,8 @@ github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKEN
 | 
			
		|||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 | 
			
		||||
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.135 h1:UbNMlPfh0GhRY3iVkvv4fXFJ+bLqXoVCwjqe6geFdPs=
 | 
			
		||||
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.135/go.mod h1:Y/+YLCFCJtS29i2MbYPTUlNNfwXvkzEsZKR0imY/2aY=
 | 
			
		||||
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.136 h1:T785NUg5245nWpPVHLVR8lBd+zGQYR14Vi/TCX1iu3A=
 | 
			
		||||
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.136/go.mod h1:Y/+YLCFCJtS29i2MbYPTUlNNfwXvkzEsZKR0imY/2aY=
 | 
			
		||||
github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo=
 | 
			
		||||
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
 | 
			
		||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,15 +29,15 @@ type AliyunALBDeployerConfig struct {
 | 
			
		|||
	// 阿里云地域。
 | 
			
		||||
	Region string `json:"region"`
 | 
			
		||||
	// 部署资源类型。
 | 
			
		||||
	ResourceType DeployResourceType `json:"resourceType"`
 | 
			
		||||
	ResourceType ResourceType `json:"resourceType"`
 | 
			
		||||
	// 负载均衡实例 ID。
 | 
			
		||||
	// 部署资源类型为 [DEPLOY_RESOURCE_LOADBALANCER] 时必填。
 | 
			
		||||
	// 部署资源类型为 [RESOURCE_TYPE_LOADBALANCER] 时必填。
 | 
			
		||||
	LoadbalancerId string `json:"loadbalancerId,omitempty"`
 | 
			
		||||
	// 负载均衡监听 ID。
 | 
			
		||||
	// 部署资源类型为 [DEPLOY_RESOURCE_LISTENER] 时必填。
 | 
			
		||||
	// 部署资源类型为 [RESOURCE_TYPE_LISTENER] 时必填。
 | 
			
		||||
	ListenerId string `json:"listenerId,omitempty"`
 | 
			
		||||
	// SNI 域名(支持泛域名)。
 | 
			
		||||
	// 部署资源类型为 [DEPLOY_RESOURCE_LOADBALANCER]、[DEPLOY_RESOURCE_LISTENER] 时选填。
 | 
			
		||||
	// 部署资源类型为 [RESOURCE_TYPE_LOADBALANCER]、[RESOURCE_TYPE_LISTENER] 时选填。
 | 
			
		||||
	Domain string `json:"domain,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -97,12 +97,12 @@ func (d *AliyunALBDeployer) Deploy(ctx context.Context, certPem string, privkeyP
 | 
			
		|||
 | 
			
		||||
	// 根据部署资源类型决定部署方式
 | 
			
		||||
	switch d.config.ResourceType {
 | 
			
		||||
	case DEPLOY_RESOURCE_LOADBALANCER:
 | 
			
		||||
	case RESOURCE_TYPE_LOADBALANCER:
 | 
			
		||||
		if err := d.deployToLoadbalancer(ctx, upres.CertId); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	case DEPLOY_RESOURCE_LISTENER:
 | 
			
		||||
	case RESOURCE_TYPE_LISTENER:
 | 
			
		||||
		if err := d.deployToListener(ctx, upres.CertId); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -67,7 +67,7 @@ func TestDeploy(t *testing.T) {
 | 
			
		|||
			AccessKeyId:     fAccessKeyId,
 | 
			
		||||
			AccessKeySecret: fAccessKeySecret,
 | 
			
		||||
			Region:          fRegion,
 | 
			
		||||
			ResourceType:    provider.DEPLOY_RESOURCE_LOADBALANCER,
 | 
			
		||||
			ResourceType:    provider.RESOURCE_TYPE_LOADBALANCER,
 | 
			
		||||
			LoadbalancerId:  fLoadbalancerId,
 | 
			
		||||
			Domain:          fDomain,
 | 
			
		||||
		})
 | 
			
		||||
| 
						 | 
				
			
			@ -102,7 +102,7 @@ func TestDeploy(t *testing.T) {
 | 
			
		|||
			AccessKeyId:     fAccessKeyId,
 | 
			
		||||
			AccessKeySecret: fAccessKeySecret,
 | 
			
		||||
			Region:          fRegion,
 | 
			
		||||
			ResourceType:    provider.DEPLOY_RESOURCE_LISTENER,
 | 
			
		||||
			ResourceType:    provider.RESOURCE_TYPE_LISTENER,
 | 
			
		||||
			ListenerId:      fListenerId,
 | 
			
		||||
			Domain:          fDomain,
 | 
			
		||||
		})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
package aliyunalb
 | 
			
		||||
 | 
			
		||||
type ResourceType string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// 资源类型:部署到指定负载均衡器。
 | 
			
		||||
	RESOURCE_TYPE_LOADBALANCER = ResourceType("loadbalancer")
 | 
			
		||||
	// 资源类型:部署到指定监听器。
 | 
			
		||||
	RESOURCE_TYPE_LISTENER = ResourceType("listener")
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -1,10 +0,0 @@
 | 
			
		|||
package aliyunalb
 | 
			
		||||
 | 
			
		||||
type DeployResourceType string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// 资源类型:部署到指定负载均衡器。
 | 
			
		||||
	DEPLOY_RESOURCE_LOADBALANCER = DeployResourceType("loadbalancer")
 | 
			
		||||
	// 资源类型:部署到指定监听器。
 | 
			
		||||
	DEPLOY_RESOURCE_LISTENER = DeployResourceType("listener")
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -24,15 +24,15 @@ type AliyunCLBDeployerConfig struct {
 | 
			
		|||
	// 阿里云地域。
 | 
			
		||||
	Region string `json:"region"`
 | 
			
		||||
	// 部署资源类型。
 | 
			
		||||
	ResourceType DeployResourceType `json:"resourceType"`
 | 
			
		||||
	ResourceType ResourceType `json:"resourceType"`
 | 
			
		||||
	// 负载均衡实例 ID。
 | 
			
		||||
	// 部署资源类型为 [DEPLOY_RESOURCE_LOADBALANCER]、[DEPLOY_RESOURCE_LISTENER] 时必填。
 | 
			
		||||
	// 部署资源类型为 [RESOURCE_TYPE_LOADBALANCER]、[RESOURCE_TYPE_LISTENER] 时必填。
 | 
			
		||||
	LoadbalancerId string `json:"loadbalancerId,omitempty"`
 | 
			
		||||
	// 负载均衡监听端口。
 | 
			
		||||
	// 部署资源类型为 [DEPLOY_RESOURCE_LISTENER] 时必填。
 | 
			
		||||
	// 部署资源类型为 [RESOURCE_TYPE_LISTENER] 时必填。
 | 
			
		||||
	ListenerPort int32 `json:"listenerPort,omitempty"`
 | 
			
		||||
	// SNI 域名(支持泛域名)。
 | 
			
		||||
	// 部署资源类型为 [DEPLOY_RESOURCE_LOADBALANCER]、[DEPLOY_RESOURCE_LISTENER] 时选填。
 | 
			
		||||
	// 部署资源类型为 [RESOURCE_TYPE_LOADBALANCER]、[RESOURCE_TYPE_LISTENER] 时选填。
 | 
			
		||||
	Domain string `json:"domain,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -91,12 +91,12 @@ func (d *AliyunCLBDeployer) Deploy(ctx context.Context, certPem string, privkeyP
 | 
			
		|||
 | 
			
		||||
	// 根据部署资源类型决定部署方式
 | 
			
		||||
	switch d.config.ResourceType {
 | 
			
		||||
	case DEPLOY_RESOURCE_LOADBALANCER:
 | 
			
		||||
	case RESOURCE_TYPE_LOADBALANCER:
 | 
			
		||||
		if err := d.deployToLoadbalancer(ctx, upres.CertId); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	case DEPLOY_RESOURCE_LISTENER:
 | 
			
		||||
	case RESOURCE_TYPE_LISTENER:
 | 
			
		||||
		if err := d.deployToListener(ctx, upres.CertId); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -67,7 +67,7 @@ func TestDeploy(t *testing.T) {
 | 
			
		|||
			AccessKeyId:     fAccessKeyId,
 | 
			
		||||
			AccessKeySecret: fAccessKeySecret,
 | 
			
		||||
			Region:          fRegion,
 | 
			
		||||
			ResourceType:    provider.DEPLOY_RESOURCE_LOADBALANCER,
 | 
			
		||||
			ResourceType:    provider.RESOURCE_TYPE_LOADBALANCER,
 | 
			
		||||
			LoadbalancerId:  fLoadbalancerId,
 | 
			
		||||
			Domain:          fDomain,
 | 
			
		||||
		})
 | 
			
		||||
| 
						 | 
				
			
			@ -103,7 +103,7 @@ func TestDeploy(t *testing.T) {
 | 
			
		|||
			AccessKeyId:     fAccessKeyId,
 | 
			
		||||
			AccessKeySecret: fAccessKeySecret,
 | 
			
		||||
			Region:          fRegion,
 | 
			
		||||
			ResourceType:    provider.DEPLOY_RESOURCE_LISTENER,
 | 
			
		||||
			ResourceType:    provider.RESOURCE_TYPE_LISTENER,
 | 
			
		||||
			LoadbalancerId:  fLoadbalancerId,
 | 
			
		||||
			ListenerPort:    int32(fListenerPort),
 | 
			
		||||
			Domain:          fDomain,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
package aliyunclb
 | 
			
		||||
 | 
			
		||||
type ResourceType string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// 资源类型:部署到指定负载均衡器。
 | 
			
		||||
	RESOURCE_TYPE_LOADBALANCER = ResourceType("loadbalancer")
 | 
			
		||||
	// 资源类型:部署到指定监听器。
 | 
			
		||||
	RESOURCE_TYPE_LISTENER = ResourceType("listener")
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -1,10 +0,0 @@
 | 
			
		|||
package aliyunclb
 | 
			
		||||
 | 
			
		||||
type DeployResourceType string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// 资源类型:部署到指定负载均衡器。
 | 
			
		||||
	DEPLOY_RESOURCE_LOADBALANCER = DeployResourceType("loadbalancer")
 | 
			
		||||
	// 资源类型:部署到指定监听器。
 | 
			
		||||
	DEPLOY_RESOURCE_LISTENER = DeployResourceType("listener")
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -25,12 +25,12 @@ type AliyunNLBDeployerConfig struct {
 | 
			
		|||
	// 阿里云地域。
 | 
			
		||||
	Region string `json:"region"`
 | 
			
		||||
	// 部署资源类型。
 | 
			
		||||
	ResourceType DeployResourceType `json:"resourceType"`
 | 
			
		||||
	ResourceType ResourceType `json:"resourceType"`
 | 
			
		||||
	// 负载均衡实例 ID。
 | 
			
		||||
	// 部署资源类型为 [DEPLOY_RESOURCE_LOADBALANCER] 时必填。
 | 
			
		||||
	// 部署资源类型为 [RESOURCE_TYPE_LOADBALANCER] 时必填。
 | 
			
		||||
	LoadbalancerId string `json:"loadbalancerId,omitempty"`
 | 
			
		||||
	// 负载均衡监听 ID。
 | 
			
		||||
	// 部署资源类型为 [DEPLOY_RESOURCE_LISTENER] 时必填。
 | 
			
		||||
	// 部署资源类型为 [RESOURCE_TYPE_LISTENER] 时必填。
 | 
			
		||||
	ListenerId string `json:"listenerId,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -85,12 +85,12 @@ func (d *AliyunNLBDeployer) Deploy(ctx context.Context, certPem string, privkeyP
 | 
			
		|||
 | 
			
		||||
	// 根据部署资源类型决定部署方式
 | 
			
		||||
	switch d.config.ResourceType {
 | 
			
		||||
	case DEPLOY_RESOURCE_LOADBALANCER:
 | 
			
		||||
	case RESOURCE_TYPE_LOADBALANCER:
 | 
			
		||||
		if err := d.deployToLoadbalancer(ctx, upres.CertId); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	case DEPLOY_RESOURCE_LISTENER:
 | 
			
		||||
	case RESOURCE_TYPE_LISTENER:
 | 
			
		||||
		if err := d.deployToListener(ctx, upres.CertId); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -63,7 +63,7 @@ func TestDeploy(t *testing.T) {
 | 
			
		|||
			AccessKeyId:     fAccessKeyId,
 | 
			
		||||
			AccessKeySecret: fAccessKeySecret,
 | 
			
		||||
			Region:          fRegion,
 | 
			
		||||
			ResourceType:    provider.DEPLOY_RESOURCE_LOADBALANCER,
 | 
			
		||||
			ResourceType:    provider.RESOURCE_TYPE_LOADBALANCER,
 | 
			
		||||
			LoadbalancerId:  fLoadbalancerId,
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -98,7 +98,7 @@ func TestDeploy(t *testing.T) {
 | 
			
		|||
			AccessKeyId:     fAccessKeyId,
 | 
			
		||||
			AccessKeySecret: fAccessKeySecret,
 | 
			
		||||
			Region:          fRegion,
 | 
			
		||||
			ResourceType:    provider.DEPLOY_RESOURCE_LISTENER,
 | 
			
		||||
			ResourceType:    provider.RESOURCE_TYPE_LISTENER,
 | 
			
		||||
			ListenerId:      fListenerId,
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
package aliyunnlb
 | 
			
		||||
 | 
			
		||||
type ResourceType string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// 资源类型:部署到指定负载均衡器。
 | 
			
		||||
	RESOURCE_TYPE_LOADBALANCER = ResourceType("loadbalancer")
 | 
			
		||||
	// 资源类型:部署到指定监听器。
 | 
			
		||||
	RESOURCE_TYPE_LISTENER = ResourceType("listener")
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -1,10 +0,0 @@
 | 
			
		|||
package aliyunnlb
 | 
			
		||||
 | 
			
		||||
type DeployResourceType string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// 资源类型:部署到指定负载均衡器。
 | 
			
		||||
	DEPLOY_RESOURCE_LOADBALANCER = DeployResourceType("loadbalancer")
 | 
			
		||||
	// 资源类型:部署到指定监听器。
 | 
			
		||||
	DEPLOY_RESOURCE_LISTENER = DeployResourceType("listener")
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
package huaweicloudelb
 | 
			
		||||
 | 
			
		||||
type ResourceType string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// 资源类型:替换指定证书。
 | 
			
		||||
	RESOURCE_TYPE_CERTIFICATE = ResourceType("certificate")
 | 
			
		||||
	// 资源类型:部署到指定负载均衡器。
 | 
			
		||||
	RESOURCE_TYPE_LOADBALANCER = ResourceType("loadbalancer")
 | 
			
		||||
	// 资源类型:部署到指定监听器。
 | 
			
		||||
	RESOURCE_TYPE_LISTENER = ResourceType("listener")
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -1,12 +0,0 @@
 | 
			
		|||
package huaweicloudelb
 | 
			
		||||
 | 
			
		||||
type DeployResourceType string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// 资源类型:替换指定证书。
 | 
			
		||||
	DEPLOY_RESOURCE_CERTIFICATE = DeployResourceType("certificate")
 | 
			
		||||
	// 资源类型:部署到指定负载均衡器。
 | 
			
		||||
	DEPLOY_RESOURCE_LOADBALANCER = DeployResourceType("loadbalancer")
 | 
			
		||||
	// 资源类型:部署到指定监听器。
 | 
			
		||||
	DEPLOY_RESOURCE_LISTENER = DeployResourceType("listener")
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -31,15 +31,15 @@ type HuaweiCloudELBDeployerConfig struct {
 | 
			
		|||
	// 华为云区域。
 | 
			
		||||
	Region string `json:"region"`
 | 
			
		||||
	// 部署资源类型。
 | 
			
		||||
	ResourceType DeployResourceType `json:"resourceType"`
 | 
			
		||||
	ResourceType ResourceType `json:"resourceType"`
 | 
			
		||||
	// 证书 ID。
 | 
			
		||||
	// 部署资源类型为 [DEPLOY_RESOURCE_CERTIFICATE] 时必填。
 | 
			
		||||
	// 部署资源类型为 [RESOURCE_TYPE_CERTIFICATE] 时必填。
 | 
			
		||||
	CertificateId string `json:"certificateId,omitempty"`
 | 
			
		||||
	// 负载均衡器 ID。
 | 
			
		||||
	// 部署资源类型为 [DEPLOY_RESOURCE_LOADBALANCER] 时必填。
 | 
			
		||||
	// 部署资源类型为 [RESOURCE_TYPE_LOADBALANCER] 时必填。
 | 
			
		||||
	LoadbalancerId string `json:"loadbalancerId,omitempty"`
 | 
			
		||||
	// 负载均衡监听 ID。
 | 
			
		||||
	// 部署资源类型为 [DEPLOY_RESOURCE_LISTENER] 时必填。
 | 
			
		||||
	// 部署资源类型为 [RESOURCE_TYPE_LISTENER] 时必填。
 | 
			
		||||
	ListenerId string `json:"listenerId,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -98,17 +98,17 @@ func (d *HuaweiCloudELBDeployer) Deploy(ctx context.Context, certPem string, pri
 | 
			
		|||
 | 
			
		||||
	// 根据部署资源类型决定部署方式
 | 
			
		||||
	switch d.config.ResourceType {
 | 
			
		||||
	case DEPLOY_RESOURCE_CERTIFICATE:
 | 
			
		||||
	case RESOURCE_TYPE_CERTIFICATE:
 | 
			
		||||
		if err := d.deployToCertificate(ctx, certPem, privkeyPem); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	case DEPLOY_RESOURCE_LOADBALANCER:
 | 
			
		||||
	case RESOURCE_TYPE_LOADBALANCER:
 | 
			
		||||
		if err := d.deployToLoadbalancer(ctx, certPem, privkeyPem); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	case DEPLOY_RESOURCE_LISTENER:
 | 
			
		||||
	case RESOURCE_TYPE_LISTENER:
 | 
			
		||||
		if err := d.deployToListener(ctx, certPem, privkeyPem); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -66,7 +66,7 @@ func TestDeploy(t *testing.T) {
 | 
			
		|||
			AccessKeyId:     fAccessKeyId,
 | 
			
		||||
			SecretAccessKey: fSecretAccessKey,
 | 
			
		||||
			Region:          fRegion,
 | 
			
		||||
			ResourceType:    provider.DEPLOY_RESOURCE_CERTIFICATE,
 | 
			
		||||
			ResourceType:    provider.RESOURCE_TYPE_CERTIFICATE,
 | 
			
		||||
			CertificateId:   fCertificateId,
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -100,7 +100,7 @@ func TestDeploy(t *testing.T) {
 | 
			
		|||
			AccessKeyId:     fAccessKeyId,
 | 
			
		||||
			SecretAccessKey: fSecretAccessKey,
 | 
			
		||||
			Region:          fRegion,
 | 
			
		||||
			ResourceType:    provider.DEPLOY_RESOURCE_LOADBALANCER,
 | 
			
		||||
			ResourceType:    provider.RESOURCE_TYPE_LOADBALANCER,
 | 
			
		||||
			LoadbalancerId:  fLoadbalancerId,
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -134,7 +134,7 @@ func TestDeploy(t *testing.T) {
 | 
			
		|||
			AccessKeyId:     fAccessKeyId,
 | 
			
		||||
			SecretAccessKey: fSecretAccessKey,
 | 
			
		||||
			Region:          fRegion,
 | 
			
		||||
			ResourceType:    provider.DEPLOY_RESOURCE_LISTENER,
 | 
			
		||||
			ResourceType:    provider.RESOURCE_TYPE_LISTENER,
 | 
			
		||||
			ListenerId:      fListenerId,
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
package tencentcloudclb
 | 
			
		||||
 | 
			
		||||
type ResourceType string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// 资源类型:通过 SSL 服务部署到云资源实例。
 | 
			
		||||
	RESOURCE_TYPE_VIA_SSLDEPLOY = ResourceType("ssl-deploy")
 | 
			
		||||
	// 资源类型:部署到指定负载均衡器。
 | 
			
		||||
	RESOURCE_TYPE_LOADBALANCER = ResourceType("loadbalancer")
 | 
			
		||||
	// 资源类型:部署到指定监听器。
 | 
			
		||||
	RESOURCE_TYPE_LISTENER = ResourceType("listener")
 | 
			
		||||
	// 资源类型:部署到指定转发规则域名。
 | 
			
		||||
	RESOURCE_TYPE_RULEDOMAIN = ResourceType("ruledomain")
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -1,14 +0,0 @@
 | 
			
		|||
package tencentcloudclb
 | 
			
		||||
 | 
			
		||||
type DeployResourceType string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// 资源类型:通过 SSL 服务部署到云资源实例。
 | 
			
		||||
	DEPLOY_RESOURCE_VIA_SSLDEPLOY = DeployResourceType("ssl-deploy")
 | 
			
		||||
	// 资源类型:部署到指定负载均衡器。
 | 
			
		||||
	DEPLOY_RESOURCE_LOADBALANCER = DeployResourceType("loadbalancer")
 | 
			
		||||
	// 资源类型:部署到指定监听器。
 | 
			
		||||
	DEPLOY_RESOURCE_LISTENER = DeployResourceType("listener")
 | 
			
		||||
	// 资源类型:部署到指定转发规则域名。
 | 
			
		||||
	DEPLOY_RESOURCE_RULEDOMAIN = DeployResourceType("ruledomain")
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -25,15 +25,15 @@ type TencentCloudCLBDeployerConfig struct {
 | 
			
		|||
	// 腾讯云地域。
 | 
			
		||||
	Region string `json:"region"`
 | 
			
		||||
	// 部署资源类型。
 | 
			
		||||
	ResourceType DeployResourceType `json:"resourceType"`
 | 
			
		||||
	ResourceType ResourceType `json:"resourceType"`
 | 
			
		||||
	// 负载均衡器 ID。
 | 
			
		||||
	// 部署资源类型为 [DEPLOY_RESOURCE_SSLDEPLOY]、[DEPLOY_RESOURCE_LOADBALANCER]、[DEPLOY_RESOURCE_RULEDOMAIN] 时必填。
 | 
			
		||||
	// 部署资源类型为 [RESOURCE_TYPE_SSLDEPLOY]、[RESOURCE_TYPE_LOADBALANCER]、[RESOURCE_TYPE_RULEDOMAIN] 时必填。
 | 
			
		||||
	LoadbalancerId string `json:"loadbalancerId,omitempty"`
 | 
			
		||||
	// 负载均衡监听 ID。
 | 
			
		||||
	// 部署资源类型为 [DEPLOY_RESOURCE_SSLDEPLOY]、[DEPLOY_RESOURCE_LOADBALANCER]、[DEPLOY_RESOURCE_LISTENER]、[DEPLOY_RESOURCE_RULEDOMAIN] 时必填。
 | 
			
		||||
	// 部署资源类型为 [RESOURCE_TYPE_SSLDEPLOY]、[RESOURCE_TYPE_LOADBALANCER]、[RESOURCE_TYPE_LISTENER]、[RESOURCE_TYPE_RULEDOMAIN] 时必填。
 | 
			
		||||
	ListenerId string `json:"listenerId,omitempty"`
 | 
			
		||||
	// SNI 域名或七层转发规则域名(支持泛域名)。
 | 
			
		||||
	// 部署资源类型为 [DEPLOY_RESOURCE_SSLDEPLOY] 时选填;部署资源类型为 [DEPLOY_RESOURCE_RULEDOMAIN] 时必填。
 | 
			
		||||
	// 部署资源类型为 [RESOURCE_TYPE_SSLDEPLOY] 时选填;部署资源类型为 [RESOURCE_TYPE_RULEDOMAIN] 时必填。
 | 
			
		||||
	Domain string `json:"domain,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -96,22 +96,22 @@ func (d *TencentCloudCLBDeployer) Deploy(ctx context.Context, certPem string, pr
 | 
			
		|||
 | 
			
		||||
	// 根据部署资源类型决定部署方式
 | 
			
		||||
	switch d.config.ResourceType {
 | 
			
		||||
	case DEPLOY_RESOURCE_VIA_SSLDEPLOY:
 | 
			
		||||
	case RESOURCE_TYPE_VIA_SSLDEPLOY:
 | 
			
		||||
		if err := d.deployViaSslService(ctx, upres.CertId); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	case DEPLOY_RESOURCE_LOADBALANCER:
 | 
			
		||||
	case RESOURCE_TYPE_LOADBALANCER:
 | 
			
		||||
		if err := d.deployToLoadbalancer(ctx, upres.CertId); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	case DEPLOY_RESOURCE_LISTENER:
 | 
			
		||||
	case RESOURCE_TYPE_LISTENER:
 | 
			
		||||
		if err := d.deployToListener(ctx, upres.CertId); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	case DEPLOY_RESOURCE_RULEDOMAIN:
 | 
			
		||||
	case RESOURCE_TYPE_RULEDOMAIN:
 | 
			
		||||
		if err := d.deployToRuleDomain(ctx, upres.CertId); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -68,7 +68,7 @@ func TestDeploy(t *testing.T) {
 | 
			
		|||
			SecretId:       fSecretId,
 | 
			
		||||
			SecretKey:      fSecretKey,
 | 
			
		||||
			Region:         fRegion,
 | 
			
		||||
			ResourceType:   provider.DEPLOY_RESOURCE_VIA_SSLDEPLOY,
 | 
			
		||||
			ResourceType:   provider.RESOURCE_TYPE_VIA_SSLDEPLOY,
 | 
			
		||||
			LoadbalancerId: fLoadbalancerId,
 | 
			
		||||
			ListenerId:     fListenerId,
 | 
			
		||||
			Domain:         fDomain,
 | 
			
		||||
| 
						 | 
				
			
			@ -104,7 +104,7 @@ func TestDeploy(t *testing.T) {
 | 
			
		|||
			SecretId:       fSecretId,
 | 
			
		||||
			SecretKey:      fSecretKey,
 | 
			
		||||
			Region:         fRegion,
 | 
			
		||||
			ResourceType:   provider.DEPLOY_RESOURCE_LOADBALANCER,
 | 
			
		||||
			ResourceType:   provider.RESOURCE_TYPE_LOADBALANCER,
 | 
			
		||||
			LoadbalancerId: fLoadbalancerId,
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -139,7 +139,7 @@ func TestDeploy(t *testing.T) {
 | 
			
		|||
			SecretId:       fSecretId,
 | 
			
		||||
			SecretKey:      fSecretKey,
 | 
			
		||||
			Region:         fRegion,
 | 
			
		||||
			ResourceType:   provider.DEPLOY_RESOURCE_LISTENER,
 | 
			
		||||
			ResourceType:   provider.RESOURCE_TYPE_LISTENER,
 | 
			
		||||
			LoadbalancerId: fLoadbalancerId,
 | 
			
		||||
			ListenerId:     fListenerId,
 | 
			
		||||
		})
 | 
			
		||||
| 
						 | 
				
			
			@ -176,7 +176,7 @@ func TestDeploy(t *testing.T) {
 | 
			
		|||
			SecretId:       fSecretId,
 | 
			
		||||
			SecretKey:      fSecretKey,
 | 
			
		||||
			Region:         fRegion,
 | 
			
		||||
			ResourceType:   provider.DEPLOY_RESOURCE_RULEDOMAIN,
 | 
			
		||||
			ResourceType:   provider.RESOURCE_TYPE_RULEDOMAIN,
 | 
			
		||||
			LoadbalancerId: fLoadbalancerId,
 | 
			
		||||
			ListenerId:     fListenerId,
 | 
			
		||||
			Domain:         fDomain,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
package volcengineclb
 | 
			
		||||
 | 
			
		||||
type ResourceType string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// 资源类型:部署到指定监听器。
 | 
			
		||||
	RESOURCE_TYPE_LISTENER = ResourceType("listener")
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -1,8 +0,0 @@
 | 
			
		|||
package volcengineclb
 | 
			
		||||
 | 
			
		||||
type DeployResourceType string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// 资源类型:部署到指定监听器。
 | 
			
		||||
	DEPLOY_RESOURCE_LISTENER = DeployResourceType("listener")
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -24,9 +24,9 @@ type VolcEngineCLBDeployerConfig struct {
 | 
			
		|||
	// 火山引擎地域。
 | 
			
		||||
	Region string `json:"region"`
 | 
			
		||||
	// 部署资源类型。
 | 
			
		||||
	ResourceType DeployResourceType `json:"resourceType"`
 | 
			
		||||
	ResourceType ResourceType `json:"resourceType"`
 | 
			
		||||
	// 负载均衡监听器 ID。
 | 
			
		||||
	// 部署资源类型为 [DEPLOY_RESOURCE_LISTENER] 时必填。
 | 
			
		||||
	// 部署资源类型为 [RESOURCE_TYPE_LISTENER] 时必填。
 | 
			
		||||
	ListenerId string `json:"listenerId,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -85,7 +85,7 @@ func (d *VolcEngineCLBDeployer) Deploy(ctx context.Context, certPem string, priv
 | 
			
		|||
 | 
			
		||||
	// 根据部署资源类型决定部署方式
 | 
			
		||||
	switch d.config.ResourceType {
 | 
			
		||||
	case DEPLOY_RESOURCE_LISTENER:
 | 
			
		||||
	case RESOURCE_TYPE_LISTENER:
 | 
			
		||||
		if err := d.deployToListener(ctx, upres.CertId); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -60,7 +60,7 @@ func TestDeploy(t *testing.T) {
 | 
			
		|||
			AccessKeyId:     fAccessKeyId,
 | 
			
		||||
			AccessKeySecret: fAccessKeySecret,
 | 
			
		||||
			Region:          fRegion,
 | 
			
		||||
			ResourceType:    provider.DEPLOY_RESOURCE_LISTENER,
 | 
			
		||||
			ResourceType:    provider.RESOURCE_TYPE_LISTENER,
 | 
			
		||||
			ListenerId:      fListenerId,
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -112,7 +112,7 @@ func (u *AliyunCASUploader) Upload(ctx context.Context, certPem string, privkeyP
 | 
			
		|||
		if listUserCertificateOrderResp.Body.CertificateOrderList == nil || len(listUserCertificateOrderResp.Body.CertificateOrderList) < int(listUserCertificateOrderLimit) {
 | 
			
		||||
			break
 | 
			
		||||
		} else {
 | 
			
		||||
			listUserCertificateOrderPage += 1
 | 
			
		||||
			listUserCertificateOrderPage++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,167 @@
 | 
			
		|||
package huaweicloudwaf
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic"
 | 
			
		||||
	hcWaf "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/waf/v1"
 | 
			
		||||
	hcWafModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/waf/v1/model"
 | 
			
		||||
	hcWafRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/waf/v1/region"
 | 
			
		||||
	xerrors "github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	"github.com/usual2970/certimate/internal/pkg/core/uploader"
 | 
			
		||||
	"github.com/usual2970/certimate/internal/pkg/utils/certs"
 | 
			
		||||
	hwsdk "github.com/usual2970/certimate/internal/pkg/vendors/huaweicloud-sdk"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type HuaweiCloudWAFUploaderConfig struct {
 | 
			
		||||
	// 华为云 AccessKeyId。
 | 
			
		||||
	AccessKeyId string `json:"accessKeyId"`
 | 
			
		||||
	// 华为云 SecretAccessKey。
 | 
			
		||||
	SecretAccessKey string `json:"secretAccessKey"`
 | 
			
		||||
	// 华为云区域。
 | 
			
		||||
	Region string `json:"region"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type HuaweiCloudWAFUploader struct {
 | 
			
		||||
	config    *HuaweiCloudWAFUploaderConfig
 | 
			
		||||
	sdkClient *hcWaf.WafClient
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var _ uploader.Uploader = (*HuaweiCloudWAFUploader)(nil)
 | 
			
		||||
 | 
			
		||||
func New(config *HuaweiCloudWAFUploaderConfig) (*HuaweiCloudWAFUploader, error) {
 | 
			
		||||
	if config == nil {
 | 
			
		||||
		return nil, errors.New("config is nil")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client, err := createSdkClient(
 | 
			
		||||
		config.AccessKeyId,
 | 
			
		||||
		config.SecretAccessKey,
 | 
			
		||||
		config.Region,
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, xerrors.Wrap(err, "failed to create sdk client")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &HuaweiCloudWAFUploader{
 | 
			
		||||
		config:    config,
 | 
			
		||||
		sdkClient: client,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (u *HuaweiCloudWAFUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
 | 
			
		||||
	// 解析证书内容
 | 
			
		||||
	certX509, err := certs.ParseCertificateFromPEM(certPem)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 遍历查询已有证书,避免重复上传
 | 
			
		||||
	// REF: https://support.huaweicloud.com/api-waf/ListCertificates.html
 | 
			
		||||
	// REF: https://support.huaweicloud.com/api-waf/ShowCertificate.html
 | 
			
		||||
	listCertificatesPage := int32(1)
 | 
			
		||||
	listCertificatesLimit := int32(100)
 | 
			
		||||
	for {
 | 
			
		||||
		listCertificatesReq := &hcWafModel.ListCertificatesRequest{
 | 
			
		||||
			Page:     hwsdk.Int32Ptr(listCertificatesPage),
 | 
			
		||||
			Pagesize: hwsdk.Int32Ptr(listCertificatesLimit),
 | 
			
		||||
		}
 | 
			
		||||
		listCertificatesResp, err := u.sdkClient.ListCertificates(listCertificatesReq)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, xerrors.Wrap(err, "failed to execute sdk request 'waf.ListCertificates'")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if listCertificatesResp.Items != nil {
 | 
			
		||||
			for _, certItem := range *listCertificatesResp.Items {
 | 
			
		||||
				showCertificateReq := &hcWafModel.ShowCertificateRequest{
 | 
			
		||||
					CertificateId: certItem.Id,
 | 
			
		||||
				}
 | 
			
		||||
				showCertificateResp, err := u.sdkClient.ShowCertificate(showCertificateReq)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return nil, xerrors.Wrap(err, "failed to execute sdk request 'waf.ShowCertificate'")
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				var isSameCert bool
 | 
			
		||||
				if *showCertificateResp.Content == certPem {
 | 
			
		||||
					isSameCert = true
 | 
			
		||||
				} else {
 | 
			
		||||
					oldCertX509, err := certs.ParseCertificateFromPEM(*showCertificateResp.Content)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						continue
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					isSameCert = certs.EqualCertificate(certX509, oldCertX509)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// 如果已存在相同证书,直接返回已有的证书信息
 | 
			
		||||
				if isSameCert {
 | 
			
		||||
					return &uploader.UploadResult{
 | 
			
		||||
						CertId:   certItem.Id,
 | 
			
		||||
						CertName: certItem.Name,
 | 
			
		||||
					}, nil
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if listCertificatesResp.Items == nil || len(*listCertificatesResp.Items) < int(listCertificatesLimit) {
 | 
			
		||||
			break
 | 
			
		||||
		} else {
 | 
			
		||||
			listCertificatesPage++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 生成新证书名(需符合华为云命名规则)
 | 
			
		||||
	var certId, certName string
 | 
			
		||||
	certName = fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
 | 
			
		||||
 | 
			
		||||
	// 创建证书
 | 
			
		||||
	// REF: https://support.huaweicloud.com/api-waf/CreateCertificate.html
 | 
			
		||||
	createCertificateReq := &hcWafModel.CreateCertificateRequest{
 | 
			
		||||
		Body: &hcWafModel.CreateCertificateRequestBody{
 | 
			
		||||
			Name:    certName,
 | 
			
		||||
			Content: certPem,
 | 
			
		||||
			Key:     privkeyPem,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	createCertificateResp, err := u.sdkClient.CreateCertificate(createCertificateReq)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, xerrors.Wrap(err, "failed to execute sdk request 'waf.CreateCertificate'")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	certId = *createCertificateResp.Id
 | 
			
		||||
	certName = *createCertificateResp.Name
 | 
			
		||||
	return &uploader.UploadResult{
 | 
			
		||||
		CertId:   certId,
 | 
			
		||||
		CertName: certName,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createSdkClient(accessKeyId, secretAccessKey, region string) (*hcWaf.WafClient, error) {
 | 
			
		||||
	auth, err := basic.NewCredentialsBuilder().
 | 
			
		||||
		WithAk(accessKeyId).
 | 
			
		||||
		WithSk(secretAccessKey).
 | 
			
		||||
		SafeBuild()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hcRegion, err := hcWafRegion.SafeValueOf(region)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hcClient, err := hcWaf.WafClientBuilder().
 | 
			
		||||
		WithRegion(hcRegion).
 | 
			
		||||
		WithCredential(auth).
 | 
			
		||||
		SafeBuild()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client := hcWaf.NewWafClient(hcClient)
 | 
			
		||||
	return client, nil
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue