diff --git a/.changelog/3057.txt b/.changelog/3057.txt new file mode 100644 index 0000000000..89fb111210 --- /dev/null +++ b/.changelog/3057.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +tencentcloud_private_dns_extend_end_point +``` \ No newline at end of file diff --git a/go.mod b/go.mod index ba65f87edf..8d3e853152 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/mozillazg/go-httpheader v0.4.0 // indirect github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.8.2 - github.com/tencentcloud/tencentcloud-sdk-go-intl-en v3.0.646+incompatible + github.com/tencentcloud/tencentcloud-sdk-go-intl-en v3.0.1114+incompatible github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/antiddos v1.0.799 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/api v1.0.285 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/apigateway v1.0.763 diff --git a/go.sum b/go.sum index 8c31b6a4d8..3aa0289cc1 100644 --- a/go.sum +++ b/go.sum @@ -818,6 +818,10 @@ github.com/tdakkota/asciicheck v0.2.0 h1:o8jvnUANo0qXtnslk2d3nMKTFNlOnJjRrNcj0j9 github.com/tdakkota/asciicheck v0.2.0/go.mod h1:Qb7Y9EgjCLJGup51gDHFzbI08/gbGhL/UVhYIPWG2rg= github.com/tencentcloud/tencentcloud-sdk-go-intl-en v3.0.646+incompatible h1:C6knK+5LOkVTypyHsPj3njTYub6StITj6n4wmMBPS+8= github.com/tencentcloud/tencentcloud-sdk-go-intl-en v3.0.646+incompatible/go.mod h1:72Wo6Gt6F8d8V+njrAmduVoT9QjPwCyXktpqCWr7PUc= +github.com/tencentcloud/tencentcloud-sdk-go-intl-en v3.0.1113+incompatible h1:Vfzv2LeRMKRU3BGxFMnfaG7KvjLZP6fhwsM/i+FIovY= +github.com/tencentcloud/tencentcloud-sdk-go-intl-en v3.0.1113+incompatible/go.mod h1:72Wo6Gt6F8d8V+njrAmduVoT9QjPwCyXktpqCWr7PUc= +github.com/tencentcloud/tencentcloud-sdk-go-intl-en v3.0.1114+incompatible h1:Qo2u4QpYzsrrTedOFmNyDIghJMsFSa/Dn3HfnTWtTfs= +github.com/tencentcloud/tencentcloud-sdk-go-intl-en v3.0.1114+incompatible/go.mod h1:72Wo6Gt6F8d8V+njrAmduVoT9QjPwCyXktpqCWr7PUc= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/antiddos v1.0.799 h1:u49r1bGFDY0CeAF46iotNnLtc5yplPf9XXa7W7zJ4fE= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/antiddos v1.0.799/go.mod h1:JQa/Ess7Kkn/BuAugq0Tt5GJR7ZQAY47ypP9LjEn/DE= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/api v1.0.285 h1:gFmukRGLtYiXVBVvg/5DP/0fM1+dKpwDjT+khtDVLmc= diff --git a/tencentcloud/connectivity/client.go b/tencentcloud/connectivity/client.go index e3f3d1aa09..0a06ce830c 100644 --- a/tencentcloud/connectivity/client.go +++ b/tencentcloud/connectivity/client.go @@ -16,6 +16,7 @@ import ( "github.com/aws/aws-sdk-go/service/s3" intlProfile "github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/profile" mdl "github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/mdl/v20200326" + privatednsIntl "github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/privatedns/v20201028" antiddos "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/antiddos/v20200309" api "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/api/v20201106" apigateway "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/apigateway/v20180808" @@ -212,20 +213,21 @@ type TencentCloudClient struct { controlcenterConn *controlcenter.Client thpcConn *thpc.Client //omit nil client - omitNilConn *common.Client - emrv20190103Conn *emr.Client - teov20220901Conn *teo.Client - sslv20191205Conn *sslCertificate.Client - postgresv20170312Conn *postgre.Client - cfwv20190904Conn *cfw.Client - ccnv20170312Conn *vpc.Client - tcssv20201101Conn *tcss.Client - cloudauditv20190319Conn *audit.Client - privatednsv20201028Conn *privatedns.Client - wafv20180125Conn *waf.Client - camv20190116Conn *cam.Client - clsv20201016Conn *cls.Client - monitor20180724Conn *monitor.Client + omitNilConn *common.Client + emrv20190103Conn *emr.Client + teov20220901Conn *teo.Client + sslv20191205Conn *sslCertificate.Client + postgresv20170312Conn *postgre.Client + cfwv20190904Conn *cfw.Client + ccnv20170312Conn *vpc.Client + tcssv20201101Conn *tcss.Client + cloudauditv20190319Conn *audit.Client + privatednsv20201028Conn *privatedns.Client + privatednsIntlv20201028Conn *privatednsIntl.Client + wafv20180125Conn *waf.Client + camv20190116Conn *cam.Client + clsv20201016Conn *cls.Client + monitor20180724Conn *monitor.Client } // NewClientProfile returns a new ClientProfile @@ -1965,6 +1967,19 @@ func (me *TencentCloudClient) UsePrivatednsV20201028Client() *privatedns.Client return me.privatednsv20201028Conn } +// UsePrivatednsV20201028Client return PRIVATEDNS Intl client for service +func (me *TencentCloudClient) UsePrivatednsIntlV20201028Client() *privatednsIntl.Client { + if me.privatednsIntlv20201028Conn != nil { + return me.privatednsIntlv20201028Conn + } + cpf := me.NewClientIntlProfile(300) + cpf.Language = "zh-CN" + me.privatednsIntlv20201028Conn, _ = privatednsIntl.NewClient(me.Credential, me.Region, cpf) + me.privatednsIntlv20201028Conn.WithHttpTransport(&LogRoundTripper{}) + + return me.privatednsIntlv20201028Conn +} + // UseWafV20180125Client return WAF client for service func (me *TencentCloudClient) UseWafV20180125Client() *waf.Client { if me.wafv20180125Conn != nil { diff --git a/tencentcloud/provider.go b/tencentcloud/provider.go index b669f8f273..84666e7400 100644 --- a/tencentcloud/provider.go +++ b/tencentcloud/provider.go @@ -1715,6 +1715,7 @@ func Provider() *schema.Provider { "tencentcloud_subscribe_private_zone_service": privatedns.ResourceTencentCloudSubscribePrivateZoneService(), "tencentcloud_private_dns_forward_rule": privatedns.ResourceTencentCloudPrivateDnsForwardRule(), "tencentcloud_private_dns_end_point": privatedns.ResourceTencentCloudPrivateDnsEndPoint(), + "tencentcloud_private_dns_extend_end_point": privatedns.ResourceTencentCloudPrivateDnsExtendEndPoint(), "tencentcloud_cls_logset": cls.ResourceTencentCloudClsLogset(), "tencentcloud_cls_topic": cls.ResourceTencentCloudClsTopic(), "tencentcloud_cls_config": cls.ResourceTencentCloudClsConfig(), diff --git a/tencentcloud/provider.md b/tencentcloud/provider.md index 2a3c3d9aeb..77101a4fbf 100644 --- a/tencentcloud/provider.md +++ b/tencentcloud/provider.md @@ -1366,6 +1366,8 @@ PrivateDNS tencentcloud_subscribe_private_zone_service tencentcloud_private_dns_forward_rule tencentcloud_private_dns_end_point + tencentcloud_private_dns_extend_end_point + Data Source tencentcloud_private_dns_records tencentcloud_private_dns_private_zone_list diff --git a/tencentcloud/services/privatedns/resource_tc_private_dns_end_point.go b/tencentcloud/services/privatedns/resource_tc_private_dns_end_point.go index 8a1f1901c8..4fe87ee28d 100644 --- a/tencentcloud/services/privatedns/resource_tc_private_dns_end_point.go +++ b/tencentcloud/services/privatedns/resource_tc_private_dns_end_point.go @@ -3,11 +3,12 @@ package privatedns import ( "context" + "fmt" "log" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - privatednsv20201028 "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/privatedns/v20201028" + privatednsIntlv20201028 "github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/privatedns/v20201028" tccommon "github.com/tencentcloudstack/terraform-provider-tencentcloud/tencentcloud/common" "github.com/tencentcloudstack/terraform-provider-tencentcloud/tencentcloud/internal/helper" @@ -75,8 +76,8 @@ func resourceTencentCloudPrivateDnsEndPointCreate(d *schema.ResourceData, meta i endPointId string ) var ( - request = privatednsv20201028.NewCreateEndPointRequest() - response = privatednsv20201028.NewCreateEndPointResponse() + request = privatednsIntlv20201028.NewCreateEndPointRequest() + response = privatednsIntlv20201028.NewCreateEndPointResponse() ) if v, ok := d.GetOk("end_point_name"); ok { @@ -96,12 +97,17 @@ func resourceTencentCloudPrivateDnsEndPointCreate(d *schema.ResourceData, meta i } err := resource.Retry(tccommon.WriteRetryTimeout, func() *resource.RetryError { - result, e := meta.(tccommon.ProviderMeta).GetAPIV3Conn().UsePrivatednsV20201028Client().CreateEndPointWithContext(ctx, request) + result, e := meta.(tccommon.ProviderMeta).GetAPIV3Conn().UsePrivatednsIntlV20201028Client().CreateEndPointWithContext(ctx, request) if e != nil { return tccommon.RetryError(e) } else { log.Printf("[DEBUG]%s api[%s] success, request body [%s], response body [%s]\n", logId, request.GetAction(), request.ToJsonString(), result.ToJsonString()) } + + if result == nil || result.Response == nil { + return resource.NonRetryableError(fmt.Errorf("Create private dns end point failed, Response is nil.")) + } + response = result return nil }) @@ -110,6 +116,10 @@ func resourceTencentCloudPrivateDnsEndPointCreate(d *schema.ResourceData, meta i return err } + if response.Response.EndPointId == nil { + return fmt.Errorf("EndPointId is nil.") + } + endPointId = *response.Response.EndPointId d.SetId(endPointId) @@ -156,14 +166,14 @@ func resourceTencentCloudPrivateDnsEndPointDelete(d *schema.ResourceData, meta i endPointId := d.Id() var ( - request = privatednsv20201028.NewDeleteEndPointRequest() - response = privatednsv20201028.NewDeleteEndPointResponse() + request = privatednsIntlv20201028.NewDeleteEndPointRequest() + response = privatednsIntlv20201028.NewDeleteEndPointResponse() ) request.EndPointId = helper.String(endPointId) err := resource.Retry(tccommon.WriteRetryTimeout, func() *resource.RetryError { - result, e := meta.(tccommon.ProviderMeta).GetAPIV3Conn().UsePrivatednsV20201028Client().DeleteEndPointWithContext(ctx, request) + result, e := meta.(tccommon.ProviderMeta).GetAPIV3Conn().UsePrivatednsIntlV20201028Client().DeleteEndPointWithContext(ctx, request) if e != nil { return tccommon.RetryError(e) } else { diff --git a/tencentcloud/services/privatedns/resource_tc_private_dns_end_point_extension.go b/tencentcloud/services/privatedns/resource_tc_private_dns_end_point_extension.go index 139d264d52..e162ccf6b9 100644 --- a/tencentcloud/services/privatedns/resource_tc_private_dns_end_point_extension.go +++ b/tencentcloud/services/privatedns/resource_tc_private_dns_end_point_extension.go @@ -5,11 +5,11 @@ import ( "fmt" "log" - privatednsv20201028 "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/privatedns/v20201028" + privatednsIntlv20201028 "github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/privatedns/v20201028" tccommon "github.com/tencentcloudstack/terraform-provider-tencentcloud/tencentcloud/common" ) -func resourceTencentCloudPrivateDnsEndPointReadPreHandleResponse0(ctx context.Context, resp *privatednsv20201028.DescribeEndPointListResponseParams) error { +func resourceTencentCloudPrivateDnsEndPointReadPreHandleResponse0(ctx context.Context, resp *privatednsIntlv20201028.DescribeEndPointListResponseParams) error { logId := tccommon.GetLogId(ctx) d := tccommon.ResourceDataFromContext(ctx) if d == nil { diff --git a/tencentcloud/services/privatedns/resource_tc_private_dns_extend_end_point.go b/tencentcloud/services/privatedns/resource_tc_private_dns_extend_end_point.go new file mode 100644 index 0000000000..08c819d16f --- /dev/null +++ b/tencentcloud/services/privatedns/resource_tc_private_dns_extend_end_point.go @@ -0,0 +1,265 @@ +package privatedns + +import ( + "context" + "fmt" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + privatednsIntlv20201028 "github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/privatedns/v20201028" + + tccommon "github.com/tencentcloudstack/terraform-provider-tencentcloud/tencentcloud/common" + "github.com/tencentcloudstack/terraform-provider-tencentcloud/tencentcloud/internal/helper" +) + +func ResourceTencentCloudPrivateDnsExtendEndPoint() *schema.Resource { + return &schema.Resource{ + Create: resourceTencentCloudPrivateDnsExtendEndPointCreate, + Read: resourceTencentCloudPrivateDnsExtendEndPointRead, + Delete: resourceTencentCloudPrivateDnsExtendEndPointDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Schema: map[string]*schema.Schema{ + "end_point_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Outbound endpoint name.", + }, + "end_point_region": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The region of the outbound endpoint must be consistent with the region of the forwarding target VIP.", + }, + "forward_ip": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Description: "Forwarding target.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "access_type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Forwarding target IP network access type. CLB: The forwarding IP is the internal CLB VIP. CCN: Forwarding IP through CCN routing.", + }, + "host": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Forwarding target IP address.", + }, + "port": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + Description: "Specifies the forwarding IP port number.", + }, + // "ip_num": { + // Type: schema.TypeInt, + // Required: true, + // Description: "Specifies the number of outbound endpoints. Minimum 1, maximum 6.", + // }, + "vpc_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Unique VPC ID.", + }, + // "subnet_id": { + // Type: schema.TypeString, + // Optional: true, + // Description: "Unique subnet ID. Required when the access type is CCN.", + // }, + "access_gateway_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "CCN id. Required when the access type is CCN.", + }, + // computed + "vip": { + Type: schema.TypeString, + Computed: true, + Description: "Specifies the forwarding target IP proxy IP.", + }, + "vport": { + Type: schema.TypeInt, + Computed: true, + Description: "Specifies the forwarding target IP proxy port.", + }, + "proto": { + Type: schema.TypeString, + Computed: true, + Description: "Specifies the forwarding target IP protocol.", + }, + "snat_vip_cidr": { + Type: schema.TypeString, + Computed: true, + Description: "The SNAT CIDR block of the outbound endpoint.", + }, + "snat_vip_set": { + Type: schema.TypeString, + Computed: true, + Description: "The SNAT IP list of the outbound endpoint.", + }, + }, + }, + }, + }, + } +} + +func resourceTencentCloudPrivateDnsExtendEndPointCreate(d *schema.ResourceData, meta interface{}) error { + defer tccommon.LogElapsed("resource.tencentcloud_private_dns_extend_end_point.create")() + defer tccommon.InconsistentCheck(d, meta)() + + var ( + logId = tccommon.GetLogId(tccommon.ContextNil) + ctx = tccommon.NewResourceLifeCycleHandleFuncContext(context.Background(), logId, d, meta) + request = privatednsIntlv20201028.NewCreateExtendEndpointRequest() + response = privatednsIntlv20201028.NewCreateExtendEndpointResponse() + endPointId string + ) + + if v, ok := d.GetOk("end_point_name"); ok { + request.EndpointName = helper.String(v.(string)) + } + + if v, ok := d.GetOk("end_point_region"); ok { + request.EndpointRegion = helper.String(v.(string)) + } + + if dMap, ok := helper.InterfacesHeadMap(d, "forward_ip"); ok { + forwardIp := &privatednsIntlv20201028.ForwardIp{} + if v, ok := dMap["access_type"]; ok { + forwardIp.AccessType = helper.String(v.(string)) + } + + if v, ok := dMap["host"]; ok { + forwardIp.Host = helper.String(v.(string)) + } + + if v, ok := dMap["port"]; ok { + forwardIp.Port = helper.IntInt64(v.(int)) + } + + // if v, ok := dMap["ip_num"]; ok { + // forwardIp.IpNum = helper.IntInt64(v.(int)) + // } + + forwardIp.IpNum = helper.IntInt64(1) + + if v, ok := dMap["vpc_id"]; ok { + forwardIp.VpcId = helper.String(v.(string)) + } + + // if v, ok := dMap["subnet_id"]; ok { + // forwardIp.SubnetId = helper.String(v.(string)) + // } + + if v, ok := dMap["access_gateway_id"]; ok { + forwardIp.AccessGatewayId = helper.String(v.(string)) + } + + request.ForwardIp = forwardIp + } + + err := resource.Retry(tccommon.WriteRetryTimeout, func() *resource.RetryError { + result, e := meta.(tccommon.ProviderMeta).GetAPIV3Conn().UsePrivatednsIntlV20201028Client().CreateExtendEndpointWithContext(ctx, request) + if e != nil { + return tccommon.RetryError(e) + } else { + log.Printf("[DEBUG]%s api[%s] success, request body [%s], response body [%s]\n", logId, request.GetAction(), request.ToJsonString(), result.ToJsonString()) + } + + if result == nil || result.Response == nil { + return resource.NonRetryableError(fmt.Errorf("Create private dns extend end point failed, Response is nil.")) + } + + response = result + return nil + }) + + if err != nil { + log.Printf("[CRITAL]%s create private dns extend end point failed, reason:%+v", logId, err) + return err + } + + if response.Response.EndpointId == nil { + return fmt.Errorf("EndpointId is nil.") + } + + endPointId = *response.Response.EndpointId + d.SetId(endPointId) + + return resourceTencentCloudPrivateDnsExtendEndPointRead(d, meta) +} + +func resourceTencentCloudPrivateDnsExtendEndPointRead(d *schema.ResourceData, meta interface{}) error { + defer tccommon.LogElapsed("resource.tencentcloud_private_dns_extend_end_point.read")() + defer tccommon.InconsistentCheck(d, meta)() + + var ( + logId = tccommon.GetLogId(tccommon.ContextNil) + ctx = tccommon.NewResourceLifeCycleHandleFuncContext(context.Background(), logId, d, meta) + service = PrivatednsService{client: meta.(tccommon.ProviderMeta).GetAPIV3Conn()} + endPointId = d.Id() + ) + + respData, err := service.DescribePrivateDnsExtendEndPointById(ctx, endPointId) + if err != nil { + return err + } + + if respData == nil { + d.SetId("") + log.Printf("[WARN]%s resource `private_dns_end_point` [%s] not found, please check if it has been deleted.\n", logId, d.Id()) + return nil + } + + if err := resourceTencentCloudPrivateDnsExtendEndPointReadPreHandleResponse0(ctx, respData); err != nil { + return err + } + + return nil +} + +func resourceTencentCloudPrivateDnsExtendEndPointDelete(d *schema.ResourceData, meta interface{}) error { + defer tccommon.LogElapsed("resource.tencentcloud_private_dns_extend_end_point.delete")() + defer tccommon.InconsistentCheck(d, meta)() + + var ( + logId = tccommon.GetLogId(tccommon.ContextNil) + ctx = tccommon.NewResourceLifeCycleHandleFuncContext(context.Background(), logId, d, meta) + request = privatednsIntlv20201028.NewDeleteEndPointRequest() + response = privatednsIntlv20201028.NewDeleteEndPointResponse() + endPointId = d.Id() + ) + + request.EndPointId = helper.String(endPointId) + err := resource.Retry(tccommon.WriteRetryTimeout, func() *resource.RetryError { + result, e := meta.(tccommon.ProviderMeta).GetAPIV3Conn().UsePrivatednsIntlV20201028Client().DeleteEndPointWithContext(ctx, request) + if e != nil { + return tccommon.RetryError(e) + } else { + log.Printf("[DEBUG]%s api[%s] success, request body [%s], response body [%s]\n", logId, request.GetAction(), request.ToJsonString(), result.ToJsonString()) + } + + response = result + return nil + }) + + if err != nil { + log.Printf("[CRITAL]%s delete private dns extend end point failed, reason:%+v", logId, err) + return err + } + + _ = response + return nil +} diff --git a/tencentcloud/services/privatedns/resource_tc_private_dns_extend_end_point.md b/tencentcloud/services/privatedns/resource_tc_private_dns_extend_end_point.md new file mode 100644 index 0000000000..cf23b0b461 --- /dev/null +++ b/tencentcloud/services/privatedns/resource_tc_private_dns_extend_end_point.md @@ -0,0 +1,42 @@ +Provides a resource to create a privatedns extend end point + +Example Usage + +If access_type is CLB + +```hcl +resource "tencentcloud_private_dns_extend_end_point" "example" { + end_point_name = "tf-example" + end_point_region = "ap-jakarta" + forward_ip { + access_type = "CLB" + host = "10.0.1.12" + port = 9000 + vpc_id = "vpc-1v2i79fc" + } +} +``` + +If access_type is CCN + +```hcl +resource "tencentcloud_private_dns_extend_end_point" "example" { + end_point_name = "tf-example" + end_point_region = "ap-jakarta" + forward_ip { + access_type = "CCN" + host = "1.1.1.1" + port = 8080 + vpc_id = "vpc-2qjckjg2" + access_gateway_id = "ccn-eo13f8ub" + } +} +``` + +Import + +private dns extend end point can be imported using the id, e.g. + +``` +terraform import tencentcloud_private_dns_extend_end_point.example eid-960fb0ee9677 +``` diff --git a/tencentcloud/services/privatedns/resource_tc_private_dns_extend_end_point_extension.go b/tencentcloud/services/privatedns/resource_tc_private_dns_extend_end_point_extension.go new file mode 100644 index 0000000000..cddc69fec0 --- /dev/null +++ b/tencentcloud/services/privatedns/resource_tc_private_dns_extend_end_point_extension.go @@ -0,0 +1,90 @@ +package privatedns + +import ( + "context" + "fmt" + "log" + + privatednsIntlv20201028 "github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/privatedns/v20201028" + tccommon "github.com/tencentcloudstack/terraform-provider-tencentcloud/tencentcloud/common" +) + +func resourceTencentCloudPrivateDnsExtendEndPointReadPreHandleResponse0(ctx context.Context, resp *privatednsIntlv20201028.DescribeExtendEndpointListResponseParams) error { + logId := tccommon.GetLogId(ctx) + d := tccommon.ResourceDataFromContext(ctx) + if d == nil { + return fmt.Errorf("resource data can not be nil") + } + + if resp.OutboundEndpointSet == nil && len(resp.OutboundEndpointSet) < 1 { + d.SetId("") + log.Printf("[WARN]%s resource `private_dns_extend_end_point` [%s] not found, please check if it has been deleted.\n", logId, d.Id()) + return nil + } + + for _, item := range resp.OutboundEndpointSet { + if item.EndpointName != nil { + _ = d.Set("end_point_name", item.EndpointName) + } + + if item.Region != nil { + _ = d.Set("end_point_region", item.Region) + } + + if item.EndpointServiceSet != nil { + tmpList := make([]map[string]interface{}, 0, len(item.EndpointServiceSet)) + for _, v := range item.EndpointServiceSet { + dMap := make(map[string]interface{}, 0) + if v.AccessType != nil { + dMap["access_type"] = v.AccessType + } + + if v.Pip != nil { + dMap["host"] = v.Pip + } + + if v.Pport != nil { + dMap["port"] = v.Pport + } + + if v.VpcId != nil { + dMap["vpc_id"] = v.VpcId + } + + if v.SubnetId != nil { + dMap["subnet_id"] = v.SubnetId + } + + if v.AccessGatewayId != nil { + dMap["access_gateway_id"] = v.AccessGatewayId + } + + if v.Vip != nil { + dMap["vip"] = v.Vip + } + + if v.Vport != nil { + dMap["vport"] = v.Vport + } + + if v.Proto != nil { + dMap["proto"] = v.Proto + } + + if v.SnatVipCidr != nil { + dMap["snat_vip_cidr"] = v.SnatVipCidr + } + + if v.SnatVipSet != nil { + dMap["snat_vip_set"] = v.SnatVipSet + } + + tmpList = append(tmpList, dMap) + } + + _ = d.Set("forward_ip", tmpList) + } + } + + return nil +} diff --git a/tencentcloud/services/privatedns/resource_tc_private_dns_extend_end_point_test.go b/tencentcloud/services/privatedns/resource_tc_private_dns_extend_end_point_test.go new file mode 100644 index 0000000000..cdf50838aa --- /dev/null +++ b/tencentcloud/services/privatedns/resource_tc_private_dns_extend_end_point_test.go @@ -0,0 +1,50 @@ +package privatedns_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + tcacctest "github.com/tencentcloudstack/terraform-provider-tencentcloud/tencentcloud/acctest" +) + +// go test -i; go test -test.run TestAccTencentCloudNeedFixPrivateDnsExtendEndPointResource_basic -v +func TestAccTencentCloudNeedFixPrivateDnsExtendEndPointResource_basic(t *testing.T) { + t.Parallel() + resource.Test(t, resource.TestCase{ + PreCheck: func() { + tcacctest.AccPreCheck(t) + }, + Providers: tcacctest.AccProviders, + Steps: []resource.TestStep{ + { + Config: testAccPrivateDnsExtendEndPoint, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("tencentcloud_private_dns_extend_end_point.example", "id"), + resource.TestCheckResourceAttrSet("tencentcloud_private_dns_extend_end_point.example", "end_point_name"), + resource.TestCheckResourceAttrSet("tencentcloud_private_dns_extend_end_point.example", "end_point_region"), + resource.TestCheckResourceAttrSet("tencentcloud_private_dns_extend_end_point.example", "forward_ip.#"), + ), + }, + { + ResourceName: "tencentcloud_private_dns_extend_end_point.example", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +const testAccPrivateDnsExtendEndPoint = ` +resource "tencentcloud_private_dns_extend_end_point" "example" { + end_point_name = "tf-example" + end_point_region = "ap-jakarta" + forward_ip { + access_type = "CCN" + host = "1.1.1.1" + port = 8080 + vpc_id = "vpc-2qjckjg2" + access_gateway_id = "ccn-eo13f8ub" + } +} +` diff --git a/tencentcloud/services/privatedns/resource_tc_private_dns_forward_rule.go b/tencentcloud/services/privatedns/resource_tc_private_dns_forward_rule.go index e83324f98f..2ae336726a 100644 --- a/tencentcloud/services/privatedns/resource_tc_private_dns_forward_rule.go +++ b/tencentcloud/services/privatedns/resource_tc_private_dns_forward_rule.go @@ -8,7 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - privatednsv20201028 "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/privatedns/v20201028" + privatednsIntlv20201028 "github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/privatedns/v20201028" tccommon "github.com/tencentcloudstack/terraform-provider-tencentcloud/tencentcloud/common" "github.com/tencentcloudstack/terraform-provider-tencentcloud/tencentcloud/internal/helper" @@ -63,8 +63,8 @@ func resourceTencentCloudPrivateDnsForwardRuleCreate(d *schema.ResourceData, met ruleId string ) var ( - request = privatednsv20201028.NewCreateForwardRuleRequest() - response = privatednsv20201028.NewCreateForwardRuleResponse() + request = privatednsIntlv20201028.NewCreateForwardRuleRequest() + response = privatednsIntlv20201028.NewCreateForwardRuleResponse() ) if v, ok := d.GetOk("rule_name"); ok { @@ -84,20 +84,30 @@ func resourceTencentCloudPrivateDnsForwardRuleCreate(d *schema.ResourceData, met } err := resource.Retry(tccommon.WriteRetryTimeout, func() *resource.RetryError { - result, e := meta.(tccommon.ProviderMeta).GetAPIV3Conn().UsePrivatednsV20201028Client().CreateForwardRuleWithContext(ctx, request) + result, e := meta.(tccommon.ProviderMeta).GetAPIV3Conn().UsePrivatednsIntlV20201028Client().CreateForwardRuleWithContext(ctx, request) if e != nil { return tccommon.RetryError(e) } else { log.Printf("[DEBUG]%s api[%s] success, request body [%s], response body [%s]\n", logId, request.GetAction(), request.ToJsonString(), result.ToJsonString()) } + + if result == nil || result.Response == nil { + return resource.NonRetryableError(fmt.Errorf("Create private dns forward rule failed, Response is nil.")) + } + response = result return nil }) + if err != nil { log.Printf("[CRITAL]%s create private dns forward rule failed, reason:%+v", logId, err) return err } + if response.Response.RuleId == nil { + return fmt.Errorf("RuleId is nil.") + } + ruleId = *response.Response.RuleId d.SetId(ruleId) @@ -172,7 +182,7 @@ func resourceTencentCloudPrivateDnsForwardRuleUpdate(d *schema.ResourceData, met } if needChange { - request := privatednsv20201028.NewModifyForwardRuleRequest() + request := privatednsIntlv20201028.NewModifyForwardRuleRequest() request.RuleId = helper.String(ruleId) @@ -185,7 +195,7 @@ func resourceTencentCloudPrivateDnsForwardRuleUpdate(d *schema.ResourceData, met } err := resource.Retry(tccommon.WriteRetryTimeout, func() *resource.RetryError { - result, e := meta.(tccommon.ProviderMeta).GetAPIV3Conn().UsePrivatednsV20201028Client().ModifyForwardRuleWithContext(ctx, request) + result, e := meta.(tccommon.ProviderMeta).GetAPIV3Conn().UsePrivatednsIntlV20201028Client().ModifyForwardRuleWithContext(ctx, request) if e != nil { return tccommon.RetryError(e) } else { @@ -212,14 +222,14 @@ func resourceTencentCloudPrivateDnsForwardRuleDelete(d *schema.ResourceData, met ruleId := d.Id() var ( - request = privatednsv20201028.NewDeleteForwardRuleRequest() - response = privatednsv20201028.NewDeleteForwardRuleResponse() + request = privatednsIntlv20201028.NewDeleteForwardRuleRequest() + response = privatednsIntlv20201028.NewDeleteForwardRuleResponse() ) request.RuleIdSet = []*string{helper.String(ruleId)} err := resource.Retry(tccommon.WriteRetryTimeout, func() *resource.RetryError { - result, e := meta.(tccommon.ProviderMeta).GetAPIV3Conn().UsePrivatednsV20201028Client().DeleteForwardRuleWithContext(ctx, request) + result, e := meta.(tccommon.ProviderMeta).GetAPIV3Conn().UsePrivatednsIntlV20201028Client().DeleteForwardRuleWithContext(ctx, request) if e != nil { return tccommon.RetryError(e) } else { diff --git a/tencentcloud/services/privatedns/resource_tc_private_dns_forward_rule.md b/tencentcloud/services/privatedns/resource_tc_private_dns_forward_rule.md index c29b9d21d3..9ac72cee9f 100644 --- a/tencentcloud/services/privatedns/resource_tc_private_dns_forward_rule.md +++ b/tencentcloud/services/privatedns/resource_tc_private_dns_forward_rule.md @@ -3,18 +3,23 @@ Provides a resource to create a privatedns forward rule Example Usage ```hcl -resource "tencentcloud_private_dns_end_point" "example" { - end_point_name = "tf-example" - end_point_service_id = "vpcsvc-61wcwmar" - end_point_region = "ap-guangzhou" - ip_num = 1 +resource "tencentcloud_private_dns_extend_end_point" "example" { + end_point_name = "tf-example" + end_point_region = "ap-jakarta" + forward_ip { + access_type = "CCN" + host = "1.1.1.1" + port = 8080 + vpc_id = "vpc-2qjckjg2" + access_gateway_id = "ccn-eo13f8ub" + } } resource "tencentcloud_private_dns_forward_rule" "example" { rule_name = "tf-example" rule_type = "DOWN" zone_id = "zone-cmmbvaq8" - end_point_id = tencentcloud_private_dns_end_point.example.id + end_point_id = tencentcloud_private_dns_extend_end_point.example.id } ``` diff --git a/tencentcloud/services/privatedns/service_tencentcloud_private_dns.go b/tencentcloud/services/privatedns/service_tencentcloud_private_dns.go index 577c8841ae..530d6c9625 100644 --- a/tencentcloud/services/privatedns/service_tencentcloud_private_dns.go +++ b/tencentcloud/services/privatedns/service_tencentcloud_private_dns.go @@ -6,6 +6,7 @@ import ( "log" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + privatednsIntlv20201028 "github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/privatedns/v20201028" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" privatedns "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/privatedns/v20201028" @@ -228,10 +229,10 @@ func (me *PrivateDnsService) DescribePrivatednsPrivateZoneListByFilter(ctx conte return } -func (me *PrivatednsService) DescribePrivateDnsForwardRuleById(ctx context.Context, ruleId string) (ret *privatedns.ForwardRule, errRet error) { +func (me *PrivatednsService) DescribePrivateDnsForwardRuleById(ctx context.Context, ruleId string) (ret *privatednsIntlv20201028.ForwardRule, errRet error) { logId := tccommon.GetLogId(ctx) - request := privatedns.NewDescribeForwardRuleRequest() + request := privatednsIntlv20201028.NewDescribeForwardRuleRequest() request.RuleId = helper.String(ruleId) defer func() { @@ -242,7 +243,7 @@ func (me *PrivatednsService) DescribePrivateDnsForwardRuleById(ctx context.Conte ratelimit.Check(request.GetAction()) - response, err := me.client.UsePrivatednsV20201028Client().DescribeForwardRule(request) + response, err := me.client.UsePrivatednsIntlV20201028Client().DescribeForwardRule(request) if err != nil { errRet = err return @@ -257,11 +258,11 @@ func (me *PrivatednsService) DescribePrivateDnsForwardRuleById(ctx context.Conte return } -func (me *PrivatednsService) DescribePrivateDnsEndPointById(ctx context.Context, endPointId string) (ret *privatedns.DescribeEndPointListResponseParams, errRet error) { +func (me *PrivatednsService) DescribePrivateDnsEndPointById(ctx context.Context, endPointId string) (ret *privatednsIntlv20201028.DescribeEndPointListResponseParams, errRet error) { logId := tccommon.GetLogId(ctx) - request := privatedns.NewDescribeEndPointListRequest() - filter := &privatedns.Filter{ + request := privatednsIntlv20201028.NewDescribeEndPointListRequest() + filter := &privatednsIntlv20201028.Filter{ Name: helper.String("EndPointId"), Values: []*string{helper.String(endPointId)}, } @@ -275,7 +276,7 @@ func (me *PrivatednsService) DescribePrivateDnsEndPointById(ctx context.Context, ratelimit.Check(request.GetAction()) - response, err := me.client.UsePrivatednsV20201028Client().DescribeEndPointList(request) + response, err := me.client.UsePrivatednsIntlV20201028Client().DescribeEndPointList(request) if err != nil { errRet = err return @@ -286,6 +287,45 @@ func (me *PrivatednsService) DescribePrivateDnsEndPointById(ctx context.Context, return } +func (me *PrivatednsService) DescribePrivateDnsExtendEndPointById(ctx context.Context, endPointId string) (ret *privatednsIntlv20201028.DescribeExtendEndpointListResponseParams, errRet error) { + logId := tccommon.GetLogId(ctx) + + request := privatednsIntlv20201028.NewDescribeExtendEndpointListRequest() + response := privatednsIntlv20201028.NewDescribeExtendEndpointListResponse() + filter := &privatednsIntlv20201028.Filter{ + Name: helper.String("EndpointId"), + Values: []*string{helper.String(endPointId)}, + } + request.Filters = append(request.Filters, filter) + + defer func() { + if errRet != nil { + log.Printf("[CRITAL]%s api[%s] fail, request body [%s], reason[%s]\n", logId, request.GetAction(), request.ToJsonString(), errRet.Error()) + } + }() + + ratelimit.Check(request.GetAction()) + err := resource.Retry(tccommon.ReadRetryTimeout, func() *resource.RetryError { + result, e := me.client.UsePrivatednsIntlV20201028Client().DescribeExtendEndpointList(request) + if e != nil { + return tccommon.RetryError(e) + } + + response = result + return nil + }) + + if err != nil { + errRet = err + return + } + + log.Printf("[DEBUG]%s api[%s] success, request body [%s], response body [%s]\n", logId, request.GetAction(), request.ToJsonString(), response.ToJsonString()) + + ret = response.Response + return +} + func (me *PrivatednsService) DescribePrivateDnsForwardRulesByFilter(ctx context.Context, param map[string]interface{}) (ret []*privatedns.ForwardRule, errRet error) { var ( logId = tccommon.GetLogId(ctx) diff --git a/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/client.go b/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/client.go index d17b061c05..c9a02df890 100644 --- a/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/client.go +++ b/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/client.go @@ -2,19 +2,21 @@ package common import ( "encoding/hex" - "encoding/json" "fmt" "log" "net/http" "net/http/httputil" "net/url" "os" + "reflect" + "regexp" "strconv" "strings" "time" tcerr "github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/errors" tchttp "github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/http" + "github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/json" "github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/profile" ) @@ -22,6 +24,8 @@ const ( octetStream = "application/octet-stream" ) +var DefaultHttpClient *http.Client + type Client struct { region string httpClient *http.Client @@ -32,10 +36,31 @@ type Client struct { unsignedPayload bool debug bool rb *circuitBreaker - logger *log.Logger + logger Logger + requestClient string } func (c *Client) Send(request tchttp.Request, response tchttp.Response) (err error) { + c.completeRequest(request) + + tchttp.CompleteCommonParams(request, c.GetRegion(), c.requestClient) + + // reflect to inject client if field ClientToken exists and retry feature is enabled + if c.profile.NetworkFailureMaxRetries > 0 || c.profile.RateLimitExceededMaxRetries > 0 { + safeInjectClientToken(request) + } + + if request.GetSkipSign() { + // Some APIs can skip signature. + return c.sendWithoutSignature(request, response) + } else if c.profile.DisableRegionBreaker == true || c.rb == nil { + return c.sendWithSignature(request, response) + } else { + return c.sendWithRegionBreaker(request, response) + } +} + +func (c *Client) completeRequest(request tchttp.Request) { if request.GetScheme() == "" { request.SetScheme(c.httpProfile.Scheme) } @@ -56,20 +81,15 @@ func (c *Client) Send(request tchttp.Request, response tchttp.Response) (err err request.SetHttpMethod(c.httpProfile.ReqMethod) } - tchttp.CompleteCommonParams(request, c.GetRegion()) - - // reflect to inject client if field ClientToken exists and retry feature is enabled - if c.profile.NetworkFailureMaxRetries > 0 || c.profile.RateLimitExceededMaxRetries > 0 { - safeInjectClientToken(request) - } - - if c.credential == nil { - // Some APIs can skip signature. - return c.sendWithoutSignature(request, response) - } else if c.profile.DisableRegionBreaker == true || c.rb == nil { - return c.sendWithSignature(request, response) - } else { - return c.sendWithRegionBreaker(request, response) + if c.profile.UnsafeRetryOnConnectionFailure { + header := request.GetHeader() + if header == nil { + header = map[string]string{} + } + // http.Transport will automatically retry the request that considered Idempotent + // see http.Request.isReplayable + header["X-Idempotency-Key"] = "x" + request.SetHeader(header) } } @@ -109,15 +129,115 @@ func (c *Client) sendWithSignature(request tchttp.Request, response tchttp.Respo } func (c *Client) sendWithoutSignature(request tchttp.Request, response tchttp.Response) error { - httpRequest, err := http.NewRequestWithContext(request.GetContext(), request.GetHttpMethod(), request.GetUrl(), request.GetBodyReader()) - if err != nil { - return err + headers := map[string]string{ + "Host": request.GetDomain(), + "X-TC-Action": request.GetAction(), + "X-TC-Version": request.GetVersion(), + "X-TC-Timestamp": request.GetParams()["Timestamp"], + "X-TC-RequestClient": request.GetParams()["RequestClient"], + "X-TC-Language": c.profile.Language, + "Authorization": "SKIP", } - if request.GetHttpMethod() == "POST" { - httpRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded") + if c.region != "" { + headers["X-TC-Region"] = c.region } + if c.credential != nil && c.credential.GetToken() != "" { + headers["X-TC-Token"] = c.credential.GetToken() + } + if request.GetHttpMethod() == "GET" { + headers["Content-Type"] = "application/x-www-form-urlencoded" + } else { + headers["Content-Type"] = "application/json" + } + isOctetStream := false + cr := &tchttp.CommonRequest{} + ok := false + var octetStreamBody []byte + if cr, ok = request.(*tchttp.CommonRequest); ok { + if cr.IsOctetStream() { + isOctetStream = true + // custom headers must contain Content-Type : application/octet-stream + // todo:the custom header may overwrite headers + for k, v := range cr.GetHeader() { + headers[k] = v + } + octetStreamBody = cr.GetOctetStreamBody() + } + } + for k, v := range request.GetHeader() { - httpRequest.Header.Set(k, v) + switch k { + case "X-TC-Action", "X-TC-Version", "X-TC-Timestamp", "X-TC-RequestClient", + "X-TC-Language", "Content-Type", "X-TC-Region", "X-TC-Token": + c.logger.Printf("Skip header \"%s\": can not specify built-in header", k) + default: + headers[k] = v + } + } + + if !isOctetStream && request.GetContentType() == octetStream { + isOctetStream = true + b, _ := json.Marshal(request) + var m map[string]string + _ = json.Unmarshal(b, &m) + for k, v := range m { + key := "X-" + strings.ToUpper(request.GetService()) + "-" + k + headers[key] = v + } + + headers["Content-Type"] = octetStream + octetStreamBody = request.GetBody() + } + // start signature v3 process + + // build canonical request string + httpRequestMethod := request.GetHttpMethod() + canonicalQueryString := "" + if httpRequestMethod == "GET" { + err := tchttp.ConstructParams(request) + if err != nil { + return err + } + params := make(map[string]string) + for key, value := range request.GetParams() { + params[key] = value + } + delete(params, "Action") + delete(params, "Version") + delete(params, "Nonce") + delete(params, "Region") + delete(params, "RequestClient") + delete(params, "Timestamp") + canonicalQueryString = tchttp.GetUrlQueriesEncoded(params) + } + requestPayload := "" + if httpRequestMethod == "POST" { + if isOctetStream { + // todo Conversion comparison between string and []byte affects performance much + requestPayload = string(octetStreamBody) + } else { + b, err := json.Marshal(request) + if err != nil { + return err + } + requestPayload = string(b) + } + } + if c.unsignedPayload { + headers["X-TC-Content-SHA256"] = "UNSIGNED-PAYLOAD" + } + + url := request.GetScheme() + "://" + request.GetDomain() + request.GetPath() + if canonicalQueryString != "" { + url = url + "?" + canonicalQueryString + } + httpRequest, err := http.NewRequest(httpRequestMethod, url, strings.NewReader(requestPayload)) + if err != nil { + return err + } + httpRequest = httpRequest.WithContext(request.GetContext()) + for k, v := range headers { + httpRequest.Header[k] = []string{v} } httpResponse, err := c.sendWithRateLimitRetry(httpRequest, isRetryable(request)) if err != nil { @@ -138,10 +258,11 @@ func (c *Client) sendWithSignatureV1(request tchttp.Request, response tchttp.Res if err != nil { return err } - httpRequest, err := http.NewRequestWithContext(request.GetContext(), request.GetHttpMethod(), request.GetUrl(), request.GetBodyReader()) + httpRequest, err := http.NewRequest(request.GetHttpMethod(), request.GetUrl(), request.GetBodyReader()) if err != nil { return err } + httpRequest = httpRequest.WithContext(request.GetContext()) if request.GetHttpMethod() == "POST" { httpRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded") } @@ -170,8 +291,9 @@ func (c *Client) sendWithSignatureV3(request tchttp.Request, response tchttp.Res if c.region != "" { headers["X-TC-Region"] = c.region } - if c.credential.GetToken() != "" { - headers["X-TC-Token"] = c.credential.GetToken() + secId, secKey, token := c.credential.GetCredential() + if token != "" { + headers["X-TC-Token"] = token } if request.GetHttpMethod() == "GET" { headers["Content-Type"] = "application/x-www-form-urlencoded" @@ -197,7 +319,7 @@ func (c *Client) sendWithSignatureV3(request tchttp.Request, response tchttp.Res for k, v := range request.GetHeader() { switch k { case "X-TC-Action", "X-TC-Version", "X-TC-Timestamp", "X-TC-RequestClient", - "X-TC-Language", "Content-Type", "X-TC-Region", "X-TC-Token": + "X-TC-Language", "X-TC-Region", "X-TC-Token": c.logger.Printf("Skip header \"%s\": can not specify built-in header", k) default: headers[k] = v @@ -288,7 +410,7 @@ func (c *Client) sendWithSignatureV3(request tchttp.Request, response tchttp.Res //log.Println("string2sign", string2sign) // sign string - secretDate := hmacsha256(date, "TC3"+c.credential.GetSecretKey()) + secretDate := hmacsha256(date, "TC3"+secKey) secretService := hmacsha256(request.GetService(), secretDate) secretKey := hmacsha256("tc3_request", secretService) signature := hex.EncodeToString([]byte(hmacsha256(string2sign, secretKey))) @@ -297,7 +419,7 @@ func (c *Client) sendWithSignatureV3(request tchttp.Request, response tchttp.Res // build authorization authorization := fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s", algorithm, - c.credential.GetSecretId(), + secId, credentialScope, signedHeaders, signature) @@ -308,10 +430,11 @@ func (c *Client) sendWithSignatureV3(request tchttp.Request, response tchttp.Res if canonicalQueryString != "" { url = url + "?" + canonicalQueryString } - httpRequest, err := http.NewRequestWithContext(request.GetContext(), httpRequestMethod, url, strings.NewReader(requestPayload)) + httpRequest, err := http.NewRequest(httpRequestMethod, url, strings.NewReader(requestPayload)) if err != nil { return err } + httpRequest = httpRequest.WithContext(request.GetContext()) for k, v := range headers { httpRequest.Header[k] = []string{v} } @@ -325,16 +448,37 @@ func (c *Client) sendWithSignatureV3(request tchttp.Request, response tchttp.Res // send http request func (c *Client) sendHttp(request *http.Request) (response *http.Response, err error) { - if c.debug { + if len(c.httpProfile.ApigwEndpoint) > 0 { + request.URL.Host = c.httpProfile.ApigwEndpoint + request.Host = c.httpProfile.ApigwEndpoint + } + + if c.debug && request != nil { outBytes, err := httputil.DumpRequest(request, true) if err != nil { - c.logger.Printf("[ERROR] dump request failed because %s", err) - return nil, err + c.logger.Printf("[ERROR] dump request failed: %s", err) + } else { + c.logger.Printf("[DEBUG] http request: %s", outBytes) } - c.logger.Printf("[DEBUG] http request = %s", outBytes) } response, err = c.httpClient.Do(request) + + if c.debug && response != nil { + dumpBody := true + switch response.Header.Get("Content-Type") { + case "text/event-stream", "application/octet-stream": + dumpBody = false + } + + out, err := httputil.DumpResponse(response, dumpBody) + if err != nil { + c.logger.Printf("[ERROR] dump response failed: %s", err) + } else { + c.logger.Printf("[DEBUG] http response: %s", out) + } + } + return response, err } @@ -343,7 +487,23 @@ func (c *Client) GetRegion() string { } func (c *Client) Init(region string) *Client { - c.httpClient = &http.Client{Transport: http.DefaultTransport.(*http.Transport).Clone()} + + if DefaultHttpClient == nil { + // try not to modify http.DefaultTransport if possible + // since we could possibly modify Transport.Proxy + transport := http.DefaultTransport + if _, ok := transport.(*http.Transport); ok { + // http.Transport.Clone is only available after go1.12 + if cloneMethod, hasClone := reflect.TypeOf(transport).MethodByName("Clone"); hasClone { + transport = cloneMethod.Func.Call([]reflect.Value{reflect.ValueOf(transport)})[0].Interface().(http.RoundTripper) + } + } + + c.httpClient = &http.Client{Transport: transport} + } else { + c.httpClient = DefaultHttpClient + } + c.region = region c.signMethod = "TC3-HMAC-SHA256" c.debug = false @@ -361,6 +521,28 @@ func (c *Client) WithCredential(cred CredentialIface) *Client { return c } +func (c *Client) WithRequestClient(rc string) *Client { + const reRequestClient = "^[0-9a-zA-Z-_ ,;.]+$" + + if len(rc) > 128 { + c.logger.Printf("the length of RequestClient should be within 128 characters, it will be truncated") + rc = rc[:128] + } + + match, err := regexp.MatchString(reRequestClient, rc) + if err != nil { + c.logger.Printf("regexp is wrong: %s", reRequestClient) + return c + } + if !match { + c.logger.Printf("RequestClient not match the regexp: %s, ignored", reRequestClient) + return c + } + + c.requestClient = rc + return c +} + func (c *Client) GetCredential() CredentialIface { return c.credential } @@ -380,7 +562,16 @@ func (c *Client) WithProfile(clientProfile *profile.ClientProfile) *Client { if err != nil { panic(err) } - c.httpClient.Transport.(*http.Transport).Proxy = http.ProxyURL(u) + + if c.httpClient.Transport == nil { + c.logger.Printf("trying to set proxy when httpClient.Transport is nil") + } + + if _, ok := c.httpClient.Transport.(*http.Transport); ok { + c.httpClient.Transport.(*http.Transport).Proxy = http.ProxyURL(u) + } else { + c.logger.Printf("setting proxy while httpClient.Transport is not a http.Transport is not supported") + } } return c } diff --git a/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/credentials.go b/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/credentials.go index 1a24156f44..c8fab3ee58 100644 --- a/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/credentials.go +++ b/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/credentials.go @@ -6,6 +6,7 @@ type CredentialIface interface { GetSecretId() string GetToken() string GetSecretKey() string + GetCredential() (string, string, string) // needRefresh() bool // refresh() } @@ -49,3 +50,7 @@ func (c *Credential) GetSecretId() string { func (c *Credential) GetToken() string { return c.Token } + +func (c *Credential) GetCredential() (string, string, string) { + return c.SecretId, c.SecretKey, c.Token +} diff --git a/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/cvm_role_credential.go b/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/cvm_role_credential.go index 62d62dfb2f..f4f4d30b72 100644 --- a/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/cvm_role_credential.go +++ b/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/cvm_role_credential.go @@ -37,6 +37,13 @@ func (c *CvmRoleCredential) GetSecretKey() string { return c.tmpSecretKey } +func (c *CvmRoleCredential) GetCredential() (string, string, string) { + if c.needRefresh() { + c.refresh() + } + return c.tmpSecretId, c.tmpSecretKey, c.token +} + func (c *CvmRoleCredential) needRefresh() bool { if c.tmpSecretId == "" || c.tmpSecretKey == "" || c.token == "" || c.expiredTime-ExpiredTimeout <= time.Now().Unix() { return true diff --git a/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/cvm_role_provider.go b/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/cvm_role_provider.go index 0047624ac8..640ade7d30 100644 --- a/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/cvm_role_provider.go +++ b/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/cvm_role_provider.go @@ -1,12 +1,13 @@ package common import ( - "encoding/json" "errors" - tcerr "github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/errors" "io/ioutil" "net/http" "time" + + tcerr "github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/errors" + "github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/json" ) const ( diff --git a/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/http/common_request.go b/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/http/common_request.go index 0edbb961aa..663bb4a69a 100644 --- a/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/http/common_request.go +++ b/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/http/common_request.go @@ -1,9 +1,9 @@ package common import ( - "encoding/json" "fmt" tcerr "github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/errors" + "github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/json" ) const ( @@ -36,12 +36,12 @@ func (cr *CommonRequest) SetActionParameters(data interface{}) error { switch data.(type) { case []byte: if err := json.Unmarshal(data.([]byte), &cr.actionParameters); err != nil { - msg := fmt.Sprintf("Fail to parse contenst %s to json,because: %s", data.([]byte), err) + msg := fmt.Sprintf("Fail to parse contents %s to json,because: %s", data.([]byte), err) return tcerr.NewTencentCloudSDKError("ClientError.ParseJsonError", msg, "") } case string: if err := json.Unmarshal([]byte(data.(string)), &cr.actionParameters); err != nil { - msg := fmt.Sprintf("Fail to parse contenst %s to json,because: %s", data.(string), err) + msg := fmt.Sprintf("Fail to parse contents %s to json,because: %s", data.(string), err) return tcerr.NewTencentCloudSDKError("ClientError.ParseJsonError", msg, "") } case map[string]interface{}: diff --git a/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/http/common_response.go b/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/http/common_response.go index a074ff0c6d..f6fec4203f 100644 --- a/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/http/common_response.go +++ b/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/http/common_response.go @@ -1,6 +1,6 @@ package common -import "encoding/json" +import "github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/json" type actionResult map[string]interface{} type CommonResponse struct { diff --git a/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/http/request.go b/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/http/request.go index 24415396ef..91607cfa98 100644 --- a/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/http/request.go +++ b/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/http/request.go @@ -40,6 +40,7 @@ type Request interface { GetContentType() string GetContext() context.Context GetHeader() map[string]string + GetSkipSign() bool SetScheme(string) SetRootDomain(string) SetDomain(string) @@ -49,6 +50,7 @@ type Request interface { SetBody([]byte) SetContext(context.Context) SetHeader(header map[string]string) + SetSkipSign(skip bool) } type BaseRequest struct { @@ -58,6 +60,7 @@ type BaseRequest struct { rootDomain string domain string path string + skipSign bool params map[string]string formParams map[string]string header map[string]string @@ -202,6 +205,14 @@ func (r *BaseRequest) SetHeader(header map[string]string) { r.header = header } +func (r *BaseRequest) GetSkipSign() bool { + return r.skipSign +} + +func (r *BaseRequest) SetSkipSign(skip bool) { + r.skipSign = skip +} + func GetUrlQueriesEncoded(params map[string]string) string { values := url.Values{} for key, value := range params { @@ -245,7 +256,7 @@ func GetServiceDomain(service string) (domain string) { return } -func CompleteCommonParams(request Request, region string) { +func CompleteCommonParams(request Request, region string, requestClient string) { params := request.GetParams() params["Region"] = region if request.GetVersion() != "" { @@ -254,7 +265,10 @@ func CompleteCommonParams(request Request, region string) { params["Action"] = request.GetAction() params["Timestamp"] = strconv.FormatInt(time.Now().Unix(), 10) params["Nonce"] = strconv.Itoa(rand.Int()) - params["RequestClient"] = "SDK_GO_3.0.646" + params["RequestClient"] = "SDK_GO_3.0.1114" + if requestClient != "" { + params["RequestClient"] += ": " + requestClient + } } func ConstructParams(req Request) (err error) { diff --git a/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/http/response.go b/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/http/response.go index c054d5f7c3..0fd5b0c469 100644 --- a/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/http/response.go +++ b/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/http/response.go @@ -1,9 +1,14 @@ package common import ( - "encoding/json" + "bufio" + "bytes" "fmt" + "github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/json" "io/ioutil" + "log" + "strconv" + //"log" "net/http" @@ -17,6 +22,28 @@ type Response interface { type BaseResponse struct { } +type SSEResponse interface { + Response + setEventsChannel(ch chan SSEvent) +} + +type SSEvent struct { + Event string + Data []byte + Id string + Retry int64 + Err error +} + +type BaseSSEResponse struct { + BaseResponse + Events chan SSEvent +} + +func (r *BaseSSEResponse) setEventsChannel(ch chan SSEvent) { + r.Events = ch +} + type ErrorResponse struct { Response struct { Error struct { @@ -79,7 +106,52 @@ func ParseErrorFromHTTPResponse(body []byte) (err error) { return nil } -func ParseFromHttpResponse(hr *http.Response, response Response) (err error) { +func TryReadErr(resp *http.Response) (err error) { + switch resp.Header.Get("Content-Type") { + case "text/event-stream", "application/octet-stream": + return nil + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + msg := fmt.Sprintf("Fail to read http body, because: %s", err) + return errors.NewTencentCloudSDKError("ClientError.ParseJsonError", msg, "") + } + resp.Body.Close() + + errResp := &ErrorResponse{} + err = json.Unmarshal(body, &errResp) + if err != nil { + msg := fmt.Sprintf("Fail to parse json content: %s, because: %s", string(body), err) + return errors.NewTencentCloudSDKError("ClientError.ParseJsonError", msg, "") + } + if errResp.Response.Error.Code != "" { + return errors.NewTencentCloudSDKError(errResp.Response.Error.Code, errResp.Response.Error.Message, errResp.Response.RequestId) + } + + depResp := &DeprecatedAPIErrorResponse{} + err = json.Unmarshal(body, &depResp) + if err != nil { + msg := fmt.Sprintf("Fail to parse json content: %s, because: %s", string(body), err) + return errors.NewTencentCloudSDKError("ClientError.ParseJsonError", msg, "") + } + if depResp.Code != 0 { + return errors.NewTencentCloudSDKError(depResp.CodeDesc, depResp.Message, "") + } + resp.Body = ioutil.NopCloser(bytes.NewReader(body)) + return nil +} + +func ParseFromHttpResponse(hr *http.Response, resp Response) error { + switch hr.Header.Get("Content-Type") { + case "text/event-stream": + return parseFromSSE(hr, resp) + default: + return parseFromJson(hr, resp) + } +} + +func parseFromJson(hr *http.Response, resp Response) error { defer hr.Body.Close() body, err := ioutil.ReadAll(hr.Body) if err != nil { @@ -90,15 +162,105 @@ func ParseFromHttpResponse(hr *http.Response, response Response) (err error) { msg := fmt.Sprintf("Request fail with http status code: %s, with body: %s", hr.Status, body) return errors.NewTencentCloudSDKError("ClientError.HttpStatusCodeError", msg, "") } - //log.Printf("[DEBUG] Response Body=%s", body) - err = response.ParseErrorFromHTTPResponse(body) + err = resp.ParseErrorFromHTTPResponse(body) if err != nil { - return + return err } - err = json.Unmarshal(body, &response) + err = json.Unmarshal(body, &resp) if err != nil { msg := fmt.Sprintf("Fail to parse json content: %s, because: %s", body, err) return errors.NewTencentCloudSDKError("ClientError.ParseJsonError", msg, "") } - return + return nil +} + +func parseFromSSE(hr *http.Response, resp Response) error { + reqId := hr.Header.Get("X-TC-RequestId") + r, ok := resp.(SSEResponse) + if !ok { + return errors.NewTencentCloudSDKError("ClientError.TypeError", + "Response type does not implement SSEResponse", reqId) + } + + ch := make(chan SSEvent) + r.setEventsChannel(ch) + + // parser + go func() { + defer hr.Body.Close() + defer close(ch) + + scanner := bufio.NewScanner(hr.Body) + scanner.Split(bufio.ScanLines) + + event := SSEvent{} + for scanner.Scan() { + line := scanner.Bytes() + + // SSE use empty line to indicate message end + if len(line) == 0 { + select { + case ch <- event: + event = SSEvent{} + continue + case <-hr.Request.Context().Done(): + select { + case ch <- SSEvent{Err: hr.Request.Context().Err()}: + default: + log.Println(hr.Request.Context().Err()) + } + return + } + } + + // comment + if line[0] == ':' { + continue + } + + idx := bytes.IndexByte(line, ':') + if idx == -1 { + select { + case ch <- SSEvent{Err: fmt.Errorf("SSE.InvalidLine:%s", line)}: + default: + } + return + } + + key := string(line[:idx]) + val := line[idx+1:] + switch key { + case "event": + event.Event = string(val) + case "data": + // The spec allows for multiple data fields per event, concat them with "\n". + if len(event.Data) > 0 { + event.Data = append(event.Data, '\n') + } + event.Data = append(event.Data, val...) + case "id": + event.Id = string(val) + case "retry": + retry, err := strconv.ParseInt(string(val), 10, 64) + if err != nil { + select { + case ch <- SSEvent{Err: fmt.Errorf("SSE.InvalidRetry:%s", line)}: + default: + } + return + } + event.Retry = retry + } + } + + if err := scanner.Err(); err != nil { + select { + case ch <- SSEvent{Err: err}: + default: + log.Println(err) + } + } + }() + + return nil } diff --git a/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/json/decode.go b/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/json/decode.go new file mode 100644 index 0000000000..a563252ce7 --- /dev/null +++ b/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/json/decode.go @@ -0,0 +1,1318 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Represents JSON data structure using native Go types: booleans, floats, +// strings, arrays, and maps. + +package json + +import ( + "bytes" + "encoding" + "encoding/base64" + "errors" + "fmt" + "reflect" + "strconv" + "unicode" + "unicode/utf16" + "unicode/utf8" +) + +// Unmarshal parses the JSON-encoded data and stores the result +// in the value pointed to by v. If v is nil or not a pointer, +// Unmarshal returns an InvalidUnmarshalError. +// +// Unmarshal uses the inverse of the encodings that +// Marshal uses, allocating maps, slices, and pointers as necessary, +// with the following additional rules: +// +// To unmarshal JSON into a pointer, Unmarshal first handles the case of +// the JSON being the JSON literal null. In that case, Unmarshal sets +// the pointer to nil. Otherwise, Unmarshal unmarshals the JSON into +// the value pointed at by the pointer. If the pointer is nil, Unmarshal +// allocates a new value for it to point to. +// +// To unmarshal JSON into a value implementing the Unmarshaler interface, +// Unmarshal calls that value's UnmarshalJSON method, including +// when the input is a JSON null. +// Otherwise, if the value implements encoding.TextUnmarshaler +// and the input is a JSON quoted string, Unmarshal calls that value's +// UnmarshalText method with the unquoted form of the string. +// +// To unmarshal JSON into a struct, Unmarshal matches incoming object +// keys to the keys used by Marshal (either the struct field name or its tag), +// preferring an exact match but also accepting a case-insensitive match. By +// default, object keys which don't have a corresponding struct field are +// ignored (see Decoder.DisallowUnknownFields for an alternative). +// +// To unmarshal JSON into an interface value, +// Unmarshal stores one of these in the interface value: +// +// bool, for JSON booleans +// float64, for JSON numbers +// string, for JSON strings +// []interface{}, for JSON arrays +// map[string]interface{}, for JSON objects +// nil for JSON null +// +// To unmarshal a JSON array into a slice, Unmarshal resets the slice length +// to zero and then appends each element to the slice. +// As a special case, to unmarshal an empty JSON array into a slice, +// Unmarshal replaces the slice with a new empty slice. +// +// To unmarshal a JSON array into a Go array, Unmarshal decodes +// JSON array elements into corresponding Go array elements. +// If the Go array is smaller than the JSON array, +// the additional JSON array elements are discarded. +// If the JSON array is smaller than the Go array, +// the additional Go array elements are set to zero values. +// +// To unmarshal a JSON object into a map, Unmarshal first establishes a map to +// use. If the map is nil, Unmarshal allocates a new map. Otherwise Unmarshal +// reuses the existing map, keeping existing entries. Unmarshal then stores +// key-value pairs from the JSON object into the map. The map's key type must +// either be a string, an integer, or implement encoding.TextUnmarshaler. +// +// If a JSON value is not appropriate for a given target type, +// or if a JSON number overflows the target type, Unmarshal +// skips that field and completes the unmarshaling as best it can. +// If no more serious errors are encountered, Unmarshal returns +// an UnmarshalTypeError describing the earliest such error. In any +// case, it's not guaranteed that all the remaining fields following +// the problematic one will be unmarshaled into the target object. +// +// The JSON null value unmarshals into an interface, map, pointer, or slice +// by setting that Go value to nil. Because null is often used in JSON to mean +// ``not present,'' unmarshaling a JSON null into any other Go type has no effect +// on the value and produces no error. +// +// When unmarshaling quoted strings, invalid UTF-8 or +// invalid UTF-16 surrogate pairs are not treated as an error. +// Instead, they are replaced by the Unicode replacement +// character U+FFFD. +// +func Unmarshal(data []byte, v interface{}) error { + // Check for well-formedness. + // Avoids filling out half a data structure + // before discovering a JSON syntax error. + var d decodeState + err := checkValid(data, &d.scan) + if err != nil { + return err + } + + d.init(data) + return d.unmarshal(v) +} + +// Unmarshaler is the interface implemented by types +// that can unmarshal a JSON description of themselves. +// The input can be assumed to be a valid encoding of +// a JSON value. UnmarshalJSON must copy the JSON data +// if it wishes to retain the data after returning. +// +// By convention, to approximate the behavior of Unmarshal itself, +// Unmarshalers implement UnmarshalJSON([]byte("null")) as a no-op. +type Unmarshaler interface { + UnmarshalJSON([]byte) error +} + +// An UnmarshalTypeError describes a JSON value that was +// not appropriate for a value of a specific Go type. +type UnmarshalTypeError struct { + Value string // description of JSON value - "bool", "array", "number -5" + Type reflect.Type // type of Go value it could not be assigned to + Offset int64 // error occurred after reading Offset bytes + Struct string // name of the struct type containing the field + Field string // name of the field holding the Go value +} + +func (e *UnmarshalTypeError) Error() string { + if e.Struct != "" || e.Field != "" { + return "json: cannot unmarshal " + e.Value + " into Go struct field " + e.Struct + "." + e.Field + " of type " + e.Type.String() + } + return "json: cannot unmarshal " + e.Value + " into Go value of type " + e.Type.String() +} + +// An UnmarshalFieldError describes a JSON object key that +// led to an unexported (and therefore unwritable) struct field. +// +// Deprecated: No longer used; kept for compatibility. +type UnmarshalFieldError struct { + Key string + Type reflect.Type + Field reflect.StructField +} + +func (e *UnmarshalFieldError) Error() string { + return "json: cannot unmarshal object key " + strconv.Quote(e.Key) + " into unexported field " + e.Field.Name + " of type " + e.Type.String() +} + +// An InvalidUnmarshalError describes an invalid argument passed to Unmarshal. +// (The argument to Unmarshal must be a non-nil pointer.) +type InvalidUnmarshalError struct { + Type reflect.Type +} + +func (e *InvalidUnmarshalError) Error() string { + if e.Type == nil { + return "json: Unmarshal(nil)" + } + + if e.Type.Kind() != reflect.Ptr { + return "json: Unmarshal(non-pointer " + e.Type.String() + ")" + } + return "json: Unmarshal(nil " + e.Type.String() + ")" +} + +func (d *decodeState) unmarshal(v interface{}) error { + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Ptr || rv.IsNil() { + return &InvalidUnmarshalError{reflect.TypeOf(v)} + } + + d.scan.reset() + d.scanWhile(scanSkipSpace) + // We decode rv not rv.Elem because the Unmarshaler interface + // test must be applied at the top level of the value. + err := d.value(rv) + if err != nil { + return d.addErrorContext(err) + } + return d.savedError +} + +// A Number represents a JSON number literal. +type Number string + +// String returns the literal text of the number. +func (n Number) String() string { return string(n) } + +// Float64 returns the number as a float64. +func (n Number) Float64() (float64, error) { + return strconv.ParseFloat(string(n), 64) +} + +// Int64 returns the number as an int64. +func (n Number) Int64() (int64, error) { + return strconv.ParseInt(string(n), 10, 64) +} + +// isValidNumber reports whether s is a valid JSON number literal. +func isValidNumber(s string) bool { + // This function implements the JSON numbers grammar. + // See https://tools.ietf.org/html/rfc7159#section-6 + // and https://json.org/number.gif + + if s == "" { + return false + } + + // Optional - + if s[0] == '-' { + s = s[1:] + if s == "" { + return false + } + } + + // Digits + switch { + default: + return false + + case s[0] == '0': + s = s[1:] + + case '1' <= s[0] && s[0] <= '9': + s = s[1:] + for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { + s = s[1:] + } + } + + // . followed by 1 or more digits. + if len(s) >= 2 && s[0] == '.' && '0' <= s[1] && s[1] <= '9' { + s = s[2:] + for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { + s = s[1:] + } + } + + // e or E followed by an optional - or + and + // 1 or more digits. + if len(s) >= 2 && (s[0] == 'e' || s[0] == 'E') { + s = s[1:] + if s[0] == '+' || s[0] == '-' { + s = s[1:] + if s == "" { + return false + } + } + for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { + s = s[1:] + } + } + + // Make sure we are at the end. + return s == "" +} + +// decodeState represents the state while decoding a JSON value. +type decodeState struct { + data []byte + off int // next read offset in data + opcode int // last read result + scan scanner + errorContext struct { // provides context for type errors + Struct string + Field string + } + savedError error + useNumber bool + disallowUnknownFields bool +} + +// readIndex returns the position of the last byte read. +func (d *decodeState) readIndex() int { + return d.off - 1 +} + +// errPhase is used for errors that should not happen unless +// there is a bug in the JSON decoder or something is editing +// the data slice while the decoder executes. +var errPhase = errors.New("JSON decoder out of sync - data changing underfoot?") + +func (d *decodeState) init(data []byte) *decodeState { + d.data = data + d.off = 0 + d.savedError = nil + d.errorContext.Struct = "" + d.errorContext.Field = "" + return d +} + +// saveError saves the first err it is called with, +// for reporting at the end of the unmarshal. +func (d *decodeState) saveError(err error) { + if d.savedError == nil { + d.savedError = d.addErrorContext(err) + } +} + +// addErrorContext returns a new error enhanced with information from d.errorContext +func (d *decodeState) addErrorContext(err error) error { + if d.errorContext.Struct != "" || d.errorContext.Field != "" { + switch err := err.(type) { + case *UnmarshalTypeError: + err.Struct = d.errorContext.Struct + err.Field = d.errorContext.Field + return err + } + } + return err +} + +// skip scans to the end of what was started. +func (d *decodeState) skip() { + s, data, i := &d.scan, d.data, d.off + depth := len(s.parseState) + for { + op := s.step(s, data[i]) + i++ + if len(s.parseState) < depth { + d.off = i + d.opcode = op + return + } + } +} + +// scanNext processes the byte at d.data[d.off]. +func (d *decodeState) scanNext() { + s, data, i := &d.scan, d.data, d.off + if i < len(data) { + d.opcode = s.step(s, data[i]) + d.off = i + 1 + } else { + d.opcode = s.eof() + d.off = len(data) + 1 // mark processed EOF with len+1 + } +} + +// scanWhile processes bytes in d.data[d.off:] until it +// receives a scan code not equal to op. +func (d *decodeState) scanWhile(op int) { + s, data, i := &d.scan, d.data, d.off + for i < len(d.data) { + newOp := s.step(s, data[i]) + i++ + if newOp != op { + d.opcode = newOp + d.off = i + return + } + } + + d.off = len(d.data) + 1 // mark processed EOF with len+1 + d.opcode = d.scan.eof() +} + +// value consumes a JSON value from d.data[d.off-1:], decoding into v, and +// reads the following byte ahead. If v is invalid, the value is discarded. +// The first byte of the value has been read already. +func (d *decodeState) value(v reflect.Value) error { + switch d.opcode { + default: + return errPhase + + case scanBeginArray: + if v.IsValid() { + if err := d.array(v); err != nil { + return err + } + } else { + d.skip() + } + d.scanNext() + + case scanBeginObject: + if v.IsValid() { + if err := d.object(v); err != nil { + return err + } + } else { + d.skip() + } + d.scanNext() + + case scanBeginLiteral: + // All bytes inside literal return scanContinue op code. + start := d.readIndex() + d.scanWhile(scanContinue) + + if v.IsValid() { + if err := d.literalStore(d.data[start:d.readIndex()], v, false); err != nil { + return err + } + } + } + return nil +} + +type unquotedValue struct{} + +// valueQuoted is like value but decodes a +// quoted string literal or literal null into an interface value. +// If it finds anything other than a quoted string literal or null, +// valueQuoted returns unquotedValue{}. +func (d *decodeState) valueQuoted() (interface{}, error) { + switch d.opcode { + default: + return nil, errPhase + + case scanBeginArray: + d.skip() + d.scanNext() + + case scanBeginObject: + d.skip() + d.scanNext() + + case scanBeginLiteral: + v, err := d.literalInterface() + if err != nil { + return nil, err + } + switch v.(type) { + case nil, string: + return v, nil + } + } + return unquotedValue{}, nil +} + +// indirect walks down v allocating pointers as needed, +// until it gets to a non-pointer. +// if it encounters an Unmarshaler, indirect stops and returns that. +// if decodingNull is true, indirect stops at the last pointer so it can be set to nil. +func indirect(v reflect.Value, decodingNull bool) (Unmarshaler, encoding.TextUnmarshaler, reflect.Value) { + // Issue #24153 indicates that it is generally not a guaranteed property + // that you may round-trip a reflect.Value by calling Value.Addr().Elem() + // and expect the value to still be settable for values derived from + // unexported embedded struct fields. + // + // The logic below effectively does this when it first addresses the value + // (to satisfy possible pointer methods) and continues to dereference + // subsequent pointers as necessary. + // + // After the first round-trip, we set v back to the original value to + // preserve the original RW flags contained in reflect.Value. + v0 := v + haveAddr := false + + // If v is a named type and is addressable, + // start with its address, so that if the type has pointer methods, + // we find them. + if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() { + haveAddr = true + v = v.Addr() + } + for { + // Load value from interface, but only if the result will be + // usefully addressable. + if v.Kind() == reflect.Interface && !v.IsNil() { + e := v.Elem() + if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) { + haveAddr = false + v = e + continue + } + } + + if v.Kind() != reflect.Ptr { + break + } + + if v.Elem().Kind() != reflect.Ptr && decodingNull && v.CanSet() { + break + } + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + if v.Type().NumMethod() > 0 { + if u, ok := v.Interface().(Unmarshaler); ok { + return u, nil, reflect.Value{} + } + if !decodingNull { + if u, ok := v.Interface().(encoding.TextUnmarshaler); ok { + return nil, u, reflect.Value{} + } + } + } + + if haveAddr { + v = v0 // restore original value after round-trip Value.Addr().Elem() + haveAddr = false + } else { + v = v.Elem() + } + } + return nil, nil, v +} + +// array consumes an array from d.data[d.off-1:], decoding into v. +// The first byte of the array ('[') has been read already. +func (d *decodeState) array(v reflect.Value) error { + // Check for unmarshaler. + u, ut, pv := indirect(v, false) + if u != nil { + start := d.readIndex() + d.skip() + return u.UnmarshalJSON(d.data[start:d.off]) + } + if ut != nil { + d.saveError(&UnmarshalTypeError{Value: "array", Type: v.Type(), Offset: int64(d.off)}) + d.skip() + return nil + } + v = pv + + // Check type of target. + switch v.Kind() { + case reflect.Interface: + if v.NumMethod() == 0 { + // Decoding into nil interface? Switch to non-reflect code. + ai, err := d.arrayInterface() + if err != nil { + return err + } + v.Set(reflect.ValueOf(ai)) + return nil + } + // Otherwise it's invalid. + fallthrough + default: + d.saveError(&UnmarshalTypeError{Value: "array", Type: v.Type(), Offset: int64(d.off)}) + d.skip() + return nil + case reflect.Array: + case reflect.Slice: + break + } + + i := 0 + for { + // Look ahead for ] - can only happen on first iteration. + d.scanWhile(scanSkipSpace) + if d.opcode == scanEndArray { + break + } + + // Get element of array, growing if necessary. + if v.Kind() == reflect.Slice { + // Grow slice if necessary + if i >= v.Cap() { + newcap := v.Cap() + v.Cap()/2 + if newcap < 4 { + newcap = 4 + } + newv := reflect.MakeSlice(v.Type(), v.Len(), newcap) + reflect.Copy(newv, v) + v.Set(newv) + } + if i >= v.Len() { + v.SetLen(i + 1) + } + } + + if i < v.Len() { + // Decode into element. + if err := d.value(v.Index(i)); err != nil { + return err + } + } else { + // Ran out of fixed array: skip. + if err := d.value(reflect.Value{}); err != nil { + return err + } + } + i++ + + // Next token must be , or ]. + if d.opcode == scanSkipSpace { + d.scanWhile(scanSkipSpace) + } + if d.opcode == scanEndArray { + break + } + if d.opcode != scanArrayValue { + return errPhase + } + } + + if i < v.Len() { + if v.Kind() == reflect.Array { + // Array. Zero the rest. + z := reflect.Zero(v.Type().Elem()) + for ; i < v.Len(); i++ { + v.Index(i).Set(z) + } + } else { + v.SetLen(i) + } + } + if i == 0 && v.Kind() == reflect.Slice { + v.Set(reflect.MakeSlice(v.Type(), 0, 0)) + } + return nil +} + +var nullLiteral = []byte("null") +var textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem() + +// object consumes an object from d.data[d.off-1:], decoding into v. +// The first byte ('{') of the object has been read already. +func (d *decodeState) object(v reflect.Value) error { + // Check for unmarshaler. + u, ut, pv := indirect(v, false) + if u != nil { + start := d.readIndex() + d.skip() + return u.UnmarshalJSON(d.data[start:d.off]) + } + if ut != nil { + d.saveError(&UnmarshalTypeError{Value: "object", Type: v.Type(), Offset: int64(d.off)}) + d.skip() + return nil + } + v = pv + + // Decoding into nil interface? Switch to non-reflect code. + if v.Kind() == reflect.Interface && v.NumMethod() == 0 { + oi, err := d.objectInterface() + if err != nil { + return err + } + v.Set(reflect.ValueOf(oi)) + return nil + } + + // Check type of target: + // struct or + // map[T1]T2 where T1 is string, an integer type, + // or an encoding.TextUnmarshaler + switch v.Kind() { + case reflect.Map: + // Map key must either have string kind, have an integer kind, + // or be an encoding.TextUnmarshaler. + t := v.Type() + switch t.Key().Kind() { + case reflect.String, + reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + default: + if !reflect.PtrTo(t.Key()).Implements(textUnmarshalerType) { + d.saveError(&UnmarshalTypeError{Value: "object", Type: v.Type(), Offset: int64(d.off)}) + d.skip() + return nil + } + } + if v.IsNil() { + v.Set(reflect.MakeMap(t)) + } + case reflect.Struct: + // ok + default: + d.saveError(&UnmarshalTypeError{Value: "object", Type: v.Type(), Offset: int64(d.off)}) + d.skip() + return nil + } + + var mapElem reflect.Value + originalErrorContext := d.errorContext + + for { + // Read opening " of string key or closing }. + d.scanWhile(scanSkipSpace) + if d.opcode == scanEndObject { + // closing } - can only happen on first iteration. + break + } + if d.opcode != scanBeginLiteral { + return errPhase + } + + // Read key. + start := d.readIndex() + d.scanWhile(scanContinue) + item := d.data[start:d.readIndex()] + key, ok := unquoteBytes(item) + if !ok { + return errPhase + } + + // Figure out field corresponding to key. + var subv reflect.Value + destring := false // whether the value is wrapped in a string to be decoded first + + if v.Kind() == reflect.Map { + elemType := v.Type().Elem() + if !mapElem.IsValid() { + mapElem = reflect.New(elemType).Elem() + } else { + mapElem.Set(reflect.Zero(elemType)) + } + subv = mapElem + } else { + var f *field + fields := cachedTypeFields(v.Type()) + for i := range fields { + ff := &fields[i] + if bytes.Equal(ff.nameBytes, key) { + f = ff + break + } + if f == nil && ff.equalFold(ff.nameBytes, key) { + f = ff + } + } + if f != nil { + subv = v + destring = f.quoted + for _, i := range f.index { + if subv.Kind() == reflect.Ptr { + if subv.IsNil() { + // If a struct embeds a pointer to an unexported type, + // it is not possible to set a newly allocated value + // since the field is unexported. + // + // See https://golang.org/issue/21357 + if !subv.CanSet() { + d.saveError(fmt.Errorf("json: cannot set embedded pointer to unexported struct: %v", subv.Type().Elem())) + // Invalidate subv to ensure d.value(subv) skips over + // the JSON value without assigning it to subv. + subv = reflect.Value{} + destring = false + break + } + subv.Set(reflect.New(subv.Type().Elem())) + } + subv = subv.Elem() + } + subv = subv.Field(i) + } + d.errorContext.Field = f.name + d.errorContext.Struct = v.Type().Name() + } else if d.disallowUnknownFields { + d.saveError(fmt.Errorf("json: unknown field %q", key)) + } + } + + // Read : before value. + if d.opcode == scanSkipSpace { + d.scanWhile(scanSkipSpace) + } + if d.opcode != scanObjectKey { + return errPhase + } + d.scanWhile(scanSkipSpace) + + if destring { + q, err := d.valueQuoted() + if err != nil { + return err + } + switch qv := q.(type) { + case nil: + if err := d.literalStore(nullLiteral, subv, false); err != nil { + return err + } + case string: + if err := d.literalStore([]byte(qv), subv, true); err != nil { + return err + } + default: + d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal unquoted value into %v", subv.Type())) + } + } else { + if err := d.value(subv); err != nil { + return err + } + } + + // Write value back to map; + // if using struct, subv points into struct already. + if v.Kind() == reflect.Map { + kt := v.Type().Key() + var kv reflect.Value + switch { + case kt.Kind() == reflect.String: + kv = reflect.ValueOf(key).Convert(kt) + case reflect.PtrTo(kt).Implements(textUnmarshalerType): + kv = reflect.New(v.Type().Key()) + if err := d.literalStore(item, kv, true); err != nil { + return err + } + kv = kv.Elem() + default: + switch kt.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + s := string(key) + n, err := strconv.ParseInt(s, 10, 64) + if err != nil || reflect.Zero(kt).OverflowInt(n) { + d.saveError(&UnmarshalTypeError{Value: "number " + s, Type: kt, Offset: int64(start + 1)}) + return nil + } + kv = reflect.ValueOf(n).Convert(kt) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + s := string(key) + n, err := strconv.ParseUint(s, 10, 64) + if err != nil || reflect.Zero(kt).OverflowUint(n) { + d.saveError(&UnmarshalTypeError{Value: "number " + s, Type: kt, Offset: int64(start + 1)}) + return nil + } + kv = reflect.ValueOf(n).Convert(kt) + default: + panic("json: Unexpected key type") // should never occur + } + } + v.SetMapIndex(kv, subv) + } + + // Next token must be , or }. + if d.opcode == scanSkipSpace { + d.scanWhile(scanSkipSpace) + } + if d.opcode == scanEndObject { + break + } + if d.opcode != scanObjectValue { + return errPhase + } + + d.errorContext = originalErrorContext + } + return nil +} + +// convertNumber converts the number literal s to a float64 or a Number +// depending on the setting of d.useNumber. +func (d *decodeState) convertNumber(s string) (interface{}, error) { + if d.useNumber { + return Number(s), nil + } + f, err := strconv.ParseFloat(s, 64) + if err != nil { + return nil, &UnmarshalTypeError{Value: "number " + s, Type: reflect.TypeOf(0.0), Offset: int64(d.off)} + } + return f, nil +} + +var numberType = reflect.TypeOf(Number("")) + +// literalStore decodes a literal stored in item into v. +// +// fromQuoted indicates whether this literal came from unwrapping a +// string from the ",string" struct tag option. this is used only to +// produce more helpful error messages. +func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool) error { + // Check for unmarshaler. + if len(item) == 0 { + //Empty string given + d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) + return nil + } + isNull := item[0] == 'n' // null + u, ut, pv := indirect(v, isNull) + if u != nil { + return u.UnmarshalJSON(item) + } + if ut != nil { + if item[0] != '"' { + if fromQuoted { + d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) + } else { + var val string + switch item[0] { + case 'n': + val = "null" + case 't', 'f': + val = "bool" + default: + val = "number" + } + d.saveError(&UnmarshalTypeError{Value: val, Type: v.Type(), Offset: int64(d.readIndex())}) + } + return nil + } + s, ok := unquoteBytes(item) + if !ok { + if fromQuoted { + return fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()) + } + return errPhase + } + return ut.UnmarshalText(s) + } + + v = pv + + switch c := item[0]; c { + case 'n': // null + // The main parser checks that only true and false can reach here, + // but if this was a quoted string input, it could be anything. + if fromQuoted && string(item) != "null" { + d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) + break + } + switch v.Kind() { + case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice: + v.Set(reflect.Zero(v.Type())) + // otherwise, ignore null for primitives/string + } + case 't', 'f': // true, false + value := item[0] == 't' + // The main parser checks that only true and false can reach here, + // but if this was a quoted string input, it could be anything. + if fromQuoted && string(item) != "true" && string(item) != "false" { + d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) + break + } + switch v.Kind() { + default: + if fromQuoted { + d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) + } else { + d.saveError(&UnmarshalTypeError{Value: "bool", Type: v.Type(), Offset: int64(d.readIndex())}) + } + case reflect.Bool: + v.SetBool(value) + case reflect.Interface: + if v.NumMethod() == 0 { + v.Set(reflect.ValueOf(value)) + } else { + d.saveError(&UnmarshalTypeError{Value: "bool", Type: v.Type(), Offset: int64(d.readIndex())}) + } + } + + case '"': // string + s, ok := unquoteBytes(item) + if !ok { + if fromQuoted { + return fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()) + } + return errPhase + } + switch v.Kind() { + default: + d.saveError(&UnmarshalTypeError{Value: "string", Type: v.Type(), Offset: int64(d.readIndex())}) + case reflect.Slice: + if v.Type().Elem().Kind() != reflect.Uint8 { + d.saveError(&UnmarshalTypeError{Value: "string", Type: v.Type(), Offset: int64(d.readIndex())}) + break + } + b := make([]byte, base64.StdEncoding.DecodedLen(len(s))) + n, err := base64.StdEncoding.Decode(b, s) + if err != nil { + d.saveError(err) + break + } + v.SetBytes(b[:n]) + case reflect.String: + v.SetString(string(s)) + case reflect.Interface: + if v.NumMethod() == 0 { + v.Set(reflect.ValueOf(string(s))) + } else { + d.saveError(&UnmarshalTypeError{Value: "string", Type: v.Type(), Offset: int64(d.readIndex())}) + } + } + + default: // number + if c != '-' && (c < '0' || c > '9') { + if fromQuoted { + return fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()) + } + return errPhase + } + s := string(item) + switch v.Kind() { + default: + if v.Kind() == reflect.String && v.Type() == numberType { + v.SetString(s) + if !isValidNumber(s) { + return fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", item) + } + break + } + if fromQuoted { + return fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()) + } + d.saveError(&UnmarshalTypeError{Value: "number", Type: v.Type(), Offset: int64(d.readIndex())}) + case reflect.Interface: + n, err := d.convertNumber(s) + if err != nil { + d.saveError(err) + break + } + if v.NumMethod() != 0 { + d.saveError(&UnmarshalTypeError{Value: "number", Type: v.Type(), Offset: int64(d.readIndex())}) + break + } + v.Set(reflect.ValueOf(n)) + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + n, err := strconv.ParseInt(s, 10, 64) + if err != nil || v.OverflowInt(n) { + d.saveError(&UnmarshalTypeError{Value: "number " + s, Type: v.Type(), Offset: int64(d.readIndex())}) + break + } + v.SetInt(n) + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + n, err := strconv.ParseUint(s, 10, 64) + if err != nil || v.OverflowUint(n) { + d.saveError(&UnmarshalTypeError{Value: "number " + s, Type: v.Type(), Offset: int64(d.readIndex())}) + break + } + v.SetUint(n) + + case reflect.Float32, reflect.Float64: + n, err := strconv.ParseFloat(s, v.Type().Bits()) + if err != nil || v.OverflowFloat(n) { + d.saveError(&UnmarshalTypeError{Value: "number " + s, Type: v.Type(), Offset: int64(d.readIndex())}) + break + } + v.SetFloat(n) + } + } + return nil +} + +// The xxxInterface routines build up a value to be stored +// in an empty interface. They are not strictly necessary, +// but they avoid the weight of reflection in this common case. + +// valueInterface is like value but returns interface{} +func (d *decodeState) valueInterface() (val interface{}, err error) { + switch d.opcode { + default: + err = errPhase + case scanBeginArray: + val, err = d.arrayInterface() + d.scanNext() + case scanBeginObject: + val, err = d.objectInterface() + d.scanNext() + case scanBeginLiteral: + val, err = d.literalInterface() + } + return +} + +// arrayInterface is like array but returns []interface{}. +func (d *decodeState) arrayInterface() ([]interface{}, error) { + var v = make([]interface{}, 0) + for { + // Look ahead for ] - can only happen on first iteration. + d.scanWhile(scanSkipSpace) + if d.opcode == scanEndArray { + break + } + + vi, err := d.valueInterface() + if err != nil { + return nil, err + } + v = append(v, vi) + + // Next token must be , or ]. + if d.opcode == scanSkipSpace { + d.scanWhile(scanSkipSpace) + } + if d.opcode == scanEndArray { + break + } + if d.opcode != scanArrayValue { + return nil, errPhase + } + } + return v, nil +} + +// objectInterface is like object but returns map[string]interface{}. +func (d *decodeState) objectInterface() (map[string]interface{}, error) { + m := make(map[string]interface{}) + for { + // Read opening " of string key or closing }. + d.scanWhile(scanSkipSpace) + if d.opcode == scanEndObject { + // closing } - can only happen on first iteration. + break + } + if d.opcode != scanBeginLiteral { + return nil, errPhase + } + + // Read string key. + start := d.readIndex() + d.scanWhile(scanContinue) + item := d.data[start:d.readIndex()] + key, ok := unquote(item) + if !ok { + return nil, errPhase + } + + // Read : before value. + if d.opcode == scanSkipSpace { + d.scanWhile(scanSkipSpace) + } + if d.opcode != scanObjectKey { + return nil, errPhase + } + d.scanWhile(scanSkipSpace) + + // Read value. + vi, err := d.valueInterface() + if err != nil { + return nil, err + } + m[key] = vi + + // Next token must be , or }. + if d.opcode == scanSkipSpace { + d.scanWhile(scanSkipSpace) + } + if d.opcode == scanEndObject { + break + } + if d.opcode != scanObjectValue { + return nil, errPhase + } + } + return m, nil +} + +// literalInterface consumes and returns a literal from d.data[d.off-1:] and +// it reads the following byte ahead. The first byte of the literal has been +// read already (that's how the caller knows it's a literal). +func (d *decodeState) literalInterface() (interface{}, error) { + // All bytes inside literal return scanContinue op code. + start := d.readIndex() + d.scanWhile(scanContinue) + + item := d.data[start:d.readIndex()] + + switch c := item[0]; c { + case 'n': // null + return nil, nil + + case 't', 'f': // true, false + return c == 't', nil + + case '"': // string + s, ok := unquote(item) + if !ok { + return nil, errPhase + } + return s, nil + + default: // number + if c != '-' && (c < '0' || c > '9') { + return nil, errPhase + } + n, err := d.convertNumber(string(item)) + if err != nil { + d.saveError(err) + } + return n, nil + } +} + +// getu4 decodes \uXXXX from the beginning of s, returning the hex value, +// or it returns -1. +func getu4(s []byte) rune { + if len(s) < 6 || s[0] != '\\' || s[1] != 'u' { + return -1 + } + var r rune + for _, c := range s[2:6] { + switch { + case '0' <= c && c <= '9': + c = c - '0' + case 'a' <= c && c <= 'f': + c = c - 'a' + 10 + case 'A' <= c && c <= 'F': + c = c - 'A' + 10 + default: + return -1 + } + r = r*16 + rune(c) + } + return r +} + +// unquote converts a quoted JSON string literal s into an actual string t. +// The rules are different than for Go, so cannot use strconv.Unquote. +func unquote(s []byte) (t string, ok bool) { + s, ok = unquoteBytes(s) + t = string(s) + return +} + +func unquoteBytes(s []byte) (t []byte, ok bool) { + if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' { + return + } + s = s[1 : len(s)-1] + + // Check for unusual characters. If there are none, + // then no unquoting is needed, so return a slice of the + // original bytes. + r := 0 + for r < len(s) { + c := s[r] + if c == '\\' || c == '"' || c < ' ' { + break + } + if c < utf8.RuneSelf { + r++ + continue + } + rr, size := utf8.DecodeRune(s[r:]) + if rr == utf8.RuneError && size == 1 { + break + } + r += size + } + if r == len(s) { + return s, true + } + + b := make([]byte, len(s)+2*utf8.UTFMax) + w := copy(b, s[0:r]) + for r < len(s) { + // Out of room? Can only happen if s is full of + // malformed UTF-8 and we're replacing each + // byte with RuneError. + if w >= len(b)-2*utf8.UTFMax { + nb := make([]byte, (len(b)+utf8.UTFMax)*2) + copy(nb, b[0:w]) + b = nb + } + switch c := s[r]; { + case c == '\\': + r++ + if r >= len(s) { + return + } + switch s[r] { + default: + return + case '"', '\\', '/', '\'': + b[w] = s[r] + r++ + w++ + case 'b': + b[w] = '\b' + r++ + w++ + case 'f': + b[w] = '\f' + r++ + w++ + case 'n': + b[w] = '\n' + r++ + w++ + case 'r': + b[w] = '\r' + r++ + w++ + case 't': + b[w] = '\t' + r++ + w++ + case 'u': + r-- + rr := getu4(s[r:]) + if rr < 0 { + return + } + r += 6 + if utf16.IsSurrogate(rr) { + rr1 := getu4(s[r:]) + if dec := utf16.DecodeRune(rr, rr1); dec != unicode.ReplacementChar { + // A valid pair; consume. + r += 6 + w += utf8.EncodeRune(b[w:], dec) + break + } + // Invalid surrogate; fall back to replacement rune. + rr = unicode.ReplacementChar + } + w += utf8.EncodeRune(b[w:], rr) + } + + // Quote, control characters are invalid. + case c == '"', c < ' ': + return + + // ASCII + case c < utf8.RuneSelf: + b[w] = c + r++ + w++ + + // Coerce to well-formed UTF-8. + default: + rr, size := utf8.DecodeRune(s[r:]) + r += size + w += utf8.EncodeRune(b[w:], rr) + } + } + return b[0:w], true +} diff --git a/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/json/encode.go b/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/json/encode.go new file mode 100644 index 0000000000..eecf128774 --- /dev/null +++ b/vendor/github.com/tencentcloud/tencentcloud-sdk-go-intl-en/tencentcloud/common/json/encode.go @@ -0,0 +1,1282 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package json implements encoding and decoding of JSON as defined in +// RFC 7159. The mapping between JSON and Go values is described +// in the documentation for the Marshal and Unmarshal functions. +// +// See "JSON and Go" for an introduction to this package: +// https://golang.org/doc/articles/json_and_go.html +package json + +import ( + "bytes" + "encoding" + "encoding/base64" + "fmt" + "math" + "reflect" + "sort" + "strconv" + "strings" + "sync" + "unicode" + "unicode/utf8" +) + +// Marshal returns the JSON encoding of v. +// +// Marshal traverses the value v recursively. +// If an encountered value implements the Marshaler interface +// and is not a nil pointer, Marshal calls its MarshalJSON method +// to produce JSON. If no MarshalJSON method is present but the +// value implements encoding.TextMarshaler instead, Marshal calls +// its MarshalText method and encodes the result as a JSON string. +// The nil pointer exception is not strictly necessary +// but mimics a similar, necessary exception in the behavior of +// UnmarshalJSON. +// +// Otherwise, Marshal uses the following type-dependent default encodings: +// +// Boolean values encode as JSON booleans. +// +// Floating point, integer, and Number values encode as JSON numbers. +// +// String values encode as JSON strings coerced to valid UTF-8, +// replacing invalid bytes with the Unicode replacement rune. +// The angle brackets "<" and ">" are escaped to "\u003c" and "\u003e" +// to keep some browsers from misinterpreting JSON output as HTML. +// Ampersand "&" is also escaped to "\u0026" for the same reason. +// This escaping can be disabled using an Encoder that had SetEscapeHTML(false) +// called on it. +// +// Array and slice values encode as JSON arrays, except that +// []byte encodes as a base64-encoded string, and a nil slice +// encodes as the null JSON value. +// +// Struct values encode as JSON objects. +// Each exported struct field becomes a member of the object, using the +// field name as the object key, unless the field is omitted for one of the +// reasons given below. +// +// The encoding of each struct field can be customized by the format string +// stored under the "json" key in the struct field's tag. +// The format string gives the name of the field, possibly followed by a +// comma-separated list of options. The name may be empty in order to +// specify options without overriding the default field name. +// +// The "omitempty" option specifies that the field should be omitted +// from the encoding if the field has an empty value, defined as +// false, 0, a nil pointer, a nil interface value, and any empty array, +// slice, map, or string. +// +// As a special case, if the field tag is "-", the field is always omitted. +// Note that a field with name "-" can still be generated using the tag "-,". +// +// Examples of struct field tags and their meanings: +// +// // Field appears in JSON as key "myName". +// Field int `json:"myName"` +// +// // Field appears in JSON as key "myName" and +// // the field is omitted from the object if its value is empty, +// // as defined above. +// Field int `json:"myName,omitempty"` +// +// // Field appears in JSON as key "Field" (the default), but +// // the field is skipped if empty. +// // Note the leading comma. +// Field int `json:",omitempty"` +// +// // Field is ignored by this package. +// Field int `json:"-"` +// +// // Field appears in JSON as key "-". +// Field int `json:"-,"` +// +// The "string" option signals that a field is stored as JSON inside a +// JSON-encoded string. It applies only to fields of string, floating point, +// integer, or boolean types. This extra level of encoding is sometimes used +// when communicating with JavaScript programs: +// +// Int64String int64 `json:",string"` +// +// The key name will be used if it's a non-empty string consisting of +// only Unicode letters, digits, and ASCII punctuation except quotation +// marks, backslash, and comma. +// +// Anonymous struct fields are usually marshaled as if their inner exported fields +// were fields in the outer struct, subject to the usual Go visibility rules amended +// as described in the next paragraph. +// An anonymous struct field with a name given in its JSON tag is treated as +// having that name, rather than being anonymous. +// An anonymous struct field of interface type is treated the same as having +// that type as its name, rather than being anonymous. +// +// The Go visibility rules for struct fields are amended for JSON when +// deciding which field to marshal or unmarshal. If there are +// multiple fields at the same level, and that level is the least +// nested (and would therefore be the nesting level selected by the +// usual Go rules), the following extra rules apply: +// +// 1) Of those fields, if any are JSON-tagged, only tagged fields are considered, +// even if there are multiple untagged fields that would otherwise conflict. +// +// 2) If there is exactly one field (tagged or not according to the first rule), that is selected. +// +// 3) Otherwise there are multiple fields, and all are ignored; no error occurs. +// +// Handling of anonymous struct fields is new in Go 1.1. +// Prior to Go 1.1, anonymous struct fields were ignored. To force ignoring of +// an anonymous struct field in both current and earlier versions, give the field +// a JSON tag of "-". +// +// Map values encode as JSON objects. The map's key type must either be a +// string, an integer type, or implement encoding.TextMarshaler. The map keys +// are sorted and used as JSON object keys by applying the following rules, +// subject to the UTF-8 coercion described for string values above: +// - string keys are used directly +// - encoding.TextMarshalers are marshaled +// - integer keys are converted to strings +// +// Pointer values encode as the value pointed to. +// A nil pointer encodes as the null JSON value. +// +// Interface values encode as the value contained in the interface. +// A nil interface value encodes as the null JSON value. +// +// Channel, complex, and function values cannot be encoded in JSON. +// Attempting to encode such a value causes Marshal to return +// an UnsupportedTypeError. +// +// JSON cannot represent cyclic data structures and Marshal does not +// handle them. Passing cyclic structures to Marshal will result in +// an infinite recursion. +func Marshal(v interface{}) ([]byte, error) { + e := newEncodeState() + + err := e.marshal(v, encOpts{escapeHTML: true}) + if err != nil { + return nil, err + } + buf := append([]byte(nil), e.Bytes()...) + + e.Reset() + encodeStatePool.Put(e) + + return buf, nil +} + +// MarshalIndent is like Marshal but applies Indent to format the output. +// Each JSON element in the output will begin on a new line beginning with prefix +// followed by one or more copies of indent according to the indentation nesting. +func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) { + b, err := Marshal(v) + if err != nil { + return nil, err + } + var buf bytes.Buffer + err = Indent(&buf, b, prefix, indent) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +// HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029 +// characters inside string literals changed to \u003c, \u003e, \u0026, \u2028, \u2029 +// so that the JSON will be safe to embed inside HTML